Extract http
from util
(#11680)
This avoids the CLI linking libssl etc... Release Notes: - N/A
This commit is contained in:
parent
df41435d1a
commit
5515ba6043
77 changed files with 419 additions and 336 deletions
|
@ -1,108 +0,0 @@
|
|||
use crate::http::HttpClient;
|
||||
use anyhow::{anyhow, bail, Context, Result};
|
||||
use futures::AsyncReadExt;
|
||||
use serde::Deserialize;
|
||||
use std::sync::Arc;
|
||||
use url::Url;
|
||||
|
||||
pub struct GitHubLspBinaryVersion {
|
||||
pub name: String,
|
||||
pub url: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct GithubRelease {
|
||||
pub tag_name: String,
|
||||
#[serde(rename = "prerelease")]
|
||||
pub pre_release: bool,
|
||||
pub assets: Vec<GithubReleaseAsset>,
|
||||
pub tarball_url: String,
|
||||
pub zipball_url: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct GithubReleaseAsset {
|
||||
pub name: String,
|
||||
pub browser_download_url: String,
|
||||
}
|
||||
|
||||
pub async fn latest_github_release(
|
||||
repo_name_with_owner: &str,
|
||||
require_assets: bool,
|
||||
pre_release: bool,
|
||||
http: Arc<dyn HttpClient>,
|
||||
) -> Result<GithubRelease, anyhow::Error> {
|
||||
let mut response = http
|
||||
.get(
|
||||
&format!("https://api.github.com/repos/{repo_name_with_owner}/releases"),
|
||||
Default::default(),
|
||||
true,
|
||||
)
|
||||
.await
|
||||
.context("error fetching latest release")?;
|
||||
|
||||
let mut body = Vec::new();
|
||||
response
|
||||
.body_mut()
|
||||
.read_to_end(&mut body)
|
||||
.await
|
||||
.context("error reading latest release")?;
|
||||
|
||||
if response.status().is_client_error() {
|
||||
let text = String::from_utf8_lossy(body.as_slice());
|
||||
bail!(
|
||||
"status error {}, response: {text:?}",
|
||||
response.status().as_u16()
|
||||
);
|
||||
}
|
||||
|
||||
let releases = match serde_json::from_slice::<Vec<GithubRelease>>(body.as_slice()) {
|
||||
Ok(releases) => releases,
|
||||
|
||||
Err(err) => {
|
||||
log::error!("Error deserializing: {:?}", err);
|
||||
log::error!(
|
||||
"GitHub API response text: {:?}",
|
||||
String::from_utf8_lossy(body.as_slice())
|
||||
);
|
||||
return Err(anyhow!("error deserializing latest release"));
|
||||
}
|
||||
};
|
||||
|
||||
releases
|
||||
.into_iter()
|
||||
.filter(|release| !require_assets || !release.assets.is_empty())
|
||||
.find(|release| release.pre_release == pre_release)
|
||||
.ok_or(anyhow!("Failed to find a release"))
|
||||
}
|
||||
|
||||
pub fn build_tarball_url(repo_name_with_owner: &str, tag: &str) -> Result<String> {
|
||||
let mut url = Url::parse(&format!(
|
||||
"https://github.com/{repo_name_with_owner}/archive/refs/tags",
|
||||
))?;
|
||||
// We're pushing this here, because tags may contain `/` and other characters
|
||||
// that need to be escaped.
|
||||
let tarball_filename = format!("{}.tar.gz", tag);
|
||||
url.path_segments_mut()
|
||||
.map_err(|_| anyhow!("cannot modify url path segments"))?
|
||||
.push(&tarball_filename);
|
||||
Ok(url.to_string())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::github::build_tarball_url;
|
||||
|
||||
#[test]
|
||||
fn test_build_tarball_url() {
|
||||
let tag = "release/2.3.5";
|
||||
let repo_name_with_owner = "microsoft/vscode-eslint";
|
||||
|
||||
let have = build_tarball_url(repo_name_with_owner, tag).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
have,
|
||||
"https://github.com/microsoft/vscode-eslint/archive/refs/tags/release%2F2.3.5.tar.gz"
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,219 +1 @@
|
|||
use crate::http_proxy_from_env;
|
||||
pub use anyhow::{anyhow, Result};
|
||||
use futures::future::BoxFuture;
|
||||
use futures_lite::FutureExt;
|
||||
use isahc::config::{Configurable, RedirectPolicy};
|
||||
pub use isahc::{
|
||||
http::{Method, StatusCode, Uri},
|
||||
AsyncBody, Error, HttpClient as IsahcHttpClient, Request, Response,
|
||||
};
|
||||
#[cfg(feature = "test-support")]
|
||||
use std::fmt;
|
||||
use std::{
|
||||
sync::{Arc, Mutex},
|
||||
time::Duration,
|
||||
};
|
||||
pub use url::Url;
|
||||
|
||||
/// An [`HttpClient`] that has a base URL.
|
||||
pub struct HttpClientWithUrl {
|
||||
base_url: Mutex<String>,
|
||||
client: Arc<dyn HttpClient>,
|
||||
}
|
||||
|
||||
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()
|
||||
.map_or_else(|_| Default::default(), |url| url.clone())
|
||||
}
|
||||
|
||||
/// Sets the base URL.
|
||||
pub fn set_base_url(&self, base_url: impl Into<String>) {
|
||||
let base_url = base_url.into();
|
||||
self.base_url
|
||||
.lock()
|
||||
.map(|mut url| {
|
||||
*url = base_url;
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
|
||||
/// Builds a URL using the given path.
|
||||
pub fn build_url(&self, path: &str) -> String {
|
||||
format!("{}{}", self.base_url(), path)
|
||||
}
|
||||
|
||||
/// Builds a Zed API URL using the given path.
|
||||
pub fn build_zed_api_url(&self, path: &str, query: &[(&str, &str)]) -> Result<Url> {
|
||||
let base_url = self.base_url();
|
||||
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,
|
||||
};
|
||||
|
||||
Ok(Url::parse_with_params(
|
||||
&format!("{}{}", base_api_url, path),
|
||||
query,
|
||||
)?)
|
||||
}
|
||||
}
|
||||
|
||||
impl HttpClient for Arc<HttpClientWithUrl> {
|
||||
fn send(
|
||||
&self,
|
||||
req: Request<AsyncBody>,
|
||||
) -> BoxFuture<'static, Result<Response<AsyncBody>, Error>> {
|
||||
self.client.send(req)
|
||||
}
|
||||
}
|
||||
|
||||
impl HttpClient for HttpClientWithUrl {
|
||||
fn send(
|
||||
&self,
|
||||
req: Request<AsyncBody>,
|
||||
) -> BoxFuture<'static, Result<Response<AsyncBody>, Error>> {
|
||||
self.client.send(req)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait HttpClient: Send + Sync {
|
||||
fn send(
|
||||
&self,
|
||||
req: Request<AsyncBody>,
|
||||
) -> BoxFuture<'static, Result<Response<AsyncBody>, Error>>;
|
||||
|
||||
fn get<'a>(
|
||||
&'a self,
|
||||
uri: &str,
|
||||
body: AsyncBody,
|
||||
follow_redirects: bool,
|
||||
) -> BoxFuture<'a, Result<Response<AsyncBody>, Error>> {
|
||||
let request = isahc::Request::builder()
|
||||
.redirect_policy(if follow_redirects {
|
||||
RedirectPolicy::Follow
|
||||
} else {
|
||||
RedirectPolicy::None
|
||||
})
|
||||
.method(Method::GET)
|
||||
.uri(uri)
|
||||
.body(body);
|
||||
match request {
|
||||
Ok(request) => self.send(request),
|
||||
Err(error) => async move { Err(error.into()) }.boxed(),
|
||||
}
|
||||
}
|
||||
|
||||
fn post_json<'a>(
|
||||
&'a self,
|
||||
uri: &str,
|
||||
body: AsyncBody,
|
||||
) -> BoxFuture<'a, Result<Response<AsyncBody>, Error>> {
|
||||
let request = isahc::Request::builder()
|
||||
.method(Method::POST)
|
||||
.uri(uri)
|
||||
.header("Content-Type", "application/json")
|
||||
.body(body);
|
||||
match request {
|
||||
Ok(request) => self.send(request),
|
||||
Err(error) => async move { Err(error.into()) }.boxed(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn client() -> Arc<dyn HttpClient> {
|
||||
Arc::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(),
|
||||
)
|
||||
}
|
||||
|
||||
impl HttpClient for isahc::HttpClient {
|
||||
fn send(
|
||||
&self,
|
||||
req: Request<AsyncBody>,
|
||||
) -> BoxFuture<'static, Result<Response<AsyncBody>, Error>> {
|
||||
let client = self.clone();
|
||||
Box::pin(async move { client.send_async(req).await })
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "test-support")]
|
||||
type FakeHttpHandler = Box<
|
||||
dyn Fn(Request<AsyncBody>) -> BoxFuture<'static, Result<Response<AsyncBody>, Error>>
|
||||
+ Send
|
||||
+ Sync
|
||||
+ 'static,
|
||||
>;
|
||||
|
||||
#[cfg(feature = "test-support")]
|
||||
pub struct FakeHttpClient {
|
||||
handler: FakeHttpHandler,
|
||||
}
|
||||
|
||||
#[cfg(feature = "test-support")]
|
||||
impl FakeHttpClient {
|
||||
pub fn create<Fut, F>(handler: F) -> Arc<HttpClientWithUrl>
|
||||
where
|
||||
Fut: futures::Future<Output = Result<Response<AsyncBody>, Error>> + Send + 'static,
|
||||
F: Fn(Request<AsyncBody>) -> Fut + Send + Sync + 'static,
|
||||
{
|
||||
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<HttpClientWithUrl> {
|
||||
Self::create(|_| async move {
|
||||
Ok(Response::builder()
|
||||
.status(404)
|
||||
.body(Default::default())
|
||||
.unwrap())
|
||||
})
|
||||
}
|
||||
|
||||
pub fn with_200_response() -> Arc<HttpClientWithUrl> {
|
||||
Self::create(|_| async move {
|
||||
Ok(Response::builder()
|
||||
.status(200)
|
||||
.body(Default::default())
|
||||
.unwrap())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "test-support")]
|
||||
impl fmt::Debug for FakeHttpClient {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("FakeHttpClient").finish()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "test-support")]
|
||||
impl HttpClient for FakeHttpClient {
|
||||
fn send(
|
||||
&self,
|
||||
req: Request<AsyncBody>,
|
||||
) -> BoxFuture<'static, Result<Response<AsyncBody>, Error>> {
|
||||
let future = (self.handler)(req);
|
||||
Box::pin(async move { future.await.map(Into::into) })
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
pub mod arc_cow;
|
||||
pub mod fs;
|
||||
pub mod github;
|
||||
pub mod http;
|
||||
pub mod paths;
|
||||
pub mod serde;
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
|
@ -43,28 +41,6 @@ pub fn truncate(s: &str, max_chars: usize) -> &str {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn http_proxy_from_env() -> Option<isahc::http::Uri> {
|
||||
macro_rules! try_env {
|
||||
($($env:literal),+) => {
|
||||
$(
|
||||
if let Ok(env) = std::env::var($env) {
|
||||
return env.parse::<isahc::http::Uri>().ok();
|
||||
}
|
||||
)+
|
||||
};
|
||||
}
|
||||
|
||||
try_env!(
|
||||
"ALL_PROXY",
|
||||
"all_proxy",
|
||||
"HTTPS_PROXY",
|
||||
"https_proxy",
|
||||
"HTTP_PROXY",
|
||||
"http_proxy"
|
||||
);
|
||||
None
|
||||
}
|
||||
|
||||
/// Removes characters from the end of the string if its length is greater than `max_chars` and
|
||||
/// appends "..." to the string. Returns string unchanged if its length is smaller than max_chars.
|
||||
pub fn truncate_and_trailoff(s: &str, max_chars: usize) -> String {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue