]> gitweb.ps.run Git - chirp/commitdiff
changessssss
authorpatrick-scho <patrick.schoenberger@posteo.de>
Thu, 21 Nov 2024 15:22:42 +0000 (16:22 +0100)
committerpatrick-scho <patrick.schoenberger@posteo.de>
Thu, 21 Nov 2024 15:22:42 +0000 (16:22 +0100)
.gitmodules
build.zig
build.zig.zon [new file with mode: 0644]
lmdb [deleted submodule]
src/epoll.zig [deleted file]
src/http.zig [new file with mode: 0644]
src/main.zig
todo.md

index f17bff6c0b6e2a4f9df58d4cf89b98360dbab39e..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 100644 (file)
@@ -1,3 +0,0 @@
-[submodule "lmdb"]
-       path = lmdb
-       url = https://github.com/LMDB/lmdb
index f7d33f1ff32c99d370d09672b90a403c8fcdd268..64b9c09ba651eaf90a47cf57a043b698d877c6a4 100644 (file)
--- a/build.zig
+++ b/build.zig
@@ -6,48 +6,34 @@ pub fn build(b: *std.Build) void {
     const optimize = b.standardOptimizeOption(.{});
 
     const exe = b.addExecutable(.{
-        .name = "lmdb",
-        // In this case the main source file is merely a path, however, in more
-        // complicated build scripts, this could be a generated file.
+        .name = "chirp",
         .root_source_file = .{ .cwd_relative = "src/main.zig" },
         .target = target,
         .optimize = optimize,
     });
 
+    // other modules
+    exe.root_module.omit_frame_pointer = false;
+
     const epoll = b.addExecutable(.{
         .name = "epoll",
-        // In this case the main source file is merely a path, however, in more
-        // complicated build scripts, this could be a generated file.
         .root_source_file = .{ .cwd_relative = "src/epoll.zig" },
         .target = target,
         .optimize = optimize,
     });
 
-    const lmdb_mod = b.createModule(.{
-        .root_source_file = .{ .cwd_relative = "../ziglmdb/src/lmdb.zig" },
-    });
-    lmdb_mod.addIncludePath(.{ .cwd_relative = "./lmdb/libraries/liblmdb" });
-    lmdb_mod.addCSourceFiles(.{ .files = &.{
-        "./lmdb/libraries/liblmdb/midl.c",
-        "./lmdb/libraries/liblmdb/mdb.c",
-    } });
-
+    const lmdb = b.dependency("lmdb", .{ .target = target, .optimize = optimize });
+    var lmdb_mod = lmdb.module("lmdb");
+    lmdb_mod.omit_frame_pointer = false;
     exe.root_module.addImport("lmdb", lmdb_mod);
     exe.linkLibC();
-
     epoll.root_module.addImport("lmdb", lmdb_mod);
     epoll.linkLibC();
 
     b.installArtifact(exe);
 
     const run_cmd = b.addRunArtifact(exe);
-
     run_cmd.step.dependOn(b.getInstallStep());
-
-    if (b.args) |args| {
-        run_cmd.addArgs(args);
-    }
-
     const run_step = b.step("run", "Run the app");
     run_step.dependOn(&run_cmd.step);
 
diff --git a/build.zig.zon b/build.zig.zon
new file mode 100644 (file)
index 0000000..ab8c6ab
--- /dev/null
@@ -0,0 +1,16 @@
+.{
+    .name = "chirp",
+    .version = "0.0.0",
+    .paths = .{
+        "src/main.zig",
+        "build.zig",
+        "build.zig.zon",
+    },
+    .dependencies = .{
+        .lmdb = .{
+            // .url = "git://pi@psch.dev:~/git/ziglmdb.git",
+            // .hash = "122001bacb21ad7eb237531fb218962270a178c52893bbfd9c5ce493232747e652e2",
+            .path = "../ziglmdb",
+        },
+    },
+}
diff --git a/lmdb b/lmdb
deleted file mode 160000 (submodule)
index da9aeda..0000000
--- a/lmdb
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit da9aeda08c3ff710a0d47d61a079f5a905b0a10a
diff --git a/src/epoll.zig b/src/epoll.zig
deleted file mode 100644 (file)
index afca339..0000000
+++ /dev/null
@@ -1,80 +0,0 @@
-const std = @import("std");
-const net = std.net;
-const posix = std.posix;
-const linux = std.os.linux;
-const lmdb = @import("lmdb");
-
-fn test_lmdb(env: *lmdb.Env) !void {
-    const txn = try env.txn();
-    defer txn.abort();
-
-    const sessions = try txn.dbi("sessions", u64, u64);
-    var cursor = try sessions.cursor();
-
-    var key: u64 = undefined;
-    var user_id_maybe = cursor.get(&key, .First);
-
-    while (user_id_maybe) |user_id| {
-        _ = user_id;
-        user_id_maybe = cursor.get(&key, .Next);
-    }
-}
-
-pub fn main() !void {
-    const address = try std.net.Address.parseIp("127.0.0.1", 5882);
-
-    const tpe: u32 = posix.SOCK.STREAM | posix.SOCK.NONBLOCK;
-    const protocol = posix.IPPROTO.TCP;
-    const listener = try posix.socket(address.any.family, tpe, protocol);
-    defer posix.close(listener);
-
-    const BACKLOG = 256;
-
-    try posix.setsockopt(listener, posix.SOL.SOCKET, posix.SO.REUSEADDR, &std.mem.toBytes(@as(c_int, 1)));
-    try posix.bind(listener, &address.any, address.getOsSockLen());
-    try posix.listen(listener, BACKLOG);
-
-    // epoll_create1 takes flags. We aren't using any in these examples
-    const efd = try posix.epoll_create1(0);
-    defer posix.close(efd);
-
-    {
-        // monitor our listening socket
-        var event = linux.epoll_event{ .events = linux.EPOLL.IN, .data = .{ .fd = listener } };
-        try posix.epoll_ctl(efd, linux.EPOLL.CTL_ADD, listener, &event);
-    }
-
-    var env = lmdb.Env.open("db", 1024 * 100);
-    defer env.close();
-
-    var ready_list: [BACKLOG]linux.epoll_event = undefined;
-    while (true) {
-        const ready_count = posix.epoll_wait(efd, &ready_list, -1);
-        for (ready_list[0..ready_count]) |ready| {
-            const ready_socket = ready.data.fd;
-            if (ready_socket == listener) {
-                const client_socket = try posix.accept(listener, null, null, posix.SOCK.NONBLOCK);
-                errdefer posix.close(client_socket);
-                var event = linux.epoll_event{ .events = linux.EPOLL.IN, .data = .{ .fd = client_socket } };
-                try posix.epoll_ctl(efd, linux.EPOLL.CTL_ADD, client_socket, &event);
-            } else {
-                var closed = false;
-                var buf: [4096]u8 = undefined;
-                const read = posix.read(ready_socket, &buf) catch 0;
-                if (read == 0) {
-                    closed = true;
-                } else {
-                    // std.debug.print("[{d}] got: {s}\n", .{ ready_socket, buf[0..read] });
-
-                    try test_lmdb(&env);
-
-                    _ = try posix.write(ready_socket, "HTTP/1.1 200 OK\r\nContent-Type: text/html; charset=UTF-8\r\nContent-Length: 5\r\n\r\nabcde");
-                }
-
-                if (closed or ready.events & linux.EPOLL.RDHUP == linux.EPOLL.RDHUP) {
-                    posix.close(ready_socket);
-                }
-            }
-        }
-    }
-}
diff --git a/src/http.zig b/src/http.zig
new file mode 100644 (file)
index 0000000..db625d6
--- /dev/null
@@ -0,0 +1,344 @@
+const std = @import("std");
+const posix = std.posix;
+const linux = std.os.linux;
+
+pub const Server = struct {
+    // TODO: factor out
+    const BACKLOG = 2048;
+
+    listener: posix.socket_t,
+    efd: i32,
+    ready_list: [BACKLOG]linux.epoll_event = undefined,
+
+    ready_count: usize = 0,
+    ready_index: usize = 0,
+
+    pub fn init(name: []const u8, port: u16) !Server {
+        const address = try std.net.Address.resolveIp(name, port);
+
+        const tpe: u32 = posix.SOCK.STREAM | posix.SOCK.NONBLOCK;
+        const protocol = posix.IPPROTO.TCP;
+        const listener = try posix.socket(address.any.family, tpe, protocol);
+
+        try posix.setsockopt(listener, posix.SOL.SOCKET, posix.SO.REUSEADDR, &std.mem.toBytes(@as(c_int, 1)));
+        try posix.bind(listener, &address.any, address.getOsSockLen());
+        try posix.listen(listener, BACKLOG);
+
+        // epoll_create1 takes flags. We aren't using any in these examples
+        const efd = try posix.epoll_create1(0);
+
+        var event = linux.epoll_event{ .events = linux.EPOLL.IN, .data = .{ .fd = listener } };
+        try posix.epoll_ctl(efd, linux.EPOLL.CTL_ADD, listener, &event);
+
+        return .{
+            .listener = listener,
+            .efd = efd,
+        };
+    }
+
+    pub fn deinit(self: Server) void {
+        posix.close(self.efd);
+        posix.close(self.listener);
+    }
+
+    pub fn wait(self: *Server) void {
+        if (self.ready_index >= self.ready_count) {
+            self.ready_index = 0;
+            self.ready_count = posix.epoll_wait(self.efd, &self.ready_list, -1);
+        }
+    }
+
+    pub fn next_request(self: *Server, buf: []u8) !?Request {
+        while (self.ready_index < self.ready_count) {
+            const ready = self.ready_list[self.ready_index];
+            const ready_socket = ready.data.fd;
+            self.ready_index += 1;
+
+            if (ready_socket == self.listener) {
+                const client_socket = try posix.accept(self.listener, null, null, posix.SOCK.NONBLOCK);
+                errdefer posix.close(client_socket);
+                var event = linux.epoll_event{ .events = linux.EPOLL.IN, .data = .{ .fd = client_socket } };
+                try posix.epoll_ctl(self.efd, linux.EPOLL.CTL_ADD, client_socket, &event);
+            } else {
+                var closed = false;
+                var req = Request{ .fd = ready_socket };
+
+                const read = posix.read(ready_socket, buf) catch 0;
+                if (read == 0) {
+                    closed = true;
+                } else {
+                    if (req.parse(buf[0..read]))
+                        return req;
+                }
+
+                if (closed or ready.events & linux.EPOLL.RDHUP == linux.EPOLL.RDHUP) {
+                    posix.close(ready_socket);
+                }
+            }
+        }
+        return null;
+    }
+};
+
+// pub const Method = enum { GET, POST };
+pub const Method = std.http.Method;
+
+// pub const Header = struct {
+//     const NAME_SIZE = 32;
+//     const VALUE_SIZE = 128;
+
+//     name: std.BoundedArray(u8, NAME_SIZE),
+//     value: std.BoundedArray(u8, VALUE_SIZE),
+// };
+pub const Header = struct {
+    const Name = std.BoundedArray(u8, 32);
+    const Value = std.BoundedArray(u8, 128);
+
+    name: Name = Name.init(0) catch unreachable,
+    value: Value = Value.init(0) catch unreachable,
+};
+pub const Status = std.http.Status;
+
+pub const Request = struct {
+    fd: posix.fd_t,
+
+    method: Method = undefined,
+    target: []const u8 = undefined,
+    version: ?[]const u8 = null,
+    head: ?[]const u8 = null,
+    body: ?[]const u8 = null,
+
+    pub fn parse(self: *Request, buf: []const u8) bool {
+        var state: u8 = 0;
+
+        var start: u32 = 0;
+        // var end: u32 = 0;
+
+        var index: u32 = 0;
+        while (index < buf.len) {
+            defer index += 1;
+
+            const c = buf[index];
+
+            switch (state) {
+                0 => {
+                    if (c == ' ') {
+                        self.method = @enumFromInt(Method.parse(buf[start..index]));
+                        start = index + 1;
+                        state += 1;
+                    }
+                },
+                1 => {
+                    if (c == ' ') {
+                        self.target = buf[start..index];
+                        start = index + 1;
+                        state += 1;
+                    }
+                },
+                2 => {
+                    if (c == '\r') {
+                        self.version = buf[start..index];
+                        start = index + 2;
+                        index += 1;
+                        state += 1;
+                    }
+                },
+                3 => {
+                    if (c == '\r' and (index + 2) < buf.len and buf[index + 2] == '\r') {
+                        self.head = buf[start..index];
+
+                        if (index + 4 < buf.len) {
+                            self.body = buf[index + 4 .. buf.len];
+                        }
+                        return true;
+                    }
+                },
+                else => {},
+            }
+        }
+
+        return true;
+    }
+
+    pub fn get_header(self: Request, name: []const u8) ?[]const u8 {
+        const head = self.head orelse return null;
+        var start: usize = 0;
+        var matching: usize = 0;
+        for (0..head.len) |i| {
+            const c = head[i];
+
+            if (matching < name.len) {
+                if (c == name[matching]) {
+                    if (matching == 0) start = i;
+                    matching += 1;
+                } else {
+                    matching = 0;
+                }
+            } else {
+                if (c == '\r') {
+                    return head[start..i];
+                }
+            }
+        }
+        return null;
+    }
+
+    pub fn get_cookie(self: Request, name: []const u8) ?[]const u8 {
+        const cookie = self.get_header("Cookie") orelse return null;
+        var start: usize = 0;
+        var matching: usize = 0;
+        for (0..cookie.len) |i| {
+            const c = cookie[i];
+
+            if (matching < name.len) {
+                if (c == name[matching]) {
+                    if (matching == 0) start = i;
+                    matching += 1;
+                } else {
+                    matching = 0;
+                }
+            } else {
+                if (c == '=') {
+                    if (std.mem.indexOfScalarPos(u8, cookie, i, ';')) |semi_index| {
+                        return cookie[i + 1 .. semi_index];
+                    } else {
+                        return cookie[i + 1 .. cookie.len];
+                    }
+                } else {
+                    matching = 0;
+                }
+            }
+        }
+        return null;
+    }
+
+    pub fn parse1(self: *Request, buf: []const u8) bool {
+        const method_start: usize = 0;
+        const method_end = std.mem.indexOfScalar(u8, buf, ' ') orelse return false;
+        self.method = @enumFromInt(Method.parse(buf[method_start..method_end]));
+
+        const target_start = method_end + 1;
+        const target_end = std.mem.indexOfScalarPos(u8, buf, target_start, ' ') orelse return false;
+        self.target = buf[target_start..target_end];
+
+        const version_start = target_end + 1;
+        const version_end = std.mem.indexOfPos(u8, buf, version_start, "\r\n") orelse buf.len;
+        self.version = buf[version_start..version_end];
+
+        if (version_end + 2 >= buf.len)
+            return true;
+        const head_start = version_end + 2;
+        const head_end = std.mem.indexOfPos(u8, buf, head_start, "\r\n\r\n") orelse buf.len;
+        self.head = buf[head_start..head_end];
+
+        if (head_end + 4 >= buf.len)
+            return true;
+        const body_start = head_end + 4;
+        const body_end = buf.len;
+        self.body = buf[body_start..body_end];
+
+        return true;
+    }
+
+    pub fn get_header1(self: Request, name: []const u8) ?[]const u8 {
+        const head = self.head orelse return null;
+        const header_start = std.mem.indexOf(u8, head, name) orelse return null;
+        const header_end = std.mem.indexOfPos(u8, head, header_start, "\r\n") orelse return null;
+        return head[header_start..header_end];
+    }
+
+    pub fn get_cookie1(self: Request, name: []const u8) ?[]const u8 {
+        const cookie = self.get_header("Cookie") orelse return null;
+        const name_index = std.mem.indexOf(u8, cookie, name) orelse return null;
+        const eql_index = std.mem.indexOfScalarPos(u8, cookie, name_index, '=') orelse return null;
+        if (std.mem.indexOfScalarPos(u8, cookie, eql_index, ';')) |semi_index| {
+            return cookie[eql_index + 1 .. semi_index];
+        } else {
+            return cookie[eql_index + 1 .. cookie.len];
+        }
+    }
+
+    pub fn get_value(self: Request, name: []const u8) ?[]const u8 {
+        const body = self.body orelse return null;
+        const name_index = std.mem.indexOf(u8, body, name) orelse return null;
+        const eql_index = std.mem.indexOfScalarPos(u8, body, name_index, '=') orelse return null;
+        if (std.mem.indexOfScalarPos(u8, body, name_index, '&')) |amp_index| {
+            return body[eql_index + 1 .. amp_index];
+        } else {
+            return body[eql_index + 1 .. body.len];
+        }
+    }
+};
+
+pub const Response = struct {
+    const ExtraHeadersMax = 16;
+    const HeaderList = std.BoundedArray(Header, ExtraHeadersMax);
+
+    fd: posix.fd_t,
+    stream_head: std.io.FixedBufferStream([]u8),
+    stream_body: std.io.FixedBufferStream([]u8),
+    status: Status = .ok,
+    extra_headers: HeaderList = HeaderList.init(0) catch unreachable,
+
+    pub fn init(fd: posix.fd_t, buf_head: []u8, buf_body: []u8) Response {
+        return .{
+            .fd = fd,
+            .stream_head = std.io.fixedBufferStream(buf_head),
+            .stream_body = std.io.fixedBufferStream(buf_body),
+        };
+    }
+
+    pub fn redirect(self: *Response, location: []const u8) !void {
+        self.status = .see_other;
+        try self.add_header("Location", .{ "{s}", .{location} });
+    }
+
+    pub fn add_header(self: *Response, name: []const u8, value: anytype) !void {
+        const header = try self.extra_headers.addOne();
+        try header.name.writer().writeAll(name);
+        if (@typeInfo(@TypeOf(value)).Struct.fields.len < 2 or @sizeOf(@TypeOf(value[1])) == 0) {
+            try header.value.writer().writeAll(value[0]);
+        } else {
+            try std.fmt.format(header.value.writer(), value[0], value[1]);
+        }
+    }
+
+    pub fn write(self: *Response, comptime fmt: []const u8, args: anytype) !void {
+        const writer = self.stream_body.writer();
+
+        if (@sizeOf(@TypeOf(args)) == 0) {
+            try writer.writeAll(fmt);
+        } else {
+            try std.fmt.format(writer, fmt, args);
+        }
+    }
+
+    pub fn send(self: *Response) !void {
+        // write head
+        const writer = self.stream_head.writer();
+
+        try std.fmt.format(writer, "HTTP/1.1 {} {?s}\r\n" ++
+            "Content-Length: {}\r\n", .{ @intFromEnum(self.status), self.status.phrase(), self.stream_body.pos });
+
+        for (self.extra_headers.constSlice()) |header| {
+            try std.fmt.format(writer, "{s}: {s}\r\n", .{ header.name.constSlice(), header.value.constSlice() });
+        }
+
+        try std.fmt.format(writer, "\r\n", .{});
+
+        // write body to head
+
+        try std.fmt.format(writer, "{s}", .{self.stream_body.getWritten()});
+
+        // send head
+
+        const res = self.stream_head.getWritten();
+        var written: usize = 0;
+        while (written < res.len) {
+            written += posix.write(self.fd, res[written..res.len]) catch |err| {
+                std.debug.print("posix.write: {}\n", .{err});
+                continue;
+            };
+        }
+    }
+};
index b016e1b89c93320fb87e8a87ea9c4d63b6caf4ba..6647619af293cb5d2f265db6bac6da0baee5ffd5 100644 (file)
@@ -1,5 +1,6 @@
 const std = @import("std");
 const lmdb = @import("lmdb");
+const http = @import("http.zig");
 
 // db {{{
 
@@ -15,51 +16,39 @@ const Prng = struct {
 
         return id;
     }
-};
-
-// }}}
-
-// http stuff {{{
-
-pub fn redirect(req: *std.http.Server.Request, location: []const u8) !void {
-    try req.respond("", .{ .status = .see_other, .extra_headers = &.{.{ .name = "Location", .value = location }} });
-}
 
-pub fn get_body(req: *std.http.Server.Request) []const u8 {
-    return req.server.read_buffer[req.head_end .. req.head_end + (req.head.content_length orelse 0)];
-}
+    pub fn gen_str(dbi: anytype, comptime len: usize) [len]u8 {
+        var buf: [len / 2]u8 = undefined;
+        var res: [len]u8 = undefined;
+        Prng.prng.fill(&buf);
+        for (0..len / 2) |i| {
+            res[i * 2 + 0] = 'a' + (buf[i] % 16);
+            res[i * 2 + 1] = 'a' + (buf[i] >> 4 % 16);
+        }
 
-pub fn get_value(req: *std.http.Server.Request, name: []const u8) ?[]const u8 {
-    const body = get_body(req);
-    if (std.mem.indexOf(u8, body, name)) |name_index| {
-        if (std.mem.indexOfScalarPos(u8, body, name_index, '=')) |eql_index| {
-            if (std.mem.indexOfScalarPos(u8, body, name_index, '&')) |amp_index| {
-                return body[eql_index + 1 .. amp_index];
+        while (dbi.has(res)) {
+            Prng.prng.fill(&buf);
+            for (0..len / 2) |i| {
+                res[i * 2 + 0] = 'a' + (buf[i] % 16);
+                res[i * 2 + 1] = 'a' + (buf[i] >> 4 % 16);
             }
-
-            return body[eql_index + 1 .. body.len];
         }
-    }
-    return null;
-}
 
-pub fn get_cookie(req: *std.http.Server.Request, name: []const u8) ?CookieValue {
-    var header_it = req.iterateHeaders();
-    while (header_it.next()) |header| {
-        if (std.mem.eql(u8, header.name, "Cookie")) {
-            if (std.mem.indexOf(u8, header.value, name)) |name_index| {
-                if (std.mem.indexOfScalarPos(u8, header.value, name_index, '=')) |eql_index| {
-                    if (std.mem.indexOfPos(u8, header.value, name_index, "; ")) |semi_index| {
-                        return CookieValue.fromSlice(header.value[eql_index + 1 .. semi_index]) catch null;
-                    }
+        return res;
+    }
+};
 
-                    return CookieValue.fromSlice(header.value[eql_index + 1 .. header.value.len]) catch null;
-                }
-            }
-        }
+const Db = struct {
+    fn users(txn: *const lmdb.Txn) !lmdb.Dbi(Id, User) {
+        return try txn.dbi("users", Id, User);
     }
-    return null;
-}
+    fn user_ids(txn: *const lmdb.Txn) !lmdb.Dbi(Username, Id) {
+        return try txn.dbi("user_ids", Username, Id);
+    }
+    fn sessions(txn: *const lmdb.Txn) !lmdb.Dbi(SessionToken, Id) {
+        return try txn.dbi("sessions", SessionToken, Id);
+    }
+};
 
 // }}}
 
@@ -71,10 +60,12 @@ const User = struct {
     password_hash: PasswordHash,
 };
 
+const SessionTokenLen = 16;
+
 const Id = u64;
 const Username = std.BoundedArray(u8, 16);
 const PasswordHash = std.BoundedArray(u8, 128);
-const SessionToken = u64;
+const SessionToken = [SessionTokenLen]u8;
 const CookieValue = std.BoundedArray(u8, 128);
 
 pub fn hash_password(password: []const u8) !PasswordHash {
@@ -110,7 +101,7 @@ pub fn verify_password(password: []const u8, hash: PasswordHash) bool {
     }
 }
 
-pub fn register_user(env: *lmdb.Env, username: []const u8, password: []const u8) !void {
+pub fn register_user(env: *lmdb.Env, username: []const u8, password: []const u8) !bool {
     const username_array = try Username.fromSlice(username);
 
     const txn = try env.txn();
@@ -119,15 +110,22 @@ pub fn register_user(env: *lmdb.Env, username: []const u8, password: []const u8)
         env.sync();
     }
 
-    const users = try txn.dbi("users", Id, User);
-    const user_id = Prng.gen_id(users);
-    users.put(user_id, User{
-        .username = username_array,
-        .password_hash = try hash_password(password),
-    });
-
-    const user_ids = try txn.dbi("user_ids", Username, Id);
-    user_ids.put(username_array, user_id);
+    const users = try Db.users(&txn);
+    const user_ids = try Db.user_ids(&txn);
+
+    if (user_ids.has(username_array)) {
+        return false;
+    } else {
+        const user_id = Prng.gen_id(users);
+        users.put(user_id, User{
+            .username = username_array,
+            .password_hash = try hash_password(password),
+        });
+
+        user_ids.put(username_array, user_id);
+
+        return true;
+    }
 }
 
 pub fn login_user(env: *lmdb.Env, username: []const u8, password: []const u8) !SessionToken {
@@ -139,15 +137,15 @@ pub fn login_user(env: *lmdb.Env, username: []const u8, password: []const u8) !S
         env.sync();
     }
 
-    const user_ids = try txn.dbi("user_ids", Username, Id);
+    const user_ids = try Db.user_ids(&txn);
     const user_id = user_ids.get(username_array) orelse return error.UnknownUsername;
     std.debug.print("id: {}\n", .{user_id});
 
-    const users = try txn.dbi("users", Id, User);
+    const users = try Db.users(&txn);
     if (users.get(user_id)) |user| {
         if (verify_password(password, user.password_hash)) {
-            const sessions = try txn.dbi("sessions", Id, Id);
-            const session_token = Prng.gen_id(sessions);
+            const sessions = try Db.sessions(&txn);
+            const session_token = Prng.gen_str(sessions, SessionTokenLen);
             sessions.put(session_token, user_id);
             return session_token;
         } else {
@@ -165,7 +163,7 @@ fn logout_user(env: *lmdb.Env, session_token: SessionToken) !void {
         env.sync();
     }
 
-    const sessions = try txn.dbi("sessions", Id, Id);
+    const sessions = try Db.sessions(&txn);
     sessions.del(session_token);
 }
 
@@ -173,8 +171,8 @@ fn get_session_user(env: *lmdb.Env, session_token: SessionToken) !User {
     const txn = try env.txn();
     defer txn.abort();
 
-    const sessions = try txn.dbi("sessions", Id, Id);
-    const users = try txn.dbi("users", Id, User);
+    const sessions = try Db.sessions(&txn);
+    const users = try Db.users(&txn);
 
     if (sessions.get(session_token)) |user_id| {
         return users.get(user_id) orelse error.UnknownUser;
@@ -189,13 +187,14 @@ fn list_users(env: *lmdb.Env) !void {
     const txn = try env.txn();
     defer txn.abort();
 
+    // const users = try Db.users(&txn);
     const users = try txn.dbi("users", Id, User);
     var cursor = try users.cursor();
 
     var key: Id = undefined;
     var user_maybe = cursor.get(&key, .First);
 
-    while (user_maybe) |user| {
+    while (user_maybe) |*user| {
         std.debug.print("[{}] {s}\n", .{ key, user.username.constSlice() });
 
         user_maybe = cursor.get(&key, .Next);
@@ -205,7 +204,7 @@ fn list_user_ids(env: *lmdb.Env) !void {
     const txn = try env.txn();
     defer txn.abort();
 
-    const user_ids = try txn.dbi("user_ids", Username, Id);
+    const user_ids = try Db.user_ids(&txn);
     var cursor = try user_ids.cursor();
 
     var key: Username = undefined;
@@ -222,26 +221,26 @@ fn list_sessions(env: *lmdb.Env) !void {
     const txn = try env.txn();
     defer txn.abort();
 
-    const sessions = try txn.dbi("sessions", SessionToken, Id);
+    const sessions = try Db.sessions(&txn);
     var cursor = try sessions.cursor();
 
     var key: SessionToken = undefined;
     var user_id_maybe = cursor.get(&key, .First);
 
     while (user_id_maybe) |user_id| {
-        std.debug.print("[{}] {}\n", .{ key, user_id });
+        std.debug.print("[{s}] {}\n", .{ key, user_id });
 
         user_id_maybe = cursor.get(&key, .Next);
     }
 }
 
+const ReqBufferSize = 4096;
+const ResHeadBufferSize = 4096;
+const ResBodyBufferSize = 4096;
+
 pub fn main() !void {
     // server
-    const address = try std.net.Address.resolveIp("::", 8080);
-
-    var server = try address.listen(.{
-        .reuse_address = true,
-    });
+    var server = try http.Server.init("::", 8080);
     defer server.deinit();
 
     // lmdb
@@ -255,127 +254,158 @@ pub fn main() !void {
     std.debug.print("Sessions:\n", .{});
     try list_sessions(&env);
 
-    accept: while (true) {
-        const conn = try server.accept();
+    try handle_connection(&server, &env);
+    // const ThreadCount = 1;
+    // var ts: [ThreadCount]std.Thread = undefined;
 
-        std.debug.print("new connection: {}\n", .{conn});
+    // for (0..ThreadCount) |i| {
+    //     ts[i] = try std.Thread.spawn(.{}, handle_connection, .{ &server, &env });
+    // }
+    // for (0..ThreadCount) |i| {
+    //     ts[i].join();
+    // }
 
-        var read_buffer: [1024]u8 = undefined;
-        var http_server = std.http.Server.init(conn, &read_buffer);
+    std.debug.print("done\n", .{});
+}
+
+fn handle_connection(server: *http.Server, env: *lmdb.Env) !void {
+    // TODO: static?
+    var req_buffer: [ReqBufferSize]u8 = undefined;
+    var res_head_buffer: [ResHeadBufferSize]u8 = undefined;
+    var res_body_buffer: [ResBodyBufferSize]u8 = undefined;
+
+    accept: while (true) {
+        server.wait();
 
-        while (http_server.state == .ready) {
-            var req = http_server.receiveHead() catch continue;
+        while (try server.next_request(&req_buffer)) |req| {
+            // std.debug.print("[{}]: {s}\n", .{ req.method, req.target });
 
-            std.debug.print("[{}]: {s}\n", .{ req.head.method, req.head.target });
+            // reponse
+            var res = http.Response.init(req.fd, &res_head_buffer, &res_body_buffer);
 
+            // check session token
             var logged_in: ?struct {
                 user: User,
                 session_token: SessionToken,
             } = null;
 
-            if (get_cookie(&req, "session_token")) |session_token_str| {
-                const session_token = try std.fmt.parseUnsigned(SessionToken, session_token_str.constSlice(), 10);
-                if (get_session_user(&env, session_token)) |user| {
+            if (req.get_cookie("session_token")) |session_token_str| {
+                var session_token: SessionToken = undefined;
+                std.mem.copyForwards(u8, &session_token, session_token_str);
+                // const session_token = try std.fmt.parseUnsigned(SessionToken, session_token_str, 10);
+                // const session_token = std.mem.bytesToValue(SessionToken, session_token_str);
+                if (get_session_user(env, session_token)) |user| {
                     logged_in = .{
                         .user = user,
                         .session_token = session_token,
                     };
                 } else |err| {
                     std.debug.print("get_session_user err: {}\n", .{err});
+
+                    try res.add_header(
+                        "Set-Cookie",
+                        .{"session_token=deleted; Expires=Thu, 01 Jan 1970 00:00:00 GMT"},
+                    );
                 }
-                // TODO: delete session token
-                // TODO: add changeable headers (set, delete cookies)
             }
 
             // html
-            if (req.head.method == .GET) {
-                if (std.mem.eql(u8, req.head.target, "/register")) {
-                    try req.respond(
+            if (req.method == .GET) {
+                if (std.mem.eql(u8, req.target, "/register")) {
+                    try res.write(
                         \\<form action="/register" method="post">
                         \\<input type="text" name="username" />
                         \\<input type="password" name="password" />
                         \\<input type="submit" value="Register" />
                         \\</form>
                     , .{});
-                } else if (std.mem.eql(u8, req.head.target, "/login")) {
-                    try req.respond(
+                    try res.send();
+                } else if (std.mem.eql(u8, req.target, "/login")) {
+                    try res.write(
                         \\<form action="/login" method="post">
                         \\<input type="text" name="username" />
                         \\<input type="password" name="password" />
                         \\<input type="submit" value="Login" />
                         \\</form>
                     , .{});
+                    try res.send();
                 } else {
                     if (logged_in) |login| {
-                        var response_buffer = try std.BoundedArray(u8, 1024).init(0);
-                        try std.fmt.format(response_buffer.writer(),
+                        try res.write(
                             \\<a href="/user/{s}">Home</a>
                             \\<form action="/logout" method="post"><input type="submit" value="Logout" /></form>
                             \\<form action="/quit" method="post"><input type="submit" value="Quit" /></form>
                         , .{login.user.username.constSlice()});
-                        try req.respond(response_buffer.constSlice(), .{});
+                        try res.send();
                     } else {
-                        try req.respond(
+                        try res.write(
                             \\<a href="/register">Register</a>
                             \\<a href="/login">Login</a>
                             \\<form action="/quit" method="post"><input type="submit" value="Quit" /></form>
                         , .{});
+                        try res.send();
                     }
                 }
             }
             // api
             else {
-                if (std.mem.eql(u8, req.head.target, "/register")) {
+                if (std.mem.eql(u8, req.target, "/register")) {
                     // TODO: handle args not supplied
-                    const username = get_value(&req, "username").?;
-                    const password = get_value(&req, "password").?;
+                    const username = req.get_value("username").?;
+                    const password = req.get_value("password").?;
 
                     std.debug.print("New user: {s} {s}\n", .{ username, password });
-                    try register_user(&env, username, password);
-
-                    try redirect(&req, "/login");
-                } else if (std.mem.eql(u8, req.head.target, "/login")) {
+                    if (try register_user(env, username, password)) {
+                        try res.redirect("/login");
+                    } else {
+                        try res.redirect("/register");
+                    }
+                    try res.send();
+                } else if (std.mem.eql(u8, req.target, "/login")) {
                     // TODO: handle args not supplied
-                    const username = get_value(&req, "username").?;
-                    const password = get_value(&req, "password").?;
+                    const username = req.get_value("username").?;
+                    const password = req.get_value("password").?;
 
                     std.debug.print("New login: {s} {s}\n", .{ username, password });
-                    if (login_user(&env, username, password)) |session_token| {
-                        var redirect_buffer = try std.BoundedArray(u8, 128).init(0);
-                        try std.fmt.format(redirect_buffer.writer(), "/user/{s}", .{username});
-
-                        var cookie_buffer = try std.BoundedArray(u8, 128).init(0);
-                        try std.fmt.format(cookie_buffer.writer(), "session_token={}; Secure; HttpOnly", .{session_token});
-
-                        try req.respond("", .{
-                            .status = .see_other,
-                            .extra_headers = &.{
-                                .{ .name = "Location", .value = redirect_buffer.constSlice() },
-                                .{ .name = "Set-Cookie", .value = cookie_buffer.constSlice() },
-                            },
-                        });
+                    if (login_user(env, username, password)) |session_token| {
+                        res.status = .see_other;
+                        try res.add_header(
+                            "Location",
+                            .{ "/user/{s}", .{username} },
+                        );
+                        try res.add_header(
+                            "Set-Cookie",
+                            .{ "session_token={s}; Secure; HttpOnly", .{session_token} },
+                        );
+
+                        try res.send();
                     } else |err| {
                         std.debug.print("login_user err: {}\n", .{err});
-                        try redirect(&req, "/login");
+                        try res.redirect("/login");
+                        try res.send();
                     }
-                } else if (std.mem.eql(u8, req.head.target, "/logout")) {
+                } else if (std.mem.eql(u8, req.target, "/logout")) {
                     if (logged_in) |login| {
-                        try logout_user(&env, login.session_token);
-                        try req.respond("", .{
-                            .status = .see_other,
-                            .extra_headers = &.{
-                                .{ .name = "Location", .value = "/" },
-                                .{ .name = "Set-Cookie", .value = "session_token=deleted; Expires=Thu, 01 Jan 1970 00:00:00 GMT" },
-                            },
-                        });
+                        try logout_user(env, login.session_token);
+
+                        try res.add_header(
+                            "Set-Cookie",
+                            .{"session_token=deleted; Expires=Thu, 01 Jan 1970 00:00:00 GMT"},
+                        );
+
+                        try res.redirect("/");
+                        try res.send();
                     }
-                } else if (std.mem.eql(u8, req.head.target, "/quit")) {
-                    try redirect(&req, "/");
+                } else if (std.mem.eql(u8, req.target, "/quit")) {
+                    try res.redirect("/");
+                    try res.send();
                     break :accept;
                 } else {
-                    try req.respond(
-                        \\<p>POST</p>
-                    , .{});
+                    // try req.respond(
+                    //     \\<p>POST</p>
+                    // , .{});
+                    try res.write("<p>{s}</p>", .{req.target});
+                    try res.send();
                 }
             }
         }
diff --git a/todo.md b/todo.md
index aa23f7ec55e6928bbfc889a3ccb091d0d6e6a96d..e1487d4883fb7d0b84b576698c8d6de9e8d13ffa 100644 (file)
--- a/todo.md
+++ b/todo.md
@@ -5,7 +5,11 @@
   - interface: dbi <KEY, VAL>
   - change optionals to errors
   - self: *const Self -> self: Self
+  - SessionToken: []u8
+  - get maybe !? weil nicht vorhanden und fehler unterschiedlich behandelt werden sollten
 - generate ids
+- Change respond (respondoptions -> request, fmt+args)
+- Rewrite get_value
 - Account
   - Register
   - Login