I/O-free HTTP/1.X client library written in Rust, based on io-socket
This library implements HTTP as I/O-agnostic coroutines — no sockets, no async runtime, no std required.
| RFC | What it covers |
|---|---|
| 1945 | HTTP/1.0 — request/response coroutine (Http10Send) |
| 6750 | OAuth 2.0 Bearer token — Authorization: Bearer <token> |
| 7617 | HTTP Basic authentication — Authorization: Basic <base64(user:pass)> |
| 8615 | .well-known URI discovery — WellKnown coroutine |
| 9110 | HTTP semantics — shared types: HttpRequest, HttpResponse, StatusCode |
| 9112 | HTTP/1.1 — request/response coroutine (Http11Send), chunked transfer encoding |
use std::{net::TcpStream, sync::Arc};
use io_http::rfc9110::request::HttpRequest;
use io_http::rfc9112::send::{Http11Send, Http11SendResult};
use io_socket::runtimes::std_stream::handle;
use rustls::{ClientConfig, ClientConnection, StreamOwned};
use rustls_platform_verifier::ConfigVerifierExt;
use url::Url;
let url = Url::parse("https://example.com/").unwrap();
let domain = url.domain().unwrap();
let config = ClientConfig::with_platform_verifier().unwrap();
let server_name = domain.to_string().try_into().unwrap();
let conn = ClientConnection::new(Arc::new(config), server_name).unwrap();
let tcp = TcpStream::connect((domain, 443)).unwrap();
let mut tls = StreamOwned::new(conn, tcp);
let request = HttpRequest::get(url)
.header("Host", domain)
.header("Connection", "close");
let mut arg = None;
let mut send = Http11Send::new(request);
let response = loop {
match send.resume(arg.take()) {
Http11SendResult::Ok { response, .. } => break response,
Http11SendResult::Redirect { url, .. } => { /* follow redirect */ break todo!() }
Http11SendResult::Err { err } => panic!("{err}"),
Http11SendResult::Io { input } => arg = Some(handle(&mut tls, input).unwrap()),
}
};
println!("{} {}", response.version, *response.status);See complete example at ./examples/send.rs.
use std::sync::Arc;
use io_http::rfc8615::well_known::{WellKnown, WellKnownResult};
use io_socket::runtimes::tokio_stream::handle;
use rustls::{ClientConfig, ClientConnection};
use rustls_platform_verifier::ConfigVerifierExt;
use tokio::net::TcpStream;
use tokio_rustls::TlsConnector;
#[tokio::main]
async fn main() {
let request = WellKnown::prepare_request("https://example.com", "caldav").unwrap();
let domain = request.url.domain().unwrap().to_owned();
let config = Arc::new(ClientConfig::with_platform_verifier().unwrap());
let connector = TlsConnector::from(config);
let server_name = domain.clone().try_into().unwrap();
let tcp = TcpStream::connect((domain.as_str(), 443)).await.unwrap();
let mut tls = connector.connect(server_name, tcp).await.unwrap();
let mut well_known = WellKnown::new(request);
let mut arg = None;
loop {
match well_known.resume(arg.take()) {
WellKnownResult::Ok { redirect_url: Some(url), .. } => {
println!("caldav endpoint: {url}");
break;
}
WellKnownResult::Ok { response, .. } => {
panic!("expected redirect, got {}", *response.status);
}
WellKnownResult::Err { err } => panic!("{err}"),
WellKnownResult::Io { input } => {
arg = Some(handle(&mut tls, input).await.unwrap());
}
}
}
}Have a look at projects built on the top of this library:
- io-addressbook: Set of I/O-free coroutines to manage contacts
- io-oauth: Set of I/O-free Rust coroutines to manage OAuth flows
- io-starttls: I/O-free Rust coroutine to upgrade any plain stream to a secure one
- Cardamum: CLI to manage contacts
- Ortie: CLI to manage OAuth access tokens
This project is licensed under either of:
at your option.
- Chat on Matrix
- News on Mastodon or RSS
- Mail at pimalaya.org@posteo.net
Special thanks to the NLnet foundation and the European Commission that have been financially supporting the project for years:
- 2022: NGI Assure
- 2023: NGI Zero Entrust
- 2024: NGI Zero Core (still ongoing in 2026)
If you appreciate the project, feel free to donate using one of the following providers:
