aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNathan Reiner <nathan@nathanreiner.xyz>2025-11-17 13:09:02 +0100
committerNathan Reiner <nathan@nathanreiner.xyz>2025-11-17 13:09:02 +0100
commit6201307fecf8398a1b53bf276bc08bfbb3524899 (patch)
tree2e623f4779b310a81b49dbb146146f8a694d9ec8
parent9c979a6fefdfc6709b3576014520d219e02c3649 (diff)
implement memora.Stream
-rw-r--r--src/root.zig1
-rw-r--r--src/routes/api/image/load.zig7
-rw-r--r--src/routes/handler-info.zig29
-rw-r--r--src/routes/static.zig10
-rw-r--r--src/stream/buffer.zig28
-rw-r--r--src/stream/file.zig22
-rw-r--r--src/stream/root.zig33
7 files changed, 109 insertions, 21 deletions
diff --git a/src/root.zig b/src/root.zig
index 81dcdb2..31cd751 100644
--- a/src/root.zig
+++ b/src/root.zig
@@ -7,3 +7,4 @@ pub const routes = @import("routes/root.zig");
pub const Server = @import("server.zig");
pub const Storage = @import("storage/root.zig");
pub const Context = @import("context.zig");
+pub const Stream = @import("stream/root.zig").Stream;
diff --git a/src/routes/api/image/load.zig b/src/routes/api/image/load.zig
index 51c0e26..4f0a072 100644
--- a/src/routes/api/image/load.zig
+++ b/src/routes/api/image/load.zig
@@ -6,11 +6,8 @@ const Storage = memora.Storage;
pub const access = .users;
-pub fn get(ctx: *Context) ![]const u8 {
+pub fn get(ctx: *Context) !memora.Stream {
const id = ctx.request.head.target["/api/image/load/".len..];
var image = Storage.Image { .id = id };
- var file = try image.file(ctx.storage);
- defer file.close();
-
- return try file.readToEndAlloc(ctx.allocator, std.math.maxInt(usize));
+ return .from_file(try image.file(ctx.storage));
}
diff --git a/src/routes/handler-info.zig b/src/routes/handler-info.zig
index e8c9dfb..97eb9bd 100644
--- a/src/routes/handler-info.zig
+++ b/src/routes/handler-info.zig
@@ -12,7 +12,7 @@ const log = std.log.scoped(.handler_info);
const Self = @This();
-const Handler = *const fn (*Context) anyerror![]const u8;
+const Handler = *const fn (*Context) anyerror!memora.Stream;
get: ?Handler,
head: ?Handler,
@@ -95,7 +95,7 @@ pub fn handle(
}
}
- const response = handler(&context) catch |err| {
+ var stream = handler(&context) catch |err| {
const response, const status_code: std.http.Status = switch (err) {
error.BadRequest => .{ "{ \"error\": \"Bad Request\" }", .bad_request },
error.Unauthorized => .{ "{ \"error\": \"Unauthorized\" }", .unauthorized },
@@ -109,6 +109,7 @@ pub fn handle(
return request.respond(response, .{ .status = status_code });
};
+ defer stream.close();
var headers: std.ArrayList(std.http.Header) = .empty;
defer headers.deinit(allocator);
@@ -129,9 +130,19 @@ pub fn handle(
});
}
- try request.respond(response, .{
- .extra_headers = headers.items
+ var read_buffer: [1024]u8 = undefined;
+ var reader = stream.reader(&read_buffer);
+
+ var write_buffer: [1024]u8 = undefined;
+ var body_writer = try request.respondStreaming(&write_buffer, .{
+ .respond_options = .{
+ .extra_headers = headers.items,
+ .transfer_encoding = .chunked,
+ },
});
+
+ _ = try reader.streamRemaining(&body_writer.writer);
+ try body_writer.end();
}
fn HandlerWrapper(T: type, name: []const u8) type {
@@ -140,7 +151,7 @@ fn HandlerWrapper(T: type, name: []const u8) type {
const payload_type = @typeInfo(return_type).error_union.payload;
return struct {
- pub fn call(ctx: *Context) anyerror![]const u8 {
+ pub fn call(ctx: *Context) anyerror!memora.Stream {
const args = args: {
const tuple = std.meta.fields(std.meta.ArgsTuple(@TypeOf(@field(T, name))));
@@ -161,7 +172,7 @@ fn HandlerWrapper(T: type, name: []const u8) type {
Body,
ctx.allocator,
writer.written(),
- .{}
+ .{},
) catch return error.BadRequest;
break :args .{ ctx, body };
} else {
@@ -169,16 +180,16 @@ fn HandlerWrapper(T: type, name: []const u8) type {
}
};
- if (payload_type == []const u8) {
+ if (payload_type == memora.Stream) {
return @call(.auto, @field(T, name), args);
} else if (payload_type == void) {
try @call(.auto, @field(T, name), args);
- return "";
+ return memora.Stream.from_buffer("");
} else {
var writer = std.Io.Writer.Allocating.init(ctx.allocator);
var stringify = std.json.Stringify { .writer = &writer.writer };
try stringify.write(try @call(.auto, @field(T, name), args));
- return writer.written();
+ return memora.Stream.from_buffer(writer.written());
}
}
};
diff --git a/src/routes/static.zig b/src/routes/static.zig
index ef7d493..f52d178 100644
--- a/src/routes/static.zig
+++ b/src/routes/static.zig
@@ -8,16 +8,14 @@ const log = std.log.scoped(.fallback);
pub const access = .everyone;
-pub fn get(ctx: *Context) anyerror![]const u8 {
+pub fn get(ctx: *Context) anyerror!memora.Stream {
var static = try std.fs.cwd().openDir("static", .{});
defer static.close();
if (static.openFile(ctx.request.head.target[1..], .{})) |file| {
- defer file.close();
- const content = file.readToEndAlloc(ctx.allocator, std.math.maxInt(usize));
const mime_type = mime.get_type(ctx.request.head.target);
ctx.response.headers.content_type = mime_type;
- return content;
+ return .from_file(file);
} else |_| {
var subdir = if (ctx.request.head.target.len == 1) static
else (static.openDir(ctx.request.head.target[1..], .{}) catch {
@@ -26,10 +24,8 @@ pub fn get(ctx: *Context) anyerror![]const u8 {
defer if (ctx.request.head.target.len > 1) subdir.close();
if (subdir.openFile("index.html", .{})) |file| {
- defer file.close();
- const content = file.readToEndAlloc(ctx.allocator, std.math.maxInt(usize));
ctx.response.headers.content_type = "text/html";
- return content;
+ return .from_file(file);
} else |_| {
log.warn("File '{s}' Not Found", .{ ctx.request.head.target });
return error.NotFound;
diff --git a/src/stream/buffer.zig b/src/stream/buffer.zig
new file mode 100644
index 0000000..fca1deb
--- /dev/null
+++ b/src/stream/buffer.zig
@@ -0,0 +1,28 @@
+const std = @import("std");
+
+const Self = @This();
+
+buffer: []const u8,
+buffer_reader: ?std.Io.Reader,
+
+pub fn init(buffer: []const u8) Self {
+ return .{
+ .buffer = buffer,
+ .buffer_reader = null,
+ };
+}
+
+pub fn reader(self: *Self, buffer: []u8) *std.Io.Reader {
+ _ = buffer;
+
+ if (self.buffer_reader) |*r| {
+ return r;
+ }
+
+ self.buffer_reader = std.Io.Reader.fixed(self.buffer);
+ return &self.buffer_reader.?;
+}
+
+pub fn close(self: *Self) void {
+ _ = self;
+}
diff --git a/src/stream/file.zig b/src/stream/file.zig
new file mode 100644
index 0000000..bb6407b
--- /dev/null
+++ b/src/stream/file.zig
@@ -0,0 +1,22 @@
+const std = @import("std");
+
+const Self = @This();
+
+file: std.fs.File,
+file_reader: ?std.fs.File.Reader,
+
+pub fn init(file: std.fs.File) Self {
+ return .{
+ .file = file,
+ .file_reader = null,
+ };
+}
+
+pub fn reader(self: *Self, buffer: []u8) *std.Io.Reader {
+ self.file_reader = self.file.reader(buffer);
+ return &self.file_reader.?.interface;
+}
+
+pub fn close(self: *Self) void {
+ self.file.close();
+}
diff --git a/src/stream/root.zig b/src/stream/root.zig
new file mode 100644
index 0000000..11580ac
--- /dev/null
+++ b/src/stream/root.zig
@@ -0,0 +1,33 @@
+const std = @import("std");
+
+pub const Stream = union(enum) {
+ const Self = @This();
+
+ pub const File = @import("file.zig");
+ pub const Buffer = @import("buffer.zig");
+
+ file: File,
+ buffer: Buffer,
+
+ pub fn from_file(file: std.fs.File) Self {
+ return .{ .file = File.init(file) };
+ }
+
+ pub fn from_buffer(buffer: []const u8) Self {
+ return .{ .buffer = Buffer.init(buffer) };
+ }
+
+ pub fn reader(self: *Self, buffer: []u8) *std.Io.Reader {
+ return switch(self.*) {
+ .file => |*f| f.reader(buffer),
+ .buffer => |*b| b.reader(buffer),
+ };
+ }
+
+ pub fn close(self: *Self) void {
+ return switch(self.*) {
+ .file => |*f| f.close(),
+ .buffer => |*b| b.close(),
+ };
+ }
+};