mio_httpc/
lib.rs

1//! mio_httpc is an async http client that runs on top of mio only.
2//!
3//! For convenience it also provides CallBuilder::exec for a simple one-line blocking HTTP call.
4//!
5//! Except CallBuilder::exec no call will block, not even for DNS resolution as it is implemented internally to avoid blocking.
6//!
7//! For https to work you must specify one of the TLS implementations using features: rtls (rustls), native, openssl.
8//! Default build will fail on any https URI.
9//!
10//! CallBuilder also has URL construction functions (host/path_segm/query/set_https/auth/https) which will take care of url-safe encoding.
11//!
12//! mio_httpc does a minimal amount of allocation and in general works with buffers you provide and an internal pool
13//! of buffers that get reused on new calls.
14//!
15//! # Examples
16//!
17//! ```no_run
18//! extern crate mio_httpc;
19//! extern crate mio;
20//!
21//! use mio_httpc::{CallBuilder,Httpc};
22//! use mio::{Poll,Events};
23//! # fn main() -> Result<(), mio_httpc::Error> {
24//!
25//! let mut poll = Poll::new().unwrap();
26//! let mut htp = Httpc::new(10,None);
27//! let mut call = CallBuilder::get()
28//!     .url("https://www.reddit.com").expect("Invalid url")
29//!     .timeout_ms(500)
30//!     .simple_call(&mut htp, poll.registry())?;
31//!
32//! let to = ::std::time::Duration::from_millis(100);
33//! let mut events = Events::with_capacity(8);
34//! 'outer: loop {
35//!     poll.poll(&mut events, Some(to)).unwrap();
36//!     for cref in htp.timeout().into_iter() {
37//!        if call.is_ref(cref) {
38//!            println!("Request timed out");
39//!            call.abort(&mut htp);
40//!            break 'outer;
41//!        }
42//!    }
43//!
44//!    for ev in events.iter() {
45//!        let cref = htp.event(&ev);
46//!
47//!        if call.is_call(&cref) {
48//!            if call.perform(&mut htp, poll.registry())? {
49//!                let (resp,body) = call.finish().ok_or(mio_httpc::Error::Other("unexpected state"))?;
50//!                if let Ok(s) = String::from_utf8(body) {
51//!                    println!("Body: {}",s);
52//!                }
53//!                break 'outer;
54//!            }
55//!        }
56//!    }
57//! }
58//!
59//! # Ok(())
60//! # }
61//! ```
62//!
63//! ```no_run
64//! extern crate mio_httpc;
65//! use mio_httpc::CallBuilder;
66//! # fn main() -> Result<(), mio_httpc::Error> {
67//!
68//! // One line blocking call.
69//!
70//! let (response_meta, body) = CallBuilder::get().timeout_ms(5000).url("http://www.example.com")?.exec()?;
71//!
72//! # Ok(())
73//! # }
74//! ```
75#![doc(html_root_url = "https://docs.rs/mio_httpc")]
76#![crate_name = "mio_httpc"]
77
78#[macro_use]
79extern crate pest_derive;
80#[cfg(any(target_os = "ios", target_os = "macos"))]
81#[macro_use]
82extern crate core_foundation;
83#[cfg(any(target_os = "ios", target_os = "macos"))]
84extern crate core_foundation_sys;
85
86#[macro_use]
87extern crate failure;
88#[cfg(test)]
89#[macro_use]
90extern crate matches;
91
92mod api;
93mod call;
94mod connection;
95#[allow(dead_code, unused_imports)]
96mod dns_parser;
97mod httpc;
98mod resolve;
99mod tls_api;
100#[allow(dead_code, unused_variables)]
101mod types;
102
103pub use crate::api::*;
104#[cfg(feature = "native")]
105pub use native_tls::Error as TLSError;
106#[cfg(feature = "openssl")]
107pub use openssl::error::Error as OpenSSLError;
108#[cfg(feature = "openssl")]
109pub use openssl::error::ErrorStack as OpenSSLErrorStack;
110#[cfg(feature = "openssl")]
111pub use openssl::ssl::Error as TLSError;
112#[cfg(feature = "rustls")]
113pub use rustls::Error as TLSError;
114
115#[cfg(not(any(feature = "rustls", feature = "native", feature = "openssl")))]
116pub use crate::tls_api::{dummy::hash, HashType};
117#[cfg(feature = "native")]
118pub use crate::tls_api::{native::hash, HashType};
119#[cfg(feature = "openssl")]
120pub use crate::tls_api::{openssl::hash, HashType};
121#[cfg(feature = "rustls")]
122pub use crate::tls_api::{rustls::hash, HashType};
123
124pub type Result<T> = ::std::result::Result<T, Error>;
125#[derive(Debug, Fail)]
126pub enum Error {
127    #[fail(display = "IO error: {}", _0)]
128    Io(#[cause] ::std::io::Error),
129
130    #[fail(display = "Utf8 error: {}", _0)]
131    Utf8(#[cause] std::str::Utf8Error),
132
133    #[fail(display = "FromUtf8 error: {}", _0)]
134    FromUtf8(#[cause] std::string::FromUtf8Error),
135
136    #[fail(display = "AddrParseError: {}", _0)]
137    Addr(#[cause] std::net::AddrParseError),
138
139    #[fail(display = "Httparse error: {}", _0)]
140    Httparse(#[cause] httparse::Error),
141
142    #[fail(display = "WebSocket setup failed")]
143    WebSocketFail(Response),
144
145    #[fail(display = "Sync call timed out")]
146    TimeOut,
147    /// Request structure did not contain body and CallSimple was used for POST/PUT.
148    #[fail(
149        display = "Request structure did not contain body and CallSimple was used for POST/PUT."
150    )]
151    MissingBody,
152    /// Response over max_response limit
153    #[fail(display = "Response over max_response limit")]
154    ResponseTooBig,
155    #[fail(display = "Redirects back to itself, missing or invalid location header")]
156    InvalidRedirect,
157    /// Connection closed.
158    #[fail(display = "Connection closed")]
159    Closed,
160    /// No host found in request
161    #[fail(display = "No host found in request")]
162    NoHost,
163    /// Invalid scheme
164    #[fail(display = "Invalid scheme")]
165    InvalidScheme,
166
167    #[cfg(any(feature = "rustls", feature = "native", feature = "openssl"))]
168    #[fail(display = "TLS error {}", _0)]
169    Tls(#[cause] TLSError),
170
171    #[cfg(feature = "openssl")]
172    #[fail(display = "OpenSSL stack error {}", _0)]
173    OpenSSLErrorStack(#[cause] OpenSSLErrorStack),
174
175    #[cfg(feature = "openssl")]
176    #[fail(display = "OpenSSL error {}", _0)]
177    OpenSSLError(#[cause] OpenSSLError),
178    /// All 0xFFFF slots for connections are full.
179    #[fail(display = "Concurrent connection limit")]
180    NoSpace,
181
182    #[fail(display = "URL parse error {}", _0)]
183    Url(#[cause] url::ParseError),
184
185    #[fail(display = "{}", _0)]
186    Other(&'static str),
187    /// You must pick one of the features: native, rustls, openssl
188    #[fail(display = "You must pick one of the features: native, rustls, openssl")]
189    NoTls,
190    /// Eror while parsing chunked stream
191    #[fail(display = "Error parsing chunked transfer")]
192    ChunkedParse,
193    /// Eror while parsing chunked stream
194    #[fail(display = "Error parsing WebSocket transfer")]
195    WebSocketParse,
196    /// Eror while parsing chunked stream
197    #[fail(display = "Error parsing WWW-Authenticate header")]
198    AuthenticateParse,
199
200    #[fail(display = "Pins were configured for domain and they did not match")]
201    InvalidPin,
202
203    #[fail(display = "Can not decompress gzip/deflate response")]
204    DecompressionFailure,
205
206    /// Chunk was larger than configured CallBuilder::chunked_max_chunk.
207    #[fail(
208        display = "Chunk was larger than configured CallBuilder::chunked_max_chunk. {}",
209        _0
210    )]
211    ChunkOverlimit(usize),
212}
213
214impl From<std::io::Error> for Error {
215    fn from(e: std::io::Error) -> Self {
216        Error::Io(e)
217    }
218}
219impl From<url::ParseError> for Error {
220    fn from(e: url::ParseError) -> Self {
221        Error::Url(e)
222    }
223}
224#[cfg(any(feature = "rustls", feature = "native", feature = "openssl"))]
225impl From<TLSError> for Error {
226    fn from(e: TLSError) -> Self {
227        Error::Tls(e)
228    }
229}
230#[cfg(feature = "openssl")]
231impl From<openssl::error::ErrorStack> for Error {
232    fn from(e: openssl::error::ErrorStack) -> Self {
233        Error::OpenSSLErrorStack(e)
234    }
235}
236#[cfg(feature = "openssl")]
237impl From<OpenSSLError> for Error {
238    fn from(e: OpenSSLError) -> Self {
239        Error::OpenSSLError(e)
240    }
241}
242impl From<httparse::Error> for Error {
243    fn from(e: httparse::Error) -> Self {
244        Error::Httparse(e)
245    }
246}
247impl From<std::string::FromUtf8Error> for Error {
248    fn from(e: std::string::FromUtf8Error) -> Self {
249        Error::FromUtf8(e)
250    }
251}
252
253#[cfg(test)]
254mod tests {
255    #[test]
256    fn parse_headers() {
257        let v = b"HTTP/1.1 200 OK\r\nContent-length: 100\r\nUpgrade: websocket\r\n".to_vec();
258        let mut r = crate::Response::new();
259        r.hdrs = v;
260
261        {
262            let hdrs = r.headers();
263            for h in hdrs {
264                println!("{}: {}", h.name, h.value);
265            }
266        }
267    }
268}