2个不稳定版本
0.2.0 | 2023年11月29日 |
---|---|
0.1.0 | 2023年11月6日 |
#824 in HTTP服务器
29KB
529 行
nanohttp
nanohttp
是一个小的零依赖库,用于解析HTTP请求和构建有效的HTTP响应。
它纯粹是作为HTTP协议的实现,因此不处理诸如路由、JSON序列化和反序列化或构建HTTP服务器之类的事情。在这方面,它与优秀的 Hyper 相似。下面是一些示例,说明如何结合TCP服务器和运行时库(如 tokio 或 async-std)构建自定义HTTP服务器。
此库旨在抽象处理HTTP的细节,同时不需要理解HTTP在高级别的工作方式。例如,有几个辅助方法会自动设置相关头。但大部分工作还是由库的消费者来确保正确设置头部,并确保构建的HTTP响应是有效的。例如,当返回303响应代码时,确保设置了 Location
头。
动机
本项目受到了《Rust编程语言》中《构建多线程Web服务器》项目的启发。[链接](https://doc.rust-lang.net.cn/book/ch20-00-final-project-a-web-server.html)。最初,它是为了了解HTTP协议的工作原理以及它如何在TCP之上运行。我还想看看我是否能用尽可能少的依赖项构建一个有用的HTTP服务器。请参阅下方的[示例](#readme-examples)。与书中项目使用标准库中的线程不同,我选择使用运行时库(async-std)以获得更好的性能,仅创建一个依赖项。然而,为了更进一步,并理解异步库的工作原理,还有一篇优秀的博客文章[链接](https://ibraheem.ca/posts/too-many-web-servers/),它能够帮助使用零依赖项构建一个HTTP服务器。但这需要大量工作...
贡献
这个库还不完整,例如,有很多HTTP状态码和一些方法缺失。然而,我包含了更多实用的、用于日常使用的例子。如果您想添加任何内容,请随时创建一个PR。
示例
解析传入的HTTP请求。
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, "/");
构建一个HTTP响应,并将其转换为有效的HTTP消息。
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>");
使用nanohttp
通过仅使用async-std crate作为依赖项来构建一个自定义TCP服务器。
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;
});
}
}