From b77ea5fd51182097482590796215495e5ce01256 Mon Sep 17 00:00:00 2001 From: Bob Farrell Date: Sun, 21 Jan 2024 19:00:41 +0000 Subject: [PATCH] Static content, init/setup tweaks, some cleanup --- README.md | 1 + init.bash | 11 ++- public/jetzig.png | Bin 0 -> 22828 bytes src/app/config/quotes.json | 102 +++++++++++++++++++++++++ src/app/views/foo/bar/baz.zig | 53 ------------- src/app/views/foo/bar/baz/get.zmpl | 2 - src/app/views/foo/bar/baz/index.zmpl | 14 ---- src/app/views/index.zmpl | 32 ++++++-- src/app/views/options.zig | 44 ----------- src/app/views/options/get.zmpl | 1 - src/app/views/options/put.zmpl | 1 - src/app/views/quotes.zig | 34 +++++++++ src/app/views/quotes/get.zmpl | 2 + src/app/views/routes.zig | 4 +- src/app/views/users.zig | 15 ++++ src/app/views/users/get.zmpl | 8 ++ src/app/views/zmpl.manifest.zig | 6 +- src/init/.gitignore | 2 + src/init/build.zig | 13 ++++ src/init/build.zig.zon | 5 +- src/init/public/jetzig.png | Bin 0 -> 22828 bytes src/init/src/app/config/quotes.json | 102 +++++++++++++++++++++++++ src/init/src/app/views/index.zmpl | 27 +++++++ src/init/src/app/views/quotes.zig | 34 +++++++++ src/init/src/app/views/quotes/get.zmpl | 2 + src/jetzig.zig | 5 ++ src/jetzig/http/Response.zig | 5 ++ src/jetzig/http/Server.zig | 92 +++++++++++++++++++--- 28 files changed, 474 insertions(+), 143 deletions(-) create mode 100644 public/jetzig.png create mode 100644 src/app/config/quotes.json delete mode 100644 src/app/views/foo/bar/baz.zig delete mode 100644 src/app/views/foo/bar/baz/get.zmpl delete mode 100644 src/app/views/foo/bar/baz/index.zmpl delete mode 100644 src/app/views/options.zig delete mode 100644 src/app/views/options/get.zmpl delete mode 100644 src/app/views/options/put.zmpl create mode 100644 src/app/views/quotes.zig create mode 100644 src/app/views/quotes/get.zmpl create mode 100644 src/app/views/users.zig create mode 100644 src/app/views/users/get.zmpl create mode 100644 src/init/.gitignore create mode 100644 src/init/public/jetzig.png create mode 100644 src/init/src/app/config/quotes.json create mode 100644 src/init/src/app/views/index.zmpl create mode 100644 src/init/src/app/views/quotes.zig create mode 100644 src/init/src/app/views/quotes/get.zmpl diff --git a/README.md b/README.md index 66256aa..139ba50 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,7 @@ If you are interested in _Jetzig_ you will probably find these tools interesting * :white_check_mark: Sessions. * :white_check_mark: Cookies. * :white_check_mark: Error handling. +* :x: Static content from /public directory. * :x: Headers (available but not yet wrapped). * :x: Param/JSON payload parsing/abstracting. * :x: Development-mode responses for debugging. diff --git a/init.bash b/init.bash index 62ec295..d2b17dd 100644 --- a/init.bash +++ b/init.bash @@ -39,6 +39,11 @@ objects=( 'src/main.zig' 'src/app/views/index.zig' 'src/app/views/index.zmpl' + 'src/app/views/quotes.zig' + 'src/app/views/quotes/get.zmpl' + 'src/app/config/quotes.json' + 'public/jetzig.png' + '.gitignore' ) for object in "${objects[@]}" @@ -57,8 +62,10 @@ do fi done -sed -i.bak -e "s,%%project_name%%,${project},g" 'src/build.zig' && rm build.zig.bak -sed -i.bak -e "s,%%project_name%%,${project},g" 'src/build.zig.zon' && rm build.zig.zon.bak +sed -i.bak -e "s,%%project_name%%,${project},g" "${project_path}/build.zig" +rm "${project_path}/build.zig.bak" +sed -i.bak -e "s,%%project_name%%,${project},g" "${project_path}/build.zig.zon" +rm "${project_path}/build.zig.zon.bak" echo echo "Finished creating new project in: ${project_path}" diff --git a/public/jetzig.png b/public/jetzig.png new file mode 100644 index 0000000000000000000000000000000000000000..314d70c06242be3c69f1c1f5e50d26d3e9eda56a GIT binary patch literal 22828 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.resource_id} -{.session_value} diff --git a/src/app/views/foo/bar/baz/index.zmpl b/src/app/views/foo/bar/baz/index.zmpl deleted file mode 100644 index 0c41920..0000000 --- a/src/app/views/foo/bar/baz/index.zmpl +++ /dev/null @@ -1,14 +0,0 @@ -if (std.mem.eql(u8, try zmpl.getValueString("message"), "hello there")) { - const foo = "foo const"; - const bar = "bar const"; - - inline for (1..4) |index| { -
Hello {:foo}!
-
Hello {:bar}!
-
Hello {:index}!
-
Hello {.foo}!
-
Hello {.bar}!
- } -} else { -
Unexpected message
-} diff --git a/src/app/views/index.zmpl b/src/app/views/index.zmpl index f9d6505..f7146b8 100644 --- a/src/app/views/index.zmpl +++ b/src/app/views/index.zmpl @@ -1,11 +1,27 @@ - + + + -
+ + + -inline for (0..10) |index| { -
-
-} + +
+
-
Select an option.
+
+

Welcome to Jetzig!

+
+ + + +
+
+
+ +
Take a look at the src/app/ directory to see how this application works.
+
Visit jetzig.dev to get started.
+
+ + diff --git a/src/app/views/options.zig b/src/app/views/options.zig deleted file mode 100644 index 1123b45..0000000 --- a/src/app/views/options.zig +++ /dev/null @@ -1,44 +0,0 @@ -const std = @import("std"); - -const root = @import("root"); -const Request = root.jetzig.http.Request; -const Data = root.jetzig.data.Data; -const View = root.jetzig.views.View; - -pub fn put(id: []const u8, request: *Request, data: *Data) anyerror!View { - try request.session.put("option", data.string(id)); - var object = try data.object(); - try object.put("option", data.string(id)); - - const count = try request.session.get("count"); - if (count) |value| { - if (value == .integer) { - try request.session.put("count", data.integer(value.integer.value + 1)); - try object.put("count", data.integer(value.integer.value + 1)); - } else { - return error.InvalidSessionData; - } - } else { - try request.session.put("count", data.integer(0)); - try object.put("count", data.integer(0)); - } - - return request.render(.ok); -} - -pub fn get(id: []const u8, request: *Request, data: *Data) anyerror!View { - if (std.mem.eql(u8, id, "latest")) { - var object = try data.object(); - const count = try request.session.get("count"); - if (count) |value| { - if (value == .integer) { - try object.put("count", data.integer(value.integer.value + 1)); - } else { - try object.put("count", data.integer(0)); - } - } else { - try object.put("count", data.integer(0)); - } - } - return request.render(.ok); -} diff --git a/src/app/views/options/get.zmpl b/src/app/views/options/get.zmpl deleted file mode 100644 index f2163ad..0000000 --- a/src/app/views/options/get.zmpl +++ /dev/null @@ -1 +0,0 @@ -
Number of times clicked: {.count}
diff --git a/src/app/views/options/put.zmpl b/src/app/views/options/put.zmpl deleted file mode 100644 index b4ee168..0000000 --- a/src/app/views/options/put.zmpl +++ /dev/null @@ -1 +0,0 @@ -
You chose option {.option}
diff --git a/src/app/views/quotes.zig b/src/app/views/quotes.zig new file mode 100644 index 0000000..3f68156 --- /dev/null +++ b/src/app/views/quotes.zig @@ -0,0 +1,34 @@ +const std = @import("std"); +const jetzig = @import("jetzig"); + +const Request = jetzig.http.Request; +const Data = jetzig.data.Data; +const View = jetzig.views.View; + +pub fn get(id: []const u8, request: *Request, data: *Data) anyerror!View { + var body = try data.object(); + + const random_quote = try randomQuote(request.allocator); + + if (std.mem.eql(u8, id, "random")) { + try body.put("quote", data.string(random_quote.quote)); + try body.put("author", data.string(random_quote.author)); + } else { + try body.put("quote", data.string("If you can dream it, you can achieve it.")); + try body.put("author", data.string("Zig Ziglar")); + } + + return request.render(.ok); +} + +const Quote = struct { + quote: []const u8, + author: []const u8, +}; + +// Quotes taken from: https://gist.github.com/natebass/b0a548425a73bdf8ea5c618149fe1fce +fn randomQuote(allocator: std.mem.Allocator) !Quote { + const json = try std.fs.cwd().readFileAlloc(allocator, "src/app/config/quotes.json", 12684); + const quotes = try std.json.parseFromSlice([]Quote, allocator, json, .{}); + return quotes.value[std.crypto.random.intRangeLessThan(usize, 0, quotes.value.len)]; +} diff --git a/src/app/views/quotes/get.zmpl b/src/app/views/quotes/get.zmpl new file mode 100644 index 0000000..dca9318 --- /dev/null +++ b/src/app/views/quotes/get.zmpl @@ -0,0 +1,2 @@ +
"{.quote}"
+
--{.author}
diff --git a/src/app/views/routes.zig b/src/app/views/routes.zig index 8ccc5dd..1d0a7a4 100644 --- a/src/app/views/routes.zig +++ b/src/app/views/routes.zig @@ -1,5 +1,5 @@ pub const routes = .{ - @import("foo/bar/baz.zig"), + @import("users.zig"), + @import("quotes.zig"), @import("index.zig"), - @import("options.zig"), }; diff --git a/src/app/views/users.zig b/src/app/views/users.zig new file mode 100644 index 0000000..7251a1e --- /dev/null +++ b/src/app/views/users.zig @@ -0,0 +1,15 @@ +const jetzig = @import("jetzig"); + +const Request = jetzig.http.Request; +const Data = jetzig.data.Data; +const View = jetzig.views.View; + +pub fn get(id: []const u8, request: *Request, data: *Data) anyerror!View { + var user = try data.object(); + + try user.put("email", data.string("user@example.com")); + try user.put("name", data.string("Ziggy Ziguana")); + try user.put("id", data.string(id)); + + return request.render(.ok); +} diff --git a/src/app/views/users/get.zmpl b/src/app/views/users/get.zmpl new file mode 100644 index 0000000..2f07a78 --- /dev/null +++ b/src/app/views/users/get.zmpl @@ -0,0 +1,8 @@ +
+ if (std.crypto.random.int(u1) == 1) { + User won the coin toss! + } + ID: {.id} + Name: {.name} + Email: {.email} +
diff --git a/src/app/views/zmpl.manifest.zig b/src/app/views/zmpl.manifest.zig index e917ec4..15cbd97 100644 --- a/src/app/views/zmpl.manifest.zig +++ b/src/app/views/zmpl.manifest.zig @@ -3,8 +3,6 @@ // This file should _not_ be stored in version control. pub const templates = struct { pub const index = @import(".index.zmpl.compiled.zig"); - pub const foo_bar_baz_index = @import("foo/bar/baz/.index.zmpl.compiled.zig"); - pub const foo_bar_baz_get = @import("foo/bar/baz/.get.zmpl.compiled.zig"); - pub const options_put = @import("options/.put.zmpl.compiled.zig"); - pub const options_get = @import("options/.get.zmpl.compiled.zig"); + pub const quotes_get = @import("quotes/.get.zmpl.compiled.zig"); + pub const users_get = @import("users/.get.zmpl.compiled.zig"); }; diff --git a/src/init/.gitignore b/src/init/.gitignore new file mode 100644 index 0000000..7b36f9d --- /dev/null +++ b/src/init/.gitignore @@ -0,0 +1,2 @@ +# Compiled Zmpl views: +src/app/views/**/*.compiled.zig diff --git a/src/init/build.zig b/src/init/build.zig index dc78b21..f0f5e6d 100644 --- a/src/init/build.zig +++ b/src/init/build.zig @@ -18,6 +18,19 @@ pub fn build(b: *std.Build) !void { exe.addModule("jetzig", jetzig.module("jetzig")); try b.modules.put("jetzig", jetzig.module("jetzig")); + const zmpl_dep = b.dependency( + "zmpl", + .{ + .target = target, + .optimize = optimize, + .zmpl_templates_path = @as([]const u8, "src/app/views/"), + .zmpl_manifest_path = @as([]const u8, "src/app/views/zmpl.manifest.zig"), + }, + ); + + exe.addModule("zmpl", zmpl_dep.module("zmpl")); + try b.modules.put("zmpl", zmpl_dep.module("zmpl")); + const run_cmd = b.addRunArtifact(exe); run_cmd.step.dependOn(b.getInstallStep()); diff --git a/src/init/build.zig.zon b/src/init/build.zig.zon index 2c64722..6dfea91 100644 --- a/src/init/build.zig.zon +++ b/src/init/build.zig.zon @@ -4,7 +4,10 @@ .dependencies = .{ .jetzig = .{ .url = "https://github.com/jetzig-framework/jetzig/archive/refs/tags/dev.tar.gz", - .hash = "122097efa94bcbc548aa170ea1c58afe6f21101032f8436a9324ea0435adbaa227ef", + }, + .zmpl = .{ + .url = "https://github.com/jetzig-framework/zmpl/archive/refs/tags/0.0.1.tar.gz", + .hash = "12204256376f262a58935d66a2a0b41ac0447299b7e63a4c6ff160ddcef6572cd3c7", }, }, diff --git a/src/init/public/jetzig.png b/src/init/public/jetzig.png new file mode 100644 index 0000000000000000000000000000000000000000..314d70c06242be3c69f1c1f5e50d26d3e9eda56a GIT binary patch literal 22828 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 + + + + + + + + +
+
+ +
+

Welcome to Jetzig!

+
+ + + +
+
+
+ +
Take a look at the src/app/ directory to see how this application works.
+
Visit jetzig.dev to get started.
+
+ + diff --git a/src/init/src/app/views/quotes.zig b/src/init/src/app/views/quotes.zig new file mode 100644 index 0000000..3f68156 --- /dev/null +++ b/src/init/src/app/views/quotes.zig @@ -0,0 +1,34 @@ +const std = @import("std"); +const jetzig = @import("jetzig"); + +const Request = jetzig.http.Request; +const Data = jetzig.data.Data; +const View = jetzig.views.View; + +pub fn get(id: []const u8, request: *Request, data: *Data) anyerror!View { + var body = try data.object(); + + const random_quote = try randomQuote(request.allocator); + + if (std.mem.eql(u8, id, "random")) { + try body.put("quote", data.string(random_quote.quote)); + try body.put("author", data.string(random_quote.author)); + } else { + try body.put("quote", data.string("If you can dream it, you can achieve it.")); + try body.put("author", data.string("Zig Ziglar")); + } + + return request.render(.ok); +} + +const Quote = struct { + quote: []const u8, + author: []const u8, +}; + +// Quotes taken from: https://gist.github.com/natebass/b0a548425a73bdf8ea5c618149fe1fce +fn randomQuote(allocator: std.mem.Allocator) !Quote { + const json = try std.fs.cwd().readFileAlloc(allocator, "src/app/config/quotes.json", 12684); + const quotes = try std.json.parseFromSlice([]Quote, allocator, json, .{}); + return quotes.value[std.crypto.random.intRangeLessThan(usize, 0, quotes.value.len)]; +} diff --git a/src/init/src/app/views/quotes/get.zmpl b/src/init/src/app/views/quotes/get.zmpl new file mode 100644 index 0000000..dca9318 --- /dev/null +++ b/src/init/src/app/views/quotes/get.zmpl @@ -0,0 +1,2 @@ +
"{.quote}"
+
--{.author}
diff --git a/src/jetzig.zig b/src/jetzig.zig index 96ddbd1..37ee765 100644 --- a/src/jetzig.zig +++ b/src/jetzig.zig @@ -10,6 +10,11 @@ pub const views = @import("jetzig/views.zig"); pub const colors = @import("jetzig/colors.zig"); pub const App = @import("jetzig/App.zig"); +pub const config = struct { + pub const max_bytes_request_body: usize = std.math.pow(usize, 2, 16); + pub const max_bytes_static_content: usize = std.math.pow(usize, 2, 16); +}; + pub fn init(allocator: std.mem.Allocator) !App { const args = try std.process.argsAlloc(allocator); defer std.process.argsFree(allocator, args); diff --git a/src/jetzig/http/Response.zig b/src/jetzig/http/Response.zig index 37e6add..e0cc617 100644 --- a/src/jetzig/http/Response.zig +++ b/src/jetzig/http/Response.zig @@ -7,15 +7,18 @@ const Self = @This(); allocator: std.mem.Allocator, content: []const u8, status_code: http.status_codes.StatusCode, +content_type: []const u8, pub fn init( allocator: std.mem.Allocator, content: []const u8, status_code: http.status_codes.StatusCode, + content_type: []const u8, ) Self { return .{ .status_code = status_code, .content = content, + .content_type = content_type, .allocator = allocator, }; } @@ -23,12 +26,14 @@ pub fn init( pub fn deinit(self: *const Self) void { _ = self; // self.allocator.free(self.content); + // self.allocator.free(self.content_type); } pub fn dupe(self: *const Self) !Self { return .{ .allocator = self.allocator, .status_code = self.status_code, + .content_type = try self.allocator.dupe(u8, self.content_type), .content = try self.allocator.dupe(u8, self.content), }; } diff --git a/src/jetzig/http/Server.zig b/src/jetzig/http/Server.zig index 6c41cf4..635c95d 100644 --- a/src/jetzig/http/Server.zig +++ b/src/jetzig/http/Server.zig @@ -61,7 +61,7 @@ pub fn listen(self: *Self) !void { fn processRequests(self: *Self) !void { while (true) { var response = try self.server.accept(.{ .allocator = self.allocator }); - defer response.deinit(); + errdefer response.deinit(); try response.headers.append("Connection", "close"); @@ -74,6 +74,8 @@ fn processRequests(self: *Self) !void { } }; } + + response.deinit(); } } @@ -82,7 +84,7 @@ fn processNextRequest(self: *Self, response: *std.http.Server.Response) !void { self.start_time = std.time.nanoTimestamp(); - const body = try response.reader().readAllAlloc(self.allocator, 8192); // FIXME: Configurable max body size + const body = try response.reader().readAllAlloc(self.allocator, jetzig.config.max_bytes_request_body); defer self.allocator.free(body); var arena = std.heap.ArenaAllocator.init(self.allocator); @@ -99,6 +101,7 @@ fn processNextRequest(self: *Self, response: *std.http.Server.Response) !void { while (try cookie_it.next()) |header| { try response.headers.append("Set-Cookie", header); } + try response.headers.append("Content-Type", result.value.content_type); response.status = switch (result.value.status_code) { inline else => |status_code| @field(std.http.Status, @tagName(status_code)), }; @@ -125,15 +128,39 @@ fn pageContent(self: *Self, request: *jetzig.http.Request) !jetzig.caches.Result } fn renderResponse(self: *Self, request: *jetzig.http.Request) !jetzig.http.Response { - const view = try self.matchView(request); + const static = self.matchStaticResource(request) catch |err| { + if (isUnhandledError(err)) return err; + const rendered = try self.internalServerError(request, err); + return .{ + .allocator = self.allocator, + .status_code = .internal_server_error, + .content = rendered.content, + .content_type = "text/html", + }; + }; + + if (static) |resource| return try self.renderStatic(request, resource); + + const route = try self.matchRoute(request); switch (request.requestFormat()) { - .HTML => return self.renderHTML(request, view), - .JSON => return self.renderJSON(request, view), - .UNKNOWN => return self.renderHTML(request, view), + .HTML => return self.renderHTML(request, route), + .JSON => return self.renderJSON(request, route), + .UNKNOWN => return self.renderHTML(request, route), } } +fn renderStatic(self: *Self, request: *jetzig.http.Request, resource: StaticResource) !jetzig.http.Response { + _ = request; + // TODO: Select an appropriate header from MIME type and add to request.response.headers + return .{ + .allocator = self.allocator, + .status_code = .ok, + .content = resource.content, + .content_type = "application/octet-stream", + }; +} + fn renderHTML( self: *Self, request: *jetzig.http.Request, @@ -152,6 +179,7 @@ fn renderHTML( .allocator = self.allocator, .content = rendered.content, .status_code = rendered.view.status_code, + .content_type = "text/html", }; } } @@ -160,12 +188,14 @@ fn renderHTML( .allocator = self.allocator, .content = "", .status_code = .not_found, + .content_type = "text/html", }; } else { return .{ .allocator = self.allocator, .content = "", .status_code = .not_found, + .content_type = "text/html", }; } } @@ -180,20 +210,24 @@ fn renderJSON( var data = rendered.view.data; if (data.value) |_| {} else _ = try data.object(); + try request.headers.append("Content-Type", "application/json"); return .{ .allocator = self.allocator, .content = try data.toJson(), .status_code = rendered.view.status_code, + .content_type = "application/json", }; } else return .{ .allocator = self.allocator, .content = "", .status_code = .not_found, + .content_type = "application/json", }; } const RenderedView = struct { view: jetzig.views.View, content: []const u8 }; + fn renderView( self: *Self, matched_route: jetzig.views.Route, @@ -202,16 +236,21 @@ fn renderView( ) !RenderedView { const view = matched_route.render(matched_route, request) catch |err| { self.logger.debug("Encountered error: {s}", .{@errorName(err)}); - switch (err) { - error.OutOfMemory => return err, - else => return try self.internalServerError(request, err), - } + if (isUnhandledError(err)) return err; + return try self.internalServerError(request, err); }; const content = if (template) |capture| try capture.render(view.data) else ""; return .{ .view = view, .content = content }; } +fn isUnhandledError(err: anyerror) bool { + return switch (err) { + error.OutOfMemory => true, + else => false, + }; +} + fn internalServerError(self: *Self, request: *jetzig.http.Request, err: anyerror) !RenderedView { _ = self; request.response_data.reset(); @@ -248,7 +287,7 @@ fn duration(self: *Self) i64 { return @intCast(std.time.nanoTimestamp() - self.start_time); } -fn matchView(self: *Self, request: *jetzig.http.Request) !?jetzig.views.Route { +fn matchRoute(self: *Self, request: *jetzig.http.Request) !?jetzig.views.Route { for (self.routes) |route| { if (route.action == .index and try request.match(route)) return route; } @@ -259,3 +298,34 @@ fn matchView(self: *Self, request: *jetzig.http.Request) !?jetzig.views.Route { return null; } + +const StaticResource = struct { content: []const u8, mime_type: []const u8 = undefined }; + +fn matchStaticResource(self: *Self, request: *jetzig.http.Request) !?StaticResource { + _ = self; + + if (request.path.len < 2) return null; + if (request.method != .GET) return null; + + var iterable_dir = std.fs.cwd().openIterableDir("public", .{}) catch |err| { + switch (err) { + error.FileNotFound => return null, + else => return err, + } + }; + var walker = try iterable_dir.walk(request.allocator); + while (try walker.next()) |file| { + if (file.kind != .file) continue; + + if (std.mem.eql(u8, file.path, request.path[1..])) { + return .{ + .content = try iterable_dir.dir.readFileAlloc( + request.allocator, + file.path, + jetzig.config.max_bytes_static_content, + ), + }; + } + } + return null; +}