This commit is contained in:
Bob Farrell 2025-04-13 18:13:41 +01:00
parent 86d82026ab
commit 2072b59937
7 changed files with 157 additions and 5 deletions

View File

@ -0,0 +1,85 @@
const std = @import("std");
const jetzig = @import("jetzig");
pub fn index(request: *jetzig.Request, data: *jetzig.Data) !jetzig.View {
_ = data;
return request.render(.ok);
}
pub fn get(id: []const u8, request: *jetzig.Request, data: *jetzig.Data) !jetzig.View {
_ = data;
_ = id;
return request.render(.ok);
}
pub fn post(request: *jetzig.Request, data: *jetzig.Data) !jetzig.View {
_ = data;
return request.render(.created);
}
pub fn put(id: []const u8, request: *jetzig.Request, data: *jetzig.Data) !jetzig.View {
_ = data;
_ = id;
return request.render(.ok);
}
pub fn patch(id: []const u8, request: *jetzig.Request, data: *jetzig.Data) !jetzig.View {
_ = data;
_ = id;
return request.render(.ok);
}
pub fn delete(id: []const u8, request: *jetzig.Request, data: *jetzig.Data) !jetzig.View {
_ = data;
_ = id;
return request.render(.ok);
}
test "index" {
var app = try jetzig.testing.app(std.testing.allocator, @import("routes"));
defer app.deinit();
const response = try app.request(.GET, "/websockets", .{});
try response.expectStatus(.ok);
}
test "get" {
var app = try jetzig.testing.app(std.testing.allocator, @import("routes"));
defer app.deinit();
const response = try app.request(.GET, "/websockets/example-id", .{});
try response.expectStatus(.ok);
}
test "post" {
var app = try jetzig.testing.app(std.testing.allocator, @import("routes"));
defer app.deinit();
const response = try app.request(.POST, "/websockets", .{});
try response.expectStatus(.created);
}
test "put" {
var app = try jetzig.testing.app(std.testing.allocator, @import("routes"));
defer app.deinit();
const response = try app.request(.PUT, "/websockets/example-id", .{});
try response.expectStatus(.ok);
}
test "patch" {
var app = try jetzig.testing.app(std.testing.allocator, @import("routes"));
defer app.deinit();
const response = try app.request(.PATCH, "/websockets/example-id", .{});
try response.expectStatus(.ok);
}
test "delete" {
var app = try jetzig.testing.app(std.testing.allocator, @import("routes"));
defer app.deinit();
const response = try app.request(.DELETE, "/websockets/example-id", .{});
try response.expectStatus(.ok);
}

View File

@ -0,0 +1,17 @@
@if (context.request) |request|
@if (request.headers.get("host")) |host|
<script>
const websocket = new WebSocket('ws://{{host}}');
console.log(websocket);
websocket.addEventListener("message", (event) => {
console.log(event.data);
});
websocket.addEventListener("open", (event) => {
websocket.send("hello jetzig websocket");
});
</script>
@end
@end

View File

@ -13,6 +13,7 @@ pub const Response = @import("http/Response.zig");
pub const Session = @import("http/Session.zig"); pub const Session = @import("http/Session.zig");
pub const Cookies = @import("http/Cookies.zig"); pub const Cookies = @import("http/Cookies.zig");
pub const Headers = @import("http/Headers.zig"); pub const Headers = @import("http/Headers.zig");
pub const Websocket = @import("http/Websocket.zig");
pub const Query = @import("http/Query.zig"); pub const Query = @import("http/Query.zig");
pub const MultipartQuery = @import("http/MultipartQuery.zig"); pub const MultipartQuery = @import("http/MultipartQuery.zig");
pub const File = @import("http/File.zig"); pub const File = @import("http/File.zig");

View File

@ -41,6 +41,12 @@ pub fn get(self: Headers, name: []const u8) ?[]const u8 {
return self.httpz_headers.get(lower); return self.httpz_headers.get(lower);
} }
/// 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);
return self.httpz_headers.get(name);
}
/// Get all values for a given header identified by `name`. Names are case insensitive. /// Get all values for a given header identified by `name`. Names are case insensitive.
pub fn getAll(self: Headers, name: []const u8) []const []const u8 { pub fn getAll(self: Headers, name: []const u8) []const []const u8 {
var headers = std.ArrayList([]const u8).init(self.allocator); var headers = std.ArrayList([]const u8).init(self.allocator);

View File

@ -26,6 +26,7 @@ allocator: std.mem.Allocator,
path: jetzig.http.Path, path: jetzig.http.Path,
method: Method, method: Method,
headers: jetzig.http.Headers, headers: jetzig.http.Headers,
host: []const u8,
server: *jetzig.http.Server, server: *jetzig.http.Server,
httpz_request: *httpz.Request, httpz_request: *httpz.Request,
httpz_response: *httpz.Response, httpz_response: *httpz.Response,
@ -146,12 +147,15 @@ pub fn init(
const response_data = try allocator.create(jetzig.data.Data); const response_data = try allocator.create(jetzig.data.Data);
response_data.* = jetzig.data.Data.init(allocator); response_data.* = jetzig.data.Data.init(allocator);
const headers = jetzig.http.Headers.init(allocator, httpz_request.headers);
const host = headers.getLower("host") orelse "";
return .{ return .{
.allocator = allocator, .allocator = allocator,
.path = path, .path = path,
.method = method, .method = method,
.headers = jetzig.http.Headers.init(allocator, httpz_request.headers), .headers = headers,
.host = host,
.server = server, .server = server,
.response = response, .response = response,
.response_data = response_data, .response_data = response_data,

View File

@ -61,10 +61,12 @@ pub fn deinit(self: *Server) void {
self.allocator.free(self.env.bind); self.allocator.free(self.env.bind);
} }
const Dispatcher = struct { const HttpzHandler = struct {
server: *Server, server: *Server,
pub fn handle(self: Dispatcher, request: *httpz.Request, response: *httpz.Response) void { pub const WebsocketHandler = jetzig.http.Websocket;
pub fn handle(self: HttpzHandler, request: *httpz.Request, response: *httpz.Response) void {
self.server.processNextRequest(request, response) catch |err| { self.server.processNextRequest(request, response) catch |err| {
self.server.errorHandlerFn(request, response, err) catch {}; self.server.errorHandlerFn(request, response, err) catch {};
}; };
@ -77,7 +79,7 @@ pub fn listen(self: *Server) !void {
const worker_count = jetzig.config.get(u16, "worker_count"); const worker_count = jetzig.config.get(u16, "worker_count");
const thread_count: u16 = jetzig.config.get(?u16, "thread_count") orelse @intCast(try std.Thread.getCpuCount()); const thread_count: u16 = jetzig.config.get(?u16, "thread_count") orelse @intCast(try std.Thread.getCpuCount());
var httpz_server = try httpz.Server(Dispatcher).init( var httpz_server = try httpz.Server(HttpzHandler).init(
self.allocator, self.allocator,
.{ .{
.port = self.env.port, .port = self.env.port,
@ -96,7 +98,7 @@ pub fn listen(self: *Server) !void {
.max_body_size = jetzig.config.get(usize, "max_bytes_request_body"), .max_body_size = jetzig.config.get(usize, "max_bytes_request_body"),
}, },
}, },
Dispatcher{ .server = self }, HttpzHandler{ .server = self },
); );
defer httpz_server.deinit(); defer httpz_server.deinit();
@ -139,6 +141,11 @@ pub fn processNextRequest(
var repo = try self.repo.bindConnect(.{ .allocator = httpz_response.arena }); var repo = try self.repo.bindConnect(.{ .allocator = httpz_response.arena });
defer repo.release(); defer repo.release();
if (try self.upgradeWebsocket(httpz_request, httpz_response)) {
try self.logger.DEBUG("Websocket upgrade request successful.", .{});
return;
}
var response = try jetzig.http.Response.init(httpz_response.arena, httpz_response); var response = try jetzig.http.Response.init(httpz_response.arena, httpz_response);
var request = try jetzig.http.Request.init( var request = try jetzig.http.Request.init(
httpz_response.arena, httpz_response.arena,
@ -169,6 +176,14 @@ pub fn processNextRequest(
try self.logger.logRequest(&request); try self.logger.logRequest(&request);
} }
fn upgradeWebsocket(self: *const Server, httpz_request: *httpz.Request, httpz_response: *httpz.Response) !bool {
return try httpz.upgradeWebsocket(
jetzig.http.Websocket,
httpz_request,
httpz_response,
jetzig.http.Websocket.Context{ .allocator = self.allocator },
);
}
fn maybeMiddlewareRender(request: *jetzig.http.Request, response: *const jetzig.http.Response) !bool { fn maybeMiddlewareRender(request: *jetzig.http.Request, response: *const jetzig.http.Response) !bool {
if (request.middleware_rendered) |_| { if (request.middleware_rendered) |_| {
// Request processing ends when a middleware renders or redirects. // Request processing ends when a middleware renders or redirects.

View File

@ -0,0 +1,24 @@
const std = @import("std");
const httpz = @import("httpz");
pub const Context = struct {
allocator: std.mem.Allocator,
};
const Websocket = @This();
connection: *httpz.websocket.Conn,
allocator: std.mem.Allocator,
pub fn init(connection: *httpz.websocket.Conn, context: Context) !Websocket {
return .{
.connection = connection,
.allocator = context.allocator,
};
}
pub fn clientMessage(self: *Websocket, data: []const u8) !void {
const message = try std.mem.concat(self.allocator, u8, &.{ "Hello from Jetzig websocket. Your message was: ", data });
try self.connection.write(message);
}