|
| 1 | +use crate::{HttpClientError, HttpRequest, HttpResponse, SyncHttpClient}; |
| 2 | + |
| 3 | +use curl::easy::Easy; |
| 4 | +use http::header::{HeaderValue, CONTENT_TYPE}; |
| 5 | +use http::method::Method; |
| 6 | +use http::status::StatusCode; |
| 7 | + |
| 8 | +use std::io::Read; |
| 9 | + |
| 10 | +/// A synchronous HTTP client using [`curl`]. |
| 11 | +pub struct CurlHttpClient; |
| 12 | +impl SyncHttpClient for CurlHttpClient { |
| 13 | + type Error = HttpClientError<curl::Error>; |
| 14 | + |
| 15 | + fn call(&self, request: HttpRequest) -> Result<HttpResponse, Self::Error> { |
| 16 | + let mut easy = Easy::new(); |
| 17 | + easy.url(&request.uri().to_string()[..]).map_err(Box::new)?; |
| 18 | + |
| 19 | + let mut headers = curl::easy::List::new(); |
| 20 | + for (name, value) in request.headers() { |
| 21 | + headers |
| 22 | + .append(&format!( |
| 23 | + "{}: {}", |
| 24 | + name, |
| 25 | + // TODO: Unnecessary fallibility, curl uses a CString under the hood |
| 26 | + value.to_str().map_err(|_| HttpClientError::Other(format!( |
| 27 | + "invalid `{name}` header value {:?}", |
| 28 | + value.as_bytes() |
| 29 | + )))? |
| 30 | + )) |
| 31 | + .map_err(Box::new)? |
| 32 | + } |
| 33 | + |
| 34 | + easy.http_headers(headers).map_err(Box::new)?; |
| 35 | + |
| 36 | + if let Method::POST = *request.method() { |
| 37 | + easy.post(true).map_err(Box::new)?; |
| 38 | + easy.post_field_size(request.body().len() as u64) |
| 39 | + .map_err(Box::new)?; |
| 40 | + } else { |
| 41 | + assert_eq!(*request.method(), Method::GET); |
| 42 | + } |
| 43 | + |
| 44 | + let mut form_slice = &request.body()[..]; |
| 45 | + let mut data = Vec::new(); |
| 46 | + { |
| 47 | + let mut transfer = easy.transfer(); |
| 48 | + |
| 49 | + transfer |
| 50 | + .read_function(|buf| Ok(form_slice.read(buf).unwrap_or(0))) |
| 51 | + .map_err(Box::new)?; |
| 52 | + |
| 53 | + transfer |
| 54 | + .write_function(|new_data| { |
| 55 | + data.extend_from_slice(new_data); |
| 56 | + Ok(new_data.len()) |
| 57 | + }) |
| 58 | + .map_err(Box::new)?; |
| 59 | + |
| 60 | + transfer.perform().map_err(Box::new)?; |
| 61 | + } |
| 62 | + |
| 63 | + let mut builder = http::Response::builder().status( |
| 64 | + StatusCode::from_u16(easy.response_code().map_err(Box::new)? as u16) |
| 65 | + .map_err(http::Error::from)?, |
| 66 | + ); |
| 67 | + |
| 68 | + if let Some(content_type) = easy |
| 69 | + .content_type() |
| 70 | + .map_err(Box::new)? |
| 71 | + .map(HeaderValue::from_str) |
| 72 | + .transpose() |
| 73 | + .map_err(http::Error::from)? |
| 74 | + { |
| 75 | + builder = builder.header(CONTENT_TYPE, content_type); |
| 76 | + } |
| 77 | + |
| 78 | + builder.body(data).map_err(HttpClientError::Http) |
| 79 | + } |
| 80 | +} |
0 commit comments