76 releases
0.10.6 | Oct 3, 2024 |
---|---|
0.10.5 | Feb 22, 2024 |
0.10.4 | Aug 10, 2023 |
0.10.2 | Jan 23, 2023 |
0.2.3 | Dec 18, 2017 |
#87 in HTTP client
Used in lightql
325KB
7.5K
SLoC
mio_httpc is an async http client that runs on top of mio only.
For convenience it also provides CallBuilder::exec for a simple one-line blocking HTTP call.
Except CallBuilder::exec no call will block, not even for DNS resolution as it is implemented internally to avoid blocking.
For https to work mio_httpc requires you specify one of the TLS implementations using features: native, openssl or rtls (rustls). Default build will fail on any https URI.
CallBuilder also has URL construction functions (host/path_segm/query/set_https/auth/https) which will take care of url-safe encoding.
mio_httpc does a minimal amount of allocation and in general works with buffers you provide and an internal pool of buffers that get reused on new calls.
Chunk transfer decoding
mio_httpc uses a default max chunk size of 128k. If you are writing a web scraper or something similar, this might trip you up since some websites have pretty large chunks. I will not increase (or remove) this default size limitation because I do not like unchecked memory growth from a third party. You can easily increase this with CallBuilder::chunked_max_chunk call.
TODO/FEATURE LIST
- Basic API
- Configurable TLS backend
- Chunked encoding download
- Chunked encoding upload
- Safe URL construction
- Basic Auth
- Digest Auth
- Automatic redirects
- Keep-alive connection pool
- DNS retries
- Timeouts
- Websockets
- gzip body decoding
- SSL pinning on subjectPublicKeyInfo (OpenSSL backend with any target_os and macos/ios with native backend)
- HTTP2
- Download to file
EXAMPLES
Include mio_httpc in your project with:
# System native TLS implementation
mio_httpc = { version = "0.8", features = ["native"] }
# Openssl
# mio_httpc = { version = "0.8", features = ["openssl"] }
# Rustls
# mio_httpc = { version = "0.8", features = ["rtls"] }
Sync call
extern crate mio_httpc;
use mio_httpc::CallBuilder;
// One line blocking call.
let (response_meta, body) = CallBuilder::get().timeout_ms(5000).url("http://www.example.com")?.exec()?;
// With URL construction.
// This way of building the URL is highly recommended as it will always result in correct
// values by percent encoding any URL unsafe characters.
// This calls: https://www.example.com/a/b?key1=val1
let (response_meta, body) = CallBuilder::get()
.timeout_ms(5000)
.https()
.host("www.example.com")
.path_segm("a")
.path_segm("b")
.query("key1","val1")
.exec()?;
Basic async get
cargo run --example get --features "native" -- "https://edition.cnn.com"
// or
cargo run --example get --features "openssl" -- "https://edition.cnn.com"
// or
cargo run --example get --features "rtls" -- "https://edition.cnn.com"
extern crate mio_httpc;
extern crate mio;
use mio_httpc::{CallBuilder,Httpc};
use mio::{Poll,Events};
fn main() {
let mut poll = Poll::new().unwrap();
let mut htp = Httpc::new(10,None);
let args: Vec<String> = ::std::env::args().collect();
let mut call = CallBuilder::get()
.url(args[1].as_str()).expect("Can not parse url")
.timeout_ms(500)
.call_simple(&mut htp, poll.registry())
.expect("Call start failed");
let to = ::std::time::Duration::from_millis(100);
let mut events = Events::with_capacity(8);
'outer: loop {
poll.poll(&mut events, Some(to)).unwrap();
for cref in htp.timeout().into_iter() {
if call.is_ref(cref) {
println!("Request timed out");
call.abort(&mut htp);
break 'outer;
}
}
for ev in events.iter() {
let cref = htp.event(&ev);
if call.is_call(&cref) {
if call.perform(&mut htp, poll.registry()).expect("Call failed") {
let (resp,body) = call.finish().expect("No response");
if let Ok(s) = String::from_utf8(v) {
println!("Body: {}",s);
}
break 'outer;
}
}
}
}
}
Websockets
cargo run --example ws --features="native" -- "wss://demos.kaazing.com/echo"
extern crate mio_httpc;
extern crate mio;
use mio_httpc::{CallBuilder,Httpc,WebSocket,WSPacket};
use mio::{Poll,Events};
// ws://demos.kaazing.com/echo
fn main() {
let mut poll = Poll::new().unwrap();
let mut htp = Httpc::new(10,None);
let args: Vec<String> = ::std::env::args().collect();
let mut ws = CallBuilder::get()
.url(args[1].as_str()).expect("Can not parse url")
.websocket(&mut htp, poll.registry())
.expect("Call start failed");
let to = ::std::time::Duration::from_millis(800);
'outer: loop {
let mut events = Events::with_capacity(8);
poll.poll(&mut events, Some(to)).unwrap();
for cref in htp.timeout().into_iter() {
if ws.is_ref(cref) {
println!("Request timed out");
break 'outer;
}
}
if events.len() == 0 {
// ws.ping(None);
println!("send yo");
ws.send_text(true, "yo!");
}
for ev in events.iter() {
let cref = htp.event(&ev);
if ws.is_call(&cref) {
if ws.is_active() {
loop {
match ws.recv_packet(&mut htp, poll.registry()).expect("Failed recv") {
WSPacket::Pong(_) => {
println!("Got pong!");
}
WSPacket::Ping(_) => {
println!("Got ping!");
ws.pong(None);
}
WSPacket::None => {
break;
}
WSPacket::Close(_,_) => {
println!("Got close!");
ws.close(None, None);
break 'outer;
}
WSPacket::Text(fin,txt) => {
println!("Got text={}, fin={}",txt,fin);
}
WSPacket::Binary(fin,b) => {
println!("Got bin={}B, fin={}",b.len(),fin);
}
}
}
} else {
if ws.sendq_len() == 0 {
ws.ping(None);
}
}
}
}
// Any ping/pong/close/send_text/send_bin has just been buffered.
// perform and recv_packet actually send over socket.
ws.perform(&mut htp, poll.registry()).expect("Call failed");
}
ws.perform(&mut htp, poll.registry());
ws.finish(&mut htp);
}
Dependencies
~5–18MB
~275K SLoC