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