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