my own ffi http library
The FFI bindings are in a seperate repo samicpp/httplib-bindings
| feature | requires | desciption |
|---|---|---|
| asyncffi | - | enables asynchronous ffi exports |
| unix-sockets | asyncffi | allows unix sockets in ffi types |
| ring | asyncffi | uses ring provider |
| aws-lc-rs | asyncffi | uses aws provider |
- HTTP/1.1
- WebSocket
- HPACK
- HTTP/2
- QUIC
- QPACK
- HTTP/3
- HTTP/1.1
- WebSocket
- HPACK
- HTTP/2
- QUIC
- QPACK
- HTTP/3
- server support TLS
- client support TLS
- FFI compatible
- custom error enums
- allow Http2Frame to live on stack
- change HttpSocket.set_status status type to
Cow<'static, str>String - change HttpSocket.set_header value type to
Cow<'static, str>String - add type in
FfiFuture<T>in asyncffi ffi definitions - implement
SerializeandDeserializefor HttpClient and HttpResponse - add example code
- allow configuring tokio runtime
- allow reconfiguring tokio runtime
- add builtin content compressions (gzip, deflate, brotli, zstd)
- add size limits in http1.1 handler
- allow setting protocol correctness enforcement
- support no-std
- change how headers are stored internally for near zero allocation header storage
rewrite http to usefuturesinstead oftokioallow compiling with different async runtimes
examples can be found in /examples
use photon::http1::server::Http1Socket;use tokio::net::TcpListener;#[tokio::main]asyncfnmain(){let listener = TcpListener::bind("0.0.0.0:8080").await.unwrap();loop{let(stream, address) = listener.accept().await.unwrap();
tokio::spawn(asyncmove{dbg!(address);letmut http1 = Http1Socket::new(stream,8*1024);
http1.read_until_complete().await.unwrap();
http1.set_header("Server","example".to_string());
http1.set_header("Content-Type","text/html".to_string());
http1.close(br#"<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <style> body { font-family: Arial, Helvetica, sans-serif; } </style></head><body> <h1>Hello, World!</h1></body></html>"#).await.unwrap();});}}use photon::{http2::{core::Http2Settings, session::Http2Session}, shared::LibError};use tokio::net::TcpListener;#[tokio::main]asyncfnmain(){let listener = TcpListener::bind("0.0.0.0:8080").await.unwrap();loop{let(stream, address) = listener.accept().await.unwrap();
tokio::spawn(asyncmove{dbg!(address);let http2 = Http2Session::new(stream);
http2.read_preface().await.unwrap();
http2.send_settings(Http2Settings::DEFAULT_NO_PUSH).await.unwrap();// DEFAULT, DEFAULT_NO_PUSH, MAXIMUM
http2.send_ping(false,b"hearbeat").await.unwrap();loop{let frame = http2.read_frame().await.unwrap();println!("\x1b[36m{:?}\x1b[0m ({}) {:?}", frame.ftype, frame.source.len(),&frame.source[..29.min(frame.source.len())]);match http2.handle(frame).await{Ok(Some(id)) => {
http2.send_headers(id,false,&[(b":status",b"200"),(b"server",b"example"),(b"content-type",b"text/html"),(b"content-length",b"300"),]).await.unwrap();
http2.send_data(id,true,br#"<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <style> body { font-family: Arial, Helvetica, sans-serif; } </style></head><body> <h1>Hello, World!</h1></body></html>"#).await.unwrap();},Ok(None) => (),Err(err @ (LibError::InvalidFrame | LibError::InvalidStream | LibError::ProtocolError | LibError::Huffman(_))) => {eprintln!("{err}");
http2.send_goaway(0,1,b"protocol error").await.unwrap();break;},Err(err) => {eprintln!("{err}");break;},}if http2.goaway.load(std::sync::atomic::Ordering::Relaxed){break;}}});}}HTTP/1.1 with H2C and HTTP/2 Prior Knowledge
use std::sync::Arc;use photon::{http1::server::Http1Socket, http2::{core::Http2Settings, server::Http2Socket}, shared::{HttpMethod,HttpSocket,HttpVersion,LibError,LibResult}};use tokio::{io::AsyncReadExt, net::TcpListener};#[tokio::main]asyncfnmain(){let listener = TcpListener::bind("0.0.0.0:8080").await.unwrap();loop{let(stream, address) = listener.accept().await.unwrap();
tokio::spawn(asyncmove{dbg!(address);letmut http1 = Http1Socket::new(stream,8*1024);let client = http1.read_until_complete().await.unwrap();if client.method == HttpMethod::Unknown(Some("PRI".to_owned())) && client.path == "*" && client.version == HttpVersion::Unknown(Some("HTTP/2.0".to_owned())) && client.headers.len() == 0{
http1.netr.read_exact(&mut[0;6]).await.unwrap();let http2 = Arc::new(http1.http2_direct(Http2Settings::DEFAULT_NO_PUSH));
http2.send_settings(Http2Settings::MAXIMUM).await.unwrap();
http2.send_ping(false,b"hearbeat").await.unwrap();loop{let frame = http2.read_frame().await.unwrap();println!("\x1b[36m{:?}\x1b[0m ({}) {:?}", frame.ftype, frame.source.len(),&frame.source[..29.min(frame.source.len())]);match http2.handle(frame).await{Ok(Some(id)) => {let http = Http2Socket::new(id, http2.clone()).unwrap();handler(http).await.unwrap()},Ok(None) => (),Err(err @ (LibError::InvalidFrame | LibError::InvalidStream | LibError::ProtocolError | LibError::Huffman(_))) => {eprintln!("{err}");
http2.send_goaway(0,1,b"protocol error").await.unwrap();break;},Err(err) => {eprintln!("{err}");break;},}if http2.goaway.load(std::sync::atomic::Ordering::Relaxed){break;}}}elseifletSome(up) = client.headers.get("upgrade") && up[0].to_lowercase() == "h2c"{let h2c = Arc::new(http1.h2c(Some(Http2Settings::MAXIMUM)).await.unwrap());
h2c.read_preface().await.unwrap();
h2c.send_settings(Http2Settings::MAXIMUM).await.unwrap();
h2c.send_ping(false,b"hearbeat").await.unwrap();let http = Http2Socket::new(1, h2c.clone()).unwrap();
tokio::spawn(asyncmove{handler(http).await.unwrap()});loop{let frame = h2c.read_frame().await.unwrap();println!("\x1b[36m{:?}\x1b[0m ({}) {:?}", frame.ftype, frame.source.len(),&frame.source[..29.min(frame.source.len())]);match h2c.handle(frame).await{Ok(Some(id)) => {let http = Http2Socket::new(id, h2c.clone()).unwrap();handler(http).await.unwrap()},Ok(None) => (),Err(err @ (LibError::InvalidFrame | LibError::InvalidStream | LibError::ProtocolError | LibError::Huffman(_))) => {eprintln!("{err}");
h2c.send_goaway(0,1,b"protocol error").await.unwrap();break;},Err(err) => {eprintln!("{err}");break;},}if h2c.goaway.load(std::sync::atomic::Ordering::Relaxed){break;}}}else{handler(http1).await.unwrap()}});}}asyncfnhandler<H:HttpSocket>(muthttp:H) -> LibResult<()>{
http.set_header("Server","example".to_string());
http.set_header("Content-Type","text/html".to_string());
http.close(br#"<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <style> body { font-family: Arial, Helvetica, sans-serif; } </style></head><body> <h1>Hello, World!</h1></body></html>"#).await}