orhun / personal-blog

The source of my blog ✍🏼
https://blog.orhun.dev
27 stars 3 forks source link

https://blog.orhun.dev/zig-bits-04/ #19

Open utterances-bot opened 11 months ago

utterances-bot commented 11 months ago

Orhun's Blog

FOSS • Linux • Programming

https://blog.orhun.dev/zig-bits-04/

luizpbraga commented 11 months ago

Good job, my friend!

cs50Mu commented 11 months ago

It seems that the std.http module doesn’t handle the request body, I just read the source code, and could not find the related processing logic, is it that I miss something or what?

orhun commented 11 months ago

@cs50Mu what exactly are you trying to do? I think you can read the request body just fine with the examples in the post.

cs50Mu commented 11 months ago

No, I know you can read the resp body, but I want to send a request body, is there any way to do that?

orhun commented 11 months ago

If you mean via POST request, there is also an example in the post as follows:

// Make the connection to the server.
var request = try client.request(.POST, uri, headers, .{});
defer request.deinit();
request.transfer_encoding = .chunked;

// Send the request and headers to the server.
try request.start();

// Send the payload.
try request.writer().writeAll("Zig Bits!\n");
try request.finish();

// Wait for the server to send use a response.
try request.wait();

Other than that, I am not sure.

cs50Mu commented 11 months ago

@orhun oh, I missed that Q&A part.. sorry about that, if only the zig doc can have some simple demo use cases

hajsf commented 10 months ago

Thanks for the great blog, Can you add a route showing how to handle the static files located at folder www?

nsxz1234 commented 10 months ago

学到了

hajsf commented 10 months ago

I got the static files loaded as below:

const std = @import("std");
const hash = @import("./hash.zig");
const routes = @import("./routes.zig");
const http = std.http;
const log = std.log.scoped(.server);

const server_addr = "127.0.0.1";
const server_port = 8000;

// Run the server and handle incoming requests.
fn runServer(server: *http.Server, allocator: std.mem.Allocator) !void {
    outer: while (true) {
        // Accept incoming connection.
        var response = try server.accept(.{
            .allocator = allocator,
        });
        defer response.deinit();

        // Avoid Nagle's algorithm.
        // <https://en.wikipedia.org/wiki/Nagle%27s_algorithm>
        try std.os.setsockopt(
            response.connection.stream.handle,
            std.os.IPPROTO.TCP,
            std.os.TCP.NODELAY,
            &std.mem.toBytes(@as(c_int, 1)),
        );

        while (response.reset() != .closing) {
            // Handle errors during request processing.
            response.wait() catch |err| switch (err) {
                error.HttpHeadersInvalid => continue :outer,
                error.EndOfStream => continue,
                else => return err,
            };

            // Process the request.
            handleRequest(&response, allocator) catch |err| {
                log.err("Error {any}", .{err});
                continue :outer;
            };
        }
        log.err("browser closed", .{});
    }
}

// Handle an individual request.
fn handleRequest(response: *http.Server.Response, allocator: std.mem.Allocator) !void {
    // Log the request details.
    log.info("{s} {s} {s}", .{ @tagName(response.request.method), @tagName(response.request.version), response.request.target });

    // Read the request body.
    const body = try response.reader().readAllAlloc(allocator, 8192);
    defer allocator.free(body);

    // Set "connection" header to "keep-alive" if present in request headers.
    if (response.request.headers.contains("connection")) {
        try response.headers.append("connection", "keep-alive");
    }

    const target = response.request.target;
    const method = response.request.method;
    log.debug("target: {any}/{s}", .{ method, target });

    if (std.mem.startsWith(u8, target, "/favicon.ico")) {
        return error.FaviconNotFound;
    }

    response.transfer_encoding = .chunked;

    if (std.mem.containsAtLeast(u8, target, 1, ".")) {
        // Set "content-type" header to "text/html".
        const file = std.mem.trimLeft(u8, target, &[_]u8{'/'});

        // Check the MIME type based on the file extension
        // const extension = std.fs.path.extension(file);

        // var mimeTypes = std.StringHashMap([]const u8).init(allocator);
        // try hash.init(&mimeTypes);
        // defer mimeTypes.deinit();
        // if (mimeTypes.get(extension)) |mimeType| {
        //     try response.headers.append("content-type", mimeType);
        //     std.debug.print("The MIME type for {s} is {s}\n", .{ extension, mimeType });
        // } else {
        //     log.err("Unknown extension: {s}", .{extension});
        //     return error.UnKnownExtension;
        // }

        // Or use instead if if do not want to use HashMap
        // if (std.mem.eql(u8, extension, ".html")) {
        try response.headers.append("content-type", "text/html");
        // } else if (std.mem.eql(u8, extension, ".js")) {
        //     try response.headers.append("content-type", "application/javascript");
        // } else if (std.mem.eql(u8, extension, ".css")) {
        //     try response.headers.append("content-type", "text/css");
        // } else {
        //     log.err("Unknown extension: {s}", .{extension});
        //     try response.headers.append("content-type", "text/plain");
        // }

        const contents = readFile(allocator, file) catch |err| {
            log.err("Error reading file {s} => {any}", .{ file, err });
            return error.FileNotFound;
        };
        log.info("file read as: \n{s}", .{contents});
        // Write the response body.
        try response.do();
        if (response.request.method != .HEAD) {
            try response.writeAll(contents);
            try response.finish();
            allocator.free(contents);
        }
    } else {
        // Set "content-type" header to "text/plain".
        try response.headers.append("content-type", "text/plain");

        var routeRegister = std.StringHashMap(*const fn (response: *http.Server.Response) void).init(allocator);
        try routes.init(&routeRegister);
        defer routeRegister.deinit();

        if (routeRegister.get(target)) |handler| {
            //     try response.headers.append("content-type", mimeType);
            std.debug.print("Calling handler: {s} for route: {s}\n", .{ handler, target });
            // Write the response body.
            handler(response);
        } else {
            log.err("Unknown route: {s}", .{target});
            // Write the response body.
            try response.do();
            if (response.request.method != .HEAD) {
                try response.writeAll("404: ");
                try response.writeAll("Wrong path!\n");
                try response.writeAll(target);
                try response.finish();
            }
            return error.ErrorNoEntity;
        }
    }
}

pub fn main() !void {
    // Create an allocator.
    const allocator = std.heap.page_allocator;

    // Initialize the server.
    var server = http.Server.init(allocator, .{ .reuse_address = true });
    defer server.deinit();

    // Log the server address and port.
    log.info("Server is running at {s}:{d}", .{ server_addr, server_port });

    // Parse the server address.
    const address = std.net.Address.parseIp(server_addr, server_port) catch unreachable;
    try server.listen(address);

    // Run the server.
    runServer(&server, allocator) catch |err| {
        // Handle server errors.
        log.err("server error: {}\n", .{err});
        if (@errorReturnTrace()) |trace| {
            std.debug.dumpStackTrace(trace.*);
        }
        std.os.exit(1);
    };
}

pub fn readFile(allocator: std.mem.Allocator, filename: []const u8) ![]u8 {
    const file = try std.fs.cwd().openFile(filename, .{});
    defer file.close();

    const contents = try file.reader().readAllAlloc(allocator, std.math.maxInt(usize));
    errdefer allocator.free(contents);

    return contents;
}

Though I found it is not required to add the MIME type, but below is the file I used to define the main MIMEs:

// hash.zig
const std = @import("std");

pub fn init(mimeTypes: *std.StringHashMap([]const u8)) !void {
    try mimeTypes.put(".html", "text/html");
    try mimeTypes.put(".js", "application/javascript");
    try mimeTypes.put(".css", "text/css");
}

And for handling the routes, i used:

// routes.zig
const std = @import("std");
const http = std.http;

//pub const LoadFunction = fn (i32) !void;
pub fn init(routeRegister: *std.StringHashMap(*const fn (response: *http.Server.Response) void)) !void {
    try routeRegister.put("/load", load);
}

pub fn load(response: *http.Server.Response) void {
    std.log.info("file is loaded, {}", .{response});
    response.do() catch |e| {
        std.log.err("{any}", .{e});
    };
    if (response.request.method != .HEAD) {
        response.writeAll("Hold on ") catch |e| {
            std.log.err("{any}", .{e});
        };
        response.writeAll("will load the route!\n") catch |e| {
            std.log.err("{any}", .{e});
        };
        response.writeAll("I executed the handler :)") catch |e| {
            std.log.err("{any}", .{e});
        };
        response.finish() catch |e| {
            std.log.err("{any}", .{e});
        };
    }
}
hajsf commented 9 months ago

How can I send JSON data with the code below:

var request = try client.request(.POST, uri, headers, .{});
defer request.deinit();

// Set the encoding for the POST request.
request.transfer_encoding = .chunked;

try request.start();

And how can I parse the returned JSON data using:

    // Make the connection to the server.
    var request = try client.request(.GET, uri, headers, .{});
    defer request.deinit();

    // Send the request and headers to the server.
    try request.start();

    // Wait for the server to send use a response.
    try request.wait();
sigod commented 9 months ago

For some reason, the connection hangs for the "not found" route unless I explicitly writeAll a few bytes into it. But at the same time it works fine for automatic /favicon.ico requests.

morrijm4 commented 5 months ago

This was great!

agavrel commented 4 months ago

amazig thanks! ;)

Any reason why zig was slower than rust hyper?

agavrel commented 4 months ago

Also I strongly agree with @jedisct1 on the TLS1.3 issue. Glad you found a solution though, that was such an interesting read!

Pismice commented 2 months ago

Could you improve performance of your http server by spawning a thread for each handleRequest ? It seems to me that this server wont be able to accept a big load since there is 0 concurency, am I right ?

orhun commented 2 months ago

Yeah, the HTTP server is very bare bones and written just for demonstration purposes. Also the code needs an update for the latest Zig version (usual Zig problem).

Pismice commented 2 months ago

Do you have a recommandation on where I could find a "robust" http server written in Zig ?

orhun commented 2 months ago

You can take a look here: https://github.com/catdevnull/awesome-zig

Pismice commented 2 months ago

Would it be even better if put behind nginx or this has nothing to do with performances ?

orhun commented 2 months ago

Nginx is just a proxy to your HTTP server, I think it can be used to parallelize things but at the end of the day you will be bottlenecked by the performance of your HTTP server.

jedisct1 commented 2 months ago

Besides HTTP serving, there are now some very cool web frameworks in Zig. For example Jetzig.

Putting these behind nginx is a good idea, as nginx can do TLS termination, but also handle things such as QUIC / HTTP/3. Also logging, filtering, etc.