Zig Cookbook

介绍

http.Server - std

自 Zig 0.12.0 起引入了 http.Server 的基本实现。

对于每个连接,我们生成一个新线程来处理它,在 accept 中它将:

  1. 首先,它使用 defer 确保返回时关闭连接。
  2. 然后初始化 HTTP 服务器以开始解析请求
  3. 对于每个请求,我们首先检查它是否可以升级到 WebSocket。
    • 如果成功,则调用 serveWebSocket,否则调用 serverHTTP

const std = @import("std");
const log = std.log;
const net = std.Io.net;
const Io = std.Io;
const Request = std.http.Server.Request;

const MAX_BUF = 1024;

pub fn main(init: std.process.Init) !void {
    const io = init.io;

    const addr = try net.IpAddress.parse("127.0.0.1", 8080);
    var server = try addr.listen(io, .{ .reuse_address = true });
    defer server.deinit(io);

    log.info("Start HTTP server at {f}", .{addr});

    while (true) {
        const stream = server.accept(io) catch |err| {
            log.err("failed to accept connection: {s}", .{@errorName(err)});
            continue;
        };
        const thread = std.Thread.spawn(.{}, accept, .{ stream, io }) catch |err| {
            log.err("unable to spawn connection thread: {s}", .{@errorName(err)});
            stream.close(io);
            continue;
        };
        thread.detach();
    }
}

fn accept(stream: net.Stream, io: Io) !void {
    defer stream.close(io);

    log.info("Got new client!", .{});

    var recv_buffer: [1024]u8 = undefined;
    var send_buffer: [100]u8 = undefined;
    var stream_reader = stream.reader(io, &recv_buffer);
    var stream_writer = stream.writer(io, &send_buffer);
    var server = std.http.Server.init(&stream_reader.interface, &stream_writer.interface);
    while (server.reader.state == .ready) {
        var request = server.receiveHead() catch |err| switch (err) {
            error.HttpConnectionClosing => return,
            else => return err,
        };

        switch (request.upgradeRequested()) {
            .other => |other_protocol| {
                log.err("Not supported protocol, {s}", .{other_protocol});
                return;
            },
            .websocket => |key| {
                var ws = try request.respondWebSocket(.{ .key = key orelse "" });
                try serveWebSocket(&ws);
            },
            .none => {
                try serveHTTP(&request);
            },
        }
    }
}

fn serveHTTP(request: *Request) !void {
    try request.respond(
        "Hello World from Zig HTTP server",
        .{
            .extra_headers = &.{
                .{ .name = "custom-header", .value = "custom value" },
            },
        },
    );
}

fn serveWebSocket(ws: *std.http.Server.WebSocket) !void {
    try ws.writeMessage("Hello from Zig WebSocket server", .text);
    while (true) {
        const msg = try ws.readSmallMessage();
        if (msg.opcode == .connection_close) {
            log.info("Client closed the WebSocket", .{});
            return;
        }
        try ws.writeMessage(msg.data, msg.opcode);
    }
}

测试

对于 HTTP,我们可以使用 curl

curl -v localhost:8080

它将输出:

< HTTP/1.1 200 OK
< content-length: 32
< custom header: custom value
<
Hello World from Zig HTTP server

对于 WebSocket,我们可以使用浏览器 开发者工具 中的控制台:

var webSocket = new WebSocket('ws://localhost:8080');
webSocket.onmessage = function(data) { console.log(data); }

然后我们可以像这样发送消息:

webSocket.send('abc')

websocket-client

有关详细信息,请参阅 Writing WebSocket client applications - Web APIs | MDN

注意

标准库实现的性能极差。如果您计划不仅仅是进行基本的实验,请考虑使用替代库,例如:

上一示例:POST
下一示例:生成随机数