From e28496d4e2ef581def4854b1e7c4df8cbb542251 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Thu, 26 Sep 2024 14:01:05 -0600 Subject: [PATCH] Stop leaking isahc assumption (#18408) Users of our http_client crate knew they were interacting with isahc as they set its extensions on the request. This change adds our own equivalents for their APIs in preparation for changing the default http client. Release Notes: - N/A --- Cargo.lock | 8 -- crates/anthropic/Cargo.toml | 1 - crates/anthropic/src/anthropic.rs | 7 +- crates/copilot/Cargo.toml | 1 - crates/copilot/src/copilot_chat.rs | 7 +- crates/extension/Cargo.toml | 1 - crates/extension/src/extension_store.rs | 2 +- .../src/wasm_host/wit/since_v0_1_0.rs | 13 +-- .../src/wasm_host/wit/since_v0_2_0.rs | 13 +-- crates/feedback/Cargo.toml | 1 - crates/feedback/src/feedback_modal.rs | 3 +- .../src/providers/codeberg.rs | 8 +- .../src/providers/github.rs | 8 +- crates/google_ai/Cargo.toml | 1 - crates/google_ai/src/google_ai.rs | 7 +- crates/gpui/src/app.rs | 3 +- crates/http_client/src/http_client.rs | 90 +++++++++++-------- .../src/isahc_http_client.rs | 26 ++++-- crates/language_model/Cargo.toml | 1 - crates/language_model/src/provider/cloud.rs | 5 +- crates/open_ai/Cargo.toml | 1 - crates/open_ai/src/open_ai.rs | 7 +- crates/zed/Cargo.toml | 1 - crates/zed/src/reliability.rs | 5 +- 24 files changed, 114 insertions(+), 106 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 68dad1f74613f192a1bb82f8b8011da6a13ef5a9..85a62c9519e0122176149e6b85c36b95f00731bc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -245,7 +245,6 @@ dependencies = [ "chrono", "futures 0.3.30", "http_client", - "isahc", "schemars", "serde", "serde_json", @@ -2850,7 +2849,6 @@ dependencies = [ "gpui", "http_client", "indoc", - "isahc", "language", "lsp", "menu", @@ -4128,7 +4126,6 @@ dependencies = [ "gpui", "http_client", "indexed_docs", - "isahc", "isahc_http_client", "language", "log", @@ -4289,7 +4286,6 @@ dependencies = [ "gpui", "http_client", "human_bytes", - "isahc", "language", "log", "menu", @@ -5016,7 +5012,6 @@ dependencies = [ "anyhow", "futures 0.3.30", "http_client", - "isahc", "schemars", "serde", "serde_json", @@ -6288,7 +6283,6 @@ dependencies = [ "http_client", "image", "inline_completion_button", - "isahc", "language", "log", "menu", @@ -7591,7 +7585,6 @@ dependencies = [ "anyhow", "futures 0.3.30", "http_client", - "isahc", "schemars", "serde", "serde_json", @@ -14435,7 +14428,6 @@ dependencies = [ "image_viewer", "inline_completion_button", "install_cli", - "isahc", "isahc_http_client", "journal", "language", diff --git a/crates/anthropic/Cargo.toml b/crates/anthropic/Cargo.toml index 9e48ad0e57d81d1434d3e872e84edcab7f233900..ec12932fb74f1e7aeea9194f993e245a813447f4 100644 --- a/crates/anthropic/Cargo.toml +++ b/crates/anthropic/Cargo.toml @@ -20,7 +20,6 @@ anyhow.workspace = true chrono.workspace = true futures.workspace = true http_client.workspace = true -isahc.workspace = true schemars = { workspace = true, optional = true } serde.workspace = true serde_json.workspace = true diff --git a/crates/anthropic/src/anthropic.rs b/crates/anthropic/src/anthropic.rs index 91b6723e90be97c858ed3992dd5fcc47a418dcf7..6b8972284208a1819996e65cd9e5279d15c6dde2 100644 --- a/crates/anthropic/src/anthropic.rs +++ b/crates/anthropic/src/anthropic.rs @@ -6,9 +6,8 @@ use std::{pin::Pin, str::FromStr}; use anyhow::{anyhow, Context, Result}; use chrono::{DateTime, Utc}; use futures::{io::BufReader, stream::BoxStream, AsyncBufReadExt, AsyncReadExt, Stream, StreamExt}; -use http_client::{AsyncBody, HttpClient, Method, Request as HttpRequest}; -use isahc::config::Configurable; -use isahc::http::{HeaderMap, HeaderValue}; +use http_client::http::{HeaderMap, HeaderValue}; +use http_client::{AsyncBody, HttpClient, HttpRequestExt, Method, Request as HttpRequest}; use serde::{Deserialize, Serialize}; use strum::{EnumIter, EnumString}; use thiserror::Error; @@ -289,7 +288,7 @@ pub async fn stream_completion_with_rate_limit_info( .header("X-Api-Key", api_key) .header("Content-Type", "application/json"); if let Some(low_speed_timeout) = low_speed_timeout { - request_builder = request_builder.low_speed_timeout(100, low_speed_timeout); + request_builder = request_builder.read_timeout(low_speed_timeout); } let serialized_request = serde_json::to_string(&request).context("failed to serialize request")?; diff --git a/crates/copilot/Cargo.toml b/crates/copilot/Cargo.toml index 54abbaa112060b48e51a94967893575ab18660ef..2a54497562a243c0ff6dab735dc13f6ae5e515c2 100644 --- a/crates/copilot/Cargo.toml +++ b/crates/copilot/Cargo.toml @@ -37,7 +37,6 @@ fs.workspace = true futures.workspace = true gpui.workspace = true http_client.workspace = true -isahc.workspace = true language.workspace = true lsp.workspace = true menu.workspace = true diff --git a/crates/copilot/src/copilot_chat.rs b/crates/copilot/src/copilot_chat.rs index 5d80c89a6649dd381c191a194f6425a187cf5472..c5ba1bfc6a5895d63b565d7ee7733ddf911f7d99 100644 --- a/crates/copilot/src/copilot_chat.rs +++ b/crates/copilot/src/copilot_chat.rs @@ -7,8 +7,7 @@ use chrono::DateTime; use fs::Fs; use futures::{io::BufReader, stream::BoxStream, AsyncBufReadExt, AsyncReadExt, StreamExt}; use gpui::{AppContext, AsyncAppContext, Global}; -use http_client::{AsyncBody, HttpClient, Method, Request as HttpRequest}; -use isahc::config::Configurable; +use http_client::{AsyncBody, HttpClient, HttpRequestExt, Method, Request as HttpRequest}; use paths::home_dir; use serde::{Deserialize, Serialize}; use settings::watch_config_file; @@ -275,7 +274,7 @@ async fn request_api_token( .header("Accept", "application/json"); if let Some(low_speed_timeout) = low_speed_timeout { - request_builder = request_builder.low_speed_timeout(100, low_speed_timeout); + request_builder = request_builder.read_timeout(low_speed_timeout); } let request = request_builder.body(AsyncBody::empty())?; @@ -332,7 +331,7 @@ async fn stream_completion( .header("Copilot-Integration-Id", "vscode-chat"); if let Some(low_speed_timeout) = low_speed_timeout { - request_builder = request_builder.low_speed_timeout(100, low_speed_timeout); + request_builder = request_builder.read_timeout(low_speed_timeout); } let request = request_builder.body(AsyncBody::from(serde_json::to_string(&request)?))?; let mut response = client.send(request).await?; diff --git a/crates/extension/Cargo.toml b/crates/extension/Cargo.toml index edf6184d38475d50f76f14946199ecdd96ab6f51..6ce1bd6862a1ddecda2bbde8fe889d13d71e96c2 100644 --- a/crates/extension/Cargo.toml +++ b/crates/extension/Cargo.toml @@ -28,7 +28,6 @@ futures.workspace = true gpui.workspace = true http_client.workspace = true indexed_docs.workspace = true -isahc.workspace = true language.workspace = true log.workspace = true lsp.workspace = true diff --git a/crates/extension/src/extension_store.rs b/crates/extension/src/extension_store.rs index 5f9fbffb11b2ecea5d1c45b79d63ea209a4723c9..535d68326f9c3eda899677b8930cdd353d6cc4a2 100644 --- a/crates/extension/src/extension_store.rs +++ b/crates/extension/src/extension_store.rs @@ -664,7 +664,7 @@ impl ExtensionStore { let content_length = response .headers() - .get(isahc::http::header::CONTENT_LENGTH) + .get(http_client::http::header::CONTENT_LENGTH) .and_then(|value| value.to_str().ok()?.parse::().ok()); let mut body = BufReader::new(response.body_mut()); diff --git a/crates/extension/src/wasm_host/wit/since_v0_1_0.rs b/crates/extension/src/wasm_host/wit/since_v0_1_0.rs index 3835f58f885290fbcd50c7b2e75234aa248e47ec..862e2e7c7f78947e74f6fcf26e23026223a11ab9 100644 --- a/crates/extension/src/wasm_host/wit/since_v0_1_0.rs +++ b/crates/extension/src/wasm_host/wit/since_v0_1_0.rs @@ -1,5 +1,5 @@ use crate::wasm_host::{wit::ToWasmtimeResult, WasmState}; -use ::http_client::AsyncBody; +use ::http_client::{AsyncBody, HttpRequestExt}; use ::settings::{Settings, WorktreeId}; use anyhow::{anyhow, bail, Context, Result}; use async_compression::futures::bufread::GzipDecoder; @@ -8,7 +8,6 @@ use async_trait::async_trait; use futures::{io::BufReader, FutureExt as _}; use futures::{lock::Mutex, AsyncReadExt}; use indexed_docs::IndexedDocsDatabase; -use isahc::config::{Configurable, RedirectPolicy}; use language::{ language_settings::AllLanguageSettings, LanguageServerBinaryStatus, LspAdapterDelegate, }; @@ -297,10 +296,12 @@ fn convert_request( let mut request = ::http_client::Request::builder() .method(::http_client::Method::from(extension_request.method)) .uri(&extension_request.url) - .redirect_policy(match extension_request.redirect_policy { - http_client::RedirectPolicy::NoFollow => RedirectPolicy::None, - http_client::RedirectPolicy::FollowLimit(limit) => RedirectPolicy::Limit(limit), - http_client::RedirectPolicy::FollowAll => RedirectPolicy::Follow, + .follow_redirects(match extension_request.redirect_policy { + http_client::RedirectPolicy::NoFollow => ::http_client::RedirectPolicy::NoFollow, + http_client::RedirectPolicy::FollowLimit(limit) => { + ::http_client::RedirectPolicy::FollowLimit(limit) + } + http_client::RedirectPolicy::FollowAll => ::http_client::RedirectPolicy::FollowAll, }); for (key, value) in &extension_request.headers { request = request.header(key, value); diff --git a/crates/extension/src/wasm_host/wit/since_v0_2_0.rs b/crates/extension/src/wasm_host/wit/since_v0_2_0.rs index eb6e1a09a2ae993304f7f14ee2947034689e9c2d..e7f5432e1d32cc10732875a3b72318fc3afd678d 100644 --- a/crates/extension/src/wasm_host/wit/since_v0_2_0.rs +++ b/crates/extension/src/wasm_host/wit/since_v0_2_0.rs @@ -1,5 +1,5 @@ use crate::wasm_host::{wit::ToWasmtimeResult, WasmState}; -use ::http_client::AsyncBody; +use ::http_client::{AsyncBody, HttpRequestExt}; use ::settings::{Settings, WorktreeId}; use anyhow::{anyhow, bail, Context, Result}; use async_compression::futures::bufread::GzipDecoder; @@ -8,7 +8,6 @@ use async_trait::async_trait; use futures::{io::BufReader, FutureExt as _}; use futures::{lock::Mutex, AsyncReadExt}; use indexed_docs::IndexedDocsDatabase; -use isahc::config::{Configurable, RedirectPolicy}; use language::{ language_settings::AllLanguageSettings, LanguageServerBinaryStatus, LspAdapterDelegate, }; @@ -213,10 +212,12 @@ fn convert_request( let mut request = ::http_client::Request::builder() .method(::http_client::Method::from(extension_request.method)) .uri(&extension_request.url) - .redirect_policy(match extension_request.redirect_policy { - http_client::RedirectPolicy::NoFollow => RedirectPolicy::None, - http_client::RedirectPolicy::FollowLimit(limit) => RedirectPolicy::Limit(limit), - http_client::RedirectPolicy::FollowAll => RedirectPolicy::Follow, + .follow_redirects(match extension_request.redirect_policy { + http_client::RedirectPolicy::NoFollow => ::http_client::RedirectPolicy::NoFollow, + http_client::RedirectPolicy::FollowLimit(limit) => { + ::http_client::RedirectPolicy::FollowLimit(limit) + } + http_client::RedirectPolicy::FollowAll => ::http_client::RedirectPolicy::FollowAll, }); for (key, value) in &extension_request.headers { request = request.header(key, value); diff --git a/crates/feedback/Cargo.toml b/crates/feedback/Cargo.toml index 83c726e3e9ab4a37bd5821879a929743789b0246..0447858ca53b4f88c7bd38768bec8bcc88043288 100644 --- a/crates/feedback/Cargo.toml +++ b/crates/feedback/Cargo.toml @@ -23,7 +23,6 @@ editor.workspace = true futures.workspace = true gpui.workspace = true human_bytes = "0.4.1" -isahc.workspace = true http_client.workspace = true language.workspace = true log.workspace = true diff --git a/crates/feedback/src/feedback_modal.rs b/crates/feedback/src/feedback_modal.rs index 4762b228d3e44f98016bc365efc240b3b2699219..5270492aee5c333fee856adb563e9bc9d593643a 100644 --- a/crates/feedback/src/feedback_modal.rs +++ b/crates/feedback/src/feedback_modal.rs @@ -11,7 +11,6 @@ use gpui::{ PromptLevel, Render, Task, View, ViewContext, }; use http_client::HttpClient; -use isahc::Request; use language::Buffer; use project::Project; use regex::Regex; @@ -299,7 +298,7 @@ impl FeedbackModal { is_staff: is_staff.unwrap_or(false), }; let json_bytes = serde_json::to_vec(&request)?; - let request = Request::post(feedback_endpoint) + let request = http_client::http::Request::post(feedback_endpoint) .header("content-type", "application/json") .body(json_bytes.into())?; let mut response = http_client.send(request).await?; diff --git a/crates/git_hosting_providers/src/providers/codeberg.rs b/crates/git_hosting_providers/src/providers/codeberg.rs index eaadca1ecf9618ca7499aed3f9c51126e55e441f..3f6a016f68fd4acf94f9a927f70a330503295d12 100644 --- a/crates/git_hosting_providers/src/providers/codeberg.rs +++ b/crates/git_hosting_providers/src/providers/codeberg.rs @@ -3,7 +3,7 @@ use std::sync::Arc; use anyhow::{bail, Context, Result}; use async_trait::async_trait; use futures::AsyncReadExt; -use http_client::{AsyncBody, HttpClient, Request}; +use http_client::{AsyncBody, HttpClient, HttpRequestExt, Request}; use serde::Deserialize; use url::Url; @@ -49,14 +49,16 @@ impl Codeberg { let url = format!("https://codeberg.org/api/v1/repos/{repo_owner}/{repo}/git/commits/{commit}"); - let mut request = Request::get(&url).header("Content-Type", "application/json"); + let mut request = Request::get(&url) + .header("Content-Type", "application/json") + .follow_redirects(http_client::RedirectPolicy::FollowAll); if let Ok(codeberg_token) = std::env::var("CODEBERG_TOKEN") { request = request.header("Authorization", format!("Bearer {}", codeberg_token)); } let mut response = client - .send_with_redirect_policy(request.body(AsyncBody::default())?, true) + .send(request.body(AsyncBody::default())?) .await .with_context(|| format!("error fetching Codeberg commit details at {:?}", url))?; diff --git a/crates/git_hosting_providers/src/providers/github.rs b/crates/git_hosting_providers/src/providers/github.rs index 77eaa80961e61c97fa32740eec88cda5e35414e6..4078025fa004fcb1afe1f5d693226b2e884c5d8e 100644 --- a/crates/git_hosting_providers/src/providers/github.rs +++ b/crates/git_hosting_providers/src/providers/github.rs @@ -3,7 +3,7 @@ use std::sync::{Arc, OnceLock}; use anyhow::{bail, Context, Result}; use async_trait::async_trait; use futures::AsyncReadExt; -use http_client::{AsyncBody, HttpClient, Request}; +use http_client::{AsyncBody, HttpClient, HttpRequestExt, Request}; use regex::Regex; use serde::Deserialize; use url::Url; @@ -53,14 +53,16 @@ impl Github { ) -> Result> { let url = format!("https://api.github.com/repos/{repo_owner}/{repo}/commits/{commit}"); - let mut request = Request::get(&url).header("Content-Type", "application/json"); + let mut request = Request::get(&url) + .header("Content-Type", "application/json") + .follow_redirects(http_client::RedirectPolicy::FollowAll); if let Ok(github_token) = std::env::var("GITHUB_TOKEN") { request = request.header("Authorization", format!("Bearer {}", github_token)); } let mut response = client - .send_with_redirect_policy(request.body(AsyncBody::default())?, true) + .send(request.body(AsyncBody::default())?) .await .with_context(|| format!("error fetching GitHub commit details at {:?}", url))?; diff --git a/crates/google_ai/Cargo.toml b/crates/google_ai/Cargo.toml index 2a52f1968dcb6b7919891f2c38170bdc4cfc2da6..f923e0ec91742652a70e9e99ddd6604321e401a5 100644 --- a/crates/google_ai/Cargo.toml +++ b/crates/google_ai/Cargo.toml @@ -18,7 +18,6 @@ schemars = ["dep:schemars"] anyhow.workspace = true futures.workspace = true http_client.workspace = true -isahc.workspace = true schemars = { workspace = true, optional = true } serde.workspace = true serde_json.workspace = true diff --git a/crates/google_ai/src/google_ai.rs b/crates/google_ai/src/google_ai.rs index f1dcedf5b31e0cd82c2d5b6d2ab5c9bfe607b833..7991c67956bb858fd3ab772e45df922d3d93c04a 100644 --- a/crates/google_ai/src/google_ai.rs +++ b/crates/google_ai/src/google_ai.rs @@ -2,8 +2,7 @@ mod supported_countries; use anyhow::{anyhow, Result}; use futures::{io::BufReader, stream::BoxStream, AsyncBufReadExt, AsyncReadExt, Stream, StreamExt}; -use http_client::{AsyncBody, HttpClient, Method, Request as HttpRequest}; -use isahc::config::Configurable; +use http_client::{AsyncBody, HttpClient, HttpRequestExt, Method, Request as HttpRequest}; use serde::{Deserialize, Serialize}; use std::time::Duration; @@ -30,7 +29,7 @@ pub async fn stream_generate_content( .header("Content-Type", "application/json"); if let Some(low_speed_timeout) = low_speed_timeout { - request_builder = request_builder.low_speed_timeout(100, low_speed_timeout); + request_builder = request_builder.read_timeout(low_speed_timeout); }; let request = request_builder.body(AsyncBody::from(serde_json::to_string(&request)?))?; @@ -85,7 +84,7 @@ pub async fn count_tokens( .header("Content-Type", "application/json"); if let Some(low_speed_timeout) = low_speed_timeout { - request_builder = request_builder.low_speed_timeout(100, low_speed_timeout); + request_builder = request_builder.read_timeout(low_speed_timeout); } let http_request = request_builder.body(AsyncBody::from(request))?; diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index 6cb491b100810b0300243a006be2e7af6e36765a..540e459ce1a31406e1cb156c4b877fac86cbfda0 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -1524,10 +1524,9 @@ pub struct KeystrokeEvent { struct NullHttpClient; impl HttpClient for NullHttpClient { - fn send_with_redirect_policy( + fn send( &self, _req: http_client::Request, - _follow_redirects: bool, ) -> futures::future::BoxFuture< 'static, Result, anyhow::Error>, diff --git a/crates/http_client/src/http_client.rs b/crates/http_client/src/http_client.rs index c0630151519c5aad064b001c5e51ae0733a983f7..2f029a1d236bbaca606cc428c59e5c5c81e8b6ac 100644 --- a/crates/http_client/src/http_client.rs +++ b/crates/http_client/src/http_client.rs @@ -10,22 +10,46 @@ use futures::future::BoxFuture; use http::request::Builder; #[cfg(feature = "test-support")] use std::fmt; -use std::sync::{Arc, Mutex}; +use std::{ + sync::{Arc, Mutex}, + time::Duration, +}; pub use url::Url; +pub struct ReadTimeout(pub Duration); +#[derive(Default, Debug, Clone)] +pub enum RedirectPolicy { + #[default] + NoFollow, + FollowLimit(u32), + FollowAll, +} +pub struct FollowRedirects(pub bool); + +pub trait HttpRequestExt { + /// Set a read timeout on the request. + /// For isahc, this is the low_speed_timeout. + /// For other clients, this is the timeout used for read calls when reading the response. + /// In all cases this prevents servers stalling completely, but allows them to send data slowly. + fn read_timeout(self, timeout: Duration) -> Self; + /// Whether or not to follow redirects + fn follow_redirects(self, follow: RedirectPolicy) -> Self; +} + +impl HttpRequestExt for http::request::Builder { + fn read_timeout(self, timeout: Duration) -> Self { + self.extension(ReadTimeout(timeout)) + } + + fn follow_redirects(self, follow: RedirectPolicy) -> Self { + self.extension(follow) + } +} + pub trait HttpClient: 'static + Send + Sync { fn send( &self, req: http::Request, - ) -> BoxFuture<'static, Result, anyhow::Error>> { - self.send_with_redirect_policy(req, false) - } - - // TODO: Make a better API for this - fn send_with_redirect_policy( - &self, - req: Request, - follow_redirects: bool, ) -> BoxFuture<'static, Result, anyhow::Error>>; fn get<'a>( @@ -34,14 +58,17 @@ pub trait HttpClient: 'static + Send + Sync { body: AsyncBody, follow_redirects: bool, ) -> BoxFuture<'a, Result, anyhow::Error>> { - let request = Builder::new().uri(uri).body(body); + let request = Builder::new() + .uri(uri) + .follow_redirects(if follow_redirects { + RedirectPolicy::FollowAll + } else { + RedirectPolicy::NoFollow + }) + .body(body); match request { - Ok(request) => Box::pin(async move { - self.send_with_redirect_policy(request, follow_redirects) - .await - .map_err(Into::into) - }), + Ok(request) => Box::pin(async move { self.send(request).await.map_err(Into::into) }), Err(e) => Box::pin(async move { Err(e.into()) }), } } @@ -92,12 +119,11 @@ impl HttpClientWithProxy { } impl HttpClient for HttpClientWithProxy { - fn send_with_redirect_policy( + fn send( &self, req: Request, - follow_redirects: bool, ) -> BoxFuture<'static, Result, anyhow::Error>> { - self.client.send_with_redirect_policy(req, follow_redirects) + self.client.send(req) } fn proxy(&self) -> Option<&Uri> { @@ -106,12 +132,11 @@ impl HttpClient for HttpClientWithProxy { } impl HttpClient for Arc { - fn send_with_redirect_policy( + fn send( &self, req: Request, - follow_redirects: bool, ) -> BoxFuture<'static, Result, anyhow::Error>> { - self.client.send_with_redirect_policy(req, follow_redirects) + self.client.send(req) } fn proxy(&self) -> Option<&Uri> { @@ -218,12 +243,11 @@ impl HttpClientWithUrl { } impl HttpClient for Arc { - fn send_with_redirect_policy( + fn send( &self, req: Request, - follow_redirects: bool, ) -> BoxFuture<'static, Result, anyhow::Error>> { - self.client.send_with_redirect_policy(req, follow_redirects) + self.client.send(req) } fn proxy(&self) -> Option<&Uri> { @@ -232,12 +256,11 @@ impl HttpClient for Arc { } impl HttpClient for HttpClientWithUrl { - fn send_with_redirect_policy( + fn send( &self, req: Request, - follow_redirects: bool, ) -> BoxFuture<'static, Result, anyhow::Error>> { - self.client.send_with_redirect_policy(req, follow_redirects) + self.client.send(req) } fn proxy(&self) -> Option<&Uri> { @@ -283,14 +306,6 @@ impl HttpClient for BlockedHttpClient { fn proxy(&self) -> Option<&Uri> { None } - - fn send_with_redirect_policy( - &self, - req: Request, - _: bool, - ) -> BoxFuture<'static, Result, anyhow::Error>> { - self.send(req) - } } #[cfg(feature = "test-support")] @@ -352,10 +367,9 @@ impl fmt::Debug for FakeHttpClient { #[cfg(feature = "test-support")] impl HttpClient for FakeHttpClient { - fn send_with_redirect_policy( + fn send( &self, req: Request, - _follow_redirects: bool, ) -> BoxFuture<'static, Result, anyhow::Error>> { let future = (self.handler)(req); future diff --git a/crates/isahc_http_client/src/isahc_http_client.rs b/crates/isahc_http_client/src/isahc_http_client.rs index 6c40b9f53b3f8ea4d57b234c3bc998cc5827fa52..778f6a04598909ed35a6e22b4e95d3d6e6c5ae55 100644 --- a/crates/isahc_http_client/src/isahc_http_client.rs +++ b/crates/isahc_http_client/src/isahc_http_client.rs @@ -1,7 +1,6 @@ use std::{mem, sync::Arc, time::Duration}; use futures::future::BoxFuture; -use isahc::config::RedirectPolicy; use util::maybe; pub use isahc::config::Configurable; @@ -36,18 +35,29 @@ impl HttpClient for IsahcHttpClient { None } - fn send_with_redirect_policy( + fn send( &self, req: http_client::http::Request, - follow_redirects: bool, ) -> BoxFuture<'static, Result, anyhow::Error>> { + let redirect_policy = req + .extensions() + .get::() + .cloned() + .unwrap_or_default(); + let read_timeout = req + .extensions() + .get::() + .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); @@ -64,10 +74,12 @@ impl HttpClient for IsahcHttpClient { }; builder - .redirect_policy(if follow_redirects { - RedirectPolicy::Follow - } else { - RedirectPolicy::None + .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() diff --git a/crates/language_model/Cargo.toml b/crates/language_model/Cargo.toml index b63428c544369b51b759501c94da924f6b6eaf6d..ef273ac44fca396418b32374867d5af2a32dd338 100644 --- a/crates/language_model/Cargo.toml +++ b/crates/language_model/Cargo.toml @@ -32,7 +32,6 @@ futures.workspace = true google_ai = { workspace = true, features = ["schemars"] } gpui.workspace = true http_client.workspace = true -isahc.workspace = true inline_completion_button.workspace = true log.workspace = true menu.workspace = true diff --git a/crates/language_model/src/provider/cloud.rs b/crates/language_model/src/provider/cloud.rs index 606a6fbacec7b0e0dd6033608856685a189549f9..3c407b77d929dea0a74ae6d87b94e2887ed30993 100644 --- a/crates/language_model/src/provider/cloud.rs +++ b/crates/language_model/src/provider/cloud.rs @@ -18,8 +18,7 @@ use gpui::{ AnyElement, AnyView, AppContext, AsyncAppContext, FontWeight, Model, ModelContext, Subscription, Task, }; -use http_client::{AsyncBody, HttpClient, Method, Response}; -use isahc::config::Configurable; +use http_client::{AsyncBody, HttpClient, HttpRequestExt, Method, Response}; use schemars::JsonSchema; use serde::{de::DeserializeOwned, Deserialize, Serialize}; use serde_json::value::RawValue; @@ -396,7 +395,7 @@ impl CloudLanguageModel { let response = loop { let mut request_builder = http_client::Request::builder(); if let Some(low_speed_timeout) = low_speed_timeout { - request_builder = request_builder.low_speed_timeout(100, low_speed_timeout); + request_builder = request_builder.read_timeout(low_speed_timeout); }; let request = request_builder .method(Method::POST) diff --git a/crates/open_ai/Cargo.toml b/crates/open_ai/Cargo.toml index db9c77bac6bfe45634a2283853a5b4f1f9b3bc88..4f729598f82cdbf5fdc0a6fb047fc717ab768e7b 100644 --- a/crates/open_ai/Cargo.toml +++ b/crates/open_ai/Cargo.toml @@ -19,7 +19,6 @@ schemars = ["dep:schemars"] anyhow.workspace = true futures.workspace = true http_client.workspace = true -isahc.workspace = true schemars = { workspace = true, optional = true } serde.workspace = true serde_json.workspace = true diff --git a/crates/open_ai/src/open_ai.rs b/crates/open_ai/src/open_ai.rs index e67fe1af27cdb8be652c5f24a7ab345dacdc2aa7..6a24eec69610c7d2418c154d1330ac67b98b945f 100644 --- a/crates/open_ai/src/open_ai.rs +++ b/crates/open_ai/src/open_ai.rs @@ -6,8 +6,7 @@ use futures::{ stream::{self, BoxStream}, AsyncBufReadExt, AsyncReadExt, Stream, StreamExt, }; -use http_client::{AsyncBody, HttpClient, Method, Request as HttpRequest}; -use isahc::config::Configurable; +use http_client::{AsyncBody, HttpClient, HttpRequestExt, Method, Request as HttpRequest}; use serde::{Deserialize, Serialize}; use serde_json::Value; use std::{ @@ -318,7 +317,7 @@ pub async fn complete( .header("Content-Type", "application/json") .header("Authorization", format!("Bearer {}", api_key)); if let Some(low_speed_timeout) = low_speed_timeout { - request_builder = request_builder.low_speed_timeout(100, low_speed_timeout); + request_builder = request_builder.read_timeout(low_speed_timeout); }; let mut request_body = request; @@ -413,7 +412,7 @@ pub async fn stream_completion( .header("Authorization", format!("Bearer {}", api_key)); if let Some(low_speed_timeout) = low_speed_timeout { - request_builder = request_builder.low_speed_timeout(100, low_speed_timeout); + request_builder = request_builder.read_timeout(low_speed_timeout); }; let request = request_builder.body(AsyncBody::from(serde_json::to_string(&request)?))?; diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index 897e0e9a28bca843da45713d4bbea4c408b39bd7..5422f8b29aa98d3c6758d583e2f68787ec0e8fba 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -57,7 +57,6 @@ http_client.workspace = true image_viewer.workspace = true inline_completion_button.workspace = true install_cli.workspace = true -isahc.workspace = true isahc_http_client.workspace = true journal.workspace = true language.workspace = true diff --git a/crates/zed/src/reliability.rs b/crates/zed/src/reliability.rs index 9e811d7c9afbb4e8ccb6c429bbe340115f148c0d..50e5a05b823ed0bd04d2e6fb82bbbc2550e994ea 100644 --- a/crates/zed/src/reliability.rs +++ b/crates/zed/src/reliability.rs @@ -4,8 +4,7 @@ use chrono::Utc; use client::telemetry; use db::kvp::KEY_VALUE_STORE; use gpui::{AppContext, SemanticVersion}; -use http_client::Method; -use isahc::config::Configurable; +use http_client::{HttpRequestExt, Method}; use http_client::{self, HttpClient, HttpClientWithUrl}; use paths::{crashes_dir, crashes_retired_dir}; @@ -491,7 +490,7 @@ async fn upload_previous_crashes( .context("error reading crash file")?; let mut request = http_client::Request::post(&crash_report_url.to_string()) - .redirect_policy(isahc::config::RedirectPolicy::Follow) + .follow_redirects(http_client::RedirectPolicy::FollowAll) .header("Content-Type", "text/plain"); if let Some((panicked_on, payload)) = most_recent_panic.as_ref() {