HTTP 服务器

使用 Toka 的现代网络库构建一个功能完整的 HTTP 服务器。

基本服务器

Toka 的 HTTP 引擎位于 stdx/net/http 中。你可以通过绑定 std/net 中的 TcpListener 并处理传入连接来构建轻量级 HTTP 服务器:

import std/io::{println}
import stdx/net/http::{HttpRequest, HttpResponse, parse_http_request}
import std/net::{TcpListener, TcpStream, Ipv4Addr, SocketAddrV4}
import core/result::{Result}

fn handle_client(stream#: TcpStream) {
    auto buf: [char; 1024] = [0 as char; 1024]
    auto *buf_ptr# = &buf[0] as *u8
    
    auto read_res = stream#.read(*buf_ptr, 1024:usize)
    match read_res {
        auto Result::Ok(n) => {
            if n > 0:usize {
                auto req_str = string::from_with_len(&buf[0] as *char, n as i32)
                auto req# = parse_http_request(req_str)
                
                auto resp# = HttpResponse::not_found()
                if req.path.eq(string::from("/")) {
                    resp = HttpResponse::html(string::from("<h1>Hello, Toka!</h1>"))
                }
                
                auto resp_str = resp#.to_string()
                auto *ptr = resp_str.c_str() as *u8
                stream#.write(*ptr, resp_str.len() as usize)
            }
        }
        _ => {}
    }
    stream#.close()
}

fn main() -> i32 {
    // 演示服务器设置
    return 0
}

带 JSON 响应的 API

你可以使用 stdx/serde/json 中的 @ToJson trait 自动序列化自定义 shape 对象,并以 JSON 响应返回:

import stdx/net/http::{HttpResponse}
import stdx/serde/json::{serialize_shape, to_json, @ToJson}

pub shape Message(text: string, status: string)

impl Message@ToJson {
    pub fn write_json(self, buf#: string) -> string {
        return serialize_shape(self, buf)
    }
}

fn api_status() -> HttpResponse {
    auto msg = Message(
        text = string::from("服务器正在运行"),
        status = string::from("ok")
    )
    auto json_body = to_json(msg)
    return HttpResponse(
        status_code = 200,
        content_type = string::from("application/json"),
        body = json_body
    )
}

fn main() -> i32 {
    return 0
}

路由参数

你可以轻松地手动匹 URL 路径中的动态部分来提取路由参数:

import stdx/net/http::{HttpRequest, HttpResponse}

fn handle_user_route(req: HttpRequest) -> HttpResponse {
    // 匹配动态路径 "/api/users/<id>"
    if req.path.as_str().starts_with("/api/users/") {
        auto user_id = req.path.substr(11, req.path.len() - 11)
        auto body# = string::from("用户 ID: ")
        body#.push_view(user_id)
        return HttpResponse(
            status_code = 200,
            content_type = string::from("text/plain"),
            body = body
        )
    }
    return HttpResponse::not_found()
}

fn main() -> i32 {
    return 0
}

中间件

包装你的路由处理器以注入自定义中间件功能,例如请求日志或执行计时:

import stdx/net/http::{HttpRequest, HttpResponse}
import std/io::{println}

fn logger_middleware(req: HttpRequest, next: fn(HttpRequest) -> HttpResponse) -> HttpResponse {
    println("请求路径:{}", req.path)
    auto res = next(req)
    println("响应状态码:{}", string::from_int(res.status_code))
    return res
}

fn main() -> i32 {
    return 0
}

静态文件

使用 std/fs 模块动态读取文件来提供本地资源:

import stdx/net/http::{HttpResponse}
import std/fs::{read_to_string}
import core/result::{Result}

fn serve_static_file(path: string) -> HttpResponse {
    auto file_content = read_to_string(path)
    match file_content {
        auto Result::Ok(&content) => {
            return HttpResponse(
                status_code = 200,
                content_type = string::from("text/html"),
                body = content.clone()
            )
        }
        _ => return HttpResponse::not_found()
    }
}

fn main() -> i32 {
    return 0
}

完整示例

一个完整的 HTTP 服务器,包含路由、HTML 渲染和自定义 API JSON 响应:

import std/io::{println}
import stdx/net/http::{HttpRequest, HttpResponse, HttpMethod, parse_http_request}
import std/net::{TcpListener, TcpStream, Ipv4Addr, SocketAddrV4}
import core/result::{Result}

fn route_request(req: HttpRequest) -> HttpResponse {
    auto is_get# = false
    match req.method {
        auto HttpMethod::GET => { is_get = true }
        _ => {}
    }
    
    if is_get && req.path.eq(string::from("/status")) {
        return HttpResponse(
            status_code = 200,
            content_type = string::from("application/json"),
            body = string::from("{\"status\":\"ok\"}")
        )
    }
    
    if is_get && req.path.eq(string::from("/")) {
        return HttpResponse::html(string::from("<h1>Toka HTTP Server</h1>"))
    }
    
    return HttpResponse::not_found()
}

fn handle_connection(stream#: TcpStream) {
    auto buf: [char; 1024] = [0 as char; 1024]
    auto *buf_ptr# = &buf[0] as *u8
    
    auto read_res = stream#.read(*buf_ptr, 1024:usize)
    match read_res {
        auto Result::Ok(n) => {
            if n > 0:usize {
                auto req_str = string::from_with_len(&buf[0] as *char, n as i32)
                auto req# = parse_http_request(req_str)
                auto resp# = route_request(req)
                auto resp_str = resp#.to_string()
                
                auto *ptr = resp_str.c_str() as *u8
                stream#.write(*ptr, resp_str.len() as usize)
            }
        }
        _ => {}
    }
    stream#.close()
}

fn main() -> i32 {
    // 演示服务器设置
    return 0
}