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 68dad1f746..85a62c9519 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 9e48ad0e57..ec12932fb7 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 91b6723e90..6b89722842 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 54abbaa112..2a54497562 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 5d80c89a66..c5ba1bfc6a 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 edf6184d38..6ce1bd6862 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 5f9fbffb11..535d68326f 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 3835f58f88..862e2e7c7f 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 eb6e1a09a2..e7f5432e1d 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 83c726e3e9..0447858ca5 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 4762b228d3..5270492aee 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 eaadca1ecf..3f6a016f68 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 77eaa80961..4078025fa0 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 2a52f1968d..f923e0ec91 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 f1dcedf5b3..7991c67956 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 6cb491b100..540e459ce1 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 c063015151..2f029a1d23 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 6c40b9f53b..778f6a0459 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 b63428c544..ef273ac44f 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 606a6fbace..3c407b77d9 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 db9c77bac6..4f729598f8 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 e67fe1af27..6a24eec696 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 897e0e9a28..5422f8b29a 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 9e811d7c9a..50e5a05b82 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() {