]> gitweb.ps.run Git - zighttp/blob - src/http.zig
parse query parameters
[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.c.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});
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                 }
77                 if (read == 0) {
78                     closed = true;
79                 } else {
80                     if (req.parse(buf[0..read]))
81                         return req;
82                 }
83
84                 if (closed or ready.events & linux.EPOLL.RDHUP == linux.EPOLL.RDHUP) {
85                     posix.close(ready_socket);
86                 }
87             }
88         }
89         return null;
90     }
91 };
92
93 // pub const Method = enum { GET, POST };
94 pub const Method = std.http.Method;
95
96 // pub const Header = struct {
97 //     const NAME_SIZE = 32;
98 //     const VALUE_SIZE = 128;
99
100 //     name: std.BoundedArray(u8, NAME_SIZE),
101 //     value: std.BoundedArray(u8, VALUE_SIZE),
102 // };
103 pub const Header = struct {
104     const Name = std.BoundedArray(u8, 32);
105     const Value = std.BoundedArray(u8, 128);
106
107     name: Name = Name.init(0) catch unreachable,
108     value: Value = Value.init(0) catch unreachable,
109 };
110 pub const Status = std.http.Status;
111
112 pub const Request = struct {
113     fd: posix.fd_t,
114
115     method: Method = undefined,
116     target: []const u8 = undefined,
117     query: ?[]const u8 = null,
118     version: ?[]const u8 = null,
119     head: ?[]const u8 = null,
120     body: ?[]u8 = null,
121
122     pub fn parse(self: *Request, buf: []u8) bool {
123         // std.debug.print("buf: {s}\n", .{buf});
124         var state: u8 = 0;
125
126         var start: u32 = 0;
127
128         var index: u32 = 0;
129         while (index < buf.len) {
130             defer index += 1;
131
132             const c = buf[index];
133
134             switch (state) {
135                 0 => {
136                     if (c == ' ') {
137                         self.method = @enumFromInt(Method.parse(buf[start..index]));
138                         start = index + 1;
139                         state = 1;
140                     }
141                 },
142                 1 => {
143                     if (c == '?') {
144                         self.target = buf[start..index];
145                         start = index + 1;
146                         state = 2;
147                     } else if (c == ' ') {
148                         self.target = buf[start..index];
149                         start = index + 1;
150                         state = 3;
151                     }
152                 },
153                 2 => {
154                     if (c == ' ') {
155                         self.query = buf[start..index];
156                         start = index + 1;
157                         state = 3;
158                     }
159                 },
160                 3 => {
161                     if (c == '\r') {
162                         self.version = buf[start..index];
163                         start = index + 2;
164                         index += 1;
165                         state = 4;
166                     }
167                 },
168                 4 => {
169                     if (c == '\r' and (index + 2) < buf.len and buf[index + 2] == '\r') {
170                         self.head = buf[start .. index + 2];
171
172                         if (index + 4 < buf.len) {
173                             self.body = buf[index + 4 .. buf.len];
174                         }
175                         return true;
176                     }
177                 },
178                 else => {},
179             }
180         }
181
182         return true;
183     }
184
185     pub fn get_cookie(self: Request, name: []const u8) ?[]const u8 {
186         const cookie = self.get_header("Cookie") orelse return null;
187         var start: usize = 0;
188         var matching: usize = 0;
189         for (0..cookie.len) |i| {
190             const c = cookie[i];
191
192             if (matching < name.len) {
193                 if (c == name[matching]) {
194                     if (matching == 0) start = i;
195                     matching += 1;
196                 } else {
197                     matching = 0;
198                 }
199             } else {
200                 if (c == '=') {
201                     if (std.mem.indexOfScalarPos(u8, cookie, i, ';')) |semi_index| {
202                         return cookie[i + 1 .. semi_index];
203                     } else {
204                         return cookie[i + 1 .. cookie.len];
205                     }
206                 } else {
207                     matching = 0;
208                 }
209             }
210         }
211         return null;
212     }
213
214     pub fn get_header(self: Request, name: []const u8) ?[]const u8 {
215         const head = self.head orelse return null;
216         const header_start = std.mem.indexOf(u8, head, name) orelse return null;
217         const colon_index = std.mem.indexOfPos(u8, head, header_start, ": ") orelse return null;
218         const header_end = std.mem.indexOfPos(u8, head, colon_index, "\r\n") orelse return null;
219         return head[colon_index + 2 .. header_end];
220     }
221
222     pub fn get_param(self: Request, name: []const u8) ?[]const u8 {
223         const query = self.query orelse return null;
224         const name_index = std.mem.indexOf(u8, query, name) orelse return null;
225         const eql_index = std.mem.indexOfScalarPos(u8, query, name_index, '=') orelse return null;
226         if (std.mem.indexOfScalarPos(u8, query, name_index, '&')) |amp_index| {
227             const result = query[eql_index + 1 .. amp_index];
228             return result;
229         } else {
230             const result = query[eql_index + 1 .. query.len];
231             return result;
232         }
233     }
234
235     pub fn get_value(self: Request, name: []const u8) ?[]const u8 {
236         const body = self.body orelse return null;
237         const name_index = std.mem.indexOf(u8, body, name) orelse return null;
238         const eql_index = std.mem.indexOfScalarPos(u8, body, name_index, '=') orelse return null;
239         if (std.mem.indexOfScalarPos(u8, body, name_index, '&')) |amp_index| {
240             const result = body[eql_index + 1 .. amp_index];
241             return result;
242         } else {
243             const result = body[eql_index + 1 .. body.len];
244             return result;
245         }
246     }
247
248     pub fn get_cookie1(self: Request, name: []const u8) ?[]const u8 {
249         const cookie = self.get_header("Cookie") orelse return null;
250         const name_index = std.mem.indexOf(u8, cookie, name) orelse return null;
251         const eql_index = std.mem.indexOfScalarPos(u8, cookie, name_index, '=') orelse return null;
252         if (std.mem.indexOfScalarPos(u8, cookie, eql_index, ';')) |semi_index| {
253             return cookie[eql_index + 1 .. semi_index];
254         } else {
255             return cookie[eql_index + 1 .. cookie.len];
256         }
257     }
258     pub fn get_header1(self: Request, name: []const u8) ?[]const u8 {
259         const head = self.head orelse return null;
260         var start: usize = 0;
261         var matching: usize = 0;
262         for (0..head.len) |i| {
263             const c = head[i];
264
265             if (matching < name.len) {
266                 if (c == name[matching]) {
267                     // if (matching == 0) start = i;
268                     matching += 1;
269                 } else {
270                     start = i;
271                     matching = 0;
272                 }
273             } else {
274                 if (c == '\r') {
275                     return head[start..i];
276                 }
277             }
278         }
279         return null;
280     }
281
282     pub fn parse1(self: *Request, buf: []const u8) bool {
283         const method_start: usize = 0;
284         const method_end = std.mem.indexOfScalar(u8, buf, ' ') orelse return false;
285         self.method = @enumFromInt(Method.parse(buf[method_start..method_end]));
286
287         const target_start = method_end + 1;
288         const target_end = std.mem.indexOfScalarPos(u8, buf, target_start, ' ') orelse return false;
289         self.target = buf[target_start..target_end];
290
291         const version_start = target_end + 1;
292         const version_end = std.mem.indexOfPos(u8, buf, version_start, "\r\n") orelse buf.len;
293         self.version = buf[version_start..version_end];
294
295         if (version_end + 2 >= buf.len)
296             return true;
297         const head_start = version_end + 2;
298         const head_end = std.mem.indexOfPos(u8, buf, head_start, "\r\n\r\n") orelse buf.len;
299         self.head = buf[head_start..head_end];
300
301         if (head_end + 4 >= buf.len)
302             return true;
303         const body_start = head_end + 4;
304         const body_end = buf.len;
305         self.body = buf[body_start..body_end];
306
307         return true;
308     }
309 };
310
311 pub const Response = struct {
312     const ExtraHeadersMax = 16;
313     const HeaderList = std.BoundedArray(Header, ExtraHeadersMax);
314
315     req: Request,
316     stream_head: std.io.FixedBufferStream([]u8),
317     stream_body: std.io.FixedBufferStream([]u8),
318     status: Status = .ok,
319     extra_headers: HeaderList = HeaderList.init(0) catch unreachable,
320
321     pub fn init(req: Request, buf_head: []u8, buf_body: []u8) Response {
322         return .{
323             .req = req,
324             .stream_head = std.io.fixedBufferStream(buf_head),
325             .stream_body = std.io.fixedBufferStream(buf_body),
326         };
327     }
328
329     pub fn redirect(self: *Response, location: []const u8) !void {
330         self.status = .see_other;
331         try self.add_header("Location", .{ "{s}", .{location} });
332     }
333
334     /// value can be a "string" or a struct .{ "fmt", .{ args }}
335     pub fn add_header(self: *Response, name: []const u8, value: anytype) !void {
336         const header = try self.extra_headers.addOne();
337         try header.name.writer().writeAll(name);
338         if (@typeInfo(@TypeOf(value)).Struct.fields.len < 2 or @sizeOf(@TypeOf(value[1])) == 0) {
339             try header.value.writer().writeAll(value[0]);
340         } else {
341             try std.fmt.format(header.value.writer(), value[0], value[1]);
342         }
343     }
344
345     pub fn has_header(self: Response, name: []const u8) bool {
346         for (self.extra_headers.constSlice()) |h| {
347             if (std.mem.eql(u8, h.name.constSlice(), name)) {
348                 return true;
349             }
350         }
351         return false;
352     }
353
354     pub fn write(self: *Response, comptime fmt: []const u8, args: anytype) !void {
355         const writer = self.stream_body.writer();
356
357         if (@sizeOf(@TypeOf(args)) == 0) {
358             try writer.writeAll(fmt);
359         } else {
360             try std.fmt.format(writer, fmt, args);
361         }
362     }
363
364     pub fn send(self: *Response) !void {
365         // TODO: Provisorium
366         const compress = false;
367         var compress_buffer = try std.BoundedArray(u8, 1024 * 32).init(0);
368
369         // write head
370         const writer = self.stream_head.writer();
371
372         if (compress) {
373             var cfbs = std.io.fixedBufferStream(self.stream_body.getWritten());
374             var compressor = try std.compress.gzip.compressor(compress_buffer.writer(), .{ .level = .default });
375             try compressor.compress(cfbs.reader());
376             // try compressor.flush();
377             try compressor.finish();
378             try std.fmt.format(writer, "HTTP/1.1 {} {?s}\r\n" ++
379                 "Content-Length: {}\r\n" ++
380                 "Content-Encoding: gzip\r\n", .{ @intFromEnum(self.status), self.status.phrase(), compress_buffer.constSlice().len });
381         } else {
382             try std.fmt.format(writer, "HTTP/1.1 {} {?s}\r\n" ++
383                 "Content-Length: {}\r\n", .{ @intFromEnum(self.status), self.status.phrase(), self.stream_body.pos });
384         }
385
386         for (self.extra_headers.constSlice()) |header| {
387             try std.fmt.format(writer, "{s}: {s}\r\n", .{ header.name.constSlice(), header.value.constSlice() });
388         }
389
390         try std.fmt.format(writer, "\r\n", .{});
391
392         // write body to head
393         if (compress) {
394             try std.fmt.format(writer, "{s}", .{compress_buffer.constSlice()});
395         } else {
396             try std.fmt.format(writer, "{s}", .{self.stream_body.getWritten()});
397         }
398
399         // send
400         const res = self.stream_head.getWritten();
401         var written: usize = 0;
402         while (written < res.len) {
403             written += posix.write(self.req.fd, res[written..res.len]) catch |err| {
404                 std.debug.print("posix.write: {}\n", .{err});
405                 continue;
406             };
407         }
408     }
409 };