const std = @import("std"); const util = @import("util"); const http = @import("./lib.zig"); const response = @import("./server/response.zig"); const request = @import("./request.zig"); const os = std.os; pub const Response = struct { alloc: std.mem.Allocator, stream: Stream, should_close: bool = false, pub const ResponseStream = response.ResponseStream(Stream.Writer); pub fn open(self: *Response, status: http.Status, headers: *const http.Fields) !ResponseStream { if (headers.get("Connection")) |hdr| { if (std.ascii.indexOfIgnoreCase(hdr, "close")) |_| self.should_close = true; } return response.open(self.alloc, self.stream.writer(), headers, status); } pub fn upgrade(self: *Response, status: http.Status, headers: *const http.Fields) !Stream { try response.writeRequestHeader(self.stream.writer(), headers, status); return self.stream; } }; pub const StreamKind = enum { tcp, }; pub const Stream = struct { kind: StreamKind, socket: os.socket_t, pub fn close(self: Stream) void { os.closeSocket(self.socket); } pub const ReadError = os.RecvFromError; pub const WriteError = os.SendError; pub const Reader = std.io.Reader(Stream, ReadError, read); pub const Writer = std.io.Writer(Stream, WriteError, write); pub fn read(self: Stream, buffer: []u8) ReadError!usize { if (std.io.is_async) @compileError("TODO: async not supported"); if (self.kind != .tcp) @panic("TODO"); return os.recv(self.socket, buffer, 0); } pub fn write(self: Stream, buffer: []const u8) WriteError!usize { if (std.io.is_async) @compileError("TODO: Async not supported yet"); if (self.kind != .tcp) @panic("TODO"); return os.send(self.socket, buffer, os.MSG.NOSIGNAL); } pub fn reader(self: Stream) Reader { return .{ .context = self }; } pub fn writer(self: Stream) Writer { return .{ .context = self }; } }; pub const Server = struct { tcp_server: std.net.StreamServer, pub fn init() Server { return Server{ .tcp_server = std.net.StreamServer.init(.{ .reuse_address = true }), }; } pub fn deinit(self: *Server) void { self.tcp_server.deinit(); } pub fn listen(self: *Server, address: std.net.Address) !void { try self.tcp_server.listen(address); } pub const Connection = struct { stream: Stream, address: std.net.Address, }; pub fn handleLoop( self: *Server, allocator: std.mem.Allocator, ctx: anytype, handler: anytype, ) void { while (true) { const conn = self.tcp_server.accept() catch |err| { if (err == error.SocketNotListening) return; std.log.err("Error occurred accepting connection: {}", .{err}); continue; }; serveConn( allocator, Connection{ .stream = Stream{ .kind = .tcp, .socket = conn.stream.handle }, .address = conn.address, }, ctx, handler, ); } } fn serveConn( allocator: std.mem.Allocator, conn: Connection, ctx: anytype, handler: anytype, ) void { while (true) { var req = request.parse(allocator, conn.stream.reader()) catch |err| { return handleError(conn.stream.writer(), err) catch {}; }; var res = Response{ .alloc = allocator, .stream = conn.stream, }; handler(ctx, &req, &res); if (req.headers.get("Connection")) |hdr| { if (std.ascii.indexOfIgnoreCase(hdr, "close")) |_| return; } else if (req.headers.get("Keep-Alive")) |_| { // TODO: Support this return; } else if (req.protocol == .http_1_0) return; if (res.should_close) return; } } }; /// Writes an error response message and requests closure of the connection fn handleError(writer: anytype, err: anyerror) !void { const status: http.Status = switch (err) { error.EndOfStream => return, // Do nothing, the client closed the connection error.BadRequest => .bad_request, error.UnsupportedMediaType => .unsupported_media_type, error.HttpVersionNotSupported => .http_version_not_supported, else => .internal_server_error, }; try writer.print("HTTP/1.1 {} {?s}\r\nConnection: close\r\n\r\n", .{ @enumToInt(status), status.phrase() }); }