Skip to content

Commit cd4c379

Browse files
committed
feat: move middlewares over to use frames
1 parent de97d1b commit cd4c379

File tree

13 files changed

+248
-362
lines changed

13 files changed

+248
-362
lines changed

examples/basic/main.zig

Lines changed: 15 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,14 @@ const Next = http.Next;
1919
const Response = http.Response;
2020
const Respond = http.Respond;
2121

22-
const CompressMiddleware = http.CompressMiddleware;
23-
const RateLimitMiddleware = http.RateLimitMiddleware;
24-
const ThreadSafeRateLimit = http.ThreadSafeRateLimit;
22+
const Compression = http.Middlewares.Compression;
23+
const RateLimitConfig = http.Middlewares.RateLimitConfig;
24+
const RateLimiting = http.Middlewares.RateLimiting;
2525

2626
fn base_handler(_: Context, _: void) !Respond {
2727
return .{
2828
.status = .OK,
29-
.mime = http.Mime.TEXT,
29+
.mime = http.Mime.HTML,
3030
.body = "Hello, world!",
3131
};
3232
}
@@ -35,25 +35,19 @@ pub fn main() !void {
3535
const host: []const u8 = "0.0.0.0";
3636
const port: u16 = 9862;
3737

38-
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
38+
var gpa = std.heap.GeneralPurposeAllocator(.{ .thread_safe = true }){};
3939
const allocator = gpa.allocator();
4040
defer _ = gpa.deinit();
4141

42-
// Creating our Tardy instance that
43-
// will spawn our runtimes.
44-
var t = try Tardy.init(allocator, .{
45-
.threading = .auto,
46-
.size_tasks_initial = 128,
47-
.size_aio_reap_max = 128,
48-
});
42+
var t = try Tardy.init(allocator, .{ .threading = .single });
4943
defer t.deinit();
5044

51-
var limiter = ThreadSafeRateLimit.init(allocator);
52-
defer limiter.deinit();
45+
var config = RateLimitConfig.init(allocator, 5, 30, null);
46+
defer config.deinit();
5347

5448
var router = try Router.init(allocator, &.{
55-
//RateLimitMiddleware(std.time.ms_per_s, &limiter),
56-
//CompressMiddleware(.{ .gzip = .{} }),
49+
RateLimiting(&config),
50+
Compression(.{ .gzip = .{} }),
5751
Route.init("/").get({}, base_handler).layer(),
5852
}, .{});
5953
defer router.deinit(allocator);
@@ -66,20 +60,19 @@ pub fn main() !void {
6660

6761
const EntryParams = struct {
6862
router: *const Router,
69-
socket: *Socket,
63+
socket: Socket,
7064
};
7165

72-
const params: EntryParams = .{ .router = &router, .socket = &socket };
73-
74-
// This provides the entry function into the Tardy runtime. This will run
75-
// exactly once inside of each runtime (each thread gets a single runtime).
66+
const params: EntryParams = .{ .router = &router, .socket = socket };
7667
try t.entry(
7768
&params,
7869
struct {
7970
fn entry(rt: *Runtime, p: *const EntryParams) !void {
8071
var server = Server.init(rt.allocator, .{
8172
.stack_size = 1024 * 1024 * 4,
82-
.socket_buffer_bytes = 1024 * 4,
73+
.socket_buffer_bytes = 1024 * 2,
74+
.keepalive_count_max = null,
75+
.connection_count_max = null,
8376
});
8477
try server.serve(rt, p.router, p.socket);
8578
}

examples/benchmark/index.html

Lines changed: 0 additions & 11 deletions
This file was deleted.

examples/benchmark/main.zig

Lines changed: 0 additions & 86 deletions
This file was deleted.

src/http/context.zig

Lines changed: 3 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,8 @@
11
const std = @import("std");
2-
const assert = std.debug.assert;
3-
const log = std.log.scoped(.@"zzz/http/context");
4-
5-
const Pseudoslice = @import("../core/pseudoslice.zig").Pseudoslice;
6-
7-
const Capture = @import("router/routing_trie.zig").Capture;
8-
const QueryMap = @import("router/routing_trie.zig").QueryMap;
9-
const Route = @import("router/route.zig").Route;
10-
const Provision = @import("provision.zig").Provision;
11-
122
const Request = @import("request.zig").Request;
133
const Response = @import("response.zig").Response;
14-
const ResponseSetOptions = Response.ResponseSetOptions;
15-
const Mime = @import("mime.zig").Mime;
16-
const SSE = @import("sse.zig").SSE;
17-
const MiddlewareWithData = @import("router/middleware.zig").MiddlewareWithData;
18-
const Next = @import("router/middleware.zig").Next;
19-
204
const Runtime = @import("tardy").Runtime;
21-
const Server = @import("server.zig").Server;
5+
const Socket = @import("tardy").Socket;
226

237
// Context is dependent on the server that gets created.
248
pub const Context = struct {
@@ -27,6 +11,6 @@ pub const Context = struct {
2711
runtime: *Runtime,
2812
/// The Request that triggered this handler.
2913
request: *const Request,
30-
/// Address for this Request/Response.
31-
address: std.net.Address,
14+
/// Socket for this Connection.
15+
socket: Socket,
3216
};

src/http/lib.zig

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,7 @@ pub const Layer = @import("router/middleware.zig").Layer;
1818
pub const Middleware = @import("router/middleware.zig").Middleware;
1919
pub const MiddlewareFn = @import("router/middleware.zig").MiddlewareFn;
2020
pub const Next = @import("router/middleware.zig").Next;
21-
22-
pub const ThreadSafeRateLimit = @import("middlewares/lib.zig").ThreadSafeRateLimit;
23-
pub const RateLimitMiddleware = @import("middlewares/lib.zig").RateLimitMiddleware;
24-
pub const CompressMiddleware = @import("middlewares/lib.zig").CompressMiddleware;
21+
pub const Middlewares = @import("middlewares/lib.zig");
2522

2623
pub const FsDir = @import("router/fs_dir.zig").FsDir;
2724

src/http/middlewares/compression.zig

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
const std = @import("std");
2+
3+
const Respond = @import("../response.zig").Respond;
4+
const Middleware = @import("../router/middleware.zig").Middleware;
5+
const Next = @import("../router/middleware.zig").Next;
6+
const Layer = @import("../router/middleware.zig").Layer;
7+
const TypedMiddlewareFn = @import("../router/middleware.zig").TypedMiddlewareFn;
8+
9+
const Kind = union(enum) {
10+
gzip: std.compress.gzip.Options,
11+
};
12+
13+
/// Compression Middleware.
14+
///
15+
/// Provides a Compression Layer for all routes under this that
16+
/// will properly compress the body and add the proper `Content-Encoding` header.
17+
pub fn Compression(comptime compression: Kind) Layer {
18+
const func: TypedMiddlewareFn(void) = switch (compression) {
19+
.gzip => |inner| struct {
20+
fn gzip_mw(next: *Next, _: void) !Respond {
21+
var respond = try next.run();
22+
23+
var compressed = std.ArrayList(u8).init(next.context.allocator);
24+
25+
var body_stream = std.io.fixedBufferStream(respond.body);
26+
try std.compress.gzip.compress(body_stream.reader(), compressed.writer(), inner);
27+
28+
// TODO: consider having the headers be a part of the provision?
29+
// might be nice to reuse them as things go on??
30+
var header_list = std.ArrayList([2][]const u8).init(next.context.allocator);
31+
try header_list.appendSlice(respond.headers);
32+
try header_list.append(.{ "Content-Encoding", "gzip" });
33+
34+
respond.body = try compressed.toOwnedSlice();
35+
respond.headers = try header_list.toOwnedSlice();
36+
return respond;
37+
}
38+
}.gzip_mw,
39+
};
40+
41+
return Middleware.init({}, func).layer();
42+
}

src/http/middlewares/lib.zig

Lines changed: 3 additions & 113 deletions
Original file line numberDiff line numberDiff line change
@@ -1,113 +1,3 @@
1-
const std = @import("std");
2-
3-
const Mime = @import("../mime.zig").Mime;
4-
const Respond = @import("../response.zig").Respond;
5-
6-
const Middleware = @import("../router/middleware.zig").Middleware;
7-
const Next = @import("../router/middleware.zig").Next;
8-
const Layer = @import("../router/middleware.zig").Layer;
9-
const TypedMiddlewareFn = @import("../router/middleware.zig").TypedMiddlewareFn;
10-
11-
pub const ThreadSafeRateLimit = struct {
12-
map: std.AutoHashMap(u128, i64),
13-
mutex: std.Thread.Mutex = .{},
14-
15-
pub fn init(allocator: std.mem.Allocator) ThreadSafeRateLimit {
16-
return .{
17-
.map = std.AutoHashMap(u128, i64).init(allocator),
18-
.mutex = .{},
19-
};
20-
}
21-
22-
pub fn deinit(self: *ThreadSafeRateLimit) void {
23-
self.map.deinit();
24-
}
25-
};
26-
27-
fn get_ip(addr: std.net.Address) u128 {
28-
return switch (addr.any.family) {
29-
std.posix.AF.INET => @intCast(addr.in.sa.addr),
30-
std.posix.AF.INET6 => std.mem.bytesAsValue(u128, &addr.in6.sa.addr[0]).*,
31-
else => unreachable,
32-
};
33-
}
34-
35-
// TODO: Do a proper rate-limiter. This is super crude.
36-
pub fn RateLimitMiddleware(comptime ms_per_request: usize, limiter: *ThreadSafeRateLimit) Layer {
37-
const func: TypedMiddlewareFn(*ThreadSafeRateLimit) = struct {
38-
fn rate_limit_mw(next: *Next, limit: *ThreadSafeRateLimit) !Respond {
39-
const ip = get_ip(next.context.address);
40-
const time = std.time.milliTimestamp();
41-
42-
limit.mutex.lock();
43-
const addr_entry = try limit.map.getOrPut(ip);
44-
const last_time = addr_entry.value_ptr.*;
45-
addr_entry.value_ptr.* = time;
46-
limit.mutex.unlock();
47-
48-
if (addr_entry.found_existing) {
49-
if (time - last_time >= ms_per_request) return try next.run();
50-
return Respond{
51-
.status = .@"Too Many Requests",
52-
.mime = Mime.TEXT,
53-
.body = "",
54-
};
55-
} else return try next.run();
56-
}
57-
}.rate_limit_mw;
58-
59-
return Middleware.init(limiter, func).layer();
60-
}
61-
62-
fn rate_limit_mw(next: *Next, limiter: *ThreadSafeRateLimit) !Respond {
63-
const ip = get_ip(next.context.address);
64-
const time = std.time.milliTimestamp();
65-
66-
limiter.mutex.lock();
67-
const addr_entry = try limiter.map.getOrPut(ip);
68-
const last_time = addr_entry.value_ptr.*;
69-
addr_entry.value_ptr.* = time;
70-
limiter.mutex.unlock();
71-
72-
if (addr_entry.found_existing) {
73-
if (time - last_time >= std.time.ms_per_s) return try next.run();
74-
return Respond{
75-
.status = .@"Too Many Requests",
76-
.mime = Mime.TEXT,
77-
.body = "",
78-
};
79-
} else return try next.run();
80-
}
81-
82-
// TODO: Consider using a C dependency here.
83-
// Might be nice to get some fast general compression such as zstd :)
84-
const Compression = union(enum) {
85-
gzip: std.compress.gzip.Options,
86-
};
87-
88-
pub fn CompressMiddleware(comptime compression: Compression) Layer {
89-
const func: TypedMiddlewareFn(void) = switch (compression) {
90-
.gzip => |inner| struct {
91-
fn gzip_mw(next: *Next, _: void) !Respond {
92-
var respond = try next.run();
93-
94-
var compressed = std.ArrayList(u8).init(next.context.allocator);
95-
96-
var body_stream = std.io.fixedBufferStream(respond.body);
97-
try std.compress.gzip.compress(body_stream.reader(), compressed.writer(), inner);
98-
99-
// TODO: consider having the headers be a part of the provision?
100-
// might be nice to reuse them as things go on??
101-
var header_list = std.ArrayList([2][]const u8).init(next.context.allocator);
102-
try header_list.appendSlice(respond.headers);
103-
try header_list.append(.{ "Content-Encoding", "gzip" });
104-
105-
respond.body = try compressed.toOwnedSlice();
106-
respond.headers = try header_list.toOwnedSlice();
107-
return respond;
108-
}
109-
}.gzip_mw,
110-
};
111-
112-
return Middleware.init({}, func).layer();
113-
}
1+
pub const Compression = @import("compression.zig").Compression;
2+
pub const RateLimitConfig = @import("rate_limit.zig").RateLimitConfig;
3+
pub const RateLimiting = @import("rate_limit.zig").RateLimiting;

0 commit comments

Comments
 (0)