2022-06-23 07:25:50 +00:00
|
|
|
const std = @import("std");
|
|
|
|
const util = @import("util");
|
|
|
|
const http = @import("./lib.zig");
|
|
|
|
|
2022-07-09 22:43:35 +00:00
|
|
|
const response = @import("./server/response.zig");
|
2022-11-05 10:09:59 +00:00
|
|
|
const request = @import("./request.zig");
|
2022-11-18 02:42:23 +00:00
|
|
|
const os = std.os;
|
2022-07-09 22:43:35 +00:00
|
|
|
|
2022-10-13 09:23:57 +00:00
|
|
|
pub const Response = struct {
|
|
|
|
alloc: std.mem.Allocator,
|
2022-11-18 02:42:23 +00:00
|
|
|
stream: Stream,
|
2022-10-13 09:23:57 +00:00
|
|
|
should_close: bool = false,
|
2022-11-27 01:52:30 +00:00
|
|
|
was_opened: bool = false,
|
2022-11-18 02:42:23 +00:00
|
|
|
|
|
|
|
pub const ResponseStream = response.ResponseStream(Stream.Writer);
|
|
|
|
pub fn open(self: *Response, status: http.Status, headers: *const http.Fields) !ResponseStream {
|
2022-11-27 01:33:46 +00:00
|
|
|
std.debug.assert(!self.was_opened);
|
|
|
|
self.was_opened = true;
|
2022-10-13 09:23:57 +00:00
|
|
|
if (headers.get("Connection")) |hdr| {
|
|
|
|
if (std.ascii.indexOfIgnoreCase(hdr, "close")) |_| self.should_close = true;
|
|
|
|
}
|
|
|
|
|
2022-11-18 02:42:23 +00:00
|
|
|
return response.open(self.alloc, self.stream.writer(), headers, status);
|
2022-10-13 09:23:57 +00:00
|
|
|
}
|
2022-10-16 12:48:12 +00:00
|
|
|
|
2022-11-18 02:42:23 +00:00
|
|
|
pub fn upgrade(self: *Response, status: http.Status, headers: *const http.Fields) !Stream {
|
2022-11-27 01:33:46 +00:00
|
|
|
std.debug.assert(!self.was_opened);
|
|
|
|
self.was_opened = true;
|
2022-10-16 12:48:12 +00:00
|
|
|
try response.writeRequestHeader(self.stream.writer(), headers, status);
|
|
|
|
return self.stream;
|
|
|
|
}
|
2022-10-13 09:23:57 +00:00
|
|
|
};
|
2022-11-18 02:42:23 +00:00
|
|
|
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);
|
|
|
|
}
|
2022-06-23 07:25:50 +00:00
|
|
|
|
2022-11-18 03:48:07 +00:00
|
|
|
pub const ReadError = os.ReadError;
|
2022-11-18 02:42:23 +00:00
|
|
|
pub const WriteError = os.SendError;
|
2022-11-17 05:11:51 +00:00
|
|
|
|
2022-11-18 02:42:23 +00:00
|
|
|
pub const Reader = std.io.Reader(Stream, ReadError, read);
|
|
|
|
pub const Writer = std.io.Writer(Stream, WriteError, write);
|
2022-11-17 05:11:51 +00:00
|
|
|
|
2022-11-18 02:42:23 +00:00
|
|
|
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");
|
|
|
|
|
2022-11-18 03:48:07 +00:00
|
|
|
return os.read(self.socket, buffer);
|
2022-11-18 02:42:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
pub fn write(self: Stream, buffer: []const u8) WriteError!usize {
|
2022-11-17 05:11:51 +00:00
|
|
|
if (std.io.is_async) @compileError("TODO: Async not supported yet");
|
2022-11-18 02:42:23 +00:00
|
|
|
if (self.kind != .tcp) @panic("TODO");
|
|
|
|
|
|
|
|
return os.send(self.socket, buffer, os.MSG.NOSIGNAL);
|
2022-11-17 05:11:51 +00:00
|
|
|
}
|
|
|
|
|
2022-11-18 02:42:23 +00:00
|
|
|
pub fn reader(self: Stream) Reader {
|
2022-11-17 05:11:51 +00:00
|
|
|
return .{ .context = self };
|
|
|
|
}
|
|
|
|
|
2022-11-18 02:42:23 +00:00
|
|
|
pub fn writer(self: Stream) Writer {
|
|
|
|
return .{ .context = self };
|
|
|
|
}
|
|
|
|
};
|
2022-06-23 07:25:50 +00:00
|
|
|
|
2022-11-18 02:42:23 +00:00
|
|
|
pub const Server = struct {
|
|
|
|
tcp_server: std.net.StreamServer,
|
2022-07-09 21:18:43 +00:00
|
|
|
|
2022-11-18 02:42:23 +00:00
|
|
|
pub fn init() Server {
|
|
|
|
return Server{
|
|
|
|
.tcp_server = std.net.StreamServer.init(.{ .reuse_address = true }),
|
2022-07-09 21:18:43 +00:00
|
|
|
};
|
2022-11-18 02:42:23 +00:00
|
|
|
}
|
2022-07-09 21:18:43 +00:00
|
|
|
|
2022-11-18 02:42:23 +00:00
|
|
|
pub fn deinit(self: *Server) void {
|
|
|
|
self.tcp_server.deinit();
|
|
|
|
}
|
2022-07-09 21:18:43 +00:00
|
|
|
|
2022-11-18 02:42:23 +00:00
|
|
|
pub fn listen(self: *Server, address: std.net.Address) !void {
|
|
|
|
try self.tcp_server.listen(address);
|
|
|
|
}
|
2022-07-09 21:18:43 +00:00
|
|
|
|
2022-11-18 02:42:23 +00:00
|
|
|
pub const Connection = struct {
|
|
|
|
stream: Stream,
|
|
|
|
address: std.net.Address,
|
|
|
|
};
|
2022-07-09 21:18:43 +00:00
|
|
|
|
2022-11-18 02:42:23 +00:00
|
|
|
pub fn handleLoop(
|
|
|
|
self: *Server,
|
|
|
|
allocator: std.mem.Allocator,
|
2022-11-27 01:33:46 +00:00
|
|
|
initial_context: anytype,
|
2022-11-18 02:42:23 +00:00
|
|
|
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,
|
|
|
|
},
|
2022-11-27 01:33:46 +00:00
|
|
|
initial_context,
|
2022-11-18 02:42:23 +00:00
|
|
|
handler,
|
|
|
|
);
|
|
|
|
}
|
2022-07-09 21:18:43 +00:00
|
|
|
}
|
2022-11-18 02:42:23 +00:00
|
|
|
|
|
|
|
fn serveConn(
|
|
|
|
allocator: std.mem.Allocator,
|
|
|
|
conn: Connection,
|
2022-11-27 01:33:46 +00:00
|
|
|
initial_context: anytype,
|
2022-11-18 02:42:23 +00:00
|
|
|
handler: anytype,
|
|
|
|
) void {
|
2022-11-27 01:52:30 +00:00
|
|
|
defer conn.stream.close();
|
2022-11-18 02:42:23 +00:00
|
|
|
while (true) {
|
|
|
|
var req = request.parse(allocator, conn.stream.reader()) catch |err| {
|
2022-11-24 11:30:49 +00:00
|
|
|
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 => blk: {
|
2022-11-27 01:33:46 +00:00
|
|
|
std.log.err("Unknown error parsing request: {}\n{?}", .{ err, @errorReturnTrace() });
|
2022-11-24 11:30:49 +00:00
|
|
|
break :blk .internal_server_error;
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
2022-11-27 01:33:46 +00:00
|
|
|
conn.stream.writer().print(
|
|
|
|
"HTTP/1.1 {} {?s}\r\nConnection: close\r\n\r\n",
|
|
|
|
.{ @enumToInt(status), status.phrase() },
|
|
|
|
) catch {};
|
|
|
|
return;
|
2022-11-18 02:42:23 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
var res = Response{
|
|
|
|
.alloc = allocator,
|
|
|
|
.stream = conn.stream,
|
|
|
|
};
|
|
|
|
|
2022-11-27 01:33:46 +00:00
|
|
|
handler.handle(&req, &res, initial_context, {}) catch |err| {
|
|
|
|
std.log.err("Unhandled error serving request: {}\n{?}", .{ err, @errorReturnTrace() });
|
2022-11-24 11:30:49 +00:00
|
|
|
return;
|
|
|
|
};
|
2022-11-18 02:42:23 +00:00
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|