diff --git a/src/server.zig b/src/server.zig index bd83095..5ff38fa 100644 --- a/src/server.zig +++ b/src/server.zig @@ -44,8 +44,6 @@ pub const Server = struct { const req_stream = try buffer.toOwnedSlice(); const req = try Request.build(req_stream); - std.debug.print("Request sent by client:\n{s}", .{buffer.items}); - for (rt) |r| { if (eql(u8, r[0], req.uri)) { _ = r[1](req); @@ -54,7 +52,7 @@ pub const Server = struct { } _ = 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("Content-Type: text/html\nUser-Agent: Testbot\r\n\r\n"); _ = try conn.stream.write("

It works!

"); } } diff --git a/src/types.zig b/src/types.zig index 2d06dca..2b0a246 100644 --- a/src/types.zig +++ b/src/types.zig @@ -14,6 +14,21 @@ pub const Route = tuple(&.{ []const u8, *const fn (Request) Response }); pub const Header = struct { key: []const u8, value: []const u8, + + pub fn stringify(header: Header) []const u8 { + var string = std.ArrayList(u8).init(allocator); + string.appendSlice(header.key) catch unreachable; + string.appendSlice(": ") catch unreachable; + string.appendSlice(header.value) catch unreachable; + const out = string.toOwnedSlice() catch ""; + return out; + } + + test "stringify Header" { + var header = Header{ .key = "User-Agent", .value = "Testbot" }; + const compare = "User-Agent: Testbot"; + try std.testing.expect(std.mem.eql(u8, header.stringify(), compare[0..])); + } }; /// The HTTP Version. @@ -89,47 +104,53 @@ pub const Request = struct { body: []const u8, pub fn build(bytes: []const u8) !Request { - var lines = std.mem.split(u8, bytes, "\n"); var req: Request = undefined; + var parts = std.mem.split(u8, bytes, "\r\n"); + const header = parts.first(); + var header_lines = std.mem.split(u8, header, "\n"); var header_buffer = std.ArrayList(Header).init(allocator); - var items = std.mem.split(u8, lines.first(), " "); - req.method = Method.parse(items.first()); - req.uri = if (items.next()) |value| value else ""; + var header_items = std.mem.split(u8, header_lines.first(), " "); + req.method = Method.parse(header_items.first()); + req.uri = if (header_items.next()) |value| value else ""; - if (items.next()) |value| { + if (header_items.next()) |value| { req.httpVersion = HTTP_Version.parse(value); } else { req.httpVersion = HTTP_Version.HTTP1_1; } - while (lines.next()) |line| { + while (header_lines.next()) |line| { var headers = std.mem.split(u8, line, ": "); const item1 = headers.first(); const item2 = if (headers.next()) |value| value else unreachable; - const header = Header{ .key = item1, .value = item2 }; - try header_buffer.append(header); + const header_pair = Header{ .key = item1, .value = item2 }; + try header_buffer.append(header_pair); } req.headers = header_buffer.toOwnedSlice() catch &[_]Header{Header{ .key = "", .value = "" }}; + req.body = if (parts.next()) |value| value else ""; return req; } -}; -test "function to build a Request" { - const bytes = "GET /test HTTP/1.1\nHost: localhost:8080\nUser-Agent: Testbot"; - const req = try Request.build(bytes); - try std.testing.expect(req.method == Method.GET); - try std.testing.expect(req.httpVersion == HTTP_Version.HTTP1_1); - try std.testing.expect(std.mem.eql(u8, req.uri, "/test")); - try std.testing.expect(std.mem.eql(u8, req.headers[1].key, "User-Agent")); - try std.testing.expect(std.mem.eql(u8, req.headers[1].value, "Testbot")); - try std.testing.expect(std.mem.eql(u8, req.headers[0].key, "Host")); - try std.testing.expect(std.mem.eql(u8, req.headers[0].value, "localhost:8080")); -} + // Test the Request build function + test "build a Request" { + const bytes = "GET /test HTTP/1.1\nHost: localhost:8080\nUser-Agent: Testbot\r\nThis is the test body!"; + const req = try Request.build(bytes); + try std.testing.expect(req.method == Method.GET); + try std.testing.expect(req.httpVersion == HTTP_Version.HTTP1_1); + try std.testing.expect(std.mem.eql(u8, req.uri, "/test")); + try std.testing.expect(std.mem.eql(u8, req.headers[1].key, "User-Agent")); + try std.testing.expect(std.mem.eql(u8, req.headers[1].value, "Testbot")); + try std.testing.expect(std.mem.eql(u8, req.headers[0].key, "Host")); + try std.testing.expect(std.mem.eql(u8, req.headers[0].value, "localhost:8080")); + try std.testing.expect(std.mem.eql(u8, req.body, "This is the test body!")); + } +}; /// Represents a standard http-Response sent by the webapp (server). /// It is the return type of every handling function. pub const Response = struct { + httpVersion: HTTP_Version = HTTP_Version.HTTP1_1, /// Response status, default is "200 OK" status: stat.Status = stat.Status.OK, /// Response eaders sent by the server @@ -156,4 +177,34 @@ pub const Response = struct { pub fn forbidden(s: []u8) Response { return Response{ .status = stat.Status.FORBIDDEN, .body = s }; } + + pub fn stringify(r: Response) ![]const u8 { + var res = std.ArrayList(u8).init(allocator); + try res.appendSlice(r.httpVersion.stringify()); + try res.append(' '); + try res.appendSlice(r.status.stringify()); + try res.appendSlice("\r\n"); + + for (r.headers) |header| { + try res.appendSlice(header.stringify()); + try res.appendSlice("\n"); + } + try res.appendSlice("\r\n\r\n"); + try res.appendSlice(r.body); + + return try res.toOwnedSlice(); + } + + test "stringify Response" { + const eql = std.mem.eql; + const headers = [_]Header{.{ .key = "User-Agent", .value = "Testbot" }}; + const res = Response{ .headers = &headers, .body = "This is the body!" }; + + try std.testing.expect(eql(u8, try res.stringify(), "HTTP/1.1 200 OK\r\nUser-Agent: Testbot\n\r\n\r\nThis is the body!")); + } }; + +// Run all tests, even the nested ones +test { + std.testing.refAllDecls(@This()); +}