curl_http/
lib.rs

1extern crate curl;
2extern crate failure;
3#[macro_use]
4extern crate failure_derive;
5extern crate serde;
6extern crate serde_json;
7
8// Magic failure::ResultExt which has context method
9// and implements for std::result::Result
10use failure::{Backtrace, Context, Fail, ResultExt};
11use serde::de::DeserializeOwned;
12use serde::Serialize;
13use std::cell::{RefCell, RefMut};
14use std::fmt;
15use std::io::{Read, Write};
16
17/// Shortcut alias for results of this module.
18pub type Result<T> = std::result::Result<T, failure::Error>;
19
20/// A enum represents HTTP methods.
21#[derive(PartialEq, Debug)]
22pub enum Method {
23    Get,
24    Head,
25    Post,
26    Put,
27    Delete,
28}
29
30impl fmt::Display for Method {
31    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
32        match *self {
33            Method::Get => write!(f, "GET"),
34            Method::Head => write!(f, "HEAD"),
35            Method::Post => write!(f, "POST"),
36            Method::Put => write!(f, "PUT"),
37            Method::Delete => write!(f, "DELETE"),
38        }
39    }
40}
41
42///
43/// A Http client base on curl.
44///
45pub struct Client {
46    shared_handle: RefCell<curl::easy::Easy>,
47    base_url: String,
48    user_agent: String,
49}
50
51impl Client {
52    /// Initialize a curl http client based on the **base_url**.
53    pub fn new(base_url: &str) -> Client {
54        Client {
55            shared_handle: RefCell::new(curl::easy::Easy::new()),
56            base_url: base_url.to_string(),
57            user_agent: "curl-http".to_string(),
58        }
59    }
60
61    /// Set the User-Agent header. Default is `curl-http`
62    pub fn set_user_agent(&mut self, user_agent: &str) {
63        self.user_agent = user_agent.to_string();
64    }
65
66    /// Make a specific method request.
67    pub fn request(&self, method: Method, endpoint: &str) -> Result<Request> {
68        let url = format!("{}{}", self.base_url, endpoint);
69        let mut handle = self.shared_handle.borrow_mut();
70        handle.reset();
71        Request::new(handle, method, &url)?.with_user_agent(&self.user_agent)
72    }
73
74    /// High level HTTP **GET** method
75    pub fn get(&self, endpoint: &str) -> Result<Response> {
76        self.request(Method::Get, endpoint)?.send()
77    }
78
79    /// High level HTTP **POST** method
80    pub fn post<S: Serialize>(&self, endpoint: &str, body: &S) -> Result<Response> {
81        self.request(Method::Post, endpoint)?
82            .with_json_body(body)?
83            .send()
84    }
85
86    /// High level HTTP **PUT** method
87    pub fn put<S: Serialize>(&self, endpoint: &str, body: &S) -> Result<Response> {
88        self.request(Method::Put, endpoint)?
89            .with_json_body(body)?
90            .send()
91    }
92
93    /// High level HTTP **DELETE** method
94    pub fn delete(&self, endpoint: &str) -> Result<Response> {
95        self.request(Method::Delete, endpoint)?.send()
96    }
97}
98
99/// The struct represents the HTTP request.
100pub struct Request<'a> {
101    handle: RefMut<'a, curl::easy::Easy>,
102    headers: curl::easy::List,
103    url: String,
104    body: Option<Vec<u8>>,
105}
106
107impl<'a> Request<'a> {
108    pub fn new(
109        mut handle: RefMut<'a, curl::easy::Easy>,
110        method: Method,
111        url: &str,
112    ) -> Result<Request<'a>> {
113        match method {
114            Method::Get => handle.get(true)?,
115            Method::Head => {
116                handle.get(true)?;
117                handle.custom_request("HEAD")?;
118                handle.nobody(true)?;
119            }
120            Method::Post => handle.custom_request("POST")?,
121            Method::Put => handle.custom_request("PUT")?,
122            Method::Delete => handle.custom_request("DELETE")?,
123        }
124
125        Ok(Request {
126            handle,
127            headers: curl::easy::List::new(),
128            url: url.to_string(),
129            body: None,
130        })
131    }
132
133    /// Set the HTTP header.
134    pub fn with_header(mut self, key: &str, value: &str) -> Result<Request<'a>> {
135        self.headers.append(&format!("{}: {}", key, value))?;
136        Ok(self)
137    }
138
139    /// Set custom User-Agent.
140    pub fn with_user_agent(mut self, ua: &str) -> Result<Request<'a>> {
141        self.headers.append(&format!("User-Agent: {}", ua))?;
142        Ok(self)
143    }
144
145    /// Set custom url arguments or querystring.
146    pub fn with_arguments(mut self, args: &str) -> Result<Request<'a>> {
147        self.url = format!("{}?{}", self.url, args);
148        Ok(self)
149    }
150
151    /// Set the JSON request body for the request.
152    pub fn with_json_body<S: Serialize>(mut self, body: &S) -> Result<Request<'a>> {
153        let mut body_bytes: Vec<u8> = vec![];
154        // Serialize json object to bytes
155        serde_json::to_writer(&mut body_bytes, &body)
156            .context(ErrorKind::InvalidJsonBody)?;
157
158        self.body = Some(body_bytes);
159        self.headers.append("Content-Type: application/json")?;
160        Ok(self)
161    }
162
163    /// Sends the request and reads the response body into the response object.
164    pub fn send(mut self) -> Result<Response> {
165        self.handle.http_headers(self.headers)?;
166        self.handle.url(&self.url)?;
167
168        match self.body {
169            Some(ref body) => {
170                let mut body: &[u8] = &body[..];
171                self.handle.upload(true)?;
172                self.handle.in_filesize(body.len() as u64)?;
173                handle_request(&mut self.handle, &mut |buffer| {
174                    body.read(buffer).unwrap_or(0)
175                })
176            }
177            None => handle_request(&mut self.handle, &mut |_| 0)
178        }
179    }
180}
181
182fn handle_request(
183    handle: &mut curl::easy::Easy,
184    read: &mut FnMut(&mut [u8]) -> usize) -> Result<Response> {
185    let mut response_body = vec![];
186    let mut response_headers = vec![];
187
188    {
189        let mut handle = handle.transfer();
190
191        handle.read_function(move |buffer| Ok(read(buffer)))?;
192
193        handle.write_function(|data| {
194            Ok(match response_body.write_all(data) {
195                Ok(_) => data.len(),
196                Err(_) => 0,
197            })
198        })?;
199
200        handle.header_function(|data| {
201            response_headers.push(String::from_utf8_lossy(data).into_owned());
202            true
203        })?;
204        handle.perform()?;
205    }
206
207    Ok(Response {
208        status: handle.response_code()?,
209        headers: response_headers,
210        body: Some(response_body),
211    })
212}
213
214/// Type alias for **u32** http status.
215pub type HttpStatus = u32;
216
217/// The struct represents the HTTP response.
218#[derive(Clone, Debug)]
219pub struct Response {
220    status: HttpStatus,
221    headers: Vec<String>,
222    body: Option<Vec<u8>>,
223}
224
225impl Response {
226    pub fn status(&self) -> HttpStatus {
227        self.status
228    }
229
230    pub fn failed(&self) -> bool {
231        self.status >= 400 && self.status <= 600
232    }
233
234    pub fn ok(&self) -> bool {
235        !self.failed()
236    }
237
238    /// Deserialize the response body into the given type
239    pub fn deserialize<T: DeserializeOwned>(&self) -> Result<T> {
240        if self.ok() {
241            Ok(serde_json::from_reader(match self.body {
242                Some(ref body) => body,
243                None => &b""[..],
244            }).context(ErrorKind::InvalidJson)?)
245        } else {
246            Err(ErrorKind::RequestFailed.into())
247        }
248    }
249}
250
251#[derive(Copy, Clone, Eq, PartialEq, Debug, Fail)]
252pub enum ErrorKind {
253    #[fail(display = "Request failed")]
254    RequestFailed,
255    #[fail(display = "Could not serialize value as JSON")]
256    InvalidJsonBody,
257    #[fail(display = "Could not parse JSON response")]
258    InvalidJson,
259}
260
261/// Curl http error.
262#[derive(Debug)]
263pub struct Error {
264    inner: Context<ErrorKind>,
265}
266
267impl self::Error {
268    pub fn kind(&self) -> ErrorKind {
269        *self.inner.get_context()
270    }
271}
272
273impl Fail for self::Error {
274    fn cause(&self) -> Option<&Fail> {
275        self.inner.cause()
276    }
277
278    fn backtrace(&self) -> Option<&Backtrace> {
279        self.inner.backtrace()
280    }
281}
282
283impl fmt::Display for self::Error {
284    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
285        fmt::Display::fmt(&self.inner, f)
286    }
287}
288
289impl From<ErrorKind> for self::Error {
290    fn from(kind: ErrorKind) -> self::Error {
291        self::Error { inner: Context::new(kind) }
292    }
293}
294
295impl From<Context<ErrorKind>> for self::Error {
296    fn from(inner: Context<ErrorKind>) -> self::Error {
297        self::Error { inner }
298    }
299}
300
301impl From<curl::Error> for self::Error {
302    fn from(error: curl::Error) -> self::Error {
303        failure::Error::from(error).context(ErrorKind::RequestFailed).into()
304    }
305}