1 const std = @import("std");
2 const posix = std.posix;
3 const linux = std.os.linux;
5 pub const Server = struct {
9 listener: posix.socket_t,
11 ready_list: [BACKLOG]linux.epoll_event = undefined,
13 ready_count: usize = 0,
14 ready_index: usize = 0,
16 pub fn init(name: []const u8, port: u16) !Server {
17 const address = try std.net.Address.resolveIp(name, port);
19 const tpe: u32 = posix.SOCK.STREAM | posix.SOCK.NONBLOCK;
20 const protocol = posix.IPPROTO.TCP;
21 const listener = try posix.socket(address.any.family, tpe, protocol);
23 try posix.setsockopt(listener, posix.SOL.SOCKET, posix.SO.REUSEADDR, &std.mem.toBytes(@as(c_int, 1)));
24 try posix.bind(listener, &address.any, address.getOsSockLen());
25 try posix.listen(listener, BACKLOG);
27 // epoll_create1 takes flags. We aren't using any in these examples
28 const efd = try posix.epoll_create1(0);
30 var event = linux.epoll_event{ .events = linux.EPOLL.IN, .data = .{ .fd = listener } };
31 try posix.epoll_ctl(efd, linux.EPOLL.CTL_ADD, listener, &event);
39 pub fn deinit(self: Server) void {
40 posix.close(self.efd);
41 posix.close(self.listener);
44 pub fn wait(self: *Server) void {
45 if (self.ready_index >= self.ready_count) {
47 self.ready_count = posix.epoll_wait(self.efd, &self.ready_list, -1);
51 pub fn next_request(self: *Server, buf: []u8) !?Request {
52 while (self.ready_index < self.ready_count) {
53 const ready = self.ready_list[self.ready_index];
54 const ready_socket = ready.data.fd;
55 self.ready_index += 1;
57 if (ready_socket == self.listener) {
58 const client_socket = try posix.accept(self.listener, null, null, posix.SOCK.NONBLOCK);
59 errdefer posix.close(client_socket);
60 var event = linux.epoll_event{ .events = linux.EPOLL.IN, .data = .{ .fd = client_socket } };
61 try posix.epoll_ctl(self.efd, linux.EPOLL.CTL_ADD, client_socket, &event);
62 var addr: std.os.linux.sockaddr = undefined;
63 var addr_size: std.c.socklen_t = @sizeOf(std.c.sockaddr);
64 _ = std.c.getpeername(client_socket, &addr, &addr_size);
65 // std.debug.print("new connection from {} [{}/{}]\n", .{ addr, @sizeOf(std.os.linux.sockaddr), addr_size });
68 var req = Request{ .fd = ready_socket };
72 const newly_read = posix.read(ready_socket, buf[read..]) catch 0;
76 // std.debug.print("[[{}/{}]]\n", .{ newly_read, read });
77 // std.time.sleep(100000000);
82 if (req.parse(buf[0..read]))
86 if (closed or ready.events & linux.EPOLL.RDHUP == linux.EPOLL.RDHUP) {
87 posix.close(ready_socket);
95 // pub const Method = enum { GET, POST };
96 pub const Method = std.http.Method;
98 // pub const Header = struct {
99 // const NAME_SIZE = 32;
100 // const VALUE_SIZE = 128;
102 // name: std.BoundedArray(u8, NAME_SIZE),
103 // value: std.BoundedArray(u8, VALUE_SIZE),
105 pub const Header = struct {
106 const Name = std.BoundedArray(u8, 32);
107 const Value = std.BoundedArray(u8, 128);
109 name: Name = Name.init(0) catch unreachable,
110 value: Value = Value.init(0) catch unreachable,
112 pub const Status = std.http.Status;
114 pub const Request = struct {
117 method: Method = undefined,
118 target: []const u8 = undefined,
119 query: ?[]const u8 = null,
120 version: ?[]const u8 = null,
121 head: ?[]const u8 = null,
124 pub fn parse(self: *Request, buf: []u8) bool {
125 // std.debug.print("buf: {s}\n", .{buf});
131 while (index < buf.len) {
134 const c = buf[index];
139 self.method = @enumFromInt(Method.parse(buf[start..index]));
146 self.target = buf[start..index];
149 } else if (c == ' ') {
150 self.target = buf[start..index];
157 self.query = buf[start..index];
164 self.version = buf[start..index];
171 if (c == '\r' and (index + 2) < buf.len and buf[index + 2] == '\r') {
172 self.head = buf[start .. index + 2];
174 if (index + 4 < buf.len) {
175 self.body = buf[index + 4 .. buf.len];
187 pub fn get_cookie(self: Request, name: []const u8) ?[]const u8 {
188 const cookie = self.get_header("Cookie") orelse return null;
189 var start: usize = 0;
190 var matching: usize = 0;
191 for (0..cookie.len) |i| {
194 if (matching < name.len) {
195 if (c == name[matching]) {
196 if (matching == 0) start = i;
203 if (std.mem.indexOfScalarPos(u8, cookie, i, ';')) |semi_index| {
204 return cookie[i + 1 .. semi_index];
206 return cookie[i + 1 .. cookie.len];
216 pub fn get_header(self: Request, name: []const u8) ?[]const u8 {
217 const head = self.head orelse return null;
218 const header_start = std.mem.indexOf(u8, head, name) orelse return null;
219 const colon_index = std.mem.indexOfPos(u8, head, header_start, ": ") orelse return null;
220 const header_end = std.mem.indexOfPos(u8, head, colon_index, "\r\n") orelse return null;
221 return head[colon_index + 2 .. header_end];
224 pub fn get_param(self: Request, name: []const u8) ?[]const u8 {
225 const query = self.query orelse return null;
226 const name_index = std.mem.indexOf(u8, query, name) orelse return null;
227 const eql_index = std.mem.indexOfScalarPos(u8, query, name_index, '=') orelse return null;
228 if (std.mem.indexOfScalarPos(u8, query, name_index, '&')) |amp_index| {
229 const result = query[eql_index + 1 .. amp_index];
232 const result = query[eql_index + 1 .. query.len];
237 pub fn get_value(self: Request, name: []const u8) ?[]const u8 {
238 const body = self.body orelse return null;
239 const name_index = std.mem.indexOf(u8, body, name) orelse return null;
240 const eql_index = std.mem.indexOfScalarPos(u8, body, name_index, '=') orelse return null;
241 if (std.mem.indexOfScalarPos(u8, body, name_index, '&')) |amp_index| {
242 const result = body[eql_index + 1 .. amp_index];
245 const result = body[eql_index + 1 .. body.len];
250 pub fn get_cookie1(self: Request, name: []const u8) ?[]const u8 {
251 const cookie = self.get_header("Cookie") orelse return null;
252 const name_index = std.mem.indexOf(u8, cookie, name) orelse return null;
253 const eql_index = std.mem.indexOfScalarPos(u8, cookie, name_index, '=') orelse return null;
254 if (std.mem.indexOfScalarPos(u8, cookie, eql_index, ';')) |semi_index| {
255 return cookie[eql_index + 1 .. semi_index];
257 return cookie[eql_index + 1 .. cookie.len];
260 pub fn get_header1(self: Request, name: []const u8) ?[]const u8 {
261 const head = self.head orelse return null;
262 var start: usize = 0;
263 var matching: usize = 0;
264 for (0..head.len) |i| {
267 if (matching < name.len) {
268 if (c == name[matching]) {
269 // if (matching == 0) start = i;
277 return head[start..i];
284 pub fn parse1(self: *Request, buf: []const u8) bool {
285 const method_start: usize = 0;
286 const method_end = std.mem.indexOfScalar(u8, buf, ' ') orelse return false;
287 self.method = @enumFromInt(Method.parse(buf[method_start..method_end]));
289 const target_start = method_end + 1;
290 const target_end = std.mem.indexOfScalarPos(u8, buf, target_start, ' ') orelse return false;
291 self.target = buf[target_start..target_end];
293 const version_start = target_end + 1;
294 const version_end = std.mem.indexOfPos(u8, buf, version_start, "\r\n") orelse buf.len;
295 self.version = buf[version_start..version_end];
297 if (version_end + 2 >= buf.len)
299 const head_start = version_end + 2;
300 const head_end = std.mem.indexOfPos(u8, buf, head_start, "\r\n\r\n") orelse buf.len;
301 self.head = buf[head_start..head_end];
303 if (head_end + 4 >= buf.len)
305 const body_start = head_end + 4;
306 const body_end = buf.len;
307 self.body = buf[body_start..body_end];
313 pub const Response = struct {
314 const ExtraHeadersMax = 16;
315 const HeaderList = std.BoundedArray(Header, ExtraHeadersMax);
318 stream_head: std.io.FixedBufferStream([]u8),
319 stream_body: std.io.FixedBufferStream([]u8),
320 status: Status = .ok,
321 extra_headers: HeaderList = HeaderList.init(0) catch unreachable,
323 pub fn init(req: Request, buf_head: []u8, buf_body: []u8) Response {
326 .stream_head = std.io.fixedBufferStream(buf_head),
327 .stream_body = std.io.fixedBufferStream(buf_body),
331 pub fn redirect(self: *Response, location: []const u8) !void {
332 self.status = .see_other;
333 try self.add_header("Location", .{ "{s}", .{location} });
336 /// value can be a "string" or a struct .{ "fmt", .{ args }}
337 pub fn add_header(self: *Response, name: []const u8, value: anytype) !void {
338 const header = try self.extra_headers.addOne();
339 try header.name.writer().writeAll(name);
340 if (@typeInfo(@TypeOf(value)).@"struct".fields.len < 2 or @sizeOf(@TypeOf(value[1])) == 0) {
341 try header.value.writer().writeAll(value[0]);
343 try std.fmt.format(header.value.writer(), value[0], value[1]);
347 pub fn has_header(self: Response, name: []const u8) bool {
348 for (self.extra_headers.constSlice()) |h| {
349 if (std.mem.eql(u8, h.name.constSlice(), name)) {
356 pub fn write(self: *Response, comptime fmt: []const u8, args: anytype) !void {
357 const writer = self.stream_body.writer();
359 if (@typeInfo(@TypeOf(args)).@"struct".fields.len == 0) {
360 try writer.writeAll(fmt);
362 try std.fmt.format(writer, fmt, args);
366 pub fn send(self: *Response) !void {
368 const compress = false;
369 const CompressBuffer = struct {
370 var compress_buffer: std.BoundedArray(u8, 1024 * 32) = undefined;
372 try CompressBuffer.compress_buffer.resize(0);
375 const writer = self.stream_head.writer();
378 var cfbs = std.io.fixedBufferStream(self.stream_body.getWritten());
379 var compressor = try std.compress.gzip.compressor(CompressBuffer.compress_buffer.writer(), .{ .level = .default });
380 try compressor.compress(cfbs.reader());
381 // try compressor.flush();
382 try compressor.finish();
383 try std.fmt.format(writer, "HTTP/1.1 {} {?s}\r\n" ++
384 "Content-Length: {}\r\n" ++
385 "Content-Encoding: gzip\r\n", .{ @intFromEnum(self.status), self.status.phrase(), CompressBuffer.compress_buffer.constSlice().len });
387 try std.fmt.format(writer, "HTTP/1.1 {} {?s}\r\n" ++
388 "Content-Length: {}\r\n", .{ @intFromEnum(self.status), self.status.phrase(), self.stream_body.pos });
391 for (self.extra_headers.constSlice()) |header| {
392 try std.fmt.format(writer, "{s}: {s}\r\n", .{ header.name.constSlice(), header.value.constSlice() });
395 try std.fmt.format(writer, "\r\n", .{});
397 // write body to head
399 try writer.writeAll(CompressBuffer.compress_buffer.constSlice());
401 try writer.writeAll(self.stream_body.getWritten());
405 const res = self.stream_head.getWritten();
406 var written: usize = 0;
407 while (written < res.len) {
408 written += posix.write(self.req.fd, res[written..res.len]) catch |err| {
409 std.debug.print("posix.write: {}\n", .{err});