diff --git a/src/jetzig/channels/Channel.zig b/src/jetzig/channels/Channel.zig index 58ae978..8be350c 100644 --- a/src/jetzig/channels/Channel.zig +++ b/src/jetzig/channels/Channel.zig @@ -113,11 +113,19 @@ pub fn RoutedChannel(Routes: type) type { break :blk id; }; - const connection_key = try std.fmt.allocPrint(channel.allocator, "{s}:{s}", .{ channel.websocket.session_id, connection_id }); + const connection_key = try std.fmt.allocPrint( + channel.allocator, + "{s}:{s}", + .{ channel.websocket.session_id, connection_id }, + ); const channel_state = try channel.websocket.channels.get(channel.data, connection_key) orelse blk: { const channel_state = try channel.data.object(); // TODO - store this in a way that won't clobber the key with each new state. - try channel_state.put("__connection_url__", try std.fmt.allocPrint(channel.allocator, "http://{s}{s}?key={s}", .{ channel.host, channel.path, connection_key })); + try channel_state.put("__connection_url__", try std.fmt.allocPrint( + channel.allocator, + "http://{s}{s}?key={s}", + .{ channel.host, channel.path, connection_key }, + )); try channel.websocket.channels.put(connection_key, channel_state); break :blk channel_state; }; @@ -126,6 +134,7 @@ pub fn RoutedChannel(Routes: type) type { } pub fn connect(channel: Channel, comptime scope: []const u8, session_id: []const u8) !void { + std.debug.print("here\n", .{}); _ = channel; _ = scope; _ = session_id; diff --git a/src/jetzig/http/Headers.zig b/src/jetzig/http/Headers.zig index 3d719a7..cdd70ab 100644 --- a/src/jetzig/http/Headers.zig +++ b/src/jetzig/http/Headers.zig @@ -7,16 +7,21 @@ const jetzig = @import("../../jetzig.zig"); allocator: std.mem.Allocator, httpz_headers: *httpz.key_value.StringKeyValue, new_headers: std.ArrayList(Header), +options: Options, const Headers = @This(); -const Header = struct { name: []const u8, value: []const u8 }; +pub const Header = struct { name: []const u8, value: []const u8 }; const max_bytes_header_name = jetzig.config.get(u8, "max_bytes_header_name"); -pub fn init(allocator: std.mem.Allocator, httpz_headers: *httpz.key_value.StringKeyValue) Headers { +pub const Options = struct { + read_only: bool = false, +}; +pub fn init(allocator: std.mem.Allocator, httpz_headers: *httpz.key_value.StringKeyValue, options: Options) Headers { return .{ .allocator = allocator, .httpz_headers = httpz_headers, .new_headers = std.ArrayList(Header).init(allocator), + .options = options, }; } @@ -41,6 +46,20 @@ pub fn get(self: Headers, name: []const u8) ?[]const u8 { return self.httpz_headers.get(lower); } +/// Determine if a header exists. Names are case-insensitive. +pub fn has(self: Headers, name: []const u8) bool { + std.debug.assert(name.len <= max_bytes_header_name); + + var buf: [max_bytes_header_name]u8 = undefined; + const lower = std.ascii.lowerString(&buf, name); + + var it = self.httpz_headers.iterator(); + while (it.next()) |header| { + if (std.mem.eql(u8, header.key, lower)) return true; + } + return false; +} + /// Get the first value for a given header identified by `name`, which is assumed to be lower case. pub fn getLower(self: Headers, name: []const u8) ?[]const u8 { std.debug.assert(name.len <= max_bytes_header_name); @@ -70,6 +89,7 @@ pub fn count(self: Headers) usize { /// Add `name` and `value` to headers. pub fn append(self: *Headers, name: []const u8, value: []const u8) !void { + if (self.options.read_only) return error.JetzigReadOnlyHeaders; if (self.httpz_headers.len >= self.httpz_headers.keys.len) return error.JetzigTooManyHeaders; var buf: [max_bytes_header_name]u8 = undefined; diff --git a/src/jetzig/http/Request.zig b/src/jetzig/http/Request.zig index 2c06db1..d83f504 100644 --- a/src/jetzig/http/Request.zig +++ b/src/jetzig/http/Request.zig @@ -162,7 +162,7 @@ pub fn init( const response_data = try allocator.create(jetzig.data.Data); response_data.* = jetzig.data.Data.init(allocator); - const headers = jetzig.http.Headers.init(allocator, httpz_request.headers); + const headers = jetzig.http.Headers.init(allocator, httpz_request.headers, .{ .read_only = true }); const host = headers.getLower("host") orelse ""; return .{ diff --git a/src/jetzig/http/Response.zig b/src/jetzig/http/Response.zig index db1e843..f08a07b 100644 --- a/src/jetzig/http/Response.zig +++ b/src/jetzig/http/Response.zig @@ -23,7 +23,7 @@ pub fn init( .httpz_response = httpz_response, .status_code = .no_content, .content = "", - .headers = jetzig.http.Headers.init(allocator, &httpz_response.headers), + .headers = jetzig.http.Headers.init(allocator, &httpz_response.headers, .{}), }; } diff --git a/src/jetzig/http/Server.zig b/src/jetzig/http/Server.zig index 20bab44..aa733a3 100644 --- a/src/jetzig/http/Server.zig +++ b/src/jetzig/http/Server.zig @@ -184,7 +184,9 @@ pub fn RoutedServer(Routes: type) type { } try self.renderResponse(&request); - try request.response.headers.append("Content-Type", response.contentType()); + if (!request.response.headers.has("content-type")) { + try request.response.headers.append("Content-Type", response.contentType()); + } try jetzig.http.middleware.beforeResponse(&middleware_data, &request); try request.respond(); @@ -241,8 +243,7 @@ pub fn RoutedServer(Routes: type) type { if (request.redirect_state) |state| { try request.renderRedirect(state); } else if (request.rendered_view) |rendered| { - // TODO: Allow middleware to set content - request.setResponse(.{ .view = rendered, .content = "" }, .{}); + request.setResponse(.{ .view = rendered, .content = rendered.content orelse "" }, .{}); } try request.response.headers.append("Content-Type", response.contentType()); try request.respond(); @@ -272,7 +273,13 @@ pub fn RoutedServer(Routes: type) type { }; request.setResponse(rendered, .{ .content_type = route.content_type }); return; - } else unreachable; // In future a MiddlewareRoute might provide a render function etc. + } else if (request.rendered_view) |rendered_view| { + request.setResponse( + .{ .view = rendered_view, .content = rendered_view.content orelse "" }, + .{ .content_type = route.content_type }, + ); + return; + } } const maybe_route = self.matchCustomRoute(request) orelse try self.matchRoute(request, false); @@ -281,9 +288,7 @@ pub fn RoutedServer(Routes: type) type { if (!route.validateFormat(request)) { return request.setResponse(try self.renderNotFound(request), .{}); } - } - if (maybe_route) |route| { for (route.before_callbacks) |callback| { try callback(request, route); if (request.rendered_view) |view| { @@ -352,10 +357,9 @@ pub fn RoutedServer(Routes: type) type { return request.setResponse(rendered_error, .{}); }; - return if (request.isRendered() or request.dynamic_assigned_template != null) - request.setResponse(rendered, .{}) - else - request.setResponse(try self.renderNotFound(request), .{}); + return if (request.isRendered() or request.dynamic_assigned_template != null) blk: { + break :blk request.setResponse(rendered, .{}); + } else request.setResponse(try self.renderNotFound(request), .{}); } } else { // If no matching route found, try to render a Markdown file in views directory. @@ -425,7 +429,7 @@ pub fn RoutedServer(Routes: type) type { }, .rendered_text => { const view = request.rendered_view.?; // a panic here is a bug. - return .{ .view = view, .content = request.response.content }; + return .{ .view = view, .content = view.content orelse request.response.content }; }, else => {}, } diff --git a/src/jetzig/middleware/ChannelsMiddleware.zig b/src/jetzig/middleware/ChannelsMiddleware.zig index bef43e4..eb4b184 100644 --- a/src/jetzig/middleware/ChannelsMiddleware.zig +++ b/src/jetzig/middleware/ChannelsMiddleware.zig @@ -32,5 +32,6 @@ pub const Blocks = struct { }; pub fn renderChannels(request: *jetzig.Request) !jetzig.View { + try request.response.headers.append("Content-Type", "text/javascript"); return request.renderContent(.ok, @embedFile("channels/channels.js")); } diff --git a/src/jetzig/middleware/channels/channels.js b/src/jetzig/middleware/channels/channels.js index 7a16b2b..6b95344 100644 --- a/src/jetzig/middleware/channels/channels.js +++ b/src/jetzig/middleware/channels/channels.js @@ -180,7 +180,7 @@ const Jetzig = window.Jetzig; } }); channel.websocket.addEventListener("open", (event) => { - // TODO + // TODO - init callback channel.publish("websockets", {}); }); }; diff --git a/src/jetzig/websockets/Websocket.zig b/src/jetzig/websockets/Websocket.zig index 39b8f3a..75e52da 100644 --- a/src/jetzig/websockets/Websocket.zig +++ b/src/jetzig/websockets/Websocket.zig @@ -56,6 +56,7 @@ pub fn RoutedWebsocket(Routes: type) type { } pub fn afterInit(websocket: *Websocket, context: Context) !void { + std.debug.print("afterInit\n", .{}); _ = context; if (router.encoded_params.get(websocket.route.path)) |params| { var stack_fallback = std.heap.stackFallback(4096, websocket.allocator);