client: Send User-Agent header on WebSocket connection requests (#35280)

This PR makes it so we send the `User-Agent` header on the WebSocket
connection requests when connecting to Collab.

We use the user agent set on the parent HTTP client.

Release Notes:

- N/A
This commit is contained in:
Marshall Bowers 2025-07-29 12:53:56 -04:00 committed by GitHub
parent aa3437e98f
commit f9224b1d74
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 50 additions and 4 deletions

View file

@ -21,7 +21,7 @@ use futures::{
channel::oneshot, future::BoxFuture, channel::oneshot, future::BoxFuture,
}; };
use gpui::{App, AsyncApp, Entity, Global, Task, WeakEntity, actions}; use gpui::{App, AsyncApp, Entity, Global, Task, WeakEntity, actions};
use http_client::{AsyncBody, HttpClient, HttpClientWithUrl}; use http_client::{AsyncBody, HttpClient, HttpClientWithUrl, http};
use parking_lot::RwLock; use parking_lot::RwLock;
use postage::watch; use postage::watch;
use proxy::connect_proxy_stream; use proxy::connect_proxy_stream;
@ -1158,6 +1158,7 @@ impl Client {
let http = self.http.clone(); let http = self.http.clone();
let proxy = http.proxy().cloned(); let proxy = http.proxy().cloned();
let user_agent = http.user_agent().cloned();
let credentials = credentials.clone(); let credentials = credentials.clone();
let rpc_url = self.rpc_url(http, release_channel); let rpc_url = self.rpc_url(http, release_channel);
let system_id = self.telemetry.system_id(); let system_id = self.telemetry.system_id();
@ -1209,7 +1210,7 @@ impl Client {
// We then modify the request to add our desired headers. // We then modify the request to add our desired headers.
let request_headers = request.headers_mut(); let request_headers = request.headers_mut();
request_headers.insert( request_headers.insert(
"Authorization", http::header::AUTHORIZATION,
HeaderValue::from_str(&credentials.authorization_header())?, HeaderValue::from_str(&credentials.authorization_header())?,
); );
request_headers.insert( request_headers.insert(
@ -1221,6 +1222,9 @@ impl Client {
"x-zed-release-channel", "x-zed-release-channel",
HeaderValue::from_str(release_channel.map(|r| r.dev_name()).unwrap_or("unknown"))?, HeaderValue::from_str(release_channel.map(|r| r.dev_name()).unwrap_or("unknown"))?,
); );
if let Some(user_agent) = user_agent {
request_headers.insert(http::header::USER_AGENT, user_agent);
}
if let Some(system_id) = system_id { if let Some(system_id) = system_id {
request_headers.insert("x-zed-system-id", HeaderValue::from_str(&system_id)?); request_headers.insert("x-zed-system-id", HeaderValue::from_str(&system_id)?);
} }

View file

@ -2023,6 +2023,10 @@ impl HttpClient for NullHttpClient {
.boxed() .boxed()
} }
fn user_agent(&self) -> Option<&http_client::http::HeaderValue> {
None
}
fn proxy(&self) -> Option<&Url> { fn proxy(&self) -> Option<&Url> {
None None
} }

View file

@ -4,6 +4,7 @@ pub mod github;
pub use anyhow::{Result, anyhow}; pub use anyhow::{Result, anyhow};
pub use async_body::{AsyncBody, Inner}; pub use async_body::{AsyncBody, Inner};
use derive_more::Deref; use derive_more::Deref;
use http::HeaderValue;
pub use http::{self, Method, Request, Response, StatusCode, Uri}; pub use http::{self, Method, Request, Response, StatusCode, Uri};
use futures::future::BoxFuture; use futures::future::BoxFuture;
@ -39,6 +40,8 @@ impl HttpRequestExt for http::request::Builder {
pub trait HttpClient: 'static + Send + Sync { pub trait HttpClient: 'static + Send + Sync {
fn type_name(&self) -> &'static str; fn type_name(&self) -> &'static str;
fn user_agent(&self) -> Option<&HeaderValue>;
fn send( fn send(
&self, &self,
req: http::Request<AsyncBody>, req: http::Request<AsyncBody>,
@ -118,6 +121,10 @@ impl HttpClient for HttpClientWithProxy {
self.client.send(req) self.client.send(req)
} }
fn user_agent(&self) -> Option<&HeaderValue> {
self.client.user_agent()
}
fn proxy(&self) -> Option<&Url> { fn proxy(&self) -> Option<&Url> {
self.proxy.as_ref() self.proxy.as_ref()
} }
@ -135,6 +142,10 @@ impl HttpClient for Arc<HttpClientWithProxy> {
self.client.send(req) self.client.send(req)
} }
fn user_agent(&self) -> Option<&HeaderValue> {
self.client.user_agent()
}
fn proxy(&self) -> Option<&Url> { fn proxy(&self) -> Option<&Url> {
self.proxy.as_ref() self.proxy.as_ref()
} }
@ -250,6 +261,10 @@ impl HttpClient for Arc<HttpClientWithUrl> {
self.client.send(req) self.client.send(req)
} }
fn user_agent(&self) -> Option<&HeaderValue> {
self.client.user_agent()
}
fn proxy(&self) -> Option<&Url> { fn proxy(&self) -> Option<&Url> {
self.client.proxy.as_ref() self.client.proxy.as_ref()
} }
@ -267,6 +282,10 @@ impl HttpClient for HttpClientWithUrl {
self.client.send(req) self.client.send(req)
} }
fn user_agent(&self) -> Option<&HeaderValue> {
self.client.user_agent()
}
fn proxy(&self) -> Option<&Url> { fn proxy(&self) -> Option<&Url> {
self.client.proxy.as_ref() self.client.proxy.as_ref()
} }
@ -314,6 +333,10 @@ impl HttpClient for BlockedHttpClient {
}) })
} }
fn user_agent(&self) -> Option<&HeaderValue> {
None
}
fn proxy(&self) -> Option<&Url> { fn proxy(&self) -> Option<&Url> {
None None
} }
@ -334,6 +357,7 @@ type FakeHttpHandler = Box<
#[cfg(feature = "test-support")] #[cfg(feature = "test-support")]
pub struct FakeHttpClient { pub struct FakeHttpClient {
handler: FakeHttpHandler, handler: FakeHttpHandler,
user_agent: HeaderValue,
} }
#[cfg(feature = "test-support")] #[cfg(feature = "test-support")]
@ -348,6 +372,7 @@ impl FakeHttpClient {
client: HttpClientWithProxy { client: HttpClientWithProxy {
client: Arc::new(Self { client: Arc::new(Self {
handler: Box::new(move |req| Box::pin(handler(req))), handler: Box::new(move |req| Box::pin(handler(req))),
user_agent: HeaderValue::from_static(type_name::<Self>()),
}), }),
proxy: None, proxy: None,
}, },
@ -390,6 +415,10 @@ impl HttpClient for FakeHttpClient {
future future
} }
fn user_agent(&self) -> Option<&HeaderValue> {
Some(&self.user_agent)
}
fn proxy(&self) -> Option<&Url> { fn proxy(&self) -> Option<&Url> {
None None
} }

View file

@ -20,6 +20,7 @@ static REDACT_REGEX: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"key=[^&]+")
pub struct ReqwestClient { pub struct ReqwestClient {
client: reqwest::Client, client: reqwest::Client,
proxy: Option<Url>, proxy: Option<Url>,
user_agent: Option<HeaderValue>,
handle: tokio::runtime::Handle, handle: tokio::runtime::Handle,
} }
@ -44,9 +45,11 @@ impl ReqwestClient {
Ok(client.into()) Ok(client.into())
} }
pub fn proxy_and_user_agent(proxy: Option<Url>, agent: &str) -> anyhow::Result<Self> { pub fn proxy_and_user_agent(proxy: Option<Url>, user_agent: &str) -> anyhow::Result<Self> {
let user_agent = HeaderValue::from_str(user_agent)?;
let mut map = HeaderMap::new(); let mut map = HeaderMap::new();
map.insert(http::header::USER_AGENT, HeaderValue::from_str(agent)?); map.insert(http::header::USER_AGENT, user_agent.clone());
let mut client = Self::builder().default_headers(map); let mut client = Self::builder().default_headers(map);
let client_has_proxy; let client_has_proxy;
@ -73,6 +76,7 @@ impl ReqwestClient {
.build()?; .build()?;
let mut client: ReqwestClient = client.into(); let mut client: ReqwestClient = client.into();
client.proxy = client_has_proxy.then_some(proxy).flatten(); client.proxy = client_has_proxy.then_some(proxy).flatten();
client.user_agent = Some(user_agent);
Ok(client) Ok(client)
} }
} }
@ -96,6 +100,7 @@ impl From<reqwest::Client> for ReqwestClient {
client, client,
handle, handle,
proxy: None, proxy: None,
user_agent: None,
} }
} }
} }
@ -216,6 +221,10 @@ impl http_client::HttpClient for ReqwestClient {
type_name::<Self>() type_name::<Self>()
} }
fn user_agent(&self) -> Option<&HeaderValue> {
self.user_agent.as_ref()
}
fn send( fn send(
&self, &self,
req: http::Request<http_client::AsyncBody>, req: http::Request<http_client::AsyncBody>,