1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
use anyhow::Context;
use futures::future::BoxFuture;
use std::convert::TryFrom;

use super::{HttpRequest, HttpResponse};

#[derive(Default, Clone, Debug)]
pub struct ReqwestHttpClient {}

impl ReqwestHttpClient {
    async fn request(&self, request: HttpRequest) -> Result<HttpResponse, anyhow::Error> {
        let method = reqwest::Method::try_from(request.method.as_str())
            .with_context(|| format!("Invalid http method {}", request.method))?;

        // TODO: use persistent client?
        let client = reqwest::ClientBuilder::default()
            .build()
            .context("Could not create reqwest client")?;

        let mut builder = client.request(method, request.url.as_str());
        for (header, val) in &request.headers {
            builder = builder.header(header, val);
        }

        if let Some(body) = request.body {
            builder = builder.body(reqwest::Body::from(body));
        }

        let request = builder
            .build()
            .context("Failed to construct http request")?;

        let response = client.execute(request).await?;

        let status = response.status().as_u16();
        let status_text = response.status().as_str().to_string();
        // TODO: prevent redundant header copy.
        let headers = response
            .headers()
            .iter()
            .map(|(k, v)| (k.to_string(), v.to_str().unwrap().to_string()))
            .collect();
        let data = response.bytes().await?.to_vec();

        Ok(HttpResponse {
            pos: 0usize,
            ok: true,
            status,
            status_text,
            redirected: false,
            body: Some(data),
            headers,
        })
    }
}

impl super::HttpClient for ReqwestHttpClient {
    fn request(&self, request: HttpRequest) -> BoxFuture<Result<HttpResponse, anyhow::Error>> {
        let client = self.clone();
        let f = async move { client.request(request).await };
        Box::pin(f)
    }
}