http.rs

  1use crate::http_proxy_from_env;
  2pub use anyhow::{anyhow, Result};
  3use futures::future::BoxFuture;
  4use isahc::config::{Configurable, RedirectPolicy};
  5pub use isahc::{
  6    http::{Method, StatusCode, Uri},
  7    Error,
  8};
  9pub use isahc::{AsyncBody, Request, Response};
 10use parking_lot::Mutex;
 11use smol::future::FutureExt;
 12#[cfg(feature = "test-support")]
 13use std::fmt;
 14use std::{sync::Arc, time::Duration};
 15pub use url::Url;
 16
 17pub struct ZedHttpClient {
 18    pub zed_host: Mutex<String>,
 19    client: Box<dyn HttpClient>,
 20}
 21
 22impl ZedHttpClient {
 23    pub fn zed_url(&self, path: &str) -> String {
 24        format!("{}{}", self.zed_host.lock(), path)
 25    }
 26}
 27
 28impl HttpClient for Arc<ZedHttpClient> {
 29    fn send(&self, req: Request<AsyncBody>) -> BoxFuture<Result<Response<AsyncBody>, Error>> {
 30        self.client.send(req)
 31    }
 32}
 33
 34impl HttpClient for ZedHttpClient {
 35    fn send(&self, req: Request<AsyncBody>) -> BoxFuture<Result<Response<AsyncBody>, Error>> {
 36        self.client.send(req)
 37    }
 38}
 39
 40pub fn zed_client(zed_host: &str) -> Arc<ZedHttpClient> {
 41    Arc::new(ZedHttpClient {
 42        zed_host: Mutex::new(zed_host.to_string()),
 43        client: Box::new(
 44            isahc::HttpClient::builder()
 45                .connect_timeout(Duration::from_secs(5))
 46                .low_speed_timeout(100, Duration::from_secs(5))
 47                .build()
 48                .unwrap(),
 49        ),
 50    })
 51}
 52
 53pub trait HttpClient: Send + Sync {
 54    fn send(&self, req: Request<AsyncBody>) -> BoxFuture<Result<Response<AsyncBody>, Error>>;
 55
 56    fn get<'a>(
 57        &'a self,
 58        uri: &str,
 59        body: AsyncBody,
 60        follow_redirects: bool,
 61    ) -> BoxFuture<'a, Result<Response<AsyncBody>, Error>> {
 62        let request = isahc::Request::builder()
 63            .redirect_policy(if follow_redirects {
 64                RedirectPolicy::Follow
 65            } else {
 66                RedirectPolicy::None
 67            })
 68            .method(Method::GET)
 69            .uri(uri)
 70            .body(body);
 71        match request {
 72            Ok(request) => self.send(request),
 73            Err(error) => async move { Err(error.into()) }.boxed(),
 74        }
 75    }
 76
 77    fn post_json<'a>(
 78        &'a self,
 79        uri: &str,
 80        body: AsyncBody,
 81    ) -> BoxFuture<'a, Result<Response<AsyncBody>, Error>> {
 82        let request = isahc::Request::builder()
 83            .method(Method::POST)
 84            .uri(uri)
 85            .header("Content-Type", "application/json")
 86            .body(body);
 87        match request {
 88            Ok(request) => self.send(request),
 89            Err(error) => async move { Err(error.into()) }.boxed(),
 90        }
 91    }
 92}
 93
 94pub fn client() -> Arc<dyn HttpClient> {
 95    Arc::new(
 96        isahc::HttpClient::builder()
 97            .connect_timeout(Duration::from_secs(5))
 98            .low_speed_timeout(100, Duration::from_secs(5))
 99            .proxy(http_proxy_from_env())
100            .build()
101            .unwrap(),
102    )
103}
104
105impl HttpClient for isahc::HttpClient {
106    fn send(&self, req: Request<AsyncBody>) -> BoxFuture<Result<Response<AsyncBody>, Error>> {
107        Box::pin(async move { self.send_async(req).await })
108    }
109}
110
111#[cfg(feature = "test-support")]
112pub struct FakeHttpClient {
113    handler: Box<
114        dyn 'static
115            + Send
116            + Sync
117            + Fn(Request<AsyncBody>) -> BoxFuture<'static, Result<Response<AsyncBody>, Error>>,
118    >,
119}
120
121#[cfg(feature = "test-support")]
122impl FakeHttpClient {
123    pub fn create<Fut, F>(handler: F) -> Arc<ZedHttpClient>
124    where
125        Fut: 'static + Send + futures::Future<Output = Result<Response<AsyncBody>, Error>>,
126        F: 'static + Send + Sync + Fn(Request<AsyncBody>) -> Fut,
127    {
128        Arc::new(ZedHttpClient {
129            zed_host: Mutex::new("http://test.example".into()),
130            client: Box::new(Self {
131                handler: Box::new(move |req| Box::pin(handler(req))),
132            }),
133        })
134    }
135
136    pub fn with_404_response() -> Arc<ZedHttpClient> {
137        Self::create(|_| async move {
138            Ok(Response::builder()
139                .status(404)
140                .body(Default::default())
141                .unwrap())
142        })
143    }
144
145    pub fn with_200_response() -> Arc<ZedHttpClient> {
146        Self::create(|_| async move {
147            Ok(Response::builder()
148                .status(200)
149                .body(Default::default())
150                .unwrap())
151        })
152    }
153}
154
155#[cfg(feature = "test-support")]
156impl fmt::Debug for FakeHttpClient {
157    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
158        f.debug_struct("FakeHttpClient").finish()
159    }
160}
161
162#[cfg(feature = "test-support")]
163impl HttpClient for FakeHttpClient {
164    fn send(&self, req: Request<AsyncBody>) -> BoxFuture<Result<Response<AsyncBody>, Error>> {
165        let future = (self.handler)(req);
166        Box::pin(async move { future.await.map(Into::into) })
167    }
168}