Http server read stream byte by byte not working?

Hi guys!

I am new to zig and tried to write a small http server… When I store the bytes of the stream into a fixed size u8 buffer, let’s say [4069]u8, all works well in the following code. But when I try to read the stream byte by byte in order to let a dynamic buffer grow, my browser won’t show me the response. It’s loading all the time, while the code seems to work well, since it throws no error.

What am I doing wrong? See my code below…

const std = @import("std");
const allocator = std.heap.page_allocator;

pub fn main() !void {

      // Erstelle einen HTTP-Server auf Port 8080

    const server_options: std.net.StreamServer.Options = .{};
    var server = std.net.StreamServer.init(server_options);
    defer server.deinit();
    const addr = try std.net.Address.parseIp("0.0.0.0", 8080);

    while (true) {
        server.listen(addr) catch {
            server.close();
            continue;
        };
        break;
    }

    // Handling connections
    while (true) {
        const conn = if (server.accept()) |conn| conn else |_| continue;
        defer conn.stream.close();

        const reader = conn.stream.reader();
        var buffer = std.ArrayList(u8).init(allocator);
        defer buffer.deinit();

        while (true) {
            if (reader.readByte()) |byte| {
                if (@TypeOf(byte) != u8) {
                    break;
                }
                try buffer.append(byte);
            } else |_| {
                break;
            }
        }

        // Creating Response
        _ = try conn.stream.write("HTTP/1.1 200 OK\r\n");
        _ = try conn.stream.write("Content-Type: text/html\r\n\r\n");
        _ = try conn.stream.write("<h1>It works!</h1>");
    }
    
}

How do determine the end of an http-request?

I try to read the incoming stream byte by byte in this section:

        while (true) {
            if (reader.readByte()) |byte| {
                if (@TypeOf(byte) != u8) {
                    break;
                }
                try buffer.append(byte);
            } else |_| {
                break;
            }
        }

The end is reached if reader.readByte() throws an error or TypeOf(Byte) != u8

This is how I try to determine the end of the incoming srteam. This method works well when I open a file and read the bytes of the stream byte by byte. It does not work with http-streams and I have no idea why… :frowning:

Because files do have the end, but tcp-streams do not.
You should detect the end of request by looking at data you got.
In particular, for GET request - as soon as you encounter \r\n\r\n sequence,
that’s the end you can proceed with a reply.

Usually I (personally) do not do it like that (I mean reading byte-by-byte, it is some overhead). Instead I grab everything that a system has in its buffers and then check If I got a full message.
For each application level protocol I have a function, protocol_need_more().

Here are two examples (in C, but it does not matter):

Redis:

int resp_need_more_data(struct dbuf *b)
{
        char *crlf;

        crlf = strstr((char*)b->loc, "\r\n");
        if (NULL == crlf)
                return 1;

        return 0;
}

More complicated, Postgres:

int pg_need_more_data(struct dbuf *b)
{
        struct psgr_msg_hdr *h;
            u8 *p = b->loc;
           int have_ready_for_query = 0, have_error = 0;

        if (b->cnt < sizeof(struct psgr_msg_hdr))
                return 1;

        h = (struct psgr_msg_hdr*)b->loc;
        if (b->cnt < 1 + ntoh32(h->len))
                return 1;

        if ('R' == h->type) {
                struct psgr_auth_rqst_hdr *arh;
                arh = (struct psgr_auth_rqst_hdr*)p;

                /* authentication request, the only message in a packet */
                if (arh->method)
                        return 0;

                /* reply to PasswordMessage ('p') */
                have_ready_for_query = have_error = 0;
                p = b->loc;

                for (;;) {
                        h = (struct psgr_msg_hdr*)p;

                        if ('Z' == h->type)
                                have_ready_for_query = 1;
                        if ('E' == h->type)
                                have_error = 1;

                        p += 1 + ntoh32(h->len);
                        if (p >= b->loc + b->cnt)
                                break;
                }
               if ((0 == have_ready_for_query) && (0 == have_error))
                        return 1;
                return 0;       /* have 'E' or 'Z' message */
        }

        /* reply to regular SQL-request */
        p = b->loc;
        for (;;) {
                h = (struct psgr_msg_hdr*)p;

                if ('Z' == h->type)
                        return 0;

                p += 1 + ntoh32(h->len);
                if (p >= b->loc + b->cnt)
                        break;
        }

        return 1;
}

Thank you so much!

Solved it now like this:

        while (true) {
            if (reader.readByte()) |byte| {
                try buffer.append(byte);
                if (buffer.items.len > 3) if (eql(u8, buffer.items[buffer.items.len-4..buffer.items.len], "\r\n\r\n")) break;
            } else |_| {
                break;
            }

Good enough, I think.

1 Like