Expand description
§nanohttp
nanohttp is a small zero-dependency library for parsing HTTP requests and building HTTP
responses.
It is intended purely as an implementation of the HTTP protocol, and therefore does not handle things like routing, json serialization and deserialization, or building a HTTP server. See the examples below for how you can use it in combination with a TCP server and a runtime library such as tokio or async-std to build a custom HTTP server.
This library is intended to abstract away the details of dealing with HTTP, without removing
the need to understand how HTTP works at a high level. For example there are a few helper
methods which will automatically set relevant headers. But for the most part, it is up to the
consumer of the library to ensure that the correct headers are set, and generally ensure that
the constructed HTTP response is valid. An example of this is ensuring that the Location
header is set when returning a 303 response code.
§Examples
Parse an incoming HTTP request.
use nanohttp::{Request, Method};
let req = "GET / HTTP/1.1\r\n";
let res = Request::from_string(req).unwrap();
assert_eq!(res.method, Method::GET);
assert_eq!(res.path.uri, "/");
Build a HTTP response, and convert it to a valid HTTP message.
use nanohttp::{Response, Status, Header};
let html = "<html><head></head><body><h1>Hello, world!</h1></body></html>";
let res = Response::body(html)
.header(Header::new("Content-Type", "text/html"))
.header(Header::new("Content-Length", &html.len().to_string()))
.status(Status::Ok);
assert_eq!(res.to_string(), "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nContent-Length: 61\r\n\r\n<html><head></head><body><h1>Hello, world!</h1></body></html>");
Use nanohttp to build a custom TCP server using only the
async-std crate as a dependency.
use std::str::from_utf8;
use async_std::io::{ReadExt, WriteExt};
use async_std::net::{TcpListener, TcpStream};
use async_std::task;
use nanohttp::{Method, Status, Request, Response};
async fn handler(req: Request) -> Response {
match req.path.uri.as_str() {
"/" => match req.method {
Method::GET => Response::empty().status(Status::Ok),
_ => Response::empty().status(Status::NotAllowed),
},
"/hello" => match req.method {
Method::GET => {
let html = "<html><head><title>Hello, world!</title></head><body><h1>Hello, world!</h1></body></html>";
Response::content(html, "text/html").status(Status::Ok)
},
_ => Response::empty().status(Status::NotAllowed),
},
_ => Response::empty().status(Status::NotFound),
}
}
async fn handle_connection(mut connection: TcpStream) {
let mut buffer = [0; 1024];
connection.read(&mut buffer).await.unwrap();
let req_text = from_utf8(&buffer).unwrap().trim_end_matches("\0");
let req = Request::from_string(req_text).unwrap();
let res = handler(req).await.to_string();
let res_bytes = res.as_bytes();
connection.write(res_bytes).await.unwrap();
connection.flush().await.unwrap();
}
#[async_std::main]
async fn main() {
let listener = TcpListener::bind("127.0.0.1:8000").await.unwrap();
loop {
let (connection, _) = listener.accept().await.unwrap();
task::spawn(async move {
handle_connection(connection).await;
});
}
}