Revert http client changes (#18892)
These proved to be too unstable. Will restore these changes once the issues have been fixed. Release Notes: - N/A
This commit is contained in:
parent
e351148152
commit
5d5c4b6677
35 changed files with 386 additions and 1125 deletions
613
Cargo.lock
generated
613
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
15
Cargo.toml
15
Cargo.toml
|
@ -52,6 +52,7 @@ members = [
|
|||
"crates/indexed_docs",
|
||||
"crates/inline_completion_button",
|
||||
"crates/install_cli",
|
||||
"crates/isahc_http_client",
|
||||
"crates/journal",
|
||||
"crates/language",
|
||||
"crates/language_model",
|
||||
|
@ -87,7 +88,6 @@ members = [
|
|||
"crates/remote",
|
||||
"crates/remote_server",
|
||||
"crates/repl",
|
||||
"crates/reqwest_client",
|
||||
"crates/rich_text",
|
||||
"crates/rope",
|
||||
"crates/rpc",
|
||||
|
@ -122,7 +122,6 @@ members = [
|
|||
"crates/ui",
|
||||
"crates/ui_input",
|
||||
"crates/ui_macros",
|
||||
"crates/ureq_client",
|
||||
"crates/util",
|
||||
"crates/vcs_menu",
|
||||
"crates/vim",
|
||||
|
@ -230,6 +229,7 @@ image_viewer = { path = "crates/image_viewer" }
|
|||
indexed_docs = { path = "crates/indexed_docs" }
|
||||
inline_completion_button = { path = "crates/inline_completion_button" }
|
||||
install_cli = { path = "crates/install_cli" }
|
||||
isahc_http_client = { path = "crates/isahc_http_client" }
|
||||
journal = { path = "crates/journal" }
|
||||
language = { path = "crates/language" }
|
||||
language_model = { path = "crates/language_model" }
|
||||
|
@ -266,7 +266,6 @@ release_channel = { path = "crates/release_channel" }
|
|||
remote = { path = "crates/remote" }
|
||||
remote_server = { path = "crates/remote_server" }
|
||||
repl = { path = "crates/repl" }
|
||||
reqwest_client = { path = "crates/reqwest_client" }
|
||||
rich_text = { path = "crates/rich_text" }
|
||||
rope = { path = "crates/rope" }
|
||||
rpc = { path = "crates/rpc" }
|
||||
|
@ -301,7 +300,6 @@ title_bar = { path = "crates/title_bar" }
|
|||
ui = { path = "crates/ui" }
|
||||
ui_input = { path = "crates/ui_input" }
|
||||
ui_macros = { path = "crates/ui_macros" }
|
||||
ureq_client = { path = "crates/ureq_client" }
|
||||
util = { path = "crates/util" }
|
||||
vcs_menu = { path = "crates/vcs_menu" }
|
||||
vim = { path = "crates/vim" }
|
||||
|
@ -329,7 +327,7 @@ async-pipe = { git = "https://github.com/zed-industries/async-pipe-rs", rev = "8
|
|||
async-recursion = "1.0.0"
|
||||
async-tar = "0.5.0"
|
||||
async-trait = "0.1"
|
||||
async-tungstenite = "0.28"
|
||||
async-tungstenite = "0.23"
|
||||
async-watch = "0.3.1"
|
||||
async_zip = { version = "0.0.17", features = ["deflate", "deflate64"] }
|
||||
base64 = "0.22"
|
||||
|
@ -369,6 +367,10 @@ ignore = "0.4.22"
|
|||
image = "0.25.1"
|
||||
indexmap = { version = "1.6.2", features = ["serde"] }
|
||||
indoc = "2"
|
||||
# We explicitly disable http2 support in isahc.
|
||||
isahc = { version = "1.7.2", default-features = false, features = [
|
||||
"text-decoding",
|
||||
] }
|
||||
itertools = "0.13.0"
|
||||
jsonwebtoken = "9.3"
|
||||
libc = "0.2"
|
||||
|
@ -393,14 +395,13 @@ pulldown-cmark = { version = "0.12.0", default-features = false }
|
|||
rand = "0.8.5"
|
||||
regex = "1.5"
|
||||
repair_json = "0.1.0"
|
||||
reqwest = { git = "https://github.com/zed-industries/reqwest.git", rev = "fd110f6998da16bbca97b6dddda9be7827c50e29" }
|
||||
rsa = "0.9.6"
|
||||
runtimelib = { version = "0.15", default-features = false, features = [
|
||||
"async-dispatcher-runtime",
|
||||
] }
|
||||
rustc-demangle = "0.1.23"
|
||||
rust-embed = { version = "8.4", features = ["include-exclude"] }
|
||||
rustls = "0.21.12"
|
||||
rustls = "0.20.3"
|
||||
rustls-native-certs = "0.8.0"
|
||||
schemars = { version = "0.8", features = ["impl_json_schema"] }
|
||||
semver = "1.0"
|
||||
|
|
|
@ -18,7 +18,6 @@ test-support = ["clock/test-support", "collections/test-support", "gpui/test-sup
|
|||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
async-recursion = "0.3"
|
||||
async-tls = "0.13"
|
||||
async-tungstenite = { workspace = true, features = ["async-std", "async-tls"] }
|
||||
chrono = { workspace = true, features = ["serde"] }
|
||||
clock.workspace = true
|
||||
|
@ -35,6 +34,8 @@ postage.workspace = true
|
|||
rand.workspace = true
|
||||
release_channel.workspace = true
|
||||
rpc = { workspace = true, features = ["gpui"] }
|
||||
rustls.workspace = true
|
||||
rustls-native-certs.workspace = true
|
||||
schemars.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
|
|
|
@ -1023,7 +1023,7 @@ impl Client {
|
|||
&self,
|
||||
http: Arc<HttpClientWithUrl>,
|
||||
release_channel: Option<ReleaseChannel>,
|
||||
) -> impl Future<Output = Result<url::Url>> {
|
||||
) -> impl Future<Output = Result<Url>> {
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
let url_override = self.rpc_url.read().clone();
|
||||
|
||||
|
@ -1117,7 +1117,7 @@ impl Client {
|
|||
// for us from the RPC URL.
|
||||
//
|
||||
// Among other things, it will generate and set a `Sec-WebSocket-Key` header for us.
|
||||
let mut request = IntoClientRequest::into_client_request(rpc_url.as_str())?;
|
||||
let mut request = rpc_url.into_client_request()?;
|
||||
|
||||
// We then modify the request to add our desired headers.
|
||||
let request_headers = request.headers_mut();
|
||||
|
@ -1137,13 +1137,30 @@ impl Client {
|
|||
|
||||
match url_scheme {
|
||||
Https => {
|
||||
let client_config = {
|
||||
let mut root_store = rustls::RootCertStore::empty();
|
||||
|
||||
let root_certs = rustls_native_certs::load_native_certs();
|
||||
for error in root_certs.errors {
|
||||
log::warn!("error loading native certs: {:?}", error);
|
||||
}
|
||||
root_store.add_parsable_certificates(
|
||||
&root_certs
|
||||
.certs
|
||||
.into_iter()
|
||||
.map(|cert| cert.as_ref().to_owned())
|
||||
.collect::<Vec<_>>(),
|
||||
);
|
||||
rustls::ClientConfig::builder()
|
||||
.with_safe_defaults()
|
||||
.with_root_certificates(root_store)
|
||||
.with_no_client_auth()
|
||||
};
|
||||
let (stream, _) =
|
||||
async_tungstenite::async_tls::client_async_tls_with_connector(
|
||||
request,
|
||||
stream,
|
||||
Some(async_tls::TlsConnector::from(
|
||||
http_client::TLS_CONFIG.clone(),
|
||||
)),
|
||||
Some(client_config.into()),
|
||||
)
|
||||
.await?;
|
||||
Ok(Connection::new(
|
||||
|
|
|
@ -37,6 +37,7 @@ futures.workspace = true
|
|||
google_ai.workspace = true
|
||||
hex.workspace = true
|
||||
http_client.workspace = true
|
||||
isahc_http_client.workspace = true
|
||||
jsonwebtoken.workspace = true
|
||||
live_kit_server.workspace = true
|
||||
log.workspace = true
|
||||
|
@ -47,7 +48,6 @@ prometheus = "0.13"
|
|||
prost.workspace = true
|
||||
rand.workspace = true
|
||||
reqwest = { version = "0.11", features = ["json"] }
|
||||
reqwest_client.workspace = true
|
||||
rpc.workspace = true
|
||||
rustc-demangle.workspace = true
|
||||
scrypt = "0.11"
|
||||
|
|
|
@ -22,8 +22,7 @@ use chrono::{DateTime, Duration, Utc};
|
|||
use collections::HashMap;
|
||||
use db::{usage_measure::UsageMeasure, ActiveUserCount, LlmDatabase};
|
||||
use futures::{Stream, StreamExt as _};
|
||||
|
||||
use reqwest_client::ReqwestClient;
|
||||
use isahc_http_client::IsahcHttpClient;
|
||||
use rpc::ListModelsResponse;
|
||||
use rpc::{
|
||||
proto::Plan, LanguageModelProvider, PerformCompletionParams, EXPIRED_LLM_TOKEN_HEADER_NAME,
|
||||
|
@ -44,7 +43,7 @@ pub struct LlmState {
|
|||
pub config: Config,
|
||||
pub executor: Executor,
|
||||
pub db: Arc<LlmDatabase>,
|
||||
pub http_client: ReqwestClient,
|
||||
pub http_client: IsahcHttpClient,
|
||||
pub clickhouse_client: Option<clickhouse::Client>,
|
||||
active_user_count_by_model:
|
||||
RwLock<HashMap<(LanguageModelProvider, String), (DateTime<Utc>, ActiveUserCount)>>,
|
||||
|
@ -70,8 +69,11 @@ impl LlmState {
|
|||
let db = Arc::new(db);
|
||||
|
||||
let user_agent = format!("Zed Server/{}", env!("CARGO_PKG_VERSION"));
|
||||
let http_client =
|
||||
ReqwestClient::user_agent(&user_agent).context("failed to construct http client")?;
|
||||
let http_client = IsahcHttpClient::builder()
|
||||
.default_header("User-Agent", user_agent)
|
||||
.build()
|
||||
.map(IsahcHttpClient::from)
|
||||
.context("failed to construct http client")?;
|
||||
|
||||
let this = Self {
|
||||
executor,
|
||||
|
|
|
@ -36,8 +36,8 @@ use collections::{HashMap, HashSet};
|
|||
pub use connection_pool::{ConnectionPool, ZedVersion};
|
||||
use core::fmt::{self, Debug, Formatter};
|
||||
use http_client::HttpClient;
|
||||
use isahc_http_client::IsahcHttpClient;
|
||||
use open_ai::{OpenAiEmbeddingModel, OPEN_AI_API_URL};
|
||||
use reqwest_client::ReqwestClient;
|
||||
use sha2::Digest;
|
||||
use supermaven_api::{CreateExternalUserRequest, SupermavenAdminApi};
|
||||
|
||||
|
@ -964,8 +964,8 @@ impl Server {
|
|||
tracing::info!("connection opened");
|
||||
|
||||
let user_agent = format!("Zed Server/{}", env!("CARGO_PKG_VERSION"));
|
||||
let http_client = match ReqwestClient::user_agent(&user_agent) {
|
||||
Ok(http_client) => Arc::new(http_client),
|
||||
let http_client = match IsahcHttpClient::builder().default_header("User-Agent", user_agent).build() {
|
||||
Ok(http_client) => Arc::new(IsahcHttpClient::from(http_client)),
|
||||
Err(error) => {
|
||||
tracing::error!(?error, "failed to create HTTP client");
|
||||
return;
|
||||
|
|
|
@ -25,6 +25,7 @@ fs.workspace = true
|
|||
git.workspace = true
|
||||
gpui.workspace = true
|
||||
http_client.workspace = true
|
||||
isahc_http_client.workspace = true
|
||||
language.workspace = true
|
||||
languages.workspace = true
|
||||
node_runtime.workspace = true
|
||||
|
@ -35,4 +36,3 @@ serde.workspace = true
|
|||
serde_json.workspace = true
|
||||
settings.workspace = true
|
||||
smol.workspace = true
|
||||
ureq_client.workspace = true
|
||||
|
|
|
@ -32,7 +32,6 @@ use std::{
|
|||
Arc,
|
||||
},
|
||||
};
|
||||
use ureq_client::UreqClient;
|
||||
|
||||
const CODESEARCH_NET_DIR: &'static str = "target/datasets/code-search-net";
|
||||
const EVAL_REPOS_DIR: &'static str = "target/datasets/eval-repos";
|
||||
|
@ -101,11 +100,7 @@ fn main() -> Result<()> {
|
|||
|
||||
gpui::App::headless().run(move |cx| {
|
||||
let executor = cx.background_executor().clone();
|
||||
let client = Arc::new(UreqClient::new(
|
||||
None,
|
||||
"Zed LLM evals".to_string(),
|
||||
executor.clone(),
|
||||
));
|
||||
let client = isahc_http_client::IsahcHttpClient::new(None, None);
|
||||
cx.set_http_client(client.clone());
|
||||
match cli.command {
|
||||
Commands::Fetch {} => {
|
||||
|
|
|
@ -56,6 +56,7 @@ wit-component.workspace = true
|
|||
workspace.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
isahc_http_client.workspace = true
|
||||
ctor.workspace = true
|
||||
env_logger.workspace = true
|
||||
fs = { workspace = true, features = ["test-support"] }
|
||||
|
@ -63,7 +64,5 @@ gpui = { workspace = true, features = ["test-support"] }
|
|||
language = { workspace = true, features = ["test-support"] }
|
||||
parking_lot.workspace = true
|
||||
project = { workspace = true, features = ["test-support"] }
|
||||
reqwest_client.workspace = true
|
||||
tokio.workspace = true
|
||||
ureq_client.workspace = true
|
||||
workspace = { workspace = true, features = ["test-support"] }
|
||||
|
|
|
@ -25,7 +25,7 @@ use wit_component::ComponentEncoder;
|
|||
/// Once Rust 1.78 is released, there will be a `wasm32-wasip2` target available, so we will
|
||||
/// not need the adapter anymore.
|
||||
const RUST_TARGET: &str = "wasm32-wasip1";
|
||||
pub const WASI_ADAPTER_URL: &str =
|
||||
const WASI_ADAPTER_URL: &str =
|
||||
"https://github.com/bytecodealliance/wasmtime/releases/download/v18.0.2/wasi_snapshot_preview1.reactor.wasm";
|
||||
|
||||
/// Compiling Tree-sitter parsers from C to WASM requires Clang 17, and a WASM build of libc
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
use crate::extension_builder::WASI_ADAPTER_URL;
|
||||
use crate::extension_manifest::SchemaVersion;
|
||||
use crate::extension_settings::ExtensionSettings;
|
||||
use crate::{
|
||||
|
@ -12,14 +11,14 @@ use collections::BTreeMap;
|
|||
use fs::{FakeFs, Fs, RealFs};
|
||||
use futures::{io::BufReader, AsyncReadExt, StreamExt};
|
||||
use gpui::{Context, SemanticVersion, TestAppContext};
|
||||
use http_client::{AsyncBody, FakeHttpClient, HttpClient, Response};
|
||||
use http_client::{FakeHttpClient, Response};
|
||||
use indexed_docs::IndexedDocsRegistry;
|
||||
use isahc_http_client::IsahcHttpClient;
|
||||
use language::{LanguageMatcher, LanguageRegistry, LanguageServerBinaryStatus, LanguageServerName};
|
||||
use node_runtime::NodeRuntime;
|
||||
use parking_lot::Mutex;
|
||||
use project::{Project, DEFAULT_COMPLETION_CONTEXT};
|
||||
use release_channel::AppVersion;
|
||||
use reqwest_client::ReqwestClient;
|
||||
use serde_json::json;
|
||||
use settings::{Settings as _, SettingsStore};
|
||||
use snippet_provider::SnippetRegistry;
|
||||
|
@ -29,7 +28,6 @@ use std::{
|
|||
sync::Arc,
|
||||
};
|
||||
use theme::ThemeRegistry;
|
||||
use ureq_client::UreqClient;
|
||||
use util::test::temp_tree;
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -578,7 +576,7 @@ async fn test_extension_store_with_test_extension(cx: &mut TestAppContext) {
|
|||
std::env::consts::ARCH
|
||||
)
|
||||
});
|
||||
let builder_client = Arc::new(UreqClient::new(None, user_agent, cx.executor().clone()));
|
||||
let builder_client = IsahcHttpClient::new(None, Some(user_agent));
|
||||
|
||||
let extension_store = cx.new_model(|cx| {
|
||||
ExtensionStore::new(
|
||||
|
@ -771,50 +769,6 @@ async fn test_extension_store_with_test_extension(cx: &mut TestAppContext) {
|
|||
assert!(fs.metadata(&expected_server_path).await.unwrap().is_none());
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_wasi_adapter_download(cx: &mut TestAppContext) {
|
||||
let client = Arc::new(UreqClient::new(
|
||||
None,
|
||||
"zed-test-wasi-adapter-download".to_string(),
|
||||
cx.executor().clone(),
|
||||
));
|
||||
|
||||
let mut response = client
|
||||
.get(WASI_ADAPTER_URL, AsyncBody::default(), true)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let mut content = Vec::new();
|
||||
let mut body = BufReader::new(response.body_mut());
|
||||
body.read_to_end(&mut content).await.unwrap();
|
||||
|
||||
assert!(wasmparser::Parser::is_core_wasm(&content));
|
||||
assert_eq!(content.len(), 96801); // Determined by downloading this to my computer
|
||||
wit_component::ComponentEncoder::default()
|
||||
.adapter("wasi_snapshot_preview1", &content)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_wasi_adapter_download_tokio() {
|
||||
let client = Arc::new(ReqwestClient::new());
|
||||
|
||||
let mut response = client
|
||||
.get(WASI_ADAPTER_URL, AsyncBody::default(), true)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let mut content = Vec::new();
|
||||
let mut body = BufReader::new(response.body_mut());
|
||||
body.read_to_end(&mut content).await.unwrap();
|
||||
|
||||
assert!(wasmparser::Parser::is_core_wasm(&content));
|
||||
assert_eq!(content.len(), 96801); // Determined by downloading this to my computer
|
||||
wit_component::ComponentEncoder::default()
|
||||
.adapter("wasi_snapshot_preview1", &content)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
fn init_test(cx: &mut TestAppContext) {
|
||||
cx.update(|cx| {
|
||||
let store = SettingsStore::test(cx);
|
||||
|
|
|
@ -18,9 +18,9 @@ clap = { workspace = true, features = ["derive"] }
|
|||
env_logger.workspace = true
|
||||
extension = { workspace = true, features = ["no-webrtc"] }
|
||||
fs.workspace = true
|
||||
isahc_http_client.workspace = true
|
||||
language.workspace = true
|
||||
log.workspace = true
|
||||
reqwest_client.workspace = true
|
||||
rpc.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
|
|
|
@ -13,8 +13,8 @@ use extension::{
|
|||
extension_builder::{CompileExtensionOptions, ExtensionBuilder},
|
||||
ExtensionManifest,
|
||||
};
|
||||
use isahc_http_client::IsahcHttpClient;
|
||||
use language::LanguageConfig;
|
||||
use reqwest_client::ReqwestClient;
|
||||
use theme::ThemeRegistry;
|
||||
use tree_sitter::{Language, Query, WasmStore};
|
||||
|
||||
|
@ -66,7 +66,12 @@ async fn main() -> Result<()> {
|
|||
std::env::consts::OS,
|
||||
std::env::consts::ARCH
|
||||
);
|
||||
let http_client = Arc::new(ReqwestClient::user_agent(&user_agent)?);
|
||||
let http_client = Arc::new(
|
||||
IsahcHttpClient::builder()
|
||||
.default_header("User-Agent", user_agent)
|
||||
.build()
|
||||
.map(IsahcHttpClient::from)?,
|
||||
);
|
||||
|
||||
let builder = ExtensionBuilder::new(http_client, scratch_dir);
|
||||
builder
|
||||
|
|
|
@ -1533,8 +1533,4 @@ impl HttpClient for NullHttpClient {
|
|||
fn proxy(&self) -> Option<&http_client::Uri> {
|
||||
None
|
||||
}
|
||||
|
||||
fn type_name(&self) -> &'static str {
|
||||
type_name::<Self>()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,13 +16,11 @@ path = "src/http_client.rs"
|
|||
doctest = true
|
||||
|
||||
[dependencies]
|
||||
http = "0.2"
|
||||
anyhow.workspace = true
|
||||
derive_more.workspace = true
|
||||
futures.workspace = true
|
||||
http = "1.1"
|
||||
log.workspace = true
|
||||
rustls-native-certs.workspace = true
|
||||
rustls.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
smol.workspace = true
|
||||
|
|
|
@ -11,22 +11,13 @@ use http::request::Builder;
|
|||
#[cfg(feature = "test-support")]
|
||||
use std::fmt;
|
||||
use std::{
|
||||
any::type_name,
|
||||
sync::{Arc, LazyLock, Mutex},
|
||||
sync::{Arc, Mutex},
|
||||
time::Duration,
|
||||
};
|
||||
pub use url::Url;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ReadTimeout(pub Duration);
|
||||
impl Default for ReadTimeout {
|
||||
fn default() -> Self {
|
||||
Self(Duration::from_secs(5))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, PartialEq, Eq, Hash)]
|
||||
|
||||
#[derive(Default, Debug, Clone)]
|
||||
pub enum RedirectPolicy {
|
||||
#[default]
|
||||
NoFollow,
|
||||
|
@ -35,23 +26,6 @@ pub enum RedirectPolicy {
|
|||
}
|
||||
pub struct FollowRedirects(pub bool);
|
||||
|
||||
pub static TLS_CONFIG: LazyLock<Arc<rustls::ClientConfig>> = LazyLock::new(|| {
|
||||
let mut root_store = rustls::RootCertStore::empty();
|
||||
|
||||
let root_certs = rustls_native_certs::load_native_certs();
|
||||
for error in root_certs.errors {
|
||||
log::warn!("error loading native certs: {:?}", error);
|
||||
}
|
||||
root_store.add_parsable_certificates(&root_certs.certs);
|
||||
|
||||
Arc::new(
|
||||
rustls::ClientConfig::builder()
|
||||
.with_safe_defaults()
|
||||
.with_root_certificates(root_store)
|
||||
.with_no_client_auth(),
|
||||
)
|
||||
});
|
||||
|
||||
pub trait HttpRequestExt {
|
||||
/// Set a read timeout on the request.
|
||||
/// For isahc, this is the low_speed_timeout.
|
||||
|
@ -73,8 +47,6 @@ impl HttpRequestExt for http::request::Builder {
|
|||
}
|
||||
|
||||
pub trait HttpClient: 'static + Send + Sync {
|
||||
fn type_name(&self) -> &'static str;
|
||||
|
||||
fn send(
|
||||
&self,
|
||||
req: http::Request<AsyncBody>,
|
||||
|
@ -157,10 +129,6 @@ impl HttpClient for HttpClientWithProxy {
|
|||
fn proxy(&self) -> Option<&Uri> {
|
||||
self.proxy.as_ref()
|
||||
}
|
||||
|
||||
fn type_name(&self) -> &'static str {
|
||||
self.client.type_name()
|
||||
}
|
||||
}
|
||||
|
||||
impl HttpClient for Arc<HttpClientWithProxy> {
|
||||
|
@ -174,10 +142,6 @@ impl HttpClient for Arc<HttpClientWithProxy> {
|
|||
fn proxy(&self) -> Option<&Uri> {
|
||||
self.proxy.as_ref()
|
||||
}
|
||||
|
||||
fn type_name(&self) -> &'static str {
|
||||
self.client.type_name()
|
||||
}
|
||||
}
|
||||
|
||||
/// An [`HttpClient`] that has a base URL.
|
||||
|
@ -289,10 +253,6 @@ impl HttpClient for Arc<HttpClientWithUrl> {
|
|||
fn proxy(&self) -> Option<&Uri> {
|
||||
self.client.proxy.as_ref()
|
||||
}
|
||||
|
||||
fn type_name(&self) -> &'static str {
|
||||
self.client.type_name()
|
||||
}
|
||||
}
|
||||
|
||||
impl HttpClient for HttpClientWithUrl {
|
||||
|
@ -306,10 +266,6 @@ impl HttpClient for HttpClientWithUrl {
|
|||
fn proxy(&self) -> Option<&Uri> {
|
||||
self.client.proxy.as_ref()
|
||||
}
|
||||
|
||||
fn type_name(&self) -> &'static str {
|
||||
self.client.type_name()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read_proxy_from_env() -> Option<Uri> {
|
||||
|
@ -350,10 +306,6 @@ impl HttpClient for BlockedHttpClient {
|
|||
fn proxy(&self) -> Option<&Uri> {
|
||||
None
|
||||
}
|
||||
|
||||
fn type_name(&self) -> &'static str {
|
||||
type_name::<Self>()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "test-support")]
|
||||
|
@ -426,8 +378,4 @@ impl HttpClient for FakeHttpClient {
|
|||
fn proxy(&self) -> Option<&Uri> {
|
||||
None
|
||||
}
|
||||
|
||||
fn type_name(&self) -> &'static str {
|
||||
type_name::<Self>()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
[package]
|
||||
name = "ureq_client"
|
||||
name = "isahc_http_client"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
publish = false
|
||||
|
@ -12,20 +12,11 @@ workspace = true
|
|||
test-support = []
|
||||
|
||||
[lib]
|
||||
path = "src/ureq_client.rs"
|
||||
doctest = true
|
||||
|
||||
[[example]]
|
||||
name = "client"
|
||||
path = "examples/client.rs"
|
||||
path = "src/isahc_http_client.rs"
|
||||
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
futures.workspace = true
|
||||
gpui.workspace = true
|
||||
http_client.workspace = true
|
||||
parking_lot.workspace = true
|
||||
serde.workspace = true
|
||||
smol.workspace = true
|
||||
ureq = "=2.9.1"
|
||||
isahc.workspace = true
|
||||
util.workspace = true
|
1
crates/isahc_http_client/LICENSE-APACHE
Symbolic link
1
crates/isahc_http_client/LICENSE-APACHE
Symbolic link
|
@ -0,0 +1 @@
|
|||
../../LICENSE-APACHE
|
105
crates/isahc_http_client/src/isahc_http_client.rs
Normal file
105
crates/isahc_http_client/src/isahc_http_client.rs
Normal file
|
@ -0,0 +1,105 @@
|
|||
use std::{mem, sync::Arc, time::Duration};
|
||||
|
||||
use futures::future::BoxFuture;
|
||||
use util::maybe;
|
||||
|
||||
pub use isahc::config::Configurable;
|
||||
pub struct IsahcHttpClient(isahc::HttpClient);
|
||||
|
||||
pub use http_client::*;
|
||||
|
||||
impl IsahcHttpClient {
|
||||
pub fn new(proxy: Option<Uri>, user_agent: Option<String>) -> Arc<IsahcHttpClient> {
|
||||
let mut builder = isahc::HttpClient::builder()
|
||||
.connect_timeout(Duration::from_secs(5))
|
||||
.low_speed_timeout(100, Duration::from_secs(5))
|
||||
.proxy(proxy.clone());
|
||||
if let Some(agent) = user_agent {
|
||||
builder = builder.default_header("User-Agent", agent);
|
||||
}
|
||||
Arc::new(IsahcHttpClient(builder.build().unwrap()))
|
||||
}
|
||||
pub fn builder() -> isahc::HttpClientBuilder {
|
||||
isahc::HttpClientBuilder::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<isahc::HttpClient> for IsahcHttpClient {
|
||||
fn from(client: isahc::HttpClient) -> Self {
|
||||
Self(client)
|
||||
}
|
||||
}
|
||||
|
||||
impl HttpClient for IsahcHttpClient {
|
||||
fn proxy(&self) -> Option<&Uri> {
|
||||
None
|
||||
}
|
||||
|
||||
fn send(
|
||||
&self,
|
||||
req: http_client::http::Request<http_client::AsyncBody>,
|
||||
) -> BoxFuture<'static, Result<http_client::Response<http_client::AsyncBody>, anyhow::Error>>
|
||||
{
|
||||
let redirect_policy = req
|
||||
.extensions()
|
||||
.get::<http_client::RedirectPolicy>()
|
||||
.cloned()
|
||||
.unwrap_or_default();
|
||||
let read_timeout = req
|
||||
.extensions()
|
||||
.get::<http_client::ReadTimeout>()
|
||||
.map(|t| t.0);
|
||||
let req = maybe!({
|
||||
let (mut parts, body) = req.into_parts();
|
||||
let mut builder = isahc::Request::builder()
|
||||
.method(parts.method)
|
||||
.uri(parts.uri)
|
||||
.version(parts.version);
|
||||
if let Some(read_timeout) = read_timeout {
|
||||
builder = builder.low_speed_timeout(100, read_timeout);
|
||||
}
|
||||
|
||||
let headers = builder.headers_mut()?;
|
||||
mem::swap(headers, &mut parts.headers);
|
||||
|
||||
let extensions = builder.extensions_mut()?;
|
||||
mem::swap(extensions, &mut parts.extensions);
|
||||
|
||||
let isahc_body = match body.0 {
|
||||
http_client::Inner::Empty => isahc::AsyncBody::empty(),
|
||||
http_client::Inner::AsyncReader(reader) => isahc::AsyncBody::from_reader(reader),
|
||||
http_client::Inner::SyncReader(reader) => {
|
||||
isahc::AsyncBody::from_bytes_static(reader.into_inner())
|
||||
}
|
||||
};
|
||||
|
||||
builder
|
||||
.redirect_policy(match redirect_policy {
|
||||
http_client::RedirectPolicy::FollowAll => isahc::config::RedirectPolicy::Follow,
|
||||
http_client::RedirectPolicy::FollowLimit(limit) => {
|
||||
isahc::config::RedirectPolicy::Limit(limit)
|
||||
}
|
||||
http_client::RedirectPolicy::NoFollow => isahc::config::RedirectPolicy::None,
|
||||
})
|
||||
.body(isahc_body)
|
||||
.ok()
|
||||
});
|
||||
|
||||
let client = self.0.clone();
|
||||
|
||||
Box::pin(async move {
|
||||
match req {
|
||||
Some(req) => client
|
||||
.send_async(req)
|
||||
.await
|
||||
.map_err(Into::into)
|
||||
.map(|response| {
|
||||
let (parts, body) = response.into_parts();
|
||||
let body = http_client::AsyncBody::from_reader(body);
|
||||
http_client::Response::from_parts(parts, body)
|
||||
}),
|
||||
None => Err(anyhow::anyhow!("Request was malformed")),
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -20,7 +20,7 @@ jsonwebtoken.workspace = true
|
|||
log.workspace = true
|
||||
prost.workspace = true
|
||||
prost-types.workspace = true
|
||||
reqwest.workspace = true
|
||||
reqwest = "0.11"
|
||||
serde.workspace = true
|
||||
|
||||
[build-dependencies]
|
||||
|
|
|
@ -1,31 +0,0 @@
|
|||
[package]
|
||||
name = "reqwest_client"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
publish = false
|
||||
license = "Apache-2.0"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[features]
|
||||
test-support = []
|
||||
|
||||
[lib]
|
||||
path = "src/reqwest_client.rs"
|
||||
doctest = true
|
||||
|
||||
[[example]]
|
||||
name = "client"
|
||||
path = "examples/client.rs"
|
||||
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
bytes = "1.0"
|
||||
futures.workspace = true
|
||||
http_client.workspace = true
|
||||
serde.workspace = true
|
||||
smol.workspace = true
|
||||
tokio.workspace = true
|
||||
|
||||
reqwest = { workspace = true, features = ["rustls-tls-manual-roots", "stream"] }
|
|
@ -1 +0,0 @@
|
|||
../../LICENSE-GPL
|
|
@ -1,16 +0,0 @@
|
|||
use futures::AsyncReadExt as _;
|
||||
use http_client::AsyncBody;
|
||||
use http_client::HttpClient;
|
||||
use reqwest_client::ReqwestClient;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
let resp = ReqwestClient::new()
|
||||
.get("http://zed.dev", AsyncBody::empty(), true)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let mut body = String::new();
|
||||
resp.into_body().read_to_string(&mut body).await.unwrap();
|
||||
println!("{}", &body);
|
||||
}
|
|
@ -1,259 +0,0 @@
|
|||
use std::{any::type_name, borrow::Cow, io::Read, pin::Pin, task::Poll};
|
||||
|
||||
use anyhow::anyhow;
|
||||
use bytes::{BufMut, Bytes, BytesMut};
|
||||
use futures::{AsyncRead, TryStreamExt};
|
||||
use http_client::{http, AsyncBody, ReadTimeout};
|
||||
use reqwest::header::{HeaderMap, HeaderValue};
|
||||
use smol::future::FutureExt;
|
||||
|
||||
const DEFAULT_CAPACITY: usize = 4096;
|
||||
|
||||
pub struct ReqwestClient {
|
||||
client: reqwest::Client,
|
||||
}
|
||||
|
||||
impl ReqwestClient {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
client: reqwest::Client::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn user_agent(agent: &str) -> anyhow::Result<Self> {
|
||||
let mut map = HeaderMap::new();
|
||||
map.insert(http::header::USER_AGENT, HeaderValue::from_str(agent)?);
|
||||
Ok(Self {
|
||||
client: reqwest::Client::builder().default_headers(map).build()?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl From<reqwest::Client> for ReqwestClient {
|
||||
fn from(client: reqwest::Client) -> Self {
|
||||
Self { client }
|
||||
}
|
||||
}
|
||||
|
||||
// This struct is essentially a re-implementation of
|
||||
// https://docs.rs/tokio-util/0.7.12/tokio_util/io/struct.ReaderStream.html
|
||||
// except outside of Tokio's aegis
|
||||
struct ReaderStream {
|
||||
reader: Option<Pin<Box<dyn futures::AsyncRead + Send + Sync>>>,
|
||||
buf: BytesMut,
|
||||
capacity: usize,
|
||||
}
|
||||
|
||||
impl ReaderStream {
|
||||
fn new(reader: Pin<Box<dyn futures::AsyncRead + Send + Sync>>) -> Self {
|
||||
Self {
|
||||
reader: Some(reader),
|
||||
buf: BytesMut::new(),
|
||||
capacity: DEFAULT_CAPACITY,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl futures::Stream for ReaderStream {
|
||||
type Item = std::io::Result<Bytes>;
|
||||
|
||||
fn poll_next(
|
||||
mut self: Pin<&mut Self>,
|
||||
cx: &mut std::task::Context<'_>,
|
||||
) -> Poll<Option<Self::Item>> {
|
||||
let mut this = self.as_mut();
|
||||
|
||||
let mut reader = match this.reader.take() {
|
||||
Some(r) => r,
|
||||
None => return Poll::Ready(None),
|
||||
};
|
||||
|
||||
if this.buf.capacity() == 0 {
|
||||
let capacity = this.capacity;
|
||||
this.buf.reserve(capacity);
|
||||
}
|
||||
|
||||
match poll_read_buf(&mut reader, cx, &mut this.buf) {
|
||||
Poll::Pending => Poll::Pending,
|
||||
Poll::Ready(Err(err)) => {
|
||||
self.reader = None;
|
||||
|
||||
Poll::Ready(Some(Err(err)))
|
||||
}
|
||||
Poll::Ready(Ok(0)) => {
|
||||
self.reader = None;
|
||||
Poll::Ready(None)
|
||||
}
|
||||
Poll::Ready(Ok(_)) => {
|
||||
let chunk = this.buf.split();
|
||||
self.reader = Some(reader);
|
||||
Poll::Ready(Some(Ok(chunk.freeze())))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Implementation from https://docs.rs/tokio-util/0.7.12/src/tokio_util/util/poll_buf.rs.html
|
||||
/// Specialized for this use case
|
||||
pub fn poll_read_buf(
|
||||
io: &mut Pin<Box<dyn futures::AsyncRead + Send + Sync>>,
|
||||
cx: &mut std::task::Context<'_>,
|
||||
buf: &mut BytesMut,
|
||||
) -> Poll<std::io::Result<usize>> {
|
||||
if !buf.has_remaining_mut() {
|
||||
return Poll::Ready(Ok(0));
|
||||
}
|
||||
|
||||
let n = {
|
||||
let dst = buf.chunk_mut();
|
||||
|
||||
// Safety: `chunk_mut()` returns a `&mut UninitSlice`, and `UninitSlice` is a
|
||||
// transparent wrapper around `[MaybeUninit<u8>]`.
|
||||
let dst = unsafe { &mut *(dst as *mut _ as *mut [std::mem::MaybeUninit<u8>]) };
|
||||
let mut buf = tokio::io::ReadBuf::uninit(dst);
|
||||
let ptr = buf.filled().as_ptr();
|
||||
let unfilled_portion = buf.initialize_unfilled();
|
||||
// SAFETY: Pin projection
|
||||
let io_pin = unsafe { Pin::new_unchecked(io) };
|
||||
std::task::ready!(io_pin.poll_read(cx, unfilled_portion)?);
|
||||
|
||||
// Ensure the pointer does not change from under us
|
||||
assert_eq!(ptr, buf.filled().as_ptr());
|
||||
buf.filled().len()
|
||||
};
|
||||
|
||||
// Safety: This is guaranteed to be the number of initialized (and read)
|
||||
// bytes due to the invariants provided by `ReadBuf::filled`.
|
||||
unsafe {
|
||||
buf.advance_mut(n);
|
||||
}
|
||||
|
||||
Poll::Ready(Ok(n))
|
||||
}
|
||||
|
||||
enum WrappedBodyInner {
|
||||
None,
|
||||
SyncReader(std::io::Cursor<Cow<'static, [u8]>>),
|
||||
Stream(ReaderStream),
|
||||
}
|
||||
|
||||
struct WrappedBody(WrappedBodyInner);
|
||||
|
||||
impl WrappedBody {
|
||||
fn new(body: AsyncBody) -> Self {
|
||||
match body.0 {
|
||||
http_client::Inner::Empty => Self(WrappedBodyInner::None),
|
||||
http_client::Inner::SyncReader(cursor) => Self(WrappedBodyInner::SyncReader(cursor)),
|
||||
http_client::Inner::AsyncReader(pin) => {
|
||||
Self(WrappedBodyInner::Stream(ReaderStream::new(pin)))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl futures::stream::Stream for WrappedBody {
|
||||
type Item = Result<Bytes, std::io::Error>;
|
||||
|
||||
fn poll_next(
|
||||
mut self: std::pin::Pin<&mut Self>,
|
||||
cx: &mut std::task::Context<'_>,
|
||||
) -> std::task::Poll<Option<Self::Item>> {
|
||||
match &mut self.0 {
|
||||
WrappedBodyInner::None => Poll::Ready(None),
|
||||
WrappedBodyInner::SyncReader(cursor) => {
|
||||
let mut buf = Vec::new();
|
||||
match cursor.read_to_end(&mut buf) {
|
||||
Ok(bytes) => {
|
||||
if bytes == 0 {
|
||||
return Poll::Ready(None);
|
||||
} else {
|
||||
return Poll::Ready(Some(Ok(Bytes::from(buf))));
|
||||
}
|
||||
}
|
||||
Err(e) => return Poll::Ready(Some(Err(e))),
|
||||
}
|
||||
}
|
||||
WrappedBodyInner::Stream(stream) => {
|
||||
// SAFETY: Pin projection
|
||||
let stream = unsafe { Pin::new_unchecked(stream) };
|
||||
futures::Stream::poll_next(stream, cx)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl http_client::HttpClient for ReqwestClient {
|
||||
fn proxy(&self) -> Option<&http::Uri> {
|
||||
None
|
||||
}
|
||||
|
||||
fn type_name(&self) -> &'static str {
|
||||
type_name::<Self>()
|
||||
}
|
||||
|
||||
fn send(
|
||||
&self,
|
||||
req: http::Request<http_client::AsyncBody>,
|
||||
) -> futures::future::BoxFuture<
|
||||
'static,
|
||||
Result<http_client::Response<http_client::AsyncBody>, anyhow::Error>,
|
||||
> {
|
||||
let (parts, body) = req.into_parts();
|
||||
|
||||
let mut request = self.client.request(parts.method, parts.uri.to_string());
|
||||
|
||||
request = request.headers(parts.headers);
|
||||
|
||||
if let Some(redirect_policy) = parts.extensions.get::<http_client::RedirectPolicy>() {
|
||||
request = request.redirect_policy(match redirect_policy {
|
||||
http_client::RedirectPolicy::NoFollow => reqwest::redirect::Policy::none(),
|
||||
http_client::RedirectPolicy::FollowLimit(limit) => {
|
||||
reqwest::redirect::Policy::limited(*limit as usize)
|
||||
}
|
||||
http_client::RedirectPolicy::FollowAll => reqwest::redirect::Policy::limited(100),
|
||||
});
|
||||
}
|
||||
|
||||
if let Some(ReadTimeout(timeout)) = parts.extensions.get::<ReadTimeout>() {
|
||||
request = request.timeout(*timeout);
|
||||
}
|
||||
|
||||
let body = WrappedBody::new(body);
|
||||
let request = request.body(reqwest::Body::wrap_stream(body));
|
||||
|
||||
async move {
|
||||
let response = request.send().await.map_err(|e| anyhow!(e))?;
|
||||
let status = response.status();
|
||||
let mut builder = http::Response::builder().status(status.as_u16());
|
||||
for (name, value) in response.headers() {
|
||||
builder = builder.header(name, value);
|
||||
}
|
||||
let bytes = response.bytes_stream();
|
||||
let bytes = bytes
|
||||
.map_err(|e| futures::io::Error::new(futures::io::ErrorKind::Other, e))
|
||||
.into_async_read();
|
||||
let body = http_client::AsyncBody::from_reader(bytes);
|
||||
builder.body(body).map_err(|e| anyhow!(e))
|
||||
}
|
||||
.boxed()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
|
||||
use core::str;
|
||||
|
||||
use http_client::AsyncBody;
|
||||
use smol::stream::StreamExt;
|
||||
|
||||
use crate::WrappedBody;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_sync_streaming_upload() {
|
||||
let mut body = WrappedBody::new(AsyncBody::from("hello there".to_string())).fuse();
|
||||
let result = body.next().await.unwrap().unwrap();
|
||||
assert!(body.next().await.is_none());
|
||||
assert_eq!(str::from_utf8(&result).unwrap(), "hello there");
|
||||
}
|
||||
}
|
|
@ -51,6 +51,7 @@ workspace.workspace = true
|
|||
worktree.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
isahc_http_client.workspace = true
|
||||
client = { workspace = true, features = ["test-support"] }
|
||||
env_logger.workspace = true
|
||||
fs = { workspace = true, features = ["test-support"] }
|
||||
|
@ -61,7 +62,6 @@ language = { workspace = true, features = ["test-support"] }
|
|||
languages.workspace = true
|
||||
project = { workspace = true, features = ["test-support"] }
|
||||
tempfile.workspace = true
|
||||
ureq_client.workspace = true
|
||||
util = { workspace = true, features = ["test-support"] }
|
||||
workspace = { workspace = true, features = ["test-support"] }
|
||||
worktree = { workspace = true, features = ["test-support"] }
|
||||
|
|
|
@ -2,6 +2,7 @@ use client::Client;
|
|||
use futures::channel::oneshot;
|
||||
use gpui::App;
|
||||
use http_client::HttpClientWithUrl;
|
||||
use isahc_http_client::IsahcHttpClient;
|
||||
use language::language_settings::AllLanguageSettings;
|
||||
use project::Project;
|
||||
use semantic_index::{OpenAiEmbeddingModel, OpenAiEmbeddingProvider, SemanticDb};
|
||||
|
@ -28,11 +29,7 @@ fn main() {
|
|||
let clock = Arc::new(FakeSystemClock::default());
|
||||
|
||||
let http = Arc::new(HttpClientWithUrl::new(
|
||||
Arc::new(ureq_client::UreqClient::new(
|
||||
None,
|
||||
"Zed semantic index example".to_string(),
|
||||
cx.background_executor().clone(),
|
||||
)),
|
||||
IsahcHttpClient::new(None, None),
|
||||
"http://localhost:11434",
|
||||
None,
|
||||
));
|
||||
|
|
|
@ -22,6 +22,7 @@ editor.workspace = true
|
|||
fuzzy.workspace = true
|
||||
gpui.workspace = true
|
||||
indoc.workspace = true
|
||||
isahc_http_client.workspace = true
|
||||
language.workspace = true
|
||||
log.workspace = true
|
||||
menu.workspace = true
|
||||
|
@ -35,7 +36,6 @@ strum = { workspace = true, features = ["derive"] }
|
|||
theme.workspace = true
|
||||
title_bar = { workspace = true, features = ["stories"] }
|
||||
ui = { workspace = true, features = ["stories"] }
|
||||
ureq_client.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
gpui = { workspace = true, features = ["test-support"] }
|
||||
|
|
|
@ -4,14 +4,13 @@ mod assets;
|
|||
mod stories;
|
||||
mod story_selector;
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use clap::Parser;
|
||||
use dialoguer::FuzzySelect;
|
||||
use gpui::{
|
||||
div, px, size, AnyView, AppContext, Bounds, Render, ViewContext, VisualContext, WindowBounds,
|
||||
WindowOptions,
|
||||
};
|
||||
use isahc_http_client::IsahcHttpClient;
|
||||
use log::LevelFilter;
|
||||
use project::Project;
|
||||
use settings::{KeymapFile, Settings};
|
||||
|
@ -19,7 +18,6 @@ use simplelog::SimpleLogger;
|
|||
use strum::IntoEnumIterator;
|
||||
use theme::{ThemeRegistry, ThemeSettings};
|
||||
use ui::prelude::*;
|
||||
use ureq_client::UreqClient;
|
||||
|
||||
use crate::app_menus::app_menus;
|
||||
use crate::assets::Assets;
|
||||
|
@ -68,12 +66,8 @@ fn main() {
|
|||
gpui::App::new().with_assets(Assets).run(move |cx| {
|
||||
load_embedded_fonts(cx).unwrap();
|
||||
|
||||
let http_client = UreqClient::new(
|
||||
None,
|
||||
"zed_storybook".to_string(),
|
||||
cx.background_executor().clone(),
|
||||
);
|
||||
cx.set_http_client(Arc::new(http_client));
|
||||
let http_client = IsahcHttpClient::new(None, Some("zed_storybook".to_string()));
|
||||
cx.set_http_client(http_client);
|
||||
|
||||
settings::init(cx);
|
||||
theme::init(theme::LoadThemes::All(Box::new(Assets)), cx);
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
../../LICENSE-GPL
|
|
@ -1,24 +0,0 @@
|
|||
use futures::AsyncReadExt;
|
||||
use http_client::{AsyncBody, HttpClient};
|
||||
use ureq_client::UreqClient;
|
||||
|
||||
fn main() {
|
||||
gpui::App::headless().run(|cx| {
|
||||
println!("{:?}", std::thread::current().id());
|
||||
cx.spawn(|cx| async move {
|
||||
let resp = UreqClient::new(
|
||||
None,
|
||||
"Conrad's bot".to_string(),
|
||||
cx.background_executor().clone(),
|
||||
)
|
||||
.get("http://zed.dev", AsyncBody::empty(), true)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let mut body = String::new();
|
||||
resp.into_body().read_to_string(&mut body).await.unwrap();
|
||||
println!("{}", body);
|
||||
})
|
||||
.detach();
|
||||
})
|
||||
}
|
|
@ -1,200 +0,0 @@
|
|||
use std::any::type_name;
|
||||
use std::collections::HashMap;
|
||||
use std::io::Read;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use std::{pin::Pin, task::Poll};
|
||||
|
||||
use anyhow::Error;
|
||||
use futures::channel::mpsc;
|
||||
use futures::future::BoxFuture;
|
||||
use futures::{AsyncRead, SinkExt, StreamExt};
|
||||
use http_client::{http, AsyncBody, HttpClient, RedirectPolicy, Uri};
|
||||
use smol::future::FutureExt;
|
||||
use util::ResultExt;
|
||||
|
||||
pub struct UreqClient {
|
||||
// Note in ureq 2.x the options are stored on the Agent.
|
||||
// In ureq 3.x we'll be able to set these on the request.
|
||||
// In practice it's probably "fine" to have many clients, the number of distinct options
|
||||
// is low; and most requests to the same connection will have the same options so the
|
||||
// connection pool will work.
|
||||
clients: Arc<parking_lot::Mutex<HashMap<(Duration, RedirectPolicy), ureq::Agent>>>,
|
||||
proxy_url: Option<Uri>,
|
||||
proxy: Option<ureq::Proxy>,
|
||||
user_agent: String,
|
||||
background_executor: gpui::BackgroundExecutor,
|
||||
}
|
||||
|
||||
impl UreqClient {
|
||||
pub fn new(
|
||||
proxy_url: Option<Uri>,
|
||||
user_agent: String,
|
||||
background_executor: gpui::BackgroundExecutor,
|
||||
) -> Self {
|
||||
Self {
|
||||
clients: Arc::default(),
|
||||
proxy_url: proxy_url.clone(),
|
||||
proxy: proxy_url.and_then(|url| ureq::Proxy::new(url.to_string()).log_err()),
|
||||
user_agent,
|
||||
background_executor,
|
||||
}
|
||||
}
|
||||
|
||||
fn agent_for(&self, redirect_policy: RedirectPolicy, timeout: Duration) -> ureq::Agent {
|
||||
let mut clients = self.clients.lock();
|
||||
// in case our assumption of distinct options is wrong, we'll sporadically clean it out.
|
||||
if clients.len() > 50 {
|
||||
clients.clear()
|
||||
}
|
||||
|
||||
clients
|
||||
.entry((timeout, redirect_policy.clone()))
|
||||
.or_insert_with(|| {
|
||||
let mut builder = ureq::AgentBuilder::new()
|
||||
.timeout_connect(Duration::from_secs(5))
|
||||
.timeout_read(timeout)
|
||||
.timeout_write(timeout)
|
||||
.user_agent(&self.user_agent)
|
||||
.tls_config(http_client::TLS_CONFIG.clone())
|
||||
.redirects(match redirect_policy {
|
||||
RedirectPolicy::NoFollow => 0,
|
||||
RedirectPolicy::FollowLimit(limit) => limit,
|
||||
RedirectPolicy::FollowAll => 100,
|
||||
});
|
||||
if let Some(proxy) = &self.proxy {
|
||||
builder = builder.proxy(proxy.clone());
|
||||
}
|
||||
builder.build()
|
||||
})
|
||||
.clone()
|
||||
}
|
||||
}
|
||||
impl HttpClient for UreqClient {
|
||||
fn proxy(&self) -> Option<&Uri> {
|
||||
self.proxy_url.as_ref()
|
||||
}
|
||||
|
||||
fn type_name(&self) -> &'static str {
|
||||
type_name::<Self>()
|
||||
}
|
||||
|
||||
fn send(
|
||||
&self,
|
||||
request: http::Request<AsyncBody>,
|
||||
) -> BoxFuture<'static, Result<http::Response<AsyncBody>, Error>> {
|
||||
let agent = self.agent_for(
|
||||
request
|
||||
.extensions()
|
||||
.get::<RedirectPolicy>()
|
||||
.cloned()
|
||||
.unwrap_or_default(),
|
||||
request
|
||||
.extensions()
|
||||
.get::<http_client::ReadTimeout>()
|
||||
.cloned()
|
||||
.unwrap_or_default()
|
||||
.0,
|
||||
);
|
||||
let mut req = agent.request(&request.method().as_ref(), &request.uri().to_string());
|
||||
for (name, value) in request.headers().into_iter() {
|
||||
req = req.set(name.as_str(), value.to_str().unwrap());
|
||||
}
|
||||
let body = request.into_body();
|
||||
let executor = self.background_executor.clone();
|
||||
|
||||
self.background_executor
|
||||
.spawn(async move {
|
||||
let response = match req.send(body) {
|
||||
Ok(response) => response,
|
||||
Err(e) => match e {
|
||||
ureq::Error::Status(_, response) => response,
|
||||
ureq::Error::Transport(transport) => {
|
||||
anyhow::bail!(transport)
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
let mut builder = http::Response::builder()
|
||||
.status(response.status())
|
||||
.version(http::Version::HTTP_11);
|
||||
for name in response.headers_names() {
|
||||
if let Some(value) = response.header(&name) {
|
||||
builder = builder.header(name, value);
|
||||
}
|
||||
}
|
||||
|
||||
let body = AsyncBody::from_reader(UreqResponseReader::new(executor, response));
|
||||
let http_response = builder.body(body)?;
|
||||
|
||||
Ok(http_response)
|
||||
})
|
||||
.boxed()
|
||||
}
|
||||
}
|
||||
|
||||
struct UreqResponseReader {
|
||||
receiver: mpsc::Receiver<std::io::Result<Vec<u8>>>,
|
||||
buffer: Vec<u8>,
|
||||
idx: usize,
|
||||
_task: gpui::Task<()>,
|
||||
}
|
||||
|
||||
impl UreqResponseReader {
|
||||
fn new(background_executor: gpui::BackgroundExecutor, response: ureq::Response) -> Self {
|
||||
let (mut sender, receiver) = mpsc::channel(1);
|
||||
let mut reader = response.into_reader();
|
||||
let task = background_executor.spawn(async move {
|
||||
let mut buffer = vec![0; 8192];
|
||||
loop {
|
||||
let n = match reader.read(&mut buffer) {
|
||||
Ok(0) => break,
|
||||
Ok(n) => n,
|
||||
Err(e) => {
|
||||
let _ = sender.send(Err(e)).await;
|
||||
break;
|
||||
}
|
||||
};
|
||||
let _ = sender.send(Ok(buffer[..n].to_vec())).await;
|
||||
}
|
||||
});
|
||||
|
||||
UreqResponseReader {
|
||||
_task: task,
|
||||
receiver,
|
||||
buffer: Vec::new(),
|
||||
idx: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AsyncRead for UreqResponseReader {
|
||||
fn poll_read(
|
||||
mut self: Pin<&mut Self>,
|
||||
cx: &mut std::task::Context<'_>,
|
||||
buf: &mut [u8],
|
||||
) -> Poll<std::io::Result<usize>> {
|
||||
if self.buffer.is_empty() {
|
||||
match self.receiver.poll_next_unpin(cx) {
|
||||
Poll::Ready(Some(Ok(data))) => self.buffer = data,
|
||||
Poll::Ready(Some(Err(e))) => {
|
||||
return Poll::Ready(Err(e));
|
||||
}
|
||||
Poll::Ready(None) => {
|
||||
return Poll::Ready(Ok(0));
|
||||
}
|
||||
Poll::Pending => {
|
||||
return Poll::Pending;
|
||||
}
|
||||
}
|
||||
}
|
||||
let n = std::cmp::min(buf.len(), self.buffer.len() - self.idx);
|
||||
buf[..n].copy_from_slice(&self.buffer[self.idx..self.idx + n]);
|
||||
self.idx += n;
|
||||
if self.idx == self.buffer.len() {
|
||||
self.buffer.clear();
|
||||
self.idx = 0;
|
||||
}
|
||||
Poll::Ready(Ok(n))
|
||||
}
|
||||
}
|
|
@ -17,7 +17,7 @@ neovim = ["nvim-rs", "async-compat", "async-trait", "tokio"]
|
|||
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
async-compat = { workspace = true, "optional" = true }
|
||||
async-compat = { version = "0.2.1", "optional" = true }
|
||||
async-trait = { workspace = true, "optional" = true }
|
||||
collections.workspace = true
|
||||
command_palette.workspace = true
|
||||
|
|
|
@ -57,6 +57,7 @@ http_client.workspace = true
|
|||
image_viewer.workspace = true
|
||||
inline_completion_button.workspace = true
|
||||
install_cli.workspace = true
|
||||
isahc_http_client.workspace = true
|
||||
journal.workspace = true
|
||||
language.workspace = true
|
||||
language_model.workspace = true
|
||||
|
@ -107,7 +108,6 @@ theme.workspace = true
|
|||
theme_selector.workspace = true
|
||||
time.workspace = true
|
||||
ui.workspace = true
|
||||
ureq_client.workspace = true
|
||||
url.workspace = true
|
||||
urlencoding = "2.1.2"
|
||||
util.workspace = true
|
||||
|
|
|
@ -24,9 +24,9 @@ use gpui::{
|
|||
UpdateGlobal as _, VisualContext,
|
||||
};
|
||||
use http_client::{read_proxy_from_env, Uri};
|
||||
use isahc_http_client::IsahcHttpClient;
|
||||
use language::LanguageRegistry;
|
||||
use log::LevelFilter;
|
||||
use ureq_client::UreqClient;
|
||||
|
||||
use assets::Assets;
|
||||
use node_runtime::{NodeBinaryOptions, NodeRuntime};
|
||||
|
@ -334,7 +334,9 @@ fn main() {
|
|||
|
||||
log::info!("========== starting zed ==========");
|
||||
|
||||
let app = App::new().with_assets(Assets);
|
||||
let app = App::new()
|
||||
.with_assets(Assets)
|
||||
.with_http_client(IsahcHttpClient::new(None, None));
|
||||
|
||||
let system_id = app.background_executor().block(system_id()).ok();
|
||||
let installation_id = app.background_executor().block(installation_id()).ok();
|
||||
|
@ -468,8 +470,8 @@ fn main() {
|
|||
.ok()
|
||||
})
|
||||
.or_else(read_proxy_from_env);
|
||||
let http = UreqClient::new(proxy_url, user_agent, cx.background_executor().clone());
|
||||
cx.set_http_client(Arc::new(http));
|
||||
let http = IsahcHttpClient::new(proxy_url, Some(user_agent));
|
||||
cx.set_http_client(http);
|
||||
|
||||
<dyn Fs>::set_global(fs.clone(), cx);
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue