]> gitweb.ps.run Git - chirp/blob - src/http.zig
Add http as external dependency
[chirp] / 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             } else {
63                 var closed = false;
64                 var req = Request{ .fd = ready_socket };
65
66                 const read = posix.read(ready_socket, buf) catch 0;
67                 if (read == 0) {
68                     closed = true;
69                 } else {
70                     if (req.parse(buf[0..read]))
71                         return req;
72                 }
73
74                 if (closed or ready.events & linux.EPOLL.RDHUP == linux.EPOLL.RDHUP) {
75                     posix.close(ready_socket);
76                 }
77             }
78         }
79         return null;
80     }
81 };
82
83 // pub const Method = enum { GET, POST };
84 pub const Method = std.http.Method;
85
86 // pub const Header = struct {
87 //     const NAME_SIZE = 32;
88 //     const VALUE_SIZE = 128;
89
90 //     name: std.BoundedArray(u8, NAME_SIZE),
91 //     value: std.BoundedArray(u8, VALUE_SIZE),
92 // };
93 pub const Header = struct {
94     const Name = std.BoundedArray(u8, 32);
95     const Value = std.BoundedArray(u8, 128);
96
97     name: Name = Name.init(0) catch unreachable,
98     value: Value = Value.init(0) catch unreachable,
99 };
100 pub const Status = std.http.Status;
101
102 pub const Request = struct {
103     fd: posix.fd_t,
104
105     method: Method = undefined,
106     target: []const u8 = undefined,
107     version: ?[]const u8 = null,
108     head: ?[]const u8 = null,
109     body: ?[]const u8 = null,
110
111     pub fn parse(self: *Request, buf: []const u8) bool {
112         var state: u8 = 0;
113
114         var start: u32 = 0;
115         // var end: u32 = 0;
116
117         var index: u32 = 0;
118         while (index < buf.len) {
119             defer index += 1;
120
121             const c = buf[index];
122
123             switch (state) {
124                 0 => {
125                     if (c == ' ') {
126                         self.method = @enumFromInt(Method.parse(buf[start..index]));
127                         start = index + 1;
128                         state += 1;
129                     }
130                 },
131                 1 => {
132                     if (c == ' ') {
133                         self.target = buf[start..index];
134                         start = index + 1;
135                         state += 1;
136                     }
137                 },
138                 2 => {
139                     if (c == '\r') {
140                         self.version = buf[start..index];
141                         start = index + 2;
142                         index += 1;
143                         state += 1;
144                     }
145                 },
146                 3 => {
147                     if (c == '\r' and (index + 2) < buf.len and buf[index + 2] == '\r') {
148                         self.head = buf[start..index];
149
150                         if (index + 4 < buf.len) {
151                             self.body = buf[index + 4 .. buf.len];
152                         }
153                         return true;
154                     }
155                 },
156                 else => {},
157             }
158         }
159
160         return true;
161     }
162
163     pub fn get_header(self: Request, name: []const u8) ?[]const u8 {
164         const head = self.head orelse return null;
165         var start: usize = 0;
166         var matching: usize = 0;
167         for (0..head.len) |i| {
168             const c = head[i];
169
170             if (matching < name.len) {
171                 if (c == name[matching]) {
172                     if (matching == 0) start = i;
173                     matching += 1;
174                 } else {
175                     matching = 0;
176                 }
177             } else {
178                 if (c == '\r') {
179                     return head[start..i];
180                 }
181             }
182         }
183         return null;
184     }
185
186     pub fn get_cookie(self: Request, name: []const u8) ?[]const u8 {
187         const cookie = self.get_header("Cookie") orelse return null;
188         var start: usize = 0;
189         var matching: usize = 0;
190         for (0..cookie.len) |i| {
191             const c = cookie[i];
192
193             if (matching < name.len) {
194                 if (c == name[matching]) {
195                     if (matching == 0) start = i;
196                     matching += 1;
197                 } else {
198                     matching = 0;
199                 }
200             } else {
201                 if (c == '=') {
202                     if (std.mem.indexOfScalarPos(u8, cookie, i, ';')) |semi_index| {
203                         return cookie[i + 1 .. semi_index];
204                     } else {
205                         return cookie[i + 1 .. cookie.len];
206                     }
207                 } else {
208                     matching = 0;
209                 }
210             }
211         }
212         return null;
213     }
214
215     pub fn parse1(self: *Request, buf: []const u8) bool {
216         const method_start: usize = 0;
217         const method_end = std.mem.indexOfScalar(u8, buf, ' ') orelse return false;
218         self.method = @enumFromInt(Method.parse(buf[method_start..method_end]));
219
220         const target_start = method_end + 1;
221         const target_end = std.mem.indexOfScalarPos(u8, buf, target_start, ' ') orelse return false;
222         self.target = buf[target_start..target_end];
223
224         const version_start = target_end + 1;
225         const version_end = std.mem.indexOfPos(u8, buf, version_start, "\r\n") orelse buf.len;
226         self.version = buf[version_start..version_end];
227
228         if (version_end + 2 >= buf.len)
229             return true;
230         const head_start = version_end + 2;
231         const head_end = std.mem.indexOfPos(u8, buf, head_start, "\r\n\r\n") orelse buf.len;
232         self.head = buf[head_start..head_end];
233
234         if (head_end + 4 >= buf.len)
235             return true;
236         const body_start = head_end + 4;
237         const body_end = buf.len;
238         self.body = buf[body_start..body_end];
239
240         return true;
241     }
242
243     pub fn get_header1(self: Request, name: []const u8) ?[]const u8 {
244         const head = self.head orelse return null;
245         const header_start = std.mem.indexOf(u8, head, name) orelse return null;
246         const header_end = std.mem.indexOfPos(u8, head, header_start, "\r\n") orelse return null;
247         return head[header_start..header_end];
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
261     pub fn get_value(self: Request, name: []const u8) ?[]const u8 {
262         const body = self.body orelse return null;
263         const name_index = std.mem.indexOf(u8, body, name) orelse return null;
264         const eql_index = std.mem.indexOfScalarPos(u8, body, name_index, '=') orelse return null;
265         if (std.mem.indexOfScalarPos(u8, body, name_index, '&')) |amp_index| {
266             return body[eql_index + 1 .. amp_index];
267         } else {
268             return body[eql_index + 1 .. body.len];
269         }
270     }
271 };
272
273 pub const Response = struct {
274     const ExtraHeadersMax = 16;
275     const HeaderList = std.BoundedArray(Header, ExtraHeadersMax);
276
277     fd: posix.fd_t,
278     stream_head: std.io.FixedBufferStream([]u8),
279     stream_body: std.io.FixedBufferStream([]u8),
280     status: Status = .ok,
281     extra_headers: HeaderList = HeaderList.init(0) catch unreachable,
282
283     pub fn init(fd: posix.fd_t, buf_head: []u8, buf_body: []u8) Response {
284         return .{
285             .fd = fd,
286             .stream_head = std.io.fixedBufferStream(buf_head),
287             .stream_body = std.io.fixedBufferStream(buf_body),
288         };
289     }
290
291     pub fn redirect(self: *Response, location: []const u8) !void {
292         self.status = .see_other;
293         try self.add_header("Location", .{ "{s}", .{location} });
294     }
295
296     pub fn add_header(self: *Response, name: []const u8, value: anytype) !void {
297         const header = try self.extra_headers.addOne();
298         try header.name.writer().writeAll(name);
299         if (@typeInfo(@TypeOf(value)).Struct.fields.len < 2 or @sizeOf(@TypeOf(value[1])) == 0) {
300             try header.value.writer().writeAll(value[0]);
301         } else {
302             try std.fmt.format(header.value.writer(), value[0], value[1]);
303         }
304     }
305
306     pub fn write(self: *Response, comptime fmt: []const u8, args: anytype) !void {
307         const writer = self.stream_body.writer();
308
309         if (@sizeOf(@TypeOf(args)) == 0) {
310             try writer.writeAll(fmt);
311         } else {
312             try std.fmt.format(writer, fmt, args);
313         }
314     }
315
316     pub fn send(self: *Response) !void {
317         // write head
318         const writer = self.stream_head.writer();
319
320         try std.fmt.format(writer, "HTTP/1.1 {} {?s}\r\n" ++
321             "Content-Length: {}\r\n", .{ @intFromEnum(self.status), self.status.phrase(), self.stream_body.pos });
322
323         for (self.extra_headers.constSlice()) |header| {
324             try std.fmt.format(writer, "{s}: {s}\r\n", .{ header.name.constSlice(), header.value.constSlice() });
325         }
326
327         try std.fmt.format(writer, "\r\n", .{});
328
329         // write body to head
330
331         try std.fmt.format(writer, "{s}", .{self.stream_body.getWritten()});
332
333         // send head
334
335         const res = self.stream_head.getWritten();
336         var written: usize = 0;
337         while (written < res.len) {
338             written += posix.write(self.fd, res[written..res.len]) catch |err| {
339                 std.debug.print("posix.write: {}\n", .{err});
340                 continue;
341             };
342         }
343     }
344 };