From 36f9d32ae632e72e4a9fca3e0dfba2aba450c182 Mon Sep 17 00:00:00 2001 From: Bob Farrell Date: Sun, 19 Jan 2025 16:17:25 +0000 Subject: [PATCH] WIP --- build.zig | 12 +++ cli/commands/server.zig | 11 ++- demo/src/app/views/channels.zig | 16 ++++ demo/src/app/views/channels/index.zmpl | 3 + demo/src/app/views/root.zig | 4 +- demo/src/app/views/root/index.zmpl | 18 +++++ demo/src/main.zig | 1 + demo/src/routes.zig | 58 +++++++-------- src/jetzig.zig | 1 + src/jetzig/App.zig | 2 +- src/jetzig/Channels.zig | 34 +++++++++ src/jetzig/http.zig | 1 + src/jetzig/http/Server.zig | 39 +++++++--- src/jetzig/http/WebsocketClient.zig | 38 ++++++++++ src/jetzig/http/middleware.zig | 8 ++ src/jetzig/middleware.zig | 1 + src/jetzig/middleware/HotReloadMiddleware.zig | 16 ++++ websockets/main.zig | 74 +++++++++++++++++++ 18 files changed, 291 insertions(+), 46 deletions(-) create mode 100644 demo/src/app/views/channels.zig create mode 100644 demo/src/app/views/channels/index.zmpl create mode 100644 src/jetzig/Channels.zig create mode 100644 src/jetzig/http/WebsocketClient.zig create mode 100644 src/jetzig/middleware/HotReloadMiddleware.zig create mode 100644 websockets/main.zig diff --git a/build.zig b/build.zig index 8e8b92a..ceb4588 100644 --- a/build.zig +++ b/build.zig @@ -92,6 +92,18 @@ pub fn build(b: *std.Build) !void { jetzig_module.addImport("smtp", smtp_client_dep.module("smtp_client")); jetzig_module.addImport("httpz", httpz_dep.module("httpz")); + const websockets_exe = b.addExecutable(.{ + .name = "websockets", + .root_source_file = b.path("websockets/main.zig"), + .optimize = optimize, + .target = target, + .use_llvm = false, + }); + websockets_exe.root_module.addImport("httpz", httpz_dep.module("httpz")); + const run_websockets_exe = b.addRunArtifact(websockets_exe); + const websockets_step = b.step("websockets", "Launch development websockets server"); + websockets_step.dependOn(&run_websockets_exe.step); + const main_tests = b.addTest(.{ .root_source_file = b.path("src/tests.zig"), .target = target, diff --git a/cli/commands/server.zig b/cli/commands/server.zig index 5f8809e..f9ae394 100644 --- a/cli/commands/server.zig +++ b/cli/commands/server.zig @@ -4,7 +4,7 @@ const args = @import("args"); const util = @import("../util.zig"); -pub const watch_changes_pause_duration = 1 * 1000 * 1000 * 1000; +pub const watch_changes_pause_duration = 100 * std.time.ns_per_ms; /// Command line options for the `server` command. pub const Options = struct { @@ -66,8 +66,6 @@ pub fn run( "build", "--watch", "-fincremental", - "--debounce", - "500", // FIXME util.environmentBuildOption(main_options.options.environment), "-Djetzig_runner=true", }); @@ -93,6 +91,13 @@ pub fn run( std.process.exit(1); }; + util.runCommandInDir( + allocator, + &.{ "zig", "build", "websockets" }, + .{ .path = realpath }, + .{ .output = .stream, .wait = false }, + ); + var zmpl_thread = try std.Thread.spawn( .{ .allocator = allocator }, zmplThread, diff --git a/demo/src/app/views/channels.zig b/demo/src/app/views/channels.zig new file mode 100644 index 0000000..0d75d76 --- /dev/null +++ b/demo/src/app/views/channels.zig @@ -0,0 +1,16 @@ +const std = @import("std"); +const jetzig = @import("jetzig"); + +pub fn index(request: *jetzig.Request) !jetzig.View { + var channel = try request.server.channels.acquire("my-channel"); + _ = &channel; + 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, "/channels", .{}); + try response.expectStatus(.ok); +} diff --git a/demo/src/app/views/channels/index.zmpl b/demo/src/app/views/channels/index.zmpl new file mode 100644 index 0000000..76457d0 --- /dev/null +++ b/demo/src/app/views/channels/index.zmpl @@ -0,0 +1,3 @@ +
+ Content goes here +
diff --git a/demo/src/app/views/root.zig b/demo/src/app/views/root.zig index 54608ef..f878fc4 100644 --- a/demo/src/app/views/root.zig +++ b/demo/src/app/views/root.zig @@ -12,8 +12,8 @@ pub fn index(request: *jetzig.Request, data: *jetzig.Data) !jetzig.View { try root.put("imported_number", importedFunction(100, 200, 300)); try request.response.headers.append("x-example-header", "example header value"); - try root.put("foobar", "bloop"); - try request.server.logger.INFO("onnnnnnnnnne time", .{}); + try root.put("foobar", "hello"); + try request.server.logger.INFO("data", .{}); return request.render(.ok); } diff --git a/demo/src/app/views/root/index.zmpl b/demo/src/app/views/root/index.zmpl index 68d6e8a..fc9fb0c 100644 --- a/demo/src/app/views/root/index.zmpl +++ b/demo/src/app/views/root/index.zmpl @@ -1,4 +1,22 @@
+

HELLO

+

SPEED

+

SPEED

+

SPEED

+

SPEED

+

SPEED

+

SPEED

+

SPEED

+

SPEED

+

SPEED

+

SPEED

+

SPEED

+

SPEED

+

SPEED

+

SPEED

+

SPEED

+

SPEED

+

NO

{{$.foobar}}
diff --git a/demo/src/main.zig b/demo/src/main.zig index 26f638a..b4a542b 100644 --- a/demo/src/main.zig +++ b/demo/src/main.zig @@ -16,6 +16,7 @@ pub const jetzig_options = struct { // jetzig.middleware.AntiCsrfMiddleware, // jetzig.middleware.HtmxMiddleware, // jetzig.middleware.CompressionMiddleware, + jetzig.middleware.HotReloadMiddleware, // @import("app/middleware/DemoMiddleware.zig"), }; diff --git a/demo/src/routes.zig b/demo/src/routes.zig index f8108e4..aed6906 100644 --- a/demo/src/routes.zig +++ b/demo/src/routes.zig @@ -2,7 +2,7 @@ const jetzig = @import("jetzig"); pub const routes = [_]jetzig.Route{ .{ - .id = "uJZa72JiFvkhip6kHN54byQl58IGiMjg", + .id = "AkI5YOzDJzuODxAPp4VDCIVTHlk9K9Et", .name = "nested_route_example_get", .action = .get, .view_name = "nested/route/example", @@ -19,7 +19,7 @@ pub const routes = [_]jetzig.Route{ .formats = if (@hasDecl(@import("app/views/nested/route/example.zig"), "formats")) @import("app/views/nested/route/example.zig").formats else null, }, .{ - .id = "BJYamvRbwEjmaARk364a4J5acl4wgL9U", + .id = "jyCCfxQ3gWI07OyKkz0DLuoSLZ2uWsvn", .name = "static_get", .action = .get, .view_name = "static", @@ -36,7 +36,7 @@ pub const routes = [_]jetzig.Route{ .formats = if (@hasDecl(@import("app/views/static.zig"), "formats")) @import("app/views/static.zig").formats else null, }, .{ - .id = "RGSs0mSc6wzyetJerCDc7RfsQuc6duGG", + .id = "MlnUk52uBuoQ8I2ew86bRMYagC7VNadv", .name = "static_index", .action = .index, .view_name = "static", @@ -53,7 +53,7 @@ pub const routes = [_]jetzig.Route{ .formats = if (@hasDecl(@import("app/views/static.zig"), "formats")) @import("app/views/static.zig").formats else null, }, .{ - .id = "vpO4u26CpMoJmbRrjopPW8ZdaFB9MOp4", + .id = "GzljS2WHIvOPF5ods7cdqJfSY9OXbTs4", .name = "session_edit", .action = .edit, .view_name = "session", @@ -69,7 +69,7 @@ pub const routes = [_]jetzig.Route{ .formats = if (@hasDecl(@import("app/views/session.zig"), "formats")) @import("app/views/session.zig").formats else null, }, .{ - .id = "WLr5eEhAkDE1nstNJSNFQRmIAmT1nmD2", + .id = "BSi50qh31yc8kS5YBMzyXxyWoq0BbjYj", .name = "root_edit", .action = .edit, .view_name = "root", @@ -85,7 +85,7 @@ pub const routes = [_]jetzig.Route{ .formats = if (@hasDecl(@import("app/views/root.zig"), "formats")) @import("app/views/root.zig").formats else null, }, .{ - .id = "nTbGohWH3dpErID5WA9oEFNy3mzZ4Zwa", + .id = "Hzfw3GrABWhP1R3wA5Ix6M2QGuL733Bh", .name = "format_get", .action = .get, .view_name = "format", @@ -101,7 +101,7 @@ pub const routes = [_]jetzig.Route{ .formats = if (@hasDecl(@import("app/views/format.zig"), "formats")) @import("app/views/format.zig").formats else null, }, .{ - .id = "nsfHVzgASes8StjDJGb3n5M6lzPEZJgV", + .id = "dbkt77CflbzPEri3c1uo5U4AGhtlC3Jn", .name = "quotes_get", .action = .get, .view_name = "quotes", @@ -117,7 +117,7 @@ pub const routes = [_]jetzig.Route{ .formats = if (@hasDecl(@import("app/views/quotes.zig"), "formats")) @import("app/views/quotes.zig").formats else null, }, .{ - .id = "CJt9JGk6xH2DdO6jCuBbPOpuQiiVx9AA", + .id = "Qgc7WwR2psbexSNcrljPqUSw5rmY3g8x", .name = "background_jobs_index", .action = .index, .view_name = "background_jobs", @@ -133,7 +133,7 @@ pub const routes = [_]jetzig.Route{ .formats = if (@hasDecl(@import("app/views/background_jobs.zig"), "formats")) @import("app/views/background_jobs.zig").formats else null, }, .{ - .id = "njFQ9UNch4wUVakZwdVxoMrSctM3IFNn", + .id = "KJOP1zzGpWfvCTIcmVNw3CSNWG4g0zY0", .name = "session_index", .action = .index, .view_name = "session", @@ -149,7 +149,7 @@ pub const routes = [_]jetzig.Route{ .formats = if (@hasDecl(@import("app/views/session.zig"), "formats")) @import("app/views/session.zig").formats else null, }, .{ - .id = "PjdQ8E8IsquUk6n4o5YjROnMf3ryhqTg", + .id = "spqGkQEUY37lw4mM50kKhibw0alHzv7g", .name = "root_index", .action = .index, .view_name = "root", @@ -165,7 +165,7 @@ pub const routes = [_]jetzig.Route{ .formats = if (@hasDecl(@import("app/views/root.zig"), "formats")) @import("app/views/root.zig").formats else null, }, .{ - .id = "xsrBSO4IcVWr3hPniSteLZjLQPvHb01z", + .id = "MDW0dk8Pf9BQE3JrtBSDpzHHwH0LGJ7o", .name = "redirect_index", .action = .index, .view_name = "redirect", @@ -181,7 +181,7 @@ pub const routes = [_]jetzig.Route{ .formats = if (@hasDecl(@import("app/views/redirect.zig"), "formats")) @import("app/views/redirect.zig").formats else null, }, .{ - .id = "pof2071a8lxXm2bOtGqa3kB9jIlFfZMK", + .id = "lqswc0Kw59ZgdA1CJACbL8WbSIr4rXRm", .name = "format_index", .action = .index, .view_name = "format", @@ -197,7 +197,7 @@ pub const routes = [_]jetzig.Route{ .formats = if (@hasDecl(@import("app/views/format.zig"), "formats")) @import("app/views/format.zig").formats else null, }, .{ - .id = "tLbQDcOUJgWFbj6V9gj8qpQKplSUPQRf", + .id = "ihQeHBf6BTYQykrGOnhjjByfs4wWdavM", .name = "nested_route_example_index", .action = .index, .view_name = "nested/route/example", @@ -213,7 +213,7 @@ pub const routes = [_]jetzig.Route{ .formats = if (@hasDecl(@import("app/views/nested/route/example.zig"), "formats")) @import("app/views/nested/route/example.zig").formats else null, }, .{ - .id = "TtBC947UM5h5jvT6tt3szPhx7PqUsXbH", + .id = "AExhngOpBhhsRjyRKnOywrnJV2VfR65Z", .name = "mail_index", .action = .index, .view_name = "mail", @@ -229,7 +229,7 @@ pub const routes = [_]jetzig.Route{ .formats = if (@hasDecl(@import("app/views/mail.zig"), "formats")) @import("app/views/mail.zig").formats else null, }, .{ - .id = "hFrag31RMb9yLcCO60iUEXQB3GCgGYym", + .id = "qefFQVtUvwXDp5JTXTNCc6utW4zcbosp", .name = "anti_csrf_index", .action = .index, .view_name = "anti_csrf", @@ -245,7 +245,7 @@ pub const routes = [_]jetzig.Route{ .formats = if (@hasDecl(@import("app/views/anti_csrf.zig"), "formats")) @import("app/views/anti_csrf.zig").formats else null, }, .{ - .id = "egmj9YOQlALfBOJ9BfmLC3wYN0ONAb4F", + .id = "qQtDJ8sV9vJYJmHEVPeNpzj2Qhmh2XMP", .name = "markdown_index", .action = .index, .view_name = "markdown", @@ -261,7 +261,7 @@ pub const routes = [_]jetzig.Route{ .formats = if (@hasDecl(@import("app/views/markdown.zig"), "formats")) @import("app/views/markdown.zig").formats else null, }, .{ - .id = "RVYWSz1PQLbhF3yFwOznmbxkUM6rHrsS", + .id = "UnNRfU7hM7J92xUMaPg20RtRJsDoXMXE", .name = "init_index", .action = .index, .view_name = "init", @@ -277,7 +277,7 @@ pub const routes = [_]jetzig.Route{ .formats = if (@hasDecl(@import("app/views/init.zig"), "formats")) @import("app/views/init.zig").formats else null, }, .{ - .id = "XcceAbNvt2vQqiJebTUjIM5W65NNhmEZ", + .id = "ZfrniROoeeuiKlk3gCgKfCr6WrDW0OY9", .name = "kvstore_index", .action = .index, .view_name = "kvstore", @@ -293,7 +293,7 @@ pub const routes = [_]jetzig.Route{ .formats = if (@hasDecl(@import("app/views/kvstore.zig"), "formats")) @import("app/views/kvstore.zig").formats else null, }, .{ - .id = "vWuB0hRyAG60CdbENpFX4k8wXyWWF3Bg", + .id = "yoonae7d0jc5bDqkypoVl3Q3EgSusMoC", .name = "render_template_index", .action = .index, .view_name = "render_template", @@ -309,7 +309,7 @@ pub const routes = [_]jetzig.Route{ .formats = if (@hasDecl(@import("app/views/render_template.zig"), "formats")) @import("app/views/render_template.zig").formats else null, }, .{ - .id = "elUHJfKyuvlfSY89JF25ZivCR9toimDd", + .id = "yMqyoHvwyQa3l6VDK0PvzoTMsY754wk4", .name = "cache_index", .action = .index, .view_name = "cache", @@ -325,7 +325,7 @@ pub const routes = [_]jetzig.Route{ .formats = if (@hasDecl(@import("app/views/cache.zig"), "formats")) @import("app/views/cache.zig").formats else null, }, .{ - .id = "XDNbJRI6TcHFElLRQSXDHf4mzNDkGIq1", + .id = "fnRX4YOb2aUS88rKQR40krb1xfp4JFG9", .name = "basic_index", .action = .index, .view_name = "basic", @@ -341,7 +341,7 @@ pub const routes = [_]jetzig.Route{ .formats = if (@hasDecl(@import("app/views/basic.zig"), "formats")) @import("app/views/basic.zig").formats else null, }, .{ - .id = "yLZD4YD5XbulEEI6qVizoU8NA1opn0og", + .id = "PixHoG933QEkAPVs1DoxwVTovKglhUfN", .name = "errors_index", .action = .index, .view_name = "errors", @@ -357,7 +357,7 @@ pub const routes = [_]jetzig.Route{ .formats = if (@hasDecl(@import("app/views/errors.zig"), "formats")) @import("app/views/errors.zig").formats else null, }, .{ - .id = "rvrgBu8dueznrruGigCIEDlgUcKS0vfU", + .id = "Efo0V2HYbMa5G8KQgx426eLdhdRdoBTe", .name = "file_upload_index", .action = .index, .view_name = "file_upload", @@ -373,7 +373,7 @@ pub const routes = [_]jetzig.Route{ .formats = if (@hasDecl(@import("app/views/file_upload.zig"), "formats")) @import("app/views/file_upload.zig").formats else null, }, .{ - .id = "rRo4FRbAB69Sup52MThXb6JgZeslwyd4", + .id = "UFcXVgtoOZX4i55aI7jd9LhQRuG34jPM", .name = "quotes_post", .action = .post, .view_name = "quotes", @@ -389,7 +389,7 @@ pub const routes = [_]jetzig.Route{ .formats = if (@hasDecl(@import("app/views/quotes.zig"), "formats")) @import("app/views/quotes.zig").formats else null, }, .{ - .id = "XlCspoWCrkCb0Ita7IVboLu8IszoudGU", + .id = "DLVKGAGIzETt2h0PFnKeFDkwpOAMLq93", .name = "anti_csrf_post", .action = .post, .view_name = "anti_csrf", @@ -405,7 +405,7 @@ pub const routes = [_]jetzig.Route{ .formats = if (@hasDecl(@import("app/views/anti_csrf.zig"), "formats")) @import("app/views/anti_csrf.zig").formats else null, }, .{ - .id = "ARjHlYPMqzO5sRAXRKAC5LEXnLRRlrx0", + .id = "mvyx5eMKF09eaoqExLqa0AXA8GdI1H1e", .name = "session_post", .action = .post, .view_name = "session", @@ -421,7 +421,7 @@ pub const routes = [_]jetzig.Route{ .formats = if (@hasDecl(@import("app/views/session.zig"), "formats")) @import("app/views/session.zig").formats else null, }, .{ - .id = "pKQJ0IwnjtDChL7IWQ2nNhsbtPFs2o5j", + .id = "joZaA2JbO0heJCowS1cwcmrN91zWYgGB", .name = "cache_post", .action = .post, .view_name = "cache", @@ -437,7 +437,7 @@ pub const routes = [_]jetzig.Route{ .formats = if (@hasDecl(@import("app/views/cache.zig"), "formats")) @import("app/views/cache.zig").formats else null, }, .{ - .id = "OebrtE1AyytRrnk8r2HKWVlzG41djdbl", + .id = "BGRJZnQctDt9GY5dXkSDYgnveu5ePT66", .name = "params_post", .action = .post, .view_name = "params", @@ -453,7 +453,7 @@ pub const routes = [_]jetzig.Route{ .formats = if (@hasDecl(@import("app/views/params.zig"), "formats")) @import("app/views/params.zig").formats else null, }, .{ - .id = "kvOFWC1wJvT1vjYM3KyNUh6JsVyY5RVF", + .id = "atwIYYFWAXOVXAszR1d794p3O2aUM3b4", .name = "file_upload_post", .action = .post, .view_name = "file_upload", diff --git a/src/jetzig.zig b/src/jetzig.zig index db5a2c1..01a8439 100644 --- a/src/jetzig.zig +++ b/src/jetzig.zig @@ -25,6 +25,7 @@ pub const auth = @import("jetzig/auth.zig"); pub const callbacks = @import("jetzig/callbacks.zig"); pub const debug = @import("jetzig/debug.zig"); pub const TemplateContext = @import("jetzig/TemplateContext.zig"); +pub const Channels = @import("jetzig/Channels.zig"); pub const DateTime = jetcommon.types.DateTime; pub const Time = jetcommon.types.Time; diff --git a/src/jetzig/App.zig b/src/jetzig/App.zig index dca1836..5f9d7cc 100644 --- a/src/jetzig/App.zig +++ b/src/jetzig/App.zig @@ -131,7 +131,7 @@ pub fn start(self: *const App, routes_module: type, options: AppOptions) !void { return; }, else => { - try server.logger.ERROR("Encountered error: {}\nExiting.\n", .{err}); + try server.logger.ERROR("Encountered error at server launch: {}\nExiting.\n", .{err}); std.process.exit(1); }, } diff --git a/src/jetzig/Channels.zig b/src/jetzig/Channels.zig new file mode 100644 index 0000000..521ad5b --- /dev/null +++ b/src/jetzig/Channels.zig @@ -0,0 +1,34 @@ +const std = @import("std"); + +const Channels = @This(); + +pub const Channel = struct { + name: []const u8, + + pub fn deliver(self: Channel, message: []const u8) !void { + _ = self; + std.debug.print("message: {s}\n", .{message}); + } +}; + +allocator: std.mem.Allocator, +channels: std.StringHashMap(Channel), + +pub fn init(allocator: std.mem.Allocator) Channels { + return .{ + .allocator = allocator, + .channels = std.StringHashMap(Channel).init(allocator), + }; +} + +pub fn acquire(self: *Channels, name: []const u8) !Channel { + const channel = Channel{ .name = name }; + try self.channels.put(name, channel); +} + +pub fn broadcast(self: Channels, message: []const u8) !void { + var it = self.channels.iterator(); + while (it.next()) |entry| { + try entry.value_ptr.deliver(message); + } +} diff --git a/src/jetzig/http.zig b/src/jetzig/http.zig index 5137903..1ca438e 100644 --- a/src/jetzig/http.zig +++ b/src/jetzig/http.zig @@ -10,6 +10,7 @@ pub const StaticRequest = if (build_options.environment == .development) else @import("http/StaticRequest.zig"); pub const Response = @import("http/Response.zig"); +pub const WebsocketClient = @import("http/WebsocketClient.zig"); pub const Session = @import("http/Session.zig"); pub const Cookies = @import("http/Cookies.zig"); pub const Headers = @import("http/Headers.zig"); diff --git a/src/jetzig/http/Server.zig b/src/jetzig/http/Server.zig index 4ba090c..31cc152 100644 --- a/src/jetzig/http/Server.zig +++ b/src/jetzig/http/Server.zig @@ -22,6 +22,8 @@ repo: *jetzig.database.Repo, global: *anyopaque, decoded_static_route_params: []const *jetzig.data.Value = &.{}, debug_mutex: std.Thread.Mutex = .{}, +websocket_client: jetzig.http.WebsocketClient = undefined, +channels: jetzig.Channels, const Server = @This(); @@ -52,6 +54,7 @@ pub fn init( .job_queue = job_queue, .cache = cache, .repo = repo, + .channels = jetzig.Channels.init(allocator), .global = global, }; } @@ -108,8 +111,15 @@ pub fn listen(self: *Server) !void { thread_count, }); + var client = try jetzig.http.WebsocketClient.init(self.allocator, .{}); + try client.handshake(); + self.websocket_client = client; + defer client.deinit(); + self.initialized = true; + try jetzig.http.middleware.afterLaunch(self); + return try httpz_server.listen(); } @@ -151,7 +161,23 @@ pub fn processNextRequest( try request.process(); var middleware_data = try jetzig.http.middleware.afterRequest(&request); + if (try maybeMiddlewareRender(&request, &response)) { + try self.logger.logRequest(&request); + return; + } + try self.renderResponse(&request); + try request.response.headers.append("Content-Type", response.content_type); + + try jetzig.http.middleware.beforeResponse(&middleware_data, &request); + try request.respond(); + try jetzig.http.middleware.afterResponse(&middleware_data, &request); + jetzig.http.middleware.deinit(&middleware_data, &request); + + try self.logger.logRequest(&request); +} + +fn maybeMiddlewareRender(request: *jetzig.http.Request, response: *const jetzig.http.Response) !bool { if (request.middleware_rendered) |_| { // Request processing ends when a middleware renders or redirects. if (request.redirect_state) |state| { @@ -162,17 +188,8 @@ pub fn processNextRequest( } try request.response.headers.append("Content-Type", response.content_type); try request.respond(); - } else { - try self.renderResponse(&request); - try request.response.headers.append("Content-Type", response.content_type); - - try jetzig.http.middleware.beforeResponse(&middleware_data, &request); - try request.respond(); - try jetzig.http.middleware.afterResponse(&middleware_data, &request); - jetzig.http.middleware.deinit(&middleware_data, &request); - } - - try self.logger.logRequest(&request); + return true; + } else return false; } fn renderResponse(self: *Server, request: *jetzig.http.Request) !void { diff --git a/src/jetzig/http/WebsocketClient.zig b/src/jetzig/http/WebsocketClient.zig new file mode 100644 index 0000000..632c08b --- /dev/null +++ b/src/jetzig/http/WebsocketClient.zig @@ -0,0 +1,38 @@ +const std = @import("std"); + +const httpz = @import("httpz"); +const jetzig = @import("../../jetzig.zig"); + +allocator: std.mem.Allocator, +websocket_client: *httpz.websocket.Client, + +const WebsocketClient = @This(); + +pub fn init(allocator: std.mem.Allocator, options: struct {}) !WebsocketClient { + _ = options; + const client = try allocator.create(httpz.websocket.Client); + client.* = try httpz.websocket.Client.init(allocator, .{ + .port = 9224, + .host = "localhost", + }); + + return .{ + .allocator = allocator, + .websocket_client = client, + }; +} + +pub fn deinit(self: WebsocketClient) void { + self.websocket_client.deinit(); +} + +pub fn handshake(self: WebsocketClient) !void { + try self.websocket_client.handshake("/ws", .{ + .timeout_ms = 1000, + .headers = "Host: localhost:9224", // TODO: Auth header ? + }); +} + +pub fn broadcast(self: WebsocketClient, message: []const u8) !void { + try self.websocket_client.write(@constCast(message)); +} diff --git a/src/jetzig/http/middleware.zig b/src/jetzig/http/middleware.zig index e049a17..e0a4a15 100644 --- a/src/jetzig/http/middleware.zig +++ b/src/jetzig/http/middleware.zig @@ -48,6 +48,14 @@ pub fn Type(comptime name: MiddlewareEnum()) type { } } +pub fn afterLaunch(server: *jetzig.http.Server) !void { + inline for (middlewares) |middleware| { + if (comptime @hasDecl(middleware, "afterLaunch")) { + try middleware.afterLaunch(server); + } + } +} + pub fn afterRequest(request: *jetzig.http.Request) !MiddlewareData { var middleware_data = MiddlewareData.init(0) catch unreachable; diff --git a/src/jetzig/middleware.zig b/src/jetzig/middleware.zig index 4be3640..b87f3e2 100644 --- a/src/jetzig/middleware.zig +++ b/src/jetzig/middleware.zig @@ -5,6 +5,7 @@ pub const HtmxMiddleware = @import("middleware/HtmxMiddleware.zig"); pub const CompressionMiddleware = @import("middleware/CompressionMiddleware.zig"); pub const AuthMiddleware = @import("middleware/AuthMiddleware.zig"); pub const AntiCsrfMiddleware = @import("middleware/AntiCsrfMiddleware.zig"); +pub const HotReloadMiddleware = @import("middleware/HotReloadMiddleware.zig"); const RouteOptions = struct { content: ?[]const u8 = null, diff --git a/src/jetzig/middleware/HotReloadMiddleware.zig b/src/jetzig/middleware/HotReloadMiddleware.zig new file mode 100644 index 0000000..85f5000 --- /dev/null +++ b/src/jetzig/middleware/HotReloadMiddleware.zig @@ -0,0 +1,16 @@ +const std = @import("std"); +const jetzig = @import("../../jetzig.zig"); + +pub const middleware_name = "hot_reload"; + +const HotReloadMiddleware = @This(); + +pub fn afterRequest(request: *jetzig.http.Request) !void { + var session = try request.session(); + try session.put("_jetzig_hot_reload", "test"); +} + +pub fn afterLaunch(server: *jetzig.http.Server) !void { + try server.logger.INFO("LAUNCH", .{}); + try server.channels.broadcast("jetzig-reload"); +} diff --git a/websockets/main.zig b/websockets/main.zig new file mode 100644 index 0000000..f5585d1 --- /dev/null +++ b/websockets/main.zig @@ -0,0 +1,74 @@ +const std = @import("std"); +const websocket = @import("httpz").websocket; + +pub fn main() !void { + var gpa = std.heap.GeneralPurposeAllocator(.{}){}; + const allocator = gpa.allocator(); + + var server = try Server.init(allocator); + defer server.deinit(); + + try server.listen(); +} + +const Server = struct { + allocator: std.mem.Allocator, + websocket_server: websocket.Server(Handler), + sessions: std.ArrayList(Stream), + + const Stream = struct { + key: []const u8, + connection: *websocket.Conn, + }; + + pub const App = struct { + streams: std.ArrayList(Stream), + }; + + pub fn init(allocator: std.mem.Allocator) !Server { + return .{ + .allocator = allocator, + .websocket_server = try websocket.Server(Handler).init(allocator, .{ + .port = 9224, + .address = "127.0.0.1", + .handshake = .{ + .timeout = 3, + .max_size = 1024, + .max_headers = 0, + }, + }), + .sessions = std.ArrayList(Stream).init(allocator), + }; + } + + pub fn deinit(self: *Server) void { + self.websocket_server.deinit(); + self.sessions.deinit(); + } + + pub fn listen(self: *Server) !void { + var app: App = .{ .streams = std.ArrayList(Stream).init(self.allocator) }; + try self.websocket_server.listen(&app); + } +}; + +const Handler = struct { + app: *Server.App, + connection: *websocket.Conn, + + pub fn init(handshake: websocket.Handshake, connection: *websocket.Conn, app: *Server.App) !Handler { + std.debug.print("handshake: {any}\n", .{handshake}); + try app.streams.append(.{ .key = handshake.key, .connection = connection }); + + return .{ + .app = app, + .connection = connection, + }; + } + + // You must defined a public clientMessage method + pub fn clientMessage(self: *Handler, data: []const u8) !void { + std.debug.print("message: {s}\n", .{data}); + try self.connection.write(data); // echo the message back + } +};