]> gitweb.ps.run Git - zighttp/blob - src/http.zig
remove print and sleep
[zighttp] / src / http.zig
1 const std = @import("std");
2 const posix = std.posix;
3 const linux = std.os.linux;
4
5 pub const Server = struct {
6     // TODO: factor out
7     const BACKLOG = 2048;
8
9     listener: posix.socket_t,
10     efd: i32,
11     ready_list: [BACKLOG]linux.epoll_event = undefined,
12
13     ready_count: usize = 0,
14     ready_index: usize = 0,
15
16     pub fn init(name: []const u8, port: u16) !Server {
17         const address = try std.net.Address.resolveIp(name, port);
18
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);
22
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);
26
27         // epoll_create1 takes flags. We aren't using any in these examples
28         const efd = try posix.epoll_create1(0);
29
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);
32
33         return .{
34             .listener = listener,
35             .efd = efd,
36         };
37     }
38
39     pub fn deinit(self: Server) void {
40         posix.close(self.efd);
41         posix.close(self.listener);
42     }
43
44     pub fn wait(self: *Server) void {
45         if (self.ready_index >= self.ready_count) {
46             self.ready_index = 0;
47             self.ready_count = posix.epoll_wait(self.efd, &self.ready_list, -1);
48         }
49     }
50
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;
56
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 });
66             } else {
67                 var closed = false;
68                 var req = Request{ .fd = ready_socket };
69
70                 var read: usize = 0;
71                 while (true) {
72                     const newly_read = posix.read(ready_socket, buf[read..]) catch 0;
73                     read += newly_read;
74                     if (newly_read == 0)
75                         break;
76                     // std.debug.print("[[{}/{}]]\n", .{ newly_read, read });
77                     // std.time.sleep(100000000);
78                 }
79                 if (read == 0) {
80                     closed = true;
81                 } else {
82                     if (req.parse(buf[0..read]))
83                         return req;
84                 }
85
86                 if (closed or ready.events & linux.EPOLL.RDHUP == linux.EPOLL.RDHUP) {
87                     posix.close(ready_socket);
88                 }
89             }
90         }
91         return null;
92     }
93 };
94
95 // pub const Method = enum { GET, POST };
96 pub const Method = std.http.Method;
97
98 // pub const Header = struct {
99 //     const NAME_SIZE = 32;
100 //     const VALUE_SIZE = 128;
101
102 //     name: std.BoundedArray(u8, NAME_SIZE),
103 //     value: std.BoundedArray(u8, VALUE_SIZE),
104 // };
105 pub const Header = struct {
106     const Name = std.BoundedArray(u8, 32);
107     const Value = std.BoundedArray(u8, 128);
108
109     name: Name = Name.init(0) catch unreachable,
110     value: Value = Value.init(0) catch unreachable,
111 };
112 pub const Status = std.http.Status;
113
114 pub const Request = struct {
115     fd: posix.fd_t,
116
117     method: Method = undefined,
118     target: []const u8 = undefined,
119     query: ?[]const u8 = null,
120     version: ?[]const u8 = null,
121     head: ?[]const u8 = null,
122     body: ?[]u8 = null,
123
124     pub fn parse(self: *Request, buf: []u8) bool {
125         // std.debug.print("buf: {s}\n", .{buf});
126         var state: u8 = 0;
127
128         var start: u32 = 0;
129
130         var index: u32 = 0;
131         while (index < buf.len) {
132             defer index += 1;
133
134             const c = buf[index];
135
136             switch (state) {
137                 0 => {
138                     if (c == ' ') {
139                         self.method = @enumFromInt(Method.parse(buf[start..index]));
140                         start = index + 1;
141                         state = 1;
142                     }
143                 },
144                 1 => {
145                     if (c == '?') {
146                         self.target = buf[start..index];
147                         start = index + 1;
148                         state = 2;
149                     } else if (c == ' ') {
150                         self.target = buf[start..index];
151                         start = index + 1;
152                         state = 3;
153                     }
154                 },
155                 2 => {
156                     if (c == ' ') {
157                         self.query = buf[start..index];
158                         start = index + 1;
159                         state = 3;
160                     }
161                 },
162                 3 => {
163                     if (c == '\r') {
164                         self.version = buf[start..index];
165                         start = index + 2;
166                         index += 1;
167                         state = 4;
168                     }
169                 },
170                 4 => {
171                     if (c == '\r' and (index + 2) < buf.len and buf[index + 2] == '\r') {
172                         self.head = buf[start .. index + 2];
173
174                         if (index + 4 < buf.len) {
175                             self.body = buf[index + 4 .. buf.len];
176                         }
177                         return true;
178                     }
179                 },
180                 else => {},
181             }
182         }
183
184         return true;
185     }
186
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| {
192             const c = cookie[i];
193
194             if (matching < name.len) {
195                 if (c == name[matching]) {
196                     if (matching == 0) start = i;
197                     matching += 1;
198                 } else {
199                     matching = 0;
200                 }
201             } else {
202                 if (c == '=') {
203                     if (std.mem.indexOfScalarPos(u8, cookie, i, ';')) |semi_index| {
204                         return cookie[i + 1 .. semi_index];
205                     } else {
206                         return cookie[i + 1 .. cookie.len];
207                     }
208                 } else {
209                     matching = 0;
210                 }
211             }
212         }
213         return null;
214     }
215
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];
222     }
223
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];
230             return result;
231         } else {
232             const result = query[eql_index + 1 .. query.len];
233             return result;
234         }
235     }
236
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];
243             return result;
244         } else {
245             const result = body[eql_index + 1 .. body.len];
246             return result;
247         }
248     }
249
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];
256         } else {
257             return cookie[eql_index + 1 .. cookie.len];
258         }
259     }
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| {
265             const c = head[i];
266
267             if (matching < name.len) {
268                 if (c == name[matching]) {
269                     // if (matching == 0) start = i;
270                     matching += 1;
271                 } else {
272                     start = i;
273                     matching = 0;
274                 }
275             } else {
276                 if (c == '\r') {
277                     return head[start..i];
278                 }
279             }
280         }
281         return null;
282     }
283
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]));
288
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];
292
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];
296
297         if (version_end + 2 >= buf.len)
298             return true;
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];
302
303         if (head_end + 4 >= buf.len)
304             return true;
305         const body_start = head_end + 4;
306         const body_end = buf.len;
307         self.body = buf[body_start..body_end];
308
309         return true;
310     }
311 };
312
313 pub const Response = struct {
314     const ExtraHeadersMax = 16;
315     const HeaderList = std.BoundedArray(Header, ExtraHeadersMax);
316
317     req: Request,
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,
322
323     pub fn init(req: Request, buf_head: []u8, buf_body: []u8) Response {
324         return .{
325             .req = req,
326             .stream_head = std.io.fixedBufferStream(buf_head),
327             .stream_body = std.io.fixedBufferStream(buf_body),
328         };
329     }
330
331     pub fn redirect(self: *Response, location: []const u8) !void {
332         self.status = .see_other;
333         try self.add_header("Location", .{ "{s}", .{location} });
334     }
335
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]);
342         } else {
343             try std.fmt.format(header.value.writer(), value[0], value[1]);
344         }
345     }
346
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)) {
350                 return true;
351             }
352         }
353         return false;
354     }
355
356     pub fn write(self: *Response, comptime fmt: []const u8, args: anytype) !void {
357         const writer = self.stream_body.writer();
358
359         if (@typeInfo(@TypeOf(args)).@"struct".fields.len == 0) {
360             try writer.writeAll(fmt);
361         } else {
362             try std.fmt.format(writer, fmt, args);
363         }
364     }
365
366     pub fn send(self: *Response) !void {
367         // TODO: Provisorium
368         const compress = false;
369         const CompressBuffer = struct {
370             var compress_buffer: std.BoundedArray(u8, 1024 * 32) = undefined;
371         };
372         try CompressBuffer.compress_buffer.resize(0);
373
374         // write head
375         const writer = self.stream_head.writer();
376
377         if (compress) {
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 });
386         } else {
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 });
389         }
390
391         for (self.extra_headers.constSlice()) |header| {
392             try std.fmt.format(writer, "{s}: {s}\r\n", .{ header.name.constSlice(), header.value.constSlice() });
393         }
394
395         try std.fmt.format(writer, "\r\n", .{});
396
397         // write body to head
398         if (compress) {
399             try writer.writeAll(CompressBuffer.compress_buffer.constSlice());
400         } else {
401             try writer.writeAll(self.stream_body.getWritten());
402         }
403
404         // send
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});
410                 continue;
411             };
412         }
413     }
414 };