Add basic proxy settings (#11852)

Adding `proxy` keyword to configure proxy while using zed. After setting
the proxy, restart Zed to acctually use the proxy.

Example setting: 
```rust
"proxy" = "socks5://localhost:10808"
"proxy" = "http://127.0.0.1:10809"
```

Closes #9424, closes #9422, closes #8650, closes #5032, closes #6701,
closes #11890

Release Notes:

- Added settings to configure proxy in Zed

---------

Co-authored-by: Jason Lee <huacnlee@gmail.com>
This commit is contained in:
张小白 2024-05-17 00:43:26 +08:00 committed by GitHub
parent 90b631ff3e
commit 1b261608c6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 110 additions and 20 deletions

View File

@ -797,5 +797,17 @@
// - `short`: "2 s, 15 l, 32 c" // - `short`: "2 s, 15 l, 32 c"
// - `long`: "2 selections, 15 lines, 32 characters" // - `long`: "2 selections, 15 lines, 32 characters"
// Default: long // Default: long
"line_indicator_format": "long" "line_indicator_format": "long",
} // Set a proxy to use. The proxy protocol is specified by the URI scheme.
//
// Supported URI scheme: `http`, `https`, `socks4`, `socks4a`, `socks5`,
// `socks5h`. `http` will be used when no scheme is specified.
//
// By default no proxy will be used, or Zed will try get proxy settings from
// environment variables.
//
// Examples:
// - "proxy" = "socks5://localhost:10808"
// - "proxy" = "http://127.0.0.1:10809"
"proxy": null
}

View File

@ -114,10 +114,36 @@ impl Settings for ClientSettings {
} }
} }
#[derive(Default, Clone, Serialize, Deserialize, JsonSchema)]
pub struct ProxySettingsContent {
proxy: Option<String>,
}
#[derive(Deserialize, Default)]
pub struct ProxySettings {
pub proxy: Option<String>,
}
impl Settings for ProxySettings {
const KEY: Option<&'static str> = None;
type FileContent = ProxySettingsContent;
fn load(sources: SettingsSources<Self::FileContent>, _: &mut AppContext) -> Result<Self> {
Ok(Self {
proxy: sources
.user
.and_then(|value| value.proxy.clone())
.or(sources.default.proxy.clone()),
})
}
}
pub fn init_settings(cx: &mut AppContext) { pub fn init_settings(cx: &mut AppContext) {
TelemetrySettings::register(cx); TelemetrySettings::register(cx);
cx.update_global(|store: &mut SettingsStore, cx| { cx.update_global(|store: &mut SettingsStore, cx| {
store.register_setting::<ClientSettings>(cx); store.register_setting::<ClientSettings>(cx);
store.register_setting::<ProxySettings>(cx);
}); });
} }
@ -512,6 +538,7 @@ impl Client {
let clock = Arc::new(clock::RealSystemClock); let clock = Arc::new(clock::RealSystemClock);
let http = Arc::new(HttpClientWithUrl::new( let http = Arc::new(HttpClientWithUrl::new(
&ClientSettings::get_global(cx).server_url, &ClientSettings::get_global(cx).server_url,
ProxySettings::get_global(cx).proxy.clone(),
)); ));
Self::new(clock, http.clone(), cx) Self::new(clock, http.clone(), cx)
} }

View File

@ -67,7 +67,7 @@ impl ExtensionBuilder {
pub fn new(cache_dir: PathBuf) -> Self { pub fn new(cache_dir: PathBuf) -> Self {
Self { Self {
cache_dir, cache_dir,
http: http::client(), http: http::client(None),
} }
} }

View File

@ -115,7 +115,7 @@ impl App {
Self(AppContext::new( Self(AppContext::new(
current_platform(), current_platform(),
Arc::new(()), Arc::new(()),
http::client(), http::client(None),
)) ))
} }
@ -651,6 +651,11 @@ impl AppContext {
self.platform.local_timezone() self.platform.local_timezone()
} }
/// Updates the http client assigned to GPUI
pub fn update_http_client(&mut self, new_client: Arc<dyn HttpClient>) {
self.http_client = new_client;
}
/// Returns the http client assigned to GPUI /// Returns the http client assigned to GPUI
pub fn http_client(&self) -> Arc<dyn HttpClient> { pub fn http_client(&self) -> Arc<dyn HttpClient> {
self.http_client.clone() self.http_client.clone()

View File

@ -16,7 +16,7 @@ use std::{
}; };
pub use url::Url; pub use url::Url;
fn http_proxy_from_env() -> Option<isahc::http::Uri> { fn get_proxy(proxy: Option<String>) -> Option<isahc::http::Uri> {
macro_rules! try_env { macro_rules! try_env {
($($env:literal),+) => { ($($env:literal),+) => {
$( $(
@ -27,29 +27,42 @@ fn http_proxy_from_env() -> Option<isahc::http::Uri> {
}; };
} }
try_env!( proxy
"ALL_PROXY", .and_then(|input| {
"all_proxy", input
"HTTPS_PROXY", .parse::<isahc::http::Uri>()
"https_proxy", .inspect_err(|e| log::error!("Error parsing proxy settings: {}", e))
"HTTP_PROXY", .ok()
"http_proxy" })
); .or_else(|| {
None try_env!(
"ALL_PROXY",
"all_proxy",
"HTTPS_PROXY",
"https_proxy",
"HTTP_PROXY",
"http_proxy"
);
None
})
} }
/// An [`HttpClient`] that has a base URL. /// An [`HttpClient`] that has a base URL.
pub struct HttpClientWithUrl { pub struct HttpClientWithUrl {
base_url: Mutex<String>, base_url: Mutex<String>,
client: Arc<dyn HttpClient>, client: Arc<dyn HttpClient>,
proxy: Option<String>,
} }
impl HttpClientWithUrl { impl HttpClientWithUrl {
/// Returns a new [`HttpClientWithUrl`] with the given base URL. /// Returns a new [`HttpClientWithUrl`] with the given base URL.
pub fn new(base_url: impl Into<String>) -> Self { pub fn new(base_url: impl Into<String>, unparsed_proxy: Option<String>) -> Self {
let parsed_proxy = get_proxy(unparsed_proxy);
let proxy_string = parsed_proxy.as_ref().map(|p| p.to_string());
Self { Self {
base_url: Mutex::new(base_url.into()), base_url: Mutex::new(base_url.into()),
client: client(), client: client(parsed_proxy),
proxy: proxy_string,
} }
} }
@ -100,6 +113,10 @@ impl HttpClient for Arc<HttpClientWithUrl> {
) -> BoxFuture<'static, Result<Response<AsyncBody>, Error>> { ) -> BoxFuture<'static, Result<Response<AsyncBody>, Error>> {
self.client.send(req) self.client.send(req)
} }
fn proxy(&self) -> Option<&str> {
self.proxy.as_deref()
}
} }
impl HttpClient for HttpClientWithUrl { impl HttpClient for HttpClientWithUrl {
@ -109,6 +126,10 @@ impl HttpClient for HttpClientWithUrl {
) -> BoxFuture<'static, Result<Response<AsyncBody>, Error>> { ) -> BoxFuture<'static, Result<Response<AsyncBody>, Error>> {
self.client.send(req) self.client.send(req)
} }
fn proxy(&self) -> Option<&str> {
self.proxy.as_deref()
}
} }
pub trait HttpClient: Send + Sync { pub trait HttpClient: Send + Sync {
@ -153,14 +174,16 @@ pub trait HttpClient: Send + Sync {
Err(error) => async move { Err(error.into()) }.boxed(), Err(error) => async move { Err(error.into()) }.boxed(),
} }
} }
fn proxy(&self) -> Option<&str>;
} }
pub fn client() -> Arc<dyn HttpClient> { pub fn client(proxy: Option<isahc::http::Uri>) -> Arc<dyn HttpClient> {
Arc::new( Arc::new(
isahc::HttpClient::builder() isahc::HttpClient::builder()
.connect_timeout(Duration::from_secs(5)) .connect_timeout(Duration::from_secs(5))
.low_speed_timeout(100, Duration::from_secs(5)) .low_speed_timeout(100, Duration::from_secs(5))
.proxy(http_proxy_from_env()) .proxy(proxy)
.build() .build()
.unwrap(), .unwrap(),
) )
@ -174,6 +197,10 @@ impl HttpClient for isahc::HttpClient {
let client = self.clone(); let client = self.clone();
Box::pin(async move { client.send_async(req).await }) Box::pin(async move { client.send_async(req).await })
} }
fn proxy(&self) -> Option<&str> {
None
}
} }
#[cfg(feature = "test-support")] #[cfg(feature = "test-support")]
@ -201,6 +228,7 @@ impl FakeHttpClient {
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))),
}), }),
proxy: None,
}) })
} }
@ -239,4 +267,8 @@ impl HttpClient for FakeHttpClient {
let future = (self.handler)(req); let future = (self.handler)(req);
Box::pin(async move { future.await.map(Into::into) }) Box::pin(async move { future.await.map(Into::into) })
} }
fn proxy(&self) -> Option<&str> {
None
}
} }

View File

@ -266,8 +266,21 @@ impl NodeRuntime for RealNodeRuntime {
command.args(["--prefix".into(), directory.to_path_buf()]); command.args(["--prefix".into(), directory.to_path_buf()]);
} }
if let Some(proxy) = self.http.proxy() {
command.args(["--proxy", proxy]);
}
#[cfg(windows)] #[cfg(windows)]
command.creation_flags(windows::Win32::System::Threading::CREATE_NO_WINDOW.0); {
// SYSTEMROOT is a critical environment variables for Windows.
if let Some(val) = std::env::var("SYSTEMROOT")
.context("Missing environment variable: SYSTEMROOT!")
.log_err()
{
command.env("SYSTEMROOT", val);
}
command.creation_flags(windows::Win32::System::Threading::CREATE_NO_WINDOW.0);
}
command.output().await.map_err(|e| anyhow!("{e}")) command.output().await.map_err(|e| anyhow!("{e}"))
}; };

View File

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

View File

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