forked from
karlseguin.tngl.sh/http.zig
An HTTP/1.1 server for zig
1const std = @import("std");
2
3const mem = std.mem;
4const ascii = std.ascii;
5const Allocator = std.mem.Allocator;
6
7fn KeyValue(V: type, hashFn: fn (key: []const u8) callconv(.@"inline") u8) type {
8 return struct {
9 len: usize,
10 keys: [][]const u8,
11 values: []V,
12 hashes: []u8,
13
14 const Self = @This();
15 pub const Value = V;
16
17 const alignment = @max(@alignOf([]const u8), @alignOf(V));
18 const size = @sizeOf([]const u8) + @sizeOf(V) + @sizeOf(u8);
19 const kFirst = @alignOf([]const u8) >= @alignOf(V);
20
21 pub fn init(allocator: Allocator, max: usize) Allocator.Error!Self {
22 // we want type with bigger alignment to be first.
23 // Since alignment is always a power of 2, the second type is guaranteed to have correct alignment.
24
25 const allocation = try allocator.alignedAlloc(u8, std.mem.Alignment.fromByteUnits(alignment), max * size);
26 return .{
27 .len = 0,
28 .keys = @as([*][]const u8, @ptrCast(@alignCast(if (kFirst) allocation.ptr else allocation[max * @sizeOf(V) ..].ptr)))[0..max],
29 .values = @as([*]V, @ptrCast(@alignCast(if (kFirst) allocation[max * @sizeOf([]const u8) ..].ptr else allocation.ptr)))[0..max],
30 .hashes = allocation[max * @sizeOf([]const u8) + max * @sizeOf(V) ..],
31 };
32 }
33
34 pub fn deinit(self: *Self, allocator: Allocator) void {
35 const allocation = @as([*]align(alignment) u8, @ptrCast(@alignCast(if (kFirst) self.keys.ptr else self.values.ptr)));
36 allocator.free(allocation[0 .. self.keys.len * size]);
37 }
38
39 pub fn add(self: *Self, key: []const u8, value: V) void {
40 const len = self.len;
41 const max = self.keys.len;
42 var keys = self.keys;
43 if (len == max) {
44 return;
45 }
46
47 keys[len] = key;
48 self.values[len] = value;
49 self.hashes[len] = hashFn(key);
50 self.len = len + 1;
51 }
52
53 pub fn get(self: *const Self, key: []const u8) ?V {
54 const hash = hashFn(key);
55 for (self.hashes[0..self.len], 0..) |h, i| {
56 if (h == hash and std.mem.eql(u8, self.keys[i], key)) {
57 return self.values[i];
58 }
59 }
60 return null;
61 }
62
63 pub fn has(self: *const Self, key: []const u8) bool {
64 return self.get(key) != null;
65 }
66
67 pub fn reset(self: *Self) void {
68 self.len = 0;
69 }
70
71 pub fn iterator(self: *const Self) Iterator {
72 const len = self.len;
73 return .{
74 .pos = 0,
75 .keys = self.keys[0..len],
76 .values = self.values[0..len],
77 };
78 }
79
80 pub const Iterator = struct {
81 pos: usize,
82 keys: [][]const u8,
83 values: []V,
84
85 const KV = struct {
86 key: []const u8,
87 value: V,
88 };
89
90 pub fn next(self: *Iterator) ?KV {
91 const pos = self.pos;
92 if (pos == self.keys.len) {
93 return null;
94 }
95
96 self.pos = pos + 1;
97 return .{
98 .key = self.keys[pos],
99 .value = self.values[pos],
100 };
101 }
102 };
103 };
104}
105
106inline fn strHash(key: []const u8) u8 {
107 if (key.len == 0) {
108 return 0;
109 }
110
111 var h: u8 = 5;
112 for (key) |c| {
113 h = h *% 31 +% c;
114 }
115 return h;
116}
117
118pub const StringKeyValue = KeyValue([]const u8, strHash);
119
120const MultiForm = struct {
121 value: []const u8,
122 filename: ?[]const u8 = null,
123};
124pub const MultiFormKeyValue = KeyValue(MultiForm, strHash);
125
126const t = @import("t.zig");
127test "KeyValue: get" {
128 var kv = try StringKeyValue.init(t.allocator, 2);
129 defer kv.deinit(t.allocator);
130
131 var key = "content-type".*;
132 kv.add(&key, "application/json");
133
134 try t.expectEqual("application/json", kv.get("content-type").?);
135
136 kv.reset();
137 try t.expectEqual(null, kv.get("content-type"));
138 kv.add(&key, "application/json2");
139 try t.expectEqual("application/json2", kv.get("content-type").?);
140}
141
142test "KeyValue: ignores beyond max" {
143 var kv = try StringKeyValue.init(t.allocator, 2);
144 defer kv.deinit(t.allocator);
145 var n1 = "content-length".*;
146 kv.add(&n1, "cl");
147
148 var n2 = "host".*;
149 kv.add(&n2, "www");
150
151 var n3 = "authorization".*;
152 kv.add(&n3, "hack");
153
154 try t.expectEqual("cl", kv.get("content-length").?);
155 try t.expectEqual("www", kv.get("host").?);
156 try t.expectEqual(null, kv.get("authorization"));
157
158 {
159 var it = kv.iterator();
160 {
161 const field = it.next().?;
162 try t.expectString("content-length", field.key);
163 try t.expectString("cl", field.value);
164 }
165
166 {
167 const field = it.next().?;
168 try t.expectString("host", field.key);
169 try t.expectString("www", field.value);
170 }
171 try t.expectEqual(null, it.next());
172 }
173}
174
175test "MultiFormKeyValue: get" {
176 var kv = try MultiFormKeyValue.init(t.allocator, 2);
177 defer kv.deinit(t.allocator);
178
179 var key = "username".*;
180 kv.add(&key, .{ .value = "leto" });
181
182 try t.expectEqual("leto", kv.get("username").?.value);
183
184 kv.reset();
185 try t.expectEqual(null, kv.get("username"));
186 kv.add(&key, .{ .value = "leto2" });
187 try t.expectEqual("leto2", kv.get("username").?.value);
188}
189
190test "MultiFormKeyValue: ignores beyond max" {
191 var kv = try MultiFormKeyValue.init(t.allocator, 2);
192 defer kv.deinit(t.allocator);
193
194 var n1 = "username".*;
195 kv.add(&n1, .{ .value = "leto" });
196
197 var n2 = "password".*;
198 kv.add(&n2, .{ .value = "ghanima" });
199
200 var n3 = "other".*;
201 kv.add(&n3, .{ .value = "hack" });
202
203 try t.expectEqual("leto", kv.get("username").?.value);
204 try t.expectEqual("ghanima", kv.get("password").?.value);
205 try t.expectEqual(null, kv.get("other"));
206
207 {
208 var it = kv.iterator();
209 {
210 const field = it.next().?;
211 try t.expectString("username", field.key);
212 try t.expectString("leto", field.value.value);
213 }
214
215 {
216 const field = it.next().?;
217 try t.expectString("password", field.key);
218 try t.expectString("ghanima", field.value.value);
219 }
220 try t.expectEqual(null, it.next());
221 }
222}