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
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
use std::io::Read;

use curl::easy::Easy;
use http::header::{HeaderMap, HeaderValue, CONTENT_TYPE};
use http::method::Method;
use http::status::StatusCode;

use super::{HttpRequest, HttpResponse};

///
/// Error type returned by failed curl HTTP requests.
///
#[derive(Debug, thiserror::Error)]
pub enum Error {
    /// Error returned by curl crate.
    #[error("curl request failed")]
    Curl(#[source] curl::Error),
    /// Non-curl HTTP error.
    #[error("HTTP error")]
    Http(#[source] http::Error),
    /// Other error.
    #[error("Other error: {}", _0)]
    Other(String),
}

///
/// Synchronous HTTP client.
///
pub fn http_client(request: HttpRequest) -> Result<HttpResponse, Error> {
    let mut easy = Easy::new();
    easy.url(&request.url.to_string()[..])
        .map_err(Error::Curl)?;

    let mut headers = curl::easy::List::new();
    request
        .headers
        .iter()
        .map(|(name, value)| {
            headers
                .append(&format!(
                    "{}: {}",
                    name,
                    value.to_str().map_err(|_| Error::Other(format!(
                        "invalid {} header value {:?}",
                        name,
                        value.as_bytes()
                    )))?
                ))
                .map_err(Error::Curl)
        })
        .collect::<Result<_, _>>()?;

    easy.http_headers(headers).map_err(Error::Curl)?;

    if let Method::POST = request.method {
        easy.post(true).map_err(Error::Curl)?;
        easy.post_field_size(request.body.len() as u64)
            .map_err(Error::Curl)?;
    } else {
        assert_eq!(request.method, Method::GET);
    }

    let mut form_slice = &request.body[..];
    let mut data = Vec::new();
    {
        let mut transfer = easy.transfer();

        transfer
            .read_function(|buf| Ok(form_slice.read(buf).unwrap_or(0)))
            .map_err(Error::Curl)?;

        transfer
            .write_function(|new_data| {
                data.extend_from_slice(new_data);
                Ok(new_data.len())
            })
            .map_err(Error::Curl)?;

        transfer.perform().map_err(Error::Curl)?;
    }

    let status_code = easy.response_code().map_err(Error::Curl)? as u16;

    Ok(HttpResponse {
        status_code: StatusCode::from_u16(status_code).map_err(|err| Error::Http(err.into()))?,
        headers: easy
            .content_type()
            .map_err(Error::Curl)?
            .map(|content_type| {
                Ok(vec![(
                    CONTENT_TYPE,
                    HeaderValue::from_str(content_type).map_err(|err| Error::Http(err.into()))?,
                )]
                .into_iter()
                .collect::<HeaderMap>())
            })
            .transpose()?
            .unwrap_or_else(HeaderMap::new),
        body: data,
    })
}