2个不稳定版本

0.2.0 2023年11月29日
0.1.0 2023年11月6日

#824 in HTTP服务器

MIT 协议

29KB
529

nanohttp

nanohttp 是一个小的零依赖库,用于解析HTTP请求和构建有效的HTTP响应。

它纯粹是作为HTTP协议的实现,因此不处理诸如路由、JSON序列化和反序列化或构建HTTP服务器之类的事情。在这方面,它与优秀的 Hyper 相似。下面是一些示例,说明如何结合TCP服务器和运行时库(如 tokioasync-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;
        });
    }
}

无运行时依赖