diff --git a/src/server.zig b/src/server.zig new file mode 100644 index 0000000..bd83095 --- /dev/null +++ b/src/server.zig @@ -0,0 +1,61 @@ +const std = @import("std"); +const allocator = std.heap.page_allocator; +const eql = std.mem.eql; + +const types = @import("types.zig"); +const Route = types.Route; +const Request = types.Request; +const Response = types.Response; + +pub const Server = struct { + pub fn listen(ip: []const u8, port: u16, rt: []const Route) !void { + + // Init server + 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(ip, port); + + 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(); + + var buffer = std.ArrayList(u8).init(allocator); + + var chunk_buf: [4096]u8 = undefined; + // Collect max 4096 bytes of data from the stream into the chunk_buf. Then add it + // to the ArrayList. Repeat this until request stream ends by counting the appearence + // of "\r\n" + while (true) { + _ = try conn.stream.read(chunk_buf[0..]); + try buffer.appendSlice(chunk_buf[0..]); + if (std.mem.containsAtLeast(u8, buffer.items, 2, "\r\n")) break; + } + // Building the Request + 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); + break; + } + } + + _ = 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("

It works!

"); + } + } +}; diff --git a/src/types.zig b/src/types.zig index 893795c..2d06dca 100644 --- a/src/types.zig +++ b/src/types.zig @@ -17,10 +17,63 @@ pub const Header = struct { }; /// The HTTP Version. -pub const HTTP_Version = enum { HTTP1_1, HTTP2 }; +pub const HTTP_Version = enum { + HTTP1_1, + HTTP2, + + pub fn parse(s: []const u8) HTTP_Version { + if (std.mem.containsAtLeast(u8, s, 1, "2")) return HTTP_Version.HTTP2 else return HTTP_Version.HTTP1_1; + } + pub fn stringify(version: HTTP_Version) []const u8 { + switch (version) { + HTTP_Version.HTTP1_1 => return "HTTP/1.1", + HTTP_Version.HTTP2 => return "HTTP/2.0", + } + } +}; /// Represents the Method of a request or a response. -pub const Method = enum { GET, POST, PUT, HEAD, DELETE, CONNECT, OPTIONS, TRACE, PATCH }; +pub const Method = enum { + GET, + POST, + PUT, + HEAD, + DELETE, + CONNECT, + OPTIONS, + TRACE, + PATCH, + UNKNOWN, + + fn parse(value: []const u8) Method { + if (std.mem.containsAtLeast(u8, value, 1, "GET")) return Method.GET; + if (std.mem.containsAtLeast(u8, value, 1, "POST")) return Method.POST; + if (std.mem.containsAtLeast(u8, value, 1, "PUT")) return Method.PUT; + if (std.mem.containsAtLeast(u8, value, 1, "HEAD")) return Method.HEAD; + if (std.mem.containsAtLeast(u8, value, 1, "DELETE")) return Method.DELETE; + if (std.mem.containsAtLeast(u8, value, 1, "CONNECT")) return Method.CONNECT; + if (std.mem.containsAtLeast(u8, value, 1, "OPTIONS")) return Method.OPTIONS; + if (std.mem.containsAtLeast(u8, value, 1, "TRACE")) return Method.TRACE; + if (std.mem.containsAtLeast(u8, value, 1, "PATCH")) return Method.PATCH; + return Method.UNKNOWN; + } + + /// Turns the HTTP_method into a u8-Slice. + pub fn stringify(m: Method) []const u8 { + switch (m) { + Method.GET => return "GET", + Method.POST => return "POST", + Method.PUT => return "PUT", + Method.PATCH => return "PATCH", + Method.DELETE => return "DELETE", + Method.HEAD => return "HEAD", + Method.CONNECT => return "CONNECT", + Method.OPTIONS => return "OPTIONS", + Method.TRACE => return "TRACE", + Method.UNKNOWN => return "UNKNOWN", + } + } +}; /// Represents a standard http-Request sent by the client. pub const Request = struct { @@ -29,32 +82,51 @@ pub const Request = struct { /// HTTP-Version of the Request sent by the client httpVersion: HTTP_Version, /// Represents the request headers sent by the client - headers: []Header, + headers: []const Header, + /// Teh Request URI + uri: []const u8, /// Represents the request body sent by the client - body: []u8, + body: []const u8, - fn build(bytes: []u8) Request { - const lines = std.mem.split(u8, bytes, "\n"); + pub fn build(bytes: []const u8) !Request { + var lines = std.mem.split(u8, bytes, "\n"); var req: Request = undefined; var header_buffer = std.ArrayList(Header).init(allocator); - defer header_buffer.deinit(); - for (lines, 0..) |line, index| { - if (index == 0) { - const items = std.mem.split(u8, line, " "); - req.method = items[0]; - req.uri = items[1]; - continue; - } - var item = std.mem.split(u8, line, ":"); - const header = Header{ .key = std.mem.trim(u8, item[0], " "), .value = std.mem.trim(u8, item[1], " ") }; + var items = std.mem.split(u8, lines.first(), " "); + req.method = Method.parse(items.first()); + req.uri = if (items.next()) |value| value else ""; + + if (items.next()) |value| { + req.httpVersion = HTTP_Version.parse(value); + } else { + req.httpVersion = HTTP_Version.HTTP1_1; + } + + while (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); } - req.headers = header_buffer.items; + req.headers = header_buffer.toOwnedSlice() catch &[_]Header{Header{ .key = "", .value = "" }}; 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")); +} + /// Represents a standard http-Response sent by the webapp (server). /// It is the return type of every handling function. pub const Response = struct { @@ -85,50 +157,3 @@ pub const Response = struct { return Response{ .status = stat.Status.FORBIDDEN, .body = s }; } }; - -pub const Server = struct { - pub fn listen(ip: []const u8, port: u16, rt: []const Route) !void { - _ = rt; - - // Init server - 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(ip, port); - - 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(); - - var buffer = std.ArrayList(u8).init(allocator); - defer buffer.deinit(); - - var chunk_buf: [4096]u8 = undefined; - // Collect max 4096 bytes of data from the stream into the chunk_buf. Then add it - // to the ArrayList. Repeat this until request stream ends by counting the appearence - // of "\r\n" - while (true) { - _ = try conn.stream.read(chunk_buf[0..]); - try buffer.appendSlice(chunk_buf[0..]); - if (std.mem.containsAtLeast(u8, buffer.items, 2, "\r\n")) break; - } - // Building the Request - // TODO write Request building! - - // TODO: Write the loop to handle the requests! - // TODO: Create 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("

It works!

"); - } - } -}; diff --git a/src/zerve.zig b/src/zerve.zig index 6c80c9d..2c10977 100644 --- a/src/zerve.zig +++ b/src/zerve.zig @@ -1,7 +1,8 @@ const types = @import("types.zig"); const status = @import("status.zig"); +const server = @import("server.zig"); -pub const Server = types.Server; +pub const Server = server.Server; pub const Route = types.Route; pub const Header = types.Header; pub const Request = types.Request;