This commit is contained in:
Bob Farrell 2025-06-09 08:44:57 +01:00
parent 69fe4e0bb1
commit fd22be783b
8 changed files with 53 additions and 18 deletions

View File

@ -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;

View File

@ -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;

View File

@ -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 .{

View File

@ -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, .{}),
};
}

View File

@ -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 => {},
}

View File

@ -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"));
}

View File

@ -180,7 +180,7 @@ const Jetzig = window.Jetzig;
}
});
channel.websocket.addEventListener("open", (event) => {
// TODO
// TODO - init callback
channel.publish("websockets", {});
});
};

View File

@ -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);