Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
utterances-bot opened this issue Jul 17, 2023 · 21 comments
Open

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

utterances-bot opened this issue Jul 17, 2023 · 21 comments

Comments

@utterances-bot
Copy link

Orhun's Blog

FOSS • Linux • Programming

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

Copy link

Good job, my friend!

Copy link

cs50Mu commented Jul 26, 2023

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
Copy link
Owner

orhun commented Jul 26, 2023

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

Copy link

cs50Mu commented Jul 26, 2023

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
Copy link
Owner

orhun commented Jul 26, 2023

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.

Copy link

cs50Mu commented Jul 26, 2023

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

Copy link

hajsf commented Aug 26, 2023

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

Copy link

学到了

@hajsf
Copy link

hajsf commented Aug 31, 2023

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});
        };
    }
}

Copy link

hajsf commented Sep 3, 2023

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();
    ```

Copy link
Contributor

sigod commented Sep 10, 2023

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.

Copy link

This was great!

Copy link

agavrel commented Feb 28, 2024

amazig thanks! ;)

Any reason why zig was slower than rust hyper?

Copy link

agavrel commented Feb 28, 2024

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

Copy link

Pismice commented Apr 3, 2024

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
Copy link
Owner

orhun commented Apr 3, 2024

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
Copy link

Pismice commented Apr 3, 2024

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

@orhun
Copy link
Owner

orhun commented Apr 3, 2024

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

@Pismice
Copy link

Pismice commented Apr 4, 2024

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

@orhun
Copy link
Owner

orhun commented Apr 4, 2024

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
Copy link

jedisct1 commented Apr 4, 2024

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests