Rename `ZedHttpClient` for clarity (#8320)

Marshall Bowers created

This PR renames the `ZedHttpClient` to `HttpClientWithUrl` to make it
slightly clearer that it still is holding a `dyn HttpClient` as opposed
to being a concrete implementation.

Release Notes:

- N/A

Change summary

crates/auto_update/src/auto_update.rs   | 14 ++--
crates/client/src/client.rs             | 18 +++---
crates/client/src/telemetry.rs          |  8 +-
crates/extension/src/extension_store.rs | 12 ++--
crates/feedback/src/feedback_modal.rs   |  2 
crates/util/src/http.rs                 | 70 ++++++++++++++------------
crates/zed/src/main.rs                  | 23 ++++----
7 files changed, 77 insertions(+), 70 deletions(-)

Detailed changes

crates/auto_update/src/auto_update.rs 🔗

@@ -29,7 +29,7 @@ use std::{
 };
 use update_notification::UpdateNotification;
 use util::{
-    http::{HttpClient, ZedHttpClient},
+    http::{HttpClient, HttpClientWithUrl},
     ResultExt,
 };
 use workspace::Workspace;
@@ -67,7 +67,7 @@ pub enum AutoUpdateStatus {
 pub struct AutoUpdater {
     status: AutoUpdateStatus,
     current_version: SemanticVersion,
-    http_client: Arc<ZedHttpClient>,
+    http_client: Arc<HttpClientWithUrl>,
     pending_poll: Option<Task<Option<()>>>,
 }
 
@@ -115,7 +115,7 @@ struct ReleaseNotesBody {
     release_notes: String,
 }
 
-pub fn init(http_client: Arc<ZedHttpClient>, cx: &mut AppContext) {
+pub fn init(http_client: Arc<HttpClientWithUrl>, cx: &mut AppContext) {
     AutoUpdateSetting::register(cx);
 
     cx.observe_new_views(|workspace: &mut Workspace, _cx| {
@@ -181,7 +181,7 @@ pub fn view_release_notes(_: &ViewReleaseNotes, cx: &mut AppContext) -> Option<(
         let current_version = auto_updater.current_version;
         let url = &auto_updater
             .http_client
-            .zed_url(&format!("/releases/{release_channel}/{current_version}"));
+            .build_url(&format!("/releases/{release_channel}/{current_version}"));
         cx.open_url(&url);
     }
 
@@ -193,7 +193,7 @@ fn view_release_notes_locally(workspace: &mut Workspace, cx: &mut ViewContext<Wo
     let version = env!("CARGO_PKG_VERSION");
 
     let client = client::Client::global(cx).http_client();
-    let url = client.zed_url(&format!(
+    let url = client.build_url(&format!(
         "/api/release_notes/{}/{}",
         release_channel.dev_name(),
         version
@@ -283,7 +283,7 @@ impl AutoUpdater {
         cx.default_global::<GlobalAutoUpdate>().0.clone()
     }
 
-    fn new(current_version: SemanticVersion, http_client: Arc<ZedHttpClient>) -> Self {
+    fn new(current_version: SemanticVersion, http_client: Arc<HttpClientWithUrl>) -> Self {
         Self {
             status: AutoUpdateStatus::Idle,
             current_version,
@@ -337,7 +337,7 @@ impl AutoUpdater {
             (this.http_client.clone(), this.current_version)
         })?;
 
-        let mut url_string = client.zed_url(&format!(
+        let mut url_string = client.build_url(&format!(
             "/api/releases/latest?asset=Zed.dmg&os={}&arch={}",
             OS, ARCH
         ));

crates/client/src/client.rs 🔗

@@ -42,7 +42,7 @@ use std::{
 use telemetry::Telemetry;
 use thiserror::Error;
 use url::Url;
-use util::http::{HttpClient, ZedHttpClient};
+use util::http::{HttpClient, HttpClientWithUrl};
 use util::{ResultExt, TryFutureExt};
 
 pub use rpc::*;
@@ -153,7 +153,7 @@ impl Global for GlobalClient {}
 pub struct Client {
     id: AtomicU64,
     peer: Arc<Peer>,
-    http: Arc<ZedHttpClient>,
+    http: Arc<HttpClientWithUrl>,
     telemetry: Arc<Telemetry>,
     state: RwLock<ClientState>,
 
@@ -424,7 +424,7 @@ impl settings::Settings for TelemetrySettings {
 impl Client {
     pub fn new(
         clock: Arc<dyn SystemClock>,
-        http: Arc<ZedHttpClient>,
+        http: Arc<HttpClientWithUrl>,
         cx: &mut AppContext,
     ) -> Arc<Self> {
         let client = Arc::new(Self {
@@ -447,7 +447,7 @@ impl Client {
         self.id.load(std::sync::atomic::Ordering::SeqCst)
     }
 
-    pub fn http_client(&self) -> Arc<ZedHttpClient> {
+    pub fn http_client(&self) -> Arc<HttpClientWithUrl> {
         self.http.clone()
     }
 
@@ -970,14 +970,14 @@ impl Client {
     }
 
     async fn get_rpc_url(
-        http: Arc<ZedHttpClient>,
+        http: Arc<HttpClientWithUrl>,
         release_channel: Option<ReleaseChannel>,
     ) -> Result<Url> {
         if let Some(url) = &*ZED_RPC_URL {
             return Url::parse(url).context("invalid rpc url");
         }
 
-        let mut url = http.zed_url("/rpc");
+        let mut url = http.build_url("/rpc");
         if let Some(preview_param) =
             release_channel.and_then(|channel| channel.release_query_param())
         {
@@ -1110,7 +1110,7 @@ impl Client {
 
                     // Open the Zed sign-in page in the user's browser, with query parameters that indicate
                     // that the user is signing in from a Zed app running on the same device.
-                    let mut url = http.zed_url(&format!(
+                    let mut url = http.build_url(&format!(
                         "/native_app_signin?native_app_port={}&native_app_public_key={}",
                         port, public_key_string
                     ));
@@ -1145,7 +1145,7 @@ impl Client {
                                     }
 
                                     let post_auth_url =
-                                        http.zed_url("/native_app_signin_succeeded");
+                                        http.build_url("/native_app_signin_succeeded");
                                     req.respond(
                                         tiny_http::Response::empty(302).with_header(
                                             tiny_http::Header::from_bytes(
@@ -1187,7 +1187,7 @@ impl Client {
     }
 
     async fn authenticate_as_admin(
-        http: Arc<ZedHttpClient>,
+        http: Arc<HttpClientWithUrl>,
         login: String,
         mut api_token: String,
     ) -> Result<Credentials> {

crates/client/src/telemetry.rs 🔗

@@ -20,7 +20,7 @@ use telemetry_events::{
     EditEvent, EditorEvent, Event, EventRequestBody, EventWrapper, MemoryEvent, SettingEvent,
 };
 use tempfile::NamedTempFile;
-use util::http::{self, HttpClient, Method, ZedHttpClient};
+use util::http::{self, HttpClient, HttpClientWithUrl, Method};
 #[cfg(not(debug_assertions))]
 use util::ResultExt;
 use util::TryFutureExt;
@@ -29,7 +29,7 @@ use self::event_coalescer::EventCoalescer;
 
 pub struct Telemetry {
     clock: Arc<dyn SystemClock>,
-    http_client: Arc<ZedHttpClient>,
+    http_client: Arc<HttpClientWithUrl>,
     executor: BackgroundExecutor,
     state: Arc<Mutex<TelemetryState>>,
 }
@@ -75,7 +75,7 @@ static ZED_CLIENT_CHECKSUM_SEED: Lazy<Option<Vec<u8>>> = Lazy::new(|| {
 impl Telemetry {
     pub fn new(
         clock: Arc<dyn SystemClock>,
-        client: Arc<ZedHttpClient>,
+        client: Arc<HttpClientWithUrl>,
         cx: &mut AppContext,
     ) -> Arc<Self> {
         let release_channel =
@@ -474,7 +474,7 @@ impl Telemetry {
 
                     let request = http::Request::builder()
                         .method(Method::POST)
-                        .uri(this.http_client.zed_api_url("/telemetry/events"))
+                        .uri(this.http_client.build_zed_api_url("/telemetry/events"))
                         .header("Content-Type", "text/plain")
                         .header("x-zed-checksum", checksum)
                         .body(json_bytes.into());

crates/extension/src/extension_store.rs 🔗

@@ -20,7 +20,7 @@ use std::{
     time::Duration,
 };
 use theme::{ThemeRegistry, ThemeSettings};
-use util::http::{AsyncBody, ZedHttpClient};
+use util::http::{AsyncBody, HttpClientWithUrl};
 use util::TryFutureExt;
 use util::{http::HttpClient, paths::EXTENSIONS_DIR, ResultExt};
 
@@ -69,7 +69,7 @@ impl ExtensionStatus {
 pub struct ExtensionStore {
     manifest: Arc<RwLock<Manifest>>,
     fs: Arc<dyn Fs>,
-    http_client: Arc<ZedHttpClient>,
+    http_client: Arc<HttpClientWithUrl>,
     extensions_dir: PathBuf,
     extensions_being_installed: HashSet<Arc<str>>,
     extensions_being_uninstalled: HashSet<Arc<str>>,
@@ -125,7 +125,7 @@ actions!(zed, [ReloadExtensions]);
 
 pub fn init(
     fs: Arc<fs::RealFs>,
-    http_client: Arc<ZedHttpClient>,
+    http_client: Arc<HttpClientWithUrl>,
     language_registry: Arc<LanguageRegistry>,
     theme_registry: Arc<ThemeRegistry>,
     cx: &mut AppContext,
@@ -157,7 +157,7 @@ impl ExtensionStore {
     pub fn new(
         extensions_dir: PathBuf,
         fs: Arc<dyn Fs>,
-        http_client: Arc<ZedHttpClient>,
+        http_client: Arc<HttpClientWithUrl>,
         language_registry: Arc<LanguageRegistry>,
         theme_registry: Arc<ThemeRegistry>,
         cx: &mut ModelContext<Self>,
@@ -236,7 +236,7 @@ impl ExtensionStore {
         search: Option<&str>,
         cx: &mut ModelContext<Self>,
     ) -> Task<Result<Vec<Extension>>> {
-        let url = self.http_client.zed_api_url(&format!(
+        let url = self.http_client.build_zed_api_url(&format!(
             "/extensions{query}",
             query = search
                 .map(|search| format!("?filter={search}"))
@@ -276,7 +276,7 @@ impl ExtensionStore {
         log::info!("installing extension {extension_id} {version}");
         let url = self
             .http_client
-            .zed_api_url(&format!("/extensions/{extension_id}/{version}/download"));
+            .build_zed_api_url(&format!("/extensions/{extension_id}/{version}/download"));
 
         let extensions_dir = self.extensions_dir();
         let http_client = self.http_client.clone();

crates/feedback/src/feedback_modal.rs 🔗

@@ -299,7 +299,7 @@ impl FeedbackModal {
         let installation_id = telemetry.installation_id();
         let is_staff = telemetry.is_staff();
         let http_client = zed_client.http_client();
-        let feedback_endpoint = http_client.zed_url("/api/feedback");
+        let feedback_endpoint = http_client.build_url("/api/feedback");
         let request = FeedbackRequestBody {
             feedback_text: &feedback_text,
             email,

crates/util/src/http.rs 🔗

@@ -14,56 +14,62 @@ use std::fmt;
 use std::{sync::Arc, time::Duration};
 pub use url::Url;
 
-pub struct ZedHttpClient {
-    pub zed_host: Mutex<String>,
-    client: Box<dyn HttpClient>,
+/// An [`HttpClient`] that has a base URL.
+pub struct HttpClientWithUrl {
+    base_url: Mutex<String>,
+    client: Arc<dyn HttpClient>,
 }
 
-impl ZedHttpClient {
-    pub fn zed_url(&self, path: &str) -> String {
-        format!("{}{}", self.zed_host.lock(), path)
+impl HttpClientWithUrl {
+    /// Returns a new [`HttpClientWithUrl`] with the given base URL.
+    pub fn new(base_url: impl Into<String>) -> Self {
+        Self {
+            base_url: Mutex::new(base_url.into()),
+            client: client(),
+        }
+    }
+
+    /// Returns the base URL.
+    pub fn base_url(&self) -> String {
+        self.base_url.lock().clone()
+    }
+
+    /// Sets the base URL.
+    pub fn set_base_url(&self, base_url: impl Into<String>) {
+        *self.base_url.lock() = base_url.into();
     }
 
-    pub fn zed_api_url(&self, path: &str) -> String {
-        let zed_host = self.zed_host.lock().clone();
+    /// Builds a URL using the given path.
+    pub fn build_url(&self, path: &str) -> String {
+        format!("{}{}", self.base_url.lock(), path)
+    }
 
-        let host = match zed_host.as_ref() {
+    /// Builds a Zed API URL using the given path.
+    pub fn build_zed_api_url(&self, path: &str) -> String {
+        let base_url = self.base_url.lock().clone();
+        let base_api_url = match base_url.as_ref() {
             "https://zed.dev" => "https://api.zed.dev",
             "https://staging.zed.dev" => "https://api-staging.zed.dev",
             "http://localhost:3000" => "http://localhost:8080",
             other => other,
         };
 
-        format!("{}{}", host, path)
+        format!("{}{}", base_api_url, path)
     }
 }
 
-impl HttpClient for Arc<ZedHttpClient> {
+impl HttpClient for Arc<HttpClientWithUrl> {
     fn send(&self, req: Request<AsyncBody>) -> BoxFuture<Result<Response<AsyncBody>, Error>> {
         self.client.send(req)
     }
 }
 
-impl HttpClient for ZedHttpClient {
+impl HttpClient for HttpClientWithUrl {
     fn send(&self, req: Request<AsyncBody>) -> BoxFuture<Result<Response<AsyncBody>, Error>> {
         self.client.send(req)
     }
 }
 
-pub fn zed_client(zed_host: &str) -> Arc<ZedHttpClient> {
-    Arc::new(ZedHttpClient {
-        zed_host: Mutex::new(zed_host.to_string()),
-        client: Box::new(
-            isahc::HttpClient::builder()
-                .connect_timeout(Duration::from_secs(5))
-                .low_speed_timeout(100, Duration::from_secs(5))
-                .proxy(http_proxy_from_env())
-                .build()
-                .unwrap(),
-        ),
-    })
-}
-
 pub trait HttpClient: Send + Sync {
     fn send(&self, req: Request<AsyncBody>) -> BoxFuture<Result<Response<AsyncBody>, Error>>;
 
@@ -134,20 +140,20 @@ pub struct FakeHttpClient {
 
 #[cfg(feature = "test-support")]
 impl FakeHttpClient {
-    pub fn create<Fut, F>(handler: F) -> Arc<ZedHttpClient>
+    pub fn create<Fut, F>(handler: F) -> Arc<HttpClientWithUrl>
     where
         Fut: 'static + Send + futures::Future<Output = Result<Response<AsyncBody>, Error>>,
         F: 'static + Send + Sync + Fn(Request<AsyncBody>) -> Fut,
     {
-        Arc::new(ZedHttpClient {
-            zed_host: Mutex::new("http://test.example".into()),
-            client: Box::new(Self {
+        Arc::new(HttpClientWithUrl {
+            base_url: Mutex::new("http://test.example".into()),
+            client: Arc::new(Self {
                 handler: Box::new(move |req| Box::pin(handler(req))),
             }),
         })
     }
 
-    pub fn with_404_response() -> Arc<ZedHttpClient> {
+    pub fn with_404_response() -> Arc<HttpClientWithUrl> {
         Self::create(|_| async move {
             Ok(Response::builder()
                 .status(404)
@@ -156,7 +162,7 @@ impl FakeHttpClient {
         })
     }
 
-    pub fn with_200_response() -> Arc<ZedHttpClient> {
+    pub fn with_200_response() -> Arc<HttpClientWithUrl> {
         Self::create(|_| async move {
             Ok(Response::builder()
                 .status(200)

crates/zed/src/main.rs 🔗

@@ -46,7 +46,7 @@ use std::{
 use theme::{ActiveTheme, SystemAppearance, ThemeRegistry, ThemeSettings};
 use util::{
     async_maybe,
-    http::{self, HttpClient, ZedHttpClient},
+    http::{HttpClient, HttpClientWithUrl},
     paths::{self, CRASHES_DIR, CRASHES_RETIRED_DIR},
     ResultExt,
 };
@@ -140,7 +140,9 @@ fn main() {
         client::init_settings(cx);
 
         let clock = Arc::new(clock::RealSystemClock);
-        let http = http::zed_client(&client::ClientSettings::get_global(cx).server_url);
+        let http = Arc::new(HttpClientWithUrl::new(
+            &client::ClientSettings::get_global(cx).server_url,
+        ));
 
         let client = client::Client::new(clock, http.clone(), cx);
         let mut languages = LanguageRegistry::new(login_shell_env_loaded);
@@ -198,9 +200,8 @@ fn main() {
             move |cx| {
                 languages.set_theme(cx.theme().clone());
                 let new_host = &client::ClientSettings::get_global(cx).server_url;
-                let mut host = http.zed_host.lock();
-                if &*host != new_host {
-                    *host = new_host.clone();
+                if &http.base_url() != new_host {
+                    http.set_base_url(new_host);
                     if client.status().borrow().is_connected() {
                         client.reconnect(&cx.to_async());
                     }
@@ -677,7 +678,7 @@ fn init_panic_hook(app: &App, installation_id: Option<String>, session_id: Strin
     }));
 }
 
-fn upload_panics_and_crashes(http: Arc<ZedHttpClient>, cx: &mut AppContext) {
+fn upload_panics_and_crashes(http: Arc<HttpClientWithUrl>, cx: &mut AppContext) {
     let telemetry_settings = *client::TelemetrySettings::get_global(cx);
     cx.background_executor()
         .spawn(async move {
@@ -692,12 +693,12 @@ fn upload_panics_and_crashes(http: Arc<ZedHttpClient>, cx: &mut AppContext) {
         .detach()
 }
 
-/// upload panics to us (via zed.dev)
+/// Uploads panics via `zed.dev`.
 async fn upload_previous_panics(
-    http: Arc<ZedHttpClient>,
+    http: Arc<HttpClientWithUrl>,
     telemetry_settings: client::TelemetrySettings,
 ) -> Result<Option<(i64, String)>> {
-    let panic_report_url = http.zed_url("/api/panic");
+    let panic_report_url = http.build_url("/api/panic");
     let mut children = smol::fs::read_dir(&*paths::LOGS_DIR).await?;
 
     let mut most_recent_panic = None;
@@ -766,7 +767,7 @@ static LAST_CRASH_UPLOADED: &'static str = "LAST_CRASH_UPLOADED";
 /// upload crashes from apple's diagnostic reports to our server.
 /// (only if telemetry is enabled)
 async fn upload_previous_crashes(
-    http: Arc<ZedHttpClient>,
+    http: Arc<HttpClientWithUrl>,
     most_recent_panic: Option<(i64, String)>,
     telemetry_settings: client::TelemetrySettings,
 ) -> Result<()> {
@@ -778,7 +779,7 @@ async fn upload_previous_crashes(
         .unwrap_or("zed-2024-01-17-221900.ips".to_string()); // don't upload old crash reports from before we had this.
     let mut uploaded = last_uploaded.clone();
 
-    let crash_report_url = http.zed_url("/api/crash");
+    let crash_report_url = http.build_url("/api/crash");
 
     for dir in [&*CRASHES_DIR, &*CRASHES_RETIRED_DIR] {
         let mut children = smol::fs::read_dir(&dir).await?;