Provide user agent when performing HTTP requests (#15470)

Antonio Scandurra and Nathan created

Release Notes:

- N/A

---------

Co-authored-by: Nathan <nathan@zed.dev>

Change summary

crates/client/src/client.rs             |  7 ++++++
crates/collab/src/rpc.rs                |  3 +
crates/extension/src/extension_store.rs |  5 ---
crates/extension_cli/src/main.rs        |  8 ++++++
crates/gpui/src/app.rs                  |  6 ++--
crates/http_client/src/http_client.rs   | 31 +++++++++++++++-----------
crates/semantic_index/examples/index.rs |  2 
crates/zed/src/main.rs                  |  2 
8 files changed, 40 insertions(+), 24 deletions(-)

Detailed changes

crates/client/src/client.rs 🔗

@@ -541,9 +541,16 @@ impl Client {
     }
 
     pub fn production(cx: &mut AppContext) -> Arc<Self> {
+        let user_agent = format!(
+            "Zed/{} ({}; {})",
+            AppVersion::global(cx),
+            std::env::consts::OS,
+            std::env::consts::ARCH
+        );
         let clock = Arc::new(clock::RealSystemClock);
         let http = Arc::new(HttpClientWithUrl::new(
             &ClientSettings::get_global(cx).server_url,
+            Some(user_agent),
             ProxySettings::get_global(cx).proxy.clone(),
         ));
         Self::new(clock, http.clone(), cx)

crates/collab/src/rpc.rs 🔗

@@ -995,7 +995,8 @@ impl Server {
             tracing::Span::current().record("connection_id", format!("{}", connection_id));
             tracing::info!("connection opened");
 
-            let http_client = match IsahcHttpClient::new() {
+            let user_agent = format!("Zed Server/{}", env!("CARGO_PKG_VERSION"));
+            let http_client = match IsahcHttpClient::builder().default_header("User-Agent", user_agent).build() {
                 Ok(http_client) => Arc::new(http_client),
                 Err(error) => {
                     tracing::error!(?error, "failed to create HTTP client");

crates/extension/src/extension_store.rs 🔗

@@ -243,10 +243,7 @@ impl ExtensionStore {
             extension_index: Default::default(),
             installed_dir,
             index_path,
-            builder: Arc::new(ExtensionBuilder::new(
-                ::http_client::client(http_client.proxy().cloned()),
-                build_dir,
-            )),
+            builder: Arc::new(ExtensionBuilder::new(http_client.clone(), build_dir)),
             outstanding_operations: Default::default(),
             modified_extensions: Default::default(),
             reload_complete_senders: Vec::new(),

crates/extension_cli/src/main.rs 🔗

@@ -60,7 +60,13 @@ async fn main() -> Result<()> {
 
     log::info!("compiling extension");
 
-    let http_client = Arc::new(HttpClientWithProxy::new(None));
+    let user_agent = format!(
+        "Zed Extension CLI/{} ({}; {})",
+        env!("CARGO_PKG_VERSION"),
+        std::env::consts::OS,
+        std::env::consts::ARCH
+    );
+    let http_client = Arc::new(HttpClientWithProxy::new(Some(user_agent), None));
     let builder = ExtensionBuilder::new(http_client, scratch_dir);
     builder
         .compile_extension(

crates/gpui/src/app.rs 🔗

@@ -114,7 +114,7 @@ impl App {
         Self(AppContext::new(
             current_platform(false),
             Arc::new(()),
-            http_client::client(None),
+            http_client::client(None, None),
         ))
     }
 
@@ -125,7 +125,7 @@ impl App {
         Self(AppContext::new(
             current_platform(true),
             Arc::new(()),
-            http_client::client(None),
+            http_client::client(None, None),
         ))
     }
 
@@ -667,7 +667,7 @@ impl AppContext {
     }
 
     /// Updates the http client assigned to GPUI
-    pub fn update_http_client(&mut self, new_client: Arc<dyn HttpClient>) {
+    pub fn set_http_client(&mut self, new_client: Arc<dyn HttpClient>) {
         self.http_client = new_client;
     }
 

crates/http_client/src/http_client.rs 🔗

@@ -73,7 +73,7 @@ pub struct HttpClientWithProxy {
 
 impl HttpClientWithProxy {
     /// Returns a new [`HttpClientWithProxy`] with the given proxy URL.
-    pub fn new(proxy_url: Option<String>) -> Self {
+    pub fn new(user_agent: Option<String>, proxy_url: Option<String>) -> Self {
         let proxy_url = proxy_url
             .and_then(|input| {
                 input
@@ -84,7 +84,7 @@ impl HttpClientWithProxy {
             .or_else(read_proxy_from_env);
 
         Self {
-            client: client(proxy_url.clone()),
+            client: client(user_agent, proxy_url.clone()),
             proxy: proxy_url,
         }
     }
@@ -124,8 +124,12 @@ pub struct HttpClientWithUrl {
 
 impl HttpClientWithUrl {
     /// Returns a new [`HttpClientWithUrl`] with the given base URL.
-    pub fn new(base_url: impl Into<String>, proxy_url: Option<String>) -> Self {
-        let client = HttpClientWithProxy::new(proxy_url);
+    pub fn new(
+        base_url: impl Into<String>,
+        user_agent: Option<String>,
+        proxy_url: Option<String>,
+    ) -> Self {
+        let client = HttpClientWithProxy::new(user_agent, proxy_url);
 
         Self {
             base_url: Mutex::new(base_url.into()),
@@ -199,16 +203,17 @@ impl HttpClient for HttpClientWithUrl {
     }
 }
 
-pub fn client(proxy: Option<Uri>) -> Arc<dyn HttpClient> {
+pub fn client(user_agent: Option<String>, proxy: Option<Uri>) -> Arc<dyn HttpClient> {
+    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(user_agent) = user_agent {
+        builder = builder.default_header("User-Agent", user_agent);
+    }
+
     Arc::new(HttpClientWithProxy {
-        client: Arc::new(
-            isahc::HttpClient::builder()
-                .connect_timeout(Duration::from_secs(5))
-                .low_speed_timeout(100, Duration::from_secs(5))
-                .proxy(proxy.clone())
-                .build()
-                .unwrap(),
-        ),
+        client: Arc::new(builder.build().unwrap()),
         proxy,
     })
 }

crates/semantic_index/examples/index.rs 🔗

@@ -26,7 +26,7 @@ fn main() {
         });
 
         let clock = Arc::new(FakeSystemClock::default());
-        let http = Arc::new(HttpClientWithUrl::new("http://localhost:11434", None));
+        let http = Arc::new(HttpClientWithUrl::new("http://localhost:11434", None, None));
 
         let client = client::Client::new(clock, http.clone(), cx);
         Client::set_global(client.clone(), cx);

crates/zed/src/main.rs 🔗

@@ -416,7 +416,7 @@ fn main() {
 
         client::init_settings(cx);
         let client = Client::production(cx);
-        cx.update_http_client(client.http_client().clone());
+        cx.set_http_client(client.http_client().clone());
         let mut languages =
             LanguageRegistry::new(login_shell_env_loaded, cx.background_executor().clone());
         languages.set_language_server_download_dir(paths::languages_dir().clone());