mirror of
https://github.com/jetzig-framework/jetzig.git
synced 2025-05-14 14:06:08 +00:00
Sessions
This commit is contained in:
parent
7748b25e64
commit
65bb626bbc
@ -16,7 +16,7 @@ If you are interested in _Jetzig_ you will probably find these tools interesting
|
|||||||
* :white_check_mark: File system-based routing with [slug] matching.
|
* :white_check_mark: File system-based routing with [slug] matching.
|
||||||
* :white_check_mark: _HTML_ and _JSON_ response (inferred from extension and/or `Accept` header).
|
* :white_check_mark: _HTML_ and _JSON_ response (inferred from extension and/or `Accept` header).
|
||||||
* :white_check_mark: _JSON_-compatible response data builder.
|
* :white_check_mark: _JSON_-compatible response data builder.
|
||||||
* :white_check_mark: _HTML_ templating (see [Zmpl](https://github.com/bobf/zmpl).
|
* :white_check_mark: _HTML_ templating (see [Zmpl](https://github.com/bobf/zmpl)).
|
||||||
* :white_check_mark: Per-request arena allocator.
|
* :white_check_mark: Per-request arena allocator.
|
||||||
* :x: Sessions.
|
* :x: Sessions.
|
||||||
* :x: Cookies.
|
* :x: Cookies.
|
||||||
@ -27,6 +27,8 @@ If you are interested in _Jetzig_ you will probably find these tools interesting
|
|||||||
* :x: Custom/dynamic routes.
|
* :x: Custom/dynamic routes.
|
||||||
* :x: General-purpose cache.
|
* :x: General-purpose cache.
|
||||||
* :x: Background jobs.
|
* :x: Background jobs.
|
||||||
|
* :x: Testing helpers for testing HTTP requests/responses.
|
||||||
|
* :x: Development server auto-reload.
|
||||||
* :x: Database integration.
|
* :x: Database integration.
|
||||||
* :x: Email receipt (via SendGrid/AWS SES/etc.)
|
* :x: Email receipt (via SendGrid/AWS SES/etc.)
|
||||||
|
|
||||||
|
21
build.zig
21
build.zig
@ -23,12 +23,20 @@ pub fn build(b: *std.Build) !void {
|
|||||||
b.installArtifact(exe);
|
b.installArtifact(exe);
|
||||||
|
|
||||||
const jetzig_module = b.createModule(.{ .source_file = .{ .path = "src/jetzig.zig" } });
|
const jetzig_module = b.createModule(.{ .source_file = .{ .path = "src/jetzig.zig" } });
|
||||||
exe.addModule("jetzig", jetzig_module);
|
// exe.addModule("jetzig", jetzig_module);
|
||||||
lib.addModule("jetzig", jetzig_module);
|
// lib.addModule("jetzig", jetzig_module);
|
||||||
try b.modules.put("jetzig", jetzig_module);
|
try b.modules.put("jetzig", jetzig_module);
|
||||||
|
|
||||||
try b.env_map.put("ZMPL_TEMPLATES_PATH", "src/app/views");
|
const zmpl_module = b.dependency(
|
||||||
const zmpl_module = b.dependency("zmpl", .{ .target = target, .optimize = optimize });
|
"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"),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
lib.addModule("zmpl", zmpl_module.module("zmpl"));
|
lib.addModule("zmpl", zmpl_module.module("zmpl"));
|
||||||
exe.addModule("zmpl", zmpl_module.module("zmpl"));
|
exe.addModule("zmpl", zmpl_module.module("zmpl"));
|
||||||
|
|
||||||
@ -53,11 +61,12 @@ pub fn build(b: *std.Build) !void {
|
|||||||
run_step.dependOn(&run_cmd.step);
|
run_step.dependOn(&run_cmd.step);
|
||||||
|
|
||||||
const main_tests = b.addTest(.{
|
const main_tests = b.addTest(.{
|
||||||
.root_source_file = .{ .path = "src/main.zig" },
|
.root_source_file = .{ .path = "src/tests.zig" },
|
||||||
.target = target,
|
.target = target,
|
||||||
.optimize = optimize,
|
.optimize = optimize,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
main_tests.addModule("zmpl", zmpl_module.module("zmpl"));
|
||||||
const run_main_tests = b.addRunArtifact(main_tests);
|
const run_main_tests = b.addRunArtifact(main_tests);
|
||||||
|
|
||||||
const test_step = b.step("test", "Run library tests");
|
const test_step = b.step("test", "Run library tests");
|
||||||
@ -79,7 +88,7 @@ fn findViews(allocator: std.mem.Allocator) !std.ArrayList(*ViewItem) {
|
|||||||
const extension = std.fs.path.extension(entry.path);
|
const extension = std.fs.path.extension(entry.path);
|
||||||
const basename = std.fs.path.basename(entry.path);
|
const basename = std.fs.path.basename(entry.path);
|
||||||
if (std.mem.eql(u8, basename, "routes.zig")) continue;
|
if (std.mem.eql(u8, basename, "routes.zig")) continue;
|
||||||
if (std.mem.eql(u8, basename, "manifest.zig")) continue;
|
if (std.mem.eql(u8, basename, "zmpl.manifest.zig")) continue;
|
||||||
if (std.mem.startsWith(u8, basename, ".")) continue;
|
if (std.mem.startsWith(u8, basename, ".")) continue;
|
||||||
if (!std.mem.eql(u8, extension, ".zig")) continue;
|
if (!std.mem.eql(u8, extension, ".zig")) continue;
|
||||||
|
|
||||||
|
@ -20,8 +20,9 @@
|
|||||||
// When updating this field to a new URL, be sure to delete the corresponding
|
// When updating this field to a new URL, be sure to delete the corresponding
|
||||||
// `hash`, otherwise you are communicating that you expect to find the old hash at
|
// `hash`, otherwise you are communicating that you expect to find the old hash at
|
||||||
// the new URL.
|
// the new URL.
|
||||||
|
//
|
||||||
.url = "https://github.com/bobf/zmpl/archive/refs/tags/0.0.1.tar.gz",
|
.url = "https://github.com/bobf/zmpl/archive/refs/tags/0.0.1.tar.gz",
|
||||||
.hash = "1220eedaec5c45067b0158251fd49f3ecc9c7c4676f7e3e0567ca2b2c465fa6cd983",
|
.hash = "12205f95df0bf7a66c9d00ed76f8c3b1548eb958f9210425045c77f88ea165f20fef",
|
||||||
|
|
||||||
// This is computed from the file contents of the directory of files that is
|
// This is computed from the file contents of the directory of files that is
|
||||||
// obtained after fetching `url` and applying the inclusion rules given by
|
// obtained after fetching `url` and applying the inclusion rules given by
|
||||||
|
@ -1,48 +1,53 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
|
||||||
const root = @import("root");
|
const root = @import("root");
|
||||||
|
const Request = root.jetzig.http.Request;
|
||||||
|
const Data = root.jetzig.data.Data;
|
||||||
|
const View = root.jetzig.views.View;
|
||||||
|
|
||||||
pub fn index(request: *root.jetzig.http.Request) anyerror!root.jetzig.views.View {
|
pub fn index(request: *Request, data: *Data) anyerror!View {
|
||||||
var data = request.data();
|
|
||||||
var object = try data.object();
|
var object = try data.object();
|
||||||
|
|
||||||
try object.add("message", data.string("hello there"));
|
try request.session.put("foo", data.string("bar"));
|
||||||
try object.add("foo", data.string("foo lookup"));
|
try object.put("message", data.string("hello there"));
|
||||||
try object.add("bar", data.string("bar lookup"));
|
try object.put("foo", data.string("foo lookup"));
|
||||||
|
try object.put("bar", data.string("bar lookup"));
|
||||||
|
try object.put("session_value", (try request.session.get("foo")).?);
|
||||||
|
|
||||||
return request.render(.ok);
|
return request.render(.ok);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get(id: []const u8, request: *root.jetzig.http.Request) anyerror!root.jetzig.views.View {
|
pub fn get(id: []const u8, request: *Request, data: *Data) anyerror!View {
|
||||||
var data = request.data();
|
|
||||||
var object = try data.object();
|
var object = try data.object();
|
||||||
|
|
||||||
try object.add("message", data.string("hello there"));
|
try request.session.put("foo", data.string("bar"));
|
||||||
try object.add("other_message", data.string("hello again"));
|
try object.put("session_value", (try request.session.get("foo")).?);
|
||||||
try object.add("an_integer", data.integer(10));
|
|
||||||
try object.add("a_float", data.float(1.345));
|
try object.put("message", data.string("hello there"));
|
||||||
try object.add("a_boolean", data.boolean(true));
|
try object.put("other_message", data.string("hello again"));
|
||||||
try object.add("Null", data.Null);
|
try object.put("an_integer", data.integer(10));
|
||||||
try object.add("a_random_integer", data.integer(std.crypto.random.int(u8)));
|
try object.put("a_float", data.float(1.345));
|
||||||
try object.add("resource_id", data.string(id));
|
try object.put("a_boolean", data.boolean(true));
|
||||||
|
try object.put("Null", data.Null);
|
||||||
|
try object.put("a_random_integer", data.integer(std.crypto.random.int(u8)));
|
||||||
|
try object.put("resource_id", data.string(id));
|
||||||
|
|
||||||
var nested_object = try data.object();
|
var nested_object = try data.object();
|
||||||
try nested_object.add("nested key", data.string("nested value"));
|
try nested_object.put("nested key", data.string("nested value"));
|
||||||
try object.add("other_message", nested_object.*);
|
try object.put("other_message", nested_object.*);
|
||||||
|
|
||||||
return request.render(.ok);
|
return request.render(.ok);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn patch(id: []const u8, request: *root.jetzig.http.Request) anyerror!root.jetzig.views.View {
|
pub fn patch(id: []const u8, request: *Request, data: *Data) anyerror!View {
|
||||||
var data = request.data();
|
|
||||||
var object = try data.object();
|
var object = try data.object();
|
||||||
|
|
||||||
const integer = std.crypto.random.int(u8);
|
const integer = std.crypto.random.int(u8);
|
||||||
|
|
||||||
try object.add("message", data.string("hello there"));
|
try object.put("message", data.string("hello there"));
|
||||||
try object.add("other_message", data.string("hello again"));
|
try object.put("other_message", data.string("hello again"));
|
||||||
try object.add("other_message", data.integer(integer));
|
try object.put("other_message", data.integer(integer));
|
||||||
try object.add("id", data.string(id));
|
try object.put("id", data.string(id));
|
||||||
|
|
||||||
return request.render(.ok);
|
return request.render(.ok);
|
||||||
}
|
}
|
||||||
|
@ -1,14 +1,2 @@
|
|||||||
if (std.mem.eql(u8, try zmpl.getValueString("message"), "hello there")) {
|
<span>{.resource_id}</span>
|
||||||
const foo = "foo const";
|
<span>{.session_value}</span>
|
||||||
const bar = "bar const";
|
|
||||||
|
|
||||||
inline for (1..4) |index| {
|
|
||||||
<div>Hello {:foo}!</div>
|
|
||||||
<div>Hello {:bar}!</div>
|
|
||||||
<div>Hello {:index}!</div>
|
|
||||||
<div>Hello {.foo}!</div>
|
|
||||||
<div>Hello {.bar}!</div>
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
<div>Unexpected message</div>
|
|
||||||
}
|
|
||||||
|
@ -1 +0,0 @@
|
|||||||
<span>{.resource_id}</span>
|
|
14
src/app/views/foo/bar/baz/index.zmpl
Normal file
14
src/app/views/foo/bar/baz/index.zmpl
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
if (std.mem.eql(u8, try zmpl.getValueString("message"), "hello there")) {
|
||||||
|
const foo = "foo const";
|
||||||
|
const bar = "bar const";
|
||||||
|
|
||||||
|
inline for (1..4) |index| {
|
||||||
|
<div>Hello {:foo}!</div>
|
||||||
|
<div>Hello {:bar}!</div>
|
||||||
|
<div>Hello {:index}!</div>
|
||||||
|
<div>Hello {.foo}!</div>
|
||||||
|
<div>Hello {.bar}!</div>
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
<div>Unexpected message</div>
|
||||||
|
}
|
12
src/app/views/index.zig
Normal file
12
src/app/views/index.zig
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
const jetzig = @import("root").jetzig;
|
||||||
|
const Request = jetzig.http.Request;
|
||||||
|
const Data = jetzig.data.Data;
|
||||||
|
const View = jetzig.views.View;
|
||||||
|
|
||||||
|
pub fn index(request: *Request, data: *Data) anyerror!View {
|
||||||
|
var object = try data.object();
|
||||||
|
try object.put("foo", data.string("hello"));
|
||||||
|
return request.render(.ok);
|
||||||
|
}
|
11
src/app/views/index.zmpl
Normal file
11
src/app/views/index.zmpl
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
<script src="https://unpkg.com/htmx.org@1.9.10"></script>
|
||||||
|
|
||||||
|
<div hx-get="/options/latest" hx-trigger="every 1s"></div>
|
||||||
|
|
||||||
|
inline for (0..10) |index| {
|
||||||
|
<div>
|
||||||
|
<button hx-trigger="click" hx-put="/options/{:index}" hx-swap="innerHTML" hx-target="#option">Option #{:index}</option>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
<div id="option">Select an option.</div>
|
36
src/app/views/options.zig
Normal file
36
src/app/views/options.zig
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
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| {
|
||||||
|
try request.session.put("count", data.integer(value.integer.value + 1));
|
||||||
|
try object.put("count", data.integer(value.integer.value + 1));
|
||||||
|
} 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| {
|
||||||
|
try object.put("count", data.integer(value.integer.value + 1));
|
||||||
|
} else {
|
||||||
|
try object.put("count", data.integer(0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return request.render(.ok);
|
||||||
|
}
|
1
src/app/views/options/get.zmpl
Normal file
1
src/app/views/options/get.zmpl
Normal file
@ -0,0 +1 @@
|
|||||||
|
<div>Number of times clicked: {.count}</div>
|
1
src/app/views/options/put.zmpl
Normal file
1
src/app/views/options/put.zmpl
Normal file
@ -0,0 +1 @@
|
|||||||
|
<div>You chose option {.option}</div>
|
@ -1,3 +1,5 @@
|
|||||||
pub const routes = .{
|
pub const routes = .{
|
||||||
@import("foo/bar/baz.zig"),
|
@import("foo/bar/baz.zig"),
|
||||||
|
@import("index.zig"),
|
||||||
|
@import("options.zig"),
|
||||||
};
|
};
|
||||||
|
@ -2,6 +2,9 @@
|
|||||||
// This file is automatically generated at build time. Manual edits will be discarded.
|
// This file is automatically generated at build time. Manual edits will be discarded.
|
||||||
// This file should _not_ be stored in version control.
|
// This file should _not_ be stored in version control.
|
||||||
pub const templates = struct {
|
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 foo_bar_baz_get = @import("foo/bar/baz/.get.zmpl.compiled.zig");
|
||||||
pub const foo_bar_baz_get_id = @import("foo/bar/baz/.get[id].zmpl.compiled.zig");
|
pub const options_put = @import("options/.put.zmpl.compiled.zig");
|
||||||
|
pub const options_get = @import("options/.get.zmpl.compiled.zig");
|
||||||
};
|
};
|
@ -4,11 +4,11 @@ pub const zmpl = @import("zmpl");
|
|||||||
|
|
||||||
pub const http = @import("jetzig/http.zig");
|
pub const http = @import("jetzig/http.zig");
|
||||||
pub const loggers = @import("jetzig/loggers.zig");
|
pub const loggers = @import("jetzig/loggers.zig");
|
||||||
|
pub const data = @import("jetzig/data.zig");
|
||||||
pub const caches = @import("jetzig/caches.zig");
|
pub const caches = @import("jetzig/caches.zig");
|
||||||
pub const views = @import("jetzig/views.zig");
|
pub const views = @import("jetzig/views.zig");
|
||||||
pub const colors = @import("jetzig/colors.zig");
|
pub const colors = @import("jetzig/colors.zig");
|
||||||
pub const App = @import("jetzig/App.zig");
|
pub const App = @import("jetzig/App.zig");
|
||||||
pub const DefaultAllocator = std.heap.GeneralPurposeAllocator(.{});
|
|
||||||
|
|
||||||
pub fn init(allocator: std.mem.Allocator) !App {
|
pub fn init(allocator: std.mem.Allocator) !App {
|
||||||
const args = try std.process.argsAlloc(allocator);
|
const args = try std.process.argsAlloc(allocator);
|
||||||
@ -22,8 +22,6 @@ pub fn init(allocator: std.mem.Allocator) !App {
|
|||||||
// TODO: Fix this up with proper arg parsing
|
// TODO: Fix this up with proper arg parsing
|
||||||
const port: u16 = if (args.len > 2) try std.fmt.parseInt(u16, args[2], 10) else 8080;
|
const port: u16 = if (args.len > 2) try std.fmt.parseInt(u16, args[2], 10) else 8080;
|
||||||
const use_cache: bool = args.len > 3 and std.mem.eql(u8, args[3], "--cache");
|
const use_cache: bool = args.len > 3 and std.mem.eql(u8, args[3], "--cache");
|
||||||
var debug: bool = args.len > 3 and std.mem.eql(u8, args[3], "--debug");
|
|
||||||
debug = debug or (args.len > 4 and std.mem.eql(u8, args[4], "--debug"));
|
|
||||||
const server_cache = switch (use_cache) {
|
const server_cache = switch (use_cache) {
|
||||||
true => caches.Cache{ .memory_cache = caches.MemoryCache.init(allocator) },
|
true => caches.Cache{ .memory_cache = caches.MemoryCache.init(allocator) },
|
||||||
false => caches.Cache{ .null_cache = caches.NullCache.init(allocator) },
|
false => caches.Cache{ .null_cache = caches.NullCache.init(allocator) },
|
||||||
@ -38,11 +36,14 @@ pub fn init(allocator: std.mem.Allocator) !App {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const logger = loggers.Logger{ .development_logger = loggers.DevelopmentLogger.init(allocator, debug) };
|
const logger = loggers.Logger{ .development_logger = loggers.DevelopmentLogger.init(allocator) };
|
||||||
|
const secret = try generateSecret(allocator);
|
||||||
|
|
||||||
const server_options = http.Server.ServerOptions{
|
const server_options = http.Server.ServerOptions{
|
||||||
.cache = server_cache,
|
.cache = server_cache,
|
||||||
.logger = logger,
|
.logger = logger,
|
||||||
.root_path = root_path,
|
.root_path = root_path,
|
||||||
|
.secret = secret,
|
||||||
};
|
};
|
||||||
|
|
||||||
return .{
|
return .{
|
||||||
@ -58,27 +59,35 @@ pub fn init(allocator: std.mem.Allocator) !App {
|
|||||||
// Each detected function is stored as a Route which can be accessed at runtime to route requests
|
// Each detected function is stored as a Route which can be accessed at runtime to route requests
|
||||||
// to the appropriate View.
|
// to the appropriate View.
|
||||||
pub fn route(comptime modules: anytype) []views.Route {
|
pub fn route(comptime modules: anytype) []views.Route {
|
||||||
var size: u16 = 0;
|
var size: usize = 0;
|
||||||
|
|
||||||
for (modules) |module| {
|
for (modules) |module| {
|
||||||
const decls = @typeInfo(module).Struct.decls;
|
const decls = @typeInfo(module).Struct.decls;
|
||||||
|
|
||||||
for (decls) |_| size += 1;
|
for (decls) |decl| {
|
||||||
|
if (@hasField(views.Route.ViewType, decl.name)) size += 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var detected: [size]views.Route = undefined;
|
var detected: [size]views.Route = undefined;
|
||||||
|
var index: usize = 0;
|
||||||
|
|
||||||
for (modules, 1..) |module, module_index| {
|
for (modules) |module| {
|
||||||
const decls = @typeInfo(module).Struct.decls;
|
const decls = @typeInfo(module).Struct.decls;
|
||||||
|
|
||||||
for (decls, 1..) |decl, decl_index| {
|
for (decls) |decl| {
|
||||||
|
if (!@hasField(views.Route.ViewType, decl.name)) {
|
||||||
|
// TODO: Figure out how to log a warning here (comptime issues).
|
||||||
|
continue;
|
||||||
|
}
|
||||||
const view = @unionInit(views.Route.ViewType, decl.name, @field(module, decl.name));
|
const view = @unionInit(views.Route.ViewType, decl.name, @field(module, decl.name));
|
||||||
|
|
||||||
detected[module_index * decl_index - 1] = .{
|
detected[index] = .{
|
||||||
.name = @typeName(module),
|
.name = @typeName(module),
|
||||||
.action = @field(views.Route.Action, decl.name),
|
.action = @field(views.Route.Action, decl.name),
|
||||||
.view = view,
|
.view = view,
|
||||||
};
|
};
|
||||||
|
index += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -109,3 +118,40 @@ pub const TemplateFn = struct {
|
|||||||
name: []const u8,
|
name: []const u8,
|
||||||
render: *const fn (*zmpl.Data) anyerror![]const u8,
|
render: *const fn (*zmpl.Data) anyerror![]const u8,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub fn generateSecret(allocator: std.mem.Allocator) ![]const u8 {
|
||||||
|
const chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
|
||||||
|
var secret: [64]u8 = undefined;
|
||||||
|
|
||||||
|
for (0..64) |index| {
|
||||||
|
secret[index] = chars[std.crypto.random.intRangeAtMost(u8, 0, chars.len)];
|
||||||
|
}
|
||||||
|
|
||||||
|
return try allocator.dupe(u8, &secret);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn base64Encode(allocator: std.mem.Allocator, string: []const u8) ![]const u8 {
|
||||||
|
const encoder = std.base64.Base64Encoder.init(
|
||||||
|
std.base64.url_safe_no_pad.alphabet_chars,
|
||||||
|
std.base64.url_safe_no_pad.pad_char,
|
||||||
|
);
|
||||||
|
const size = encoder.calcSize(string.len);
|
||||||
|
const ptr = try allocator.alloc(u8, size);
|
||||||
|
_ = encoder.encode(ptr, string);
|
||||||
|
return ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn base64Decode(allocator: std.mem.Allocator, string: []const u8) ![]const u8 {
|
||||||
|
const decoder = std.base64.Base64Decoder.init(
|
||||||
|
std.base64.url_safe_no_pad.alphabet_chars,
|
||||||
|
std.base64.url_safe_no_pad.pad_char,
|
||||||
|
);
|
||||||
|
const size = try decoder.calcSizeForSlice(string);
|
||||||
|
const ptr = try allocator.alloc(u8, size);
|
||||||
|
try decoder.decode(ptr, string);
|
||||||
|
return ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
test {
|
||||||
|
@import("std").testing.refAllDeclsRecursive(@This());
|
||||||
|
}
|
||||||
|
@ -1,26 +1,21 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
|
||||||
const root = @import("root");
|
const jetzig = @import("../jetzig.zig");
|
||||||
|
|
||||||
const Self = @This();
|
const Self = @This();
|
||||||
|
|
||||||
server_options: root.jetzig.http.Server.ServerOptions,
|
server_options: jetzig.http.Server.ServerOptions,
|
||||||
allocator: std.mem.Allocator,
|
allocator: std.mem.Allocator,
|
||||||
host: []const u8,
|
host: []const u8,
|
||||||
port: u16,
|
port: u16,
|
||||||
root_path: []const u8,
|
root_path: []const u8,
|
||||||
|
|
||||||
pub fn render(self: *const Self, data: anytype) root.views.View {
|
|
||||||
_ = self;
|
|
||||||
return .{ .data = data };
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn deinit(self: Self) void {
|
pub fn deinit(self: Self) void {
|
||||||
_ = self;
|
_ = self;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn start(self: Self, views: []root.jetzig.views.Route, templates: []root.jetzig.TemplateFn) !void {
|
pub fn start(self: Self, views: []jetzig.views.Route, templates: []jetzig.TemplateFn) !void {
|
||||||
var server = root.jetzig.http.Server.init(
|
var server = jetzig.http.Server.init(
|
||||||
self.allocator,
|
self.allocator,
|
||||||
self.host,
|
self.host,
|
||||||
self.port,
|
self.port,
|
||||||
@ -32,6 +27,7 @@ pub fn start(self: Self, views: []root.jetzig.views.Route, templates: []root.jet
|
|||||||
defer server.deinit();
|
defer server.deinit();
|
||||||
defer self.allocator.free(self.root_path);
|
defer self.allocator.free(self.root_path);
|
||||||
defer self.allocator.free(self.host);
|
defer self.allocator.free(self.host);
|
||||||
|
defer self.allocator.free(server.options.secret);
|
||||||
|
|
||||||
server.listen() catch |err| {
|
server.listen() catch |err| {
|
||||||
switch (err) {
|
switch (err) {
|
||||||
|
@ -12,7 +12,7 @@ pub const Cache = union(enum) {
|
|||||||
|
|
||||||
pub fn deinit(self: *Cache) void {
|
pub fn deinit(self: *Cache) void {
|
||||||
switch (self.*) {
|
switch (self.*) {
|
||||||
inline else => |*case| try case.deinit(),
|
inline else => |*case| case.deinit(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,13 +2,13 @@ const std = @import("std");
|
|||||||
|
|
||||||
const Self = @This();
|
const Self = @This();
|
||||||
|
|
||||||
const root = @import("root");
|
const jetzig = @import("../../jetzig.zig");
|
||||||
|
|
||||||
value: root.jetzig.http.Response,
|
value: jetzig.http.Response,
|
||||||
cached: bool,
|
cached: bool,
|
||||||
allocator: std.mem.Allocator,
|
allocator: std.mem.Allocator,
|
||||||
|
|
||||||
pub fn init(allocator: std.mem.Allocator, value: root.jetzig.http.Response, cached: bool) Self {
|
pub fn init(allocator: std.mem.Allocator, value: jetzig.http.Response, cached: bool) Self {
|
||||||
return .{ .allocator = allocator, .cached = cached, .value = value };
|
return .{ .allocator = allocator, .cached = cached, .value = value };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,4 +5,6 @@ const colors = @import("colors.zig");
|
|||||||
pub const Server = @import("http/Server.zig");
|
pub const Server = @import("http/Server.zig");
|
||||||
pub const Request = @import("http/Request.zig");
|
pub const Request = @import("http/Request.zig");
|
||||||
pub const Response = @import("http/Response.zig");
|
pub const Response = @import("http/Response.zig");
|
||||||
|
pub const Session = @import("http/Session.zig");
|
||||||
|
pub const Cookies = @import("http/Cookies.zig");
|
||||||
pub const status_codes = @import("http/status_codes.zig");
|
pub const status_codes = @import("http/status_codes.zig");
|
||||||
|
180
src/jetzig/http/Cookies.zig
Normal file
180
src/jetzig/http/Cookies.zig
Normal file
@ -0,0 +1,180 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
const jetzig = @import("../../jetzig.zig");
|
||||||
|
|
||||||
|
allocator: std.mem.Allocator,
|
||||||
|
cookie_string: []const u8,
|
||||||
|
buf: std.ArrayList(u8),
|
||||||
|
cookies: std.StringArrayHashMap(*Cookie),
|
||||||
|
|
||||||
|
const Self = @This();
|
||||||
|
|
||||||
|
pub const Cookie = struct {
|
||||||
|
value: []const u8,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn init(allocator: std.mem.Allocator, cookie_string: []const u8) Self {
|
||||||
|
return .{
|
||||||
|
.allocator = allocator,
|
||||||
|
.cookie_string = cookie_string,
|
||||||
|
.cookies = std.StringArrayHashMap(*Cookie).init(allocator),
|
||||||
|
.buf = std.ArrayList(u8).init(allocator),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *Self) void {
|
||||||
|
var it = self.cookies.iterator();
|
||||||
|
while (it.next()) |item| {
|
||||||
|
self.allocator.free(item.key_ptr.*);
|
||||||
|
self.allocator.free(item.value_ptr.*.value);
|
||||||
|
self.allocator.destroy(item.value_ptr.*);
|
||||||
|
}
|
||||||
|
self.cookies.deinit();
|
||||||
|
self.buf.deinit();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get(self: *Self, key: []const u8) ?*Cookie {
|
||||||
|
return self.cookies.get(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn put(self: *Self, key: []const u8, value: Cookie) !void {
|
||||||
|
if (self.cookies.fetchSwapRemove(key)) |entry| {
|
||||||
|
self.allocator.free(entry.key);
|
||||||
|
self.allocator.free(entry.value.value);
|
||||||
|
self.allocator.destroy(entry.value);
|
||||||
|
}
|
||||||
|
const ptr = try self.allocator.create(Cookie);
|
||||||
|
ptr.* = value;
|
||||||
|
ptr.*.value = try self.allocator.dupe(u8, value.value);
|
||||||
|
try self.cookies.put(try self.allocator.dupe(u8, key), ptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const HeaderIterator = struct {
|
||||||
|
allocator: std.mem.Allocator,
|
||||||
|
cookies_iterator: std.StringArrayHashMap(*Cookie).Iterator,
|
||||||
|
|
||||||
|
pub fn init(allocator: std.mem.Allocator, cookies: *Self) HeaderIterator {
|
||||||
|
return .{ .allocator = allocator, .cookies_iterator = cookies.cookies.iterator() };
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn next(self: *HeaderIterator) !?[]const u8 {
|
||||||
|
if (self.cookies_iterator.next()) |*item| {
|
||||||
|
return try std.fmt.allocPrint(
|
||||||
|
self.allocator,
|
||||||
|
"{s}={s}; path=/; domain=localhost", // TODO: Add all options, remove hardcoded domain
|
||||||
|
.{ item.key_ptr.*, item.value_ptr.*.value },
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn headerIterator(self: *Self) HeaderIterator {
|
||||||
|
var buf = std.ArrayList([]const u8).init(self.allocator);
|
||||||
|
|
||||||
|
defer buf.deinit();
|
||||||
|
defer for (buf.items) |item| self.allocator.free(item);
|
||||||
|
|
||||||
|
return HeaderIterator.init(self.allocator, self);
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://datatracker.ietf.org/doc/html/rfc6265#section-4.2.1
|
||||||
|
// cookie-header = "Cookie:" OWS cookie-string OWS
|
||||||
|
// cookie-string = cookie-pair *( ";" SP cookie-pair )
|
||||||
|
pub fn parse(self: *Self) !void {
|
||||||
|
var key_buf = std.ArrayList(u8).init(self.allocator);
|
||||||
|
var value_buf = std.ArrayList(u8).init(self.allocator);
|
||||||
|
var key_terminated = false;
|
||||||
|
var value_started = false;
|
||||||
|
|
||||||
|
defer key_buf.deinit();
|
||||||
|
defer value_buf.deinit();
|
||||||
|
|
||||||
|
for (self.cookie_string, 0..) |char, index| {
|
||||||
|
if (char == '=') {
|
||||||
|
key_terminated = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (char == ';' or index == self.cookie_string.len - 1) {
|
||||||
|
if (char != ';') try value_buf.append(char);
|
||||||
|
try self.put(
|
||||||
|
key_buf.items,
|
||||||
|
Cookie{ .value = value_buf.items },
|
||||||
|
);
|
||||||
|
key_buf.clearAndFree();
|
||||||
|
value_buf.clearAndFree();
|
||||||
|
value_started = false;
|
||||||
|
key_terminated = false;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!key_terminated and char == ' ') continue;
|
||||||
|
|
||||||
|
if (!key_terminated) {
|
||||||
|
try key_buf.append(char);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (char == ' ' and !value_started) continue;
|
||||||
|
if (char != ' ' and !value_started) value_started = true;
|
||||||
|
|
||||||
|
if (key_terminated and value_started) {
|
||||||
|
try value_buf.append(char);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
unreachable;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
test "basic cookie string" {
|
||||||
|
const allocator = std.testing.allocator;
|
||||||
|
var cookies = Self.init(allocator, "foo=bar; baz=qux;");
|
||||||
|
defer cookies.deinit();
|
||||||
|
try cookies.parse();
|
||||||
|
try std.testing.expectEqualStrings("bar", cookies.get("foo").?.value);
|
||||||
|
try std.testing.expectEqualStrings("qux", cookies.get("baz").?.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "empty cookie string" {
|
||||||
|
const allocator = std.testing.allocator;
|
||||||
|
var cookies = Self.init(allocator, "");
|
||||||
|
defer cookies.deinit();
|
||||||
|
try cookies.parse();
|
||||||
|
}
|
||||||
|
|
||||||
|
test "cookie string with irregular spaces" {
|
||||||
|
const allocator = std.testing.allocator;
|
||||||
|
var cookies = Self.init(allocator, "foo= bar; baz= qux;");
|
||||||
|
defer cookies.deinit();
|
||||||
|
try cookies.parse();
|
||||||
|
try std.testing.expectEqualStrings("bar", cookies.get("foo").?.value);
|
||||||
|
try std.testing.expectEqualStrings("qux", cookies.get("baz").?.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "headerIterator" {
|
||||||
|
const allocator = std.testing.allocator;
|
||||||
|
var buf = std.ArrayList(u8).init(allocator);
|
||||||
|
defer buf.deinit();
|
||||||
|
|
||||||
|
const writer = buf.writer();
|
||||||
|
|
||||||
|
var cookies = Self.init(allocator, "foo=bar; baz=qux;");
|
||||||
|
defer cookies.deinit();
|
||||||
|
try cookies.parse();
|
||||||
|
|
||||||
|
var it = cookies.headerIterator();
|
||||||
|
while (try it.next()) |*header| {
|
||||||
|
try writer.writeAll(header.*);
|
||||||
|
try writer.writeAll("\n");
|
||||||
|
allocator.free(header.*);
|
||||||
|
}
|
||||||
|
|
||||||
|
try std.testing.expectEqualStrings(
|
||||||
|
\\foo=bar; path=/; domain=localhost
|
||||||
|
\\baz=qux; path=/; domain=localhost
|
||||||
|
\\
|
||||||
|
, buf.items);
|
||||||
|
}
|
@ -1,6 +1,6 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
|
||||||
const root = @import("root");
|
const jetzig = @import("../../jetzig.zig");
|
||||||
|
|
||||||
const Self = @This();
|
const Self = @This();
|
||||||
const default_content_type = "text/html";
|
const default_content_type = "text/html";
|
||||||
@ -14,16 +14,18 @@ path: []const u8,
|
|||||||
method: Method,
|
method: Method,
|
||||||
headers: std.http.Headers,
|
headers: std.http.Headers,
|
||||||
segments: std.ArrayList([]const u8),
|
segments: std.ArrayList([]const u8),
|
||||||
server: *root.jetzig.http.Server,
|
server: *jetzig.http.Server,
|
||||||
status_code: root.jetzig.http.status_codes.StatusCode = undefined,
|
session: *jetzig.http.Session,
|
||||||
response_data: root.jetzig.views.data.Data = undefined,
|
status_code: jetzig.http.status_codes.StatusCode = undefined,
|
||||||
|
response_data: *jetzig.data.Data,
|
||||||
|
cookies: *jetzig.http.Cookies,
|
||||||
|
|
||||||
pub fn init(
|
pub fn init(
|
||||||
allocator: std.mem.Allocator,
|
allocator: std.mem.Allocator,
|
||||||
server: *root.jetzig.http.Server,
|
server: *jetzig.http.Server,
|
||||||
request: std.http.Server.Request,
|
response: *std.http.Server.Response,
|
||||||
) !Self {
|
) !Self {
|
||||||
const method = switch (request.method) {
|
const method = switch (response.request.method) {
|
||||||
.DELETE => Method.DELETE,
|
.DELETE => Method.DELETE,
|
||||||
.GET => Method.GET,
|
.GET => Method.GET,
|
||||||
.PATCH => Method.PATCH,
|
.PATCH => Method.PATCH,
|
||||||
@ -35,29 +37,57 @@ pub fn init(
|
|||||||
.TRACE => Method.TRACE,
|
.TRACE => Method.TRACE,
|
||||||
};
|
};
|
||||||
|
|
||||||
var it = std.mem.splitScalar(u8, request.target, '/');
|
var it = std.mem.splitScalar(u8, response.request.target, '/');
|
||||||
var segments = std.ArrayList([]const u8).init(allocator);
|
var segments = std.ArrayList([]const u8).init(allocator);
|
||||||
while (it.next()) |segment| try segments.append(segment);
|
while (it.next()) |segment| try segments.append(segment);
|
||||||
|
|
||||||
|
var cookies = try allocator.create(jetzig.http.Cookies);
|
||||||
|
cookies.* = jetzig.http.Cookies.init(
|
||||||
|
allocator,
|
||||||
|
response.request.headers.getFirstValue("Cookie") orelse "",
|
||||||
|
);
|
||||||
|
try cookies.parse();
|
||||||
|
|
||||||
|
var session = try allocator.create(jetzig.http.Session);
|
||||||
|
session.* = jetzig.http.Session.init(allocator, cookies, server.options.secret);
|
||||||
|
session.parse() catch |err| {
|
||||||
|
switch (err) {
|
||||||
|
error.JetzigInvalidSessionCookie => {
|
||||||
|
server.logger.debug("Invalid session cookie detected. Resetting session.", .{});
|
||||||
|
try session.reset();
|
||||||
|
},
|
||||||
|
else => return err,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const response_data = try allocator.create(jetzig.data.Data);
|
||||||
|
response_data.* = jetzig.data.Data.init(allocator);
|
||||||
|
|
||||||
return .{
|
return .{
|
||||||
.allocator = allocator,
|
.allocator = allocator,
|
||||||
.path = request.target,
|
.path = response.request.target,
|
||||||
.method = method,
|
.method = method,
|
||||||
.headers = request.headers,
|
.headers = response.request.headers,
|
||||||
.server = server,
|
.server = server,
|
||||||
.segments = segments,
|
.segments = segments,
|
||||||
|
.cookies = cookies,
|
||||||
|
.session = session,
|
||||||
|
.response_data = response_data,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn deinit(self: *Self) void {
|
pub fn deinit(self: *Self) void {
|
||||||
defer self.segments.deinit();
|
self.session.deinit();
|
||||||
|
self.segments.deinit();
|
||||||
|
self.allocator.destroy(self.cookies);
|
||||||
|
self.allocator.destroy(self.session);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn render(self: *Self, status_code: root.jetzig.http.status_codes.StatusCode) root.jetzig.views.View {
|
pub fn render(self: *Self, status_code: jetzig.http.status_codes.StatusCode) jetzig.views.View {
|
||||||
return .{ .data = &self.response_data, .status_code = status_code };
|
return .{ .data = self.response_data, .status_code = status_code };
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn requestFormat(self: *Self) root.jetzig.http.Request.Format {
|
pub fn requestFormat(self: *Self) jetzig.http.Request.Format {
|
||||||
return self.extensionFormat() orelse self.acceptHeaderFormat() orelse .UNKNOWN;
|
return self.extensionFormat() orelse self.acceptHeaderFormat() orelse .UNKNOWN;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -65,7 +95,7 @@ pub fn getHeader(self: *Self, key: []const u8) ?[]const u8 {
|
|||||||
return self.headers.getFirstValue(key);
|
return self.headers.getFirstValue(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn extensionFormat(self: *Self) ?root.jetzig.http.Request.Format {
|
fn extensionFormat(self: *Self) ?jetzig.http.Request.Format {
|
||||||
const extension = std.fs.path.extension(self.path);
|
const extension = std.fs.path.extension(self.path);
|
||||||
|
|
||||||
if (std.mem.eql(u8, extension, ".html")) {
|
if (std.mem.eql(u8, extension, ".html")) {
|
||||||
@ -77,7 +107,7 @@ fn extensionFormat(self: *Self) ?root.jetzig.http.Request.Format {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn acceptHeaderFormat(self: *Self) ?root.jetzig.http.Request.Format {
|
pub fn acceptHeaderFormat(self: *Self) ?jetzig.http.Request.Format {
|
||||||
const acceptHeader = self.getHeader("Accept");
|
const acceptHeader = self.getHeader("Accept");
|
||||||
|
|
||||||
if (acceptHeader) |item| {
|
if (acceptHeader) |item| {
|
||||||
@ -96,25 +126,6 @@ pub fn hash(self: *Self) ![]const u8 {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn fullPath(self: *Self) ![]const u8 {
|
|
||||||
const base_path = try std.fs.path.join(self.allocator, &[_][]const u8{
|
|
||||||
self.server.options.root_path,
|
|
||||||
"views",
|
|
||||||
});
|
|
||||||
defer self.allocator.free(base_path);
|
|
||||||
|
|
||||||
const resource_path = try self.resourcePath();
|
|
||||||
defer self.allocator.free(resource_path);
|
|
||||||
const full_path = try std.fs.path.join(self.allocator, &[_][]const u8{
|
|
||||||
base_path,
|
|
||||||
resource_path,
|
|
||||||
self.resourceName(),
|
|
||||||
self.templateName(),
|
|
||||||
});
|
|
||||||
defer self.allocator.free(full_path);
|
|
||||||
return self.allocator.dupe(u8, full_path);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn resourceModifier(self: *Self) ?Modifier {
|
pub fn resourceModifier(self: *Self) ?Modifier {
|
||||||
const basename = std.fs.path.basename(self.segments.items[self.segments.items.len - 1]);
|
const basename = std.fs.path.basename(self.segments.items[self.segments.items.len - 1]);
|
||||||
const extension = std.fs.path.extension(basename);
|
const extension = std.fs.path.extension(basename);
|
||||||
@ -144,12 +155,18 @@ pub fn resourceId(self: *Self) []const u8 {
|
|||||||
return self.resourceName();
|
return self.resourceName();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn match(self: *Self, route: root.jetzig.views.Route) !bool {
|
pub fn match(self: *Self, route: jetzig.views.Route) !bool {
|
||||||
switch (self.method) {
|
switch (self.method) {
|
||||||
.GET => {
|
.GET => {
|
||||||
return switch (route.action) {
|
return switch (route.action) {
|
||||||
.index => std.mem.eql(u8, try self.nameWithResourceId(), route.name),
|
.index => blk: {
|
||||||
.get => std.mem.eql(u8, try self.nameWithoutResourceId(), route.name),
|
if (std.mem.eql(u8, self.path, "/") and std.mem.eql(u8, route.name, "app.views.index")) {
|
||||||
|
break :blk true;
|
||||||
|
} else {
|
||||||
|
break :blk std.mem.eql(u8, try self.fullName(), route.name);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
.get => std.mem.eql(u8, try self.fullNameWithStrippedResourceId(), route.name),
|
||||||
else => false,
|
else => false,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
@ -163,19 +180,6 @@ pub fn match(self: *Self, route: root.jetzig.views.Route) !bool {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn data(self: *Self) *root.jetzig.views.data.Data {
|
|
||||||
self.response_data = root.jetzig.views.data.Data.init(self.allocator);
|
|
||||||
return &self.response_data;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn templateName(self: *Self) []const u8 {
|
|
||||||
switch (self.method) {
|
|
||||||
.GET => return "index.html.zmpl",
|
|
||||||
.SHOW => return "[id].html.zmpl",
|
|
||||||
else => unreachable, // TODO: Missing HTTP verbs.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn isEditAction(self: *Self) bool {
|
fn isEditAction(self: *Self) bool {
|
||||||
if (self.resourceModifier()) |modifier| {
|
if (self.resourceModifier()) |modifier| {
|
||||||
return modifier == .edit;
|
return modifier == .edit;
|
||||||
@ -188,11 +192,11 @@ fn isNewAction(self: *Self) bool {
|
|||||||
} else return false;
|
} else return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn nameWithResourceId(self: *Self) ![]const u8 {
|
fn fullName(self: *Self) ![]const u8 {
|
||||||
return try self.name(true);
|
return try self.name(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn nameWithoutResourceId(self: *Self) ![]const u8 {
|
fn fullNameWithStrippedResourceId(self: *Self) ![]const u8 {
|
||||||
return try self.name(false);
|
return try self.name(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,6 +8,18 @@ allocator: std.mem.Allocator,
|
|||||||
content: []const u8,
|
content: []const u8,
|
||||||
status_code: http.status_codes.StatusCode,
|
status_code: http.status_codes.StatusCode,
|
||||||
|
|
||||||
|
pub fn init(
|
||||||
|
allocator: std.mem.Allocator,
|
||||||
|
content: []const u8,
|
||||||
|
status_code: http.status_codes.StatusCode,
|
||||||
|
) Self {
|
||||||
|
return .{
|
||||||
|
.status_code = status_code,
|
||||||
|
.content = content,
|
||||||
|
.allocator = allocator,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
pub fn deinit(self: *const Self) void {
|
pub fn deinit(self: *const Self) void {
|
||||||
_ = self;
|
_ = self;
|
||||||
// self.allocator.free(self.content);
|
// self.allocator.free(self.content);
|
||||||
|
@ -1,23 +1,24 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
|
||||||
const root = @import("root");
|
const jetzig = @import("../../jetzig.zig");
|
||||||
|
|
||||||
pub const ServerOptions = struct {
|
pub const ServerOptions = struct {
|
||||||
cache: root.jetzig.caches.Cache,
|
cache: jetzig.caches.Cache,
|
||||||
logger: root.jetzig.loggers.Logger,
|
logger: jetzig.loggers.Logger,
|
||||||
root_path: []const u8,
|
root_path: []const u8,
|
||||||
|
secret: []const u8,
|
||||||
};
|
};
|
||||||
|
|
||||||
server: std.http.Server,
|
server: std.http.Server,
|
||||||
allocator: std.mem.Allocator,
|
allocator: std.mem.Allocator,
|
||||||
port: u16,
|
port: u16,
|
||||||
host: []const u8,
|
host: []const u8,
|
||||||
cache: root.jetzig.caches.Cache,
|
cache: jetzig.caches.Cache,
|
||||||
logger: root.jetzig.loggers.Logger,
|
logger: jetzig.loggers.Logger,
|
||||||
options: ServerOptions,
|
options: ServerOptions,
|
||||||
start_time: i128 = undefined,
|
start_time: i128 = undefined,
|
||||||
routes: []root.jetzig.views.Route,
|
routes: []jetzig.views.Route,
|
||||||
templates: []root.jetzig.TemplateFn,
|
templates: []jetzig.TemplateFn,
|
||||||
|
|
||||||
const Self = @This();
|
const Self = @This();
|
||||||
|
|
||||||
@ -26,8 +27,8 @@ pub fn init(
|
|||||||
host: []const u8,
|
host: []const u8,
|
||||||
port: u16,
|
port: u16,
|
||||||
options: ServerOptions,
|
options: ServerOptions,
|
||||||
routes: []root.jetzig.views.Route,
|
routes: []jetzig.views.Route,
|
||||||
templates: []root.jetzig.TemplateFn,
|
templates: []jetzig.TemplateFn,
|
||||||
) Self {
|
) Self {
|
||||||
const server = std.http.Server.init(allocator, .{ .reuse_address = true });
|
const server = std.http.Server.init(allocator, .{ .reuse_address = true });
|
||||||
|
|
||||||
@ -59,7 +60,13 @@ pub fn listen(self: *Self) !void {
|
|||||||
|
|
||||||
fn processRequests(self: *Self) !void {
|
fn processRequests(self: *Self) !void {
|
||||||
while (true) {
|
while (true) {
|
||||||
self.processNextRequest() catch |err| {
|
var response = try self.server.accept(.{ .allocator = self.allocator });
|
||||||
|
defer response.deinit();
|
||||||
|
|
||||||
|
try response.headers.append("Connection", "close");
|
||||||
|
|
||||||
|
while (response.reset() != .closing) {
|
||||||
|
self.processNextRequest(&response) catch |err| {
|
||||||
switch (err) {
|
switch (err) {
|
||||||
error.EndOfStream => continue,
|
error.EndOfStream => continue,
|
||||||
error.ConnectionResetByPeer => continue,
|
error.ConnectionResetByPeer => continue,
|
||||||
@ -67,24 +74,31 @@ fn processRequests(self: *Self) !void {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn processNextRequest(self: *Self) !void {
|
fn processNextRequest(self: *Self, response: *std.http.Server.Response) !void {
|
||||||
var response = try self.server.accept(.{ .allocator = self.allocator });
|
|
||||||
defer response.deinit();
|
|
||||||
try response.wait();
|
try response.wait();
|
||||||
|
|
||||||
self.start_time = std.time.nanoTimestamp();
|
self.start_time = std.time.nanoTimestamp();
|
||||||
|
|
||||||
|
const body = try response.reader().readAllAlloc(self.allocator, 8192); // FIXME: Configurable max body size
|
||||||
|
defer self.allocator.free(body);
|
||||||
|
|
||||||
var arena = std.heap.ArenaAllocator.init(self.allocator);
|
var arena = std.heap.ArenaAllocator.init(self.allocator);
|
||||||
defer arena.deinit();
|
defer arena.deinit();
|
||||||
|
|
||||||
var request = try root.jetzig.http.Request.init(arena.allocator(), self, response.request);
|
var request = try jetzig.http.Request.init(arena.allocator(), self, response);
|
||||||
defer request.deinit();
|
defer request.deinit();
|
||||||
|
|
||||||
const result = try self.pageContent(&request);
|
const result = try self.pageContent(&request);
|
||||||
defer result.deinit();
|
defer result.deinit();
|
||||||
|
|
||||||
response.transfer_encoding = .{ .content_length = result.value.content.len };
|
response.transfer_encoding = .{ .content_length = result.value.content.len };
|
||||||
|
var cookie_it = request.cookies.headerIterator();
|
||||||
|
while (try cookie_it.next()) |header| {
|
||||||
|
try response.headers.append("Set-Cookie", header);
|
||||||
|
}
|
||||||
response.status = switch (result.value.status_code) {
|
response.status = switch (result.value.status_code) {
|
||||||
.ok => .ok,
|
.ok => .ok,
|
||||||
.not_found => .not_found,
|
.not_found => .not_found,
|
||||||
@ -92,6 +106,7 @@ fn processNextRequest(self: *Self) !void {
|
|||||||
|
|
||||||
try response.do();
|
try response.do();
|
||||||
try response.writeAll(result.value.content);
|
try response.writeAll(result.value.content);
|
||||||
|
|
||||||
try response.finish();
|
try response.finish();
|
||||||
|
|
||||||
const log_message = try self.requestLogMessage(&request, result);
|
const log_message = try self.requestLogMessage(&request, result);
|
||||||
@ -99,7 +114,7 @@ fn processNextRequest(self: *Self) !void {
|
|||||||
self.logger.debug("{s}", .{log_message});
|
self.logger.debug("{s}", .{log_message});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn pageContent(self: *Self, request: *root.jetzig.http.Request) !root.jetzig.caches.Result {
|
fn pageContent(self: *Self, request: *jetzig.http.Request) !jetzig.caches.Result {
|
||||||
const cache_key = try request.hash();
|
const cache_key = try request.hash();
|
||||||
|
|
||||||
if (self.cache.get(cache_key)) |item| {
|
if (self.cache.get(cache_key)) |item| {
|
||||||
@ -110,7 +125,7 @@ fn pageContent(self: *Self, request: *root.jetzig.http.Request) !root.jetzig.cac
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn renderResponse(self: *Self, request: *root.jetzig.http.Request) !root.jetzig.http.Response {
|
fn renderResponse(self: *Self, request: *jetzig.http.Request) !jetzig.http.Response {
|
||||||
const view = try self.matchView(request);
|
const view = try self.matchView(request);
|
||||||
|
|
||||||
switch (request.requestFormat()) {
|
switch (request.requestFormat()) {
|
||||||
@ -122,15 +137,16 @@ fn renderResponse(self: *Self, request: *root.jetzig.http.Request) !root.jetzig.
|
|||||||
|
|
||||||
fn renderHTML(
|
fn renderHTML(
|
||||||
self: *Self,
|
self: *Self,
|
||||||
request: *root.jetzig.http.Request,
|
request: *jetzig.http.Request,
|
||||||
route: ?root.jetzig.views.Route,
|
route: ?jetzig.views.Route,
|
||||||
) !root.jetzig.http.Response {
|
) !jetzig.http.Response {
|
||||||
if (route) |matched_route| {
|
if (route) |matched_route| {
|
||||||
const expected_name = try matched_route.templateName(self.allocator);
|
const expected_name = try matched_route.templateName(self.allocator);
|
||||||
defer self.allocator.free(expected_name);
|
defer self.allocator.free(expected_name);
|
||||||
|
|
||||||
for (self.templates) |template| {
|
for (self.templates) |template| {
|
||||||
// FIXME: Tidy this up and use a hashmap for templates instead of an array.
|
// FIXME: Tidy this up and use a hashmap for templates (or a more comprehensive
|
||||||
|
// matching system) instead of an array.
|
||||||
if (std.mem.eql(u8, expected_name, template.name)) {
|
if (std.mem.eql(u8, expected_name, template.name)) {
|
||||||
const view = try matched_route.render(matched_route, request);
|
const view = try matched_route.render(matched_route, request);
|
||||||
const content = try template.render(view.data);
|
const content = try template.render(view.data);
|
||||||
@ -154,9 +170,9 @@ fn renderHTML(
|
|||||||
|
|
||||||
fn renderJSON(
|
fn renderJSON(
|
||||||
self: *Self,
|
self: *Self,
|
||||||
request: *root.jetzig.http.Request,
|
request: *jetzig.http.Request,
|
||||||
route: ?root.jetzig.views.Route,
|
route: ?jetzig.views.Route,
|
||||||
) !root.jetzig.http.Response {
|
) !jetzig.http.Response {
|
||||||
if (route) |matched_route| {
|
if (route) |matched_route| {
|
||||||
const view = try matched_route.render(matched_route, request);
|
const view = try matched_route.render(matched_route, request);
|
||||||
var data = view.data;
|
var data = view.data;
|
||||||
@ -172,13 +188,13 @@ fn renderJSON(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
fn requestLogMessage(self: *Self, request: *root.jetzig.http.Request, result: root.jetzig.caches.Result) ![]const u8 {
|
fn requestLogMessage(self: *Self, request: *jetzig.http.Request, result: jetzig.caches.Result) ![]const u8 {
|
||||||
const status: root.jetzig.http.status_codes.TaggedStatusCode = switch (result.value.status_code) {
|
const status: jetzig.http.status_codes.TaggedStatusCode = switch (result.value.status_code) {
|
||||||
.ok => .{ .ok = .{} },
|
.ok => .{ .ok = .{} },
|
||||||
.not_found => .{ .not_found = .{} },
|
.not_found => .{ .not_found = .{} },
|
||||||
};
|
};
|
||||||
|
|
||||||
const formatted_duration = try root.jetzig.colors.duration(self.allocator, self.duration());
|
const formatted_duration = try jetzig.colors.duration(self.allocator, self.duration());
|
||||||
defer self.allocator.free(formatted_duration);
|
defer self.allocator.free(formatted_duration);
|
||||||
|
|
||||||
return try std.fmt.allocPrint(self.allocator, "[{s} {s}] {s} {s}", .{
|
return try std.fmt.allocPrint(self.allocator, "[{s} {s}] {s} {s}", .{
|
||||||
@ -193,16 +209,14 @@ fn duration(self: *Self) i64 {
|
|||||||
return @intCast(std.time.nanoTimestamp() - self.start_time);
|
return @intCast(std.time.nanoTimestamp() - self.start_time);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn matchView(self: *Self, request: *root.jetzig.http.Request) !?root.jetzig.views.Route {
|
fn matchView(self: *Self, request: *jetzig.http.Request) !?jetzig.views.Route {
|
||||||
for (self.routes) |route| {
|
for (self.routes) |route| {
|
||||||
if (route.action == .index and try request.match(route)) return route;
|
if (route.action == .index and try request.match(route)) return route;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (self.routes) |route| {
|
for (self.routes) |route| {
|
||||||
if (route.action == .get and try request.match(route)) return route;
|
if (try request.match(route)) return route;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: edit, new, update, delete
|
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
221
src/jetzig/http/Session.zig
Normal file
221
src/jetzig/http/Session.zig
Normal file
@ -0,0 +1,221 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
const jetzig = @import("../../jetzig.zig");
|
||||||
|
|
||||||
|
pub const cookie_name = "_jetzig-session";
|
||||||
|
const Cipher = std.crypto.aead.aes_gcm.Aes256Gcm;
|
||||||
|
|
||||||
|
allocator: std.mem.Allocator,
|
||||||
|
encryption_key: ?[]const u8,
|
||||||
|
cookies: *jetzig.http.Cookies,
|
||||||
|
|
||||||
|
hashmap: std.StringHashMap(jetzig.data.Value),
|
||||||
|
|
||||||
|
cookie: ?jetzig.http.Cookies.Cookie = null,
|
||||||
|
initialized: bool = false,
|
||||||
|
data: jetzig.data.Data = undefined,
|
||||||
|
state: enum { parsed, pending } = .pending,
|
||||||
|
encrypted: ?[]const u8 = null,
|
||||||
|
decrypted: ?[]const u8 = null,
|
||||||
|
encoded: ?[]const u8 = null,
|
||||||
|
|
||||||
|
const Self = @This();
|
||||||
|
|
||||||
|
pub fn init(
|
||||||
|
allocator: std.mem.Allocator,
|
||||||
|
cookies: *jetzig.http.Cookies,
|
||||||
|
encryption_key: ?[]const u8,
|
||||||
|
) Self {
|
||||||
|
return .{
|
||||||
|
.allocator = allocator,
|
||||||
|
.hashmap = std.StringHashMap(jetzig.data.Value).init(allocator),
|
||||||
|
.cookies = cookies,
|
||||||
|
.encryption_key = encryption_key,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse(self: *Self) !void {
|
||||||
|
if (self.cookies.get(cookie_name)) |cookie| {
|
||||||
|
try self.parseSessionCookie(cookie.value);
|
||||||
|
} else {
|
||||||
|
try self.reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn reset(self: *Self) !void {
|
||||||
|
self.data = jetzig.data.Data.init(self.allocator);
|
||||||
|
_ = try self.data.object();
|
||||||
|
self.state = .parsed;
|
||||||
|
try self.save();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *Self) void {
|
||||||
|
if (self.state != .parsed) return;
|
||||||
|
|
||||||
|
var it = self.hashmap.iterator();
|
||||||
|
while (it.next()) |item| {
|
||||||
|
self.allocator.destroy(item.key_ptr);
|
||||||
|
self.allocator.destroy(item.value_ptr);
|
||||||
|
}
|
||||||
|
self.hashmap.deinit();
|
||||||
|
|
||||||
|
if (self.encoded) |*ptr| self.allocator.free(ptr.*);
|
||||||
|
if (self.decrypted) |*ptr| self.allocator.free(ptr.*);
|
||||||
|
if (self.encrypted) |*ptr| self.allocator.free(ptr.*);
|
||||||
|
if (self.cookie) |*ptr| self.allocator.free(ptr.*.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get(self: *Self, key: []const u8) !?jetzig.data.Value {
|
||||||
|
if (self.state != .parsed) return error.UnparsedSessionCookie;
|
||||||
|
|
||||||
|
return switch (self.data.value.?) {
|
||||||
|
.object => self.data.value.?.object.get(key),
|
||||||
|
else => unreachable,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn put(self: *Self, key: []const u8, value: jetzig.data.Value) !void {
|
||||||
|
if (self.state != .parsed) return error.UnparsedSessionCookie;
|
||||||
|
|
||||||
|
switch (self.data.value.?) {
|
||||||
|
.object => |*object| {
|
||||||
|
try object.put(key, value);
|
||||||
|
},
|
||||||
|
else => unreachable,
|
||||||
|
}
|
||||||
|
|
||||||
|
try self.save();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn save(self: *Self) !void {
|
||||||
|
if (self.state != .parsed) return error.UnparsedSessionCookie;
|
||||||
|
|
||||||
|
const json = try self.data.toJson();
|
||||||
|
defer self.allocator.free(json);
|
||||||
|
|
||||||
|
if (self.encrypted) |*ptr| {
|
||||||
|
self.allocator.free(ptr.*);
|
||||||
|
self.encrypted = null;
|
||||||
|
}
|
||||||
|
self.encrypted = try self.encrypt(json);
|
||||||
|
|
||||||
|
const encoded = try jetzig.base64Encode(self.allocator, self.encrypted.?);
|
||||||
|
defer self.allocator.free(encoded);
|
||||||
|
|
||||||
|
if (self.cookie) |*ptr| self.allocator.free(ptr.*.value);
|
||||||
|
self.cookie = .{ .value = try self.allocator.dupe(u8, encoded) };
|
||||||
|
|
||||||
|
try self.cookies.put(
|
||||||
|
cookie_name,
|
||||||
|
self.cookie.?,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parseSessionCookie(self: *Self, cookie_value: []const u8) !void {
|
||||||
|
self.data = jetzig.data.Data.init(self.allocator);
|
||||||
|
const decoded = try jetzig.base64Decode(self.allocator, cookie_value);
|
||||||
|
defer self.allocator.free(decoded);
|
||||||
|
|
||||||
|
const buf = self.decrypt(decoded) catch |err| {
|
||||||
|
switch (err) {
|
||||||
|
error.AuthenticationFailed => return error.JetzigInvalidSessionCookie,
|
||||||
|
else => return err,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
defer self.allocator.free(buf);
|
||||||
|
if (self.decrypted) |*ptr| self.allocator.free(ptr.*);
|
||||||
|
self.decrypted = try self.allocator.dupe(u8, buf);
|
||||||
|
|
||||||
|
try self.data.fromJson(self.decrypted.?);
|
||||||
|
self.state = .parsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decrypt(self: *Self, data: []const u8) ![]const u8 {
|
||||||
|
if (self.encryption_key) |secret| {
|
||||||
|
const encrypted = data[0 .. data.len - Cipher.tag_length];
|
||||||
|
const secret_bytes = std.mem.sliceAsBytes(secret);
|
||||||
|
const key: [Cipher.key_length]u8 = secret_bytes[0..Cipher.key_length].*;
|
||||||
|
const nonce: [Cipher.nonce_length]u8 = secret_bytes[Cipher.key_length .. Cipher.key_length + Cipher.nonce_length].*;
|
||||||
|
const buf = try self.allocator.alloc(u8, data.len - Cipher.tag_length);
|
||||||
|
const additional_data = "";
|
||||||
|
var tag: [Cipher.tag_length]u8 = undefined;
|
||||||
|
std.mem.copyForwards(u8, &tag, data[data.len - Cipher.tag_length ..]);
|
||||||
|
|
||||||
|
try Cipher.decrypt(
|
||||||
|
buf,
|
||||||
|
encrypted,
|
||||||
|
tag,
|
||||||
|
additional_data,
|
||||||
|
nonce,
|
||||||
|
key,
|
||||||
|
);
|
||||||
|
return buf[0..];
|
||||||
|
} else {
|
||||||
|
return self.allocator.dupe(u8, "hello");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn encrypt(self: *Self, value: []const u8) ![]const u8 {
|
||||||
|
if (self.encryption_key) |secret| {
|
||||||
|
const secret_bytes = std.mem.sliceAsBytes(secret);
|
||||||
|
const key: [Cipher.key_length]u8 = secret_bytes[0..Cipher.key_length].*;
|
||||||
|
const nonce: [Cipher.nonce_length]u8 = secret_bytes[Cipher.key_length .. Cipher.key_length + Cipher.nonce_length].*;
|
||||||
|
const associated_data = "";
|
||||||
|
|
||||||
|
if (self.encrypted) |*val| {
|
||||||
|
self.allocator.free(val.*);
|
||||||
|
self.encrypted = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const buf = try self.allocator.alloc(u8, value.len);
|
||||||
|
defer self.allocator.free(buf);
|
||||||
|
var tag: [Cipher.tag_length]u8 = undefined;
|
||||||
|
|
||||||
|
Cipher.encrypt(buf, &tag, value, associated_data, nonce, key);
|
||||||
|
if (self.encrypted) |*ptr| self.allocator.free(ptr.*);
|
||||||
|
self.encrypted = try std.mem.concat(
|
||||||
|
self.allocator,
|
||||||
|
u8,
|
||||||
|
&[_][]const u8{ buf, tag[0..] },
|
||||||
|
);
|
||||||
|
return self.encrypted.?;
|
||||||
|
} else {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
test "put and get session key/value" {
|
||||||
|
const allocator = std.testing.allocator;
|
||||||
|
var cookies = jetzig.http.Cookies.init(allocator, "");
|
||||||
|
defer cookies.deinit();
|
||||||
|
try cookies.parse();
|
||||||
|
|
||||||
|
const secret: [Cipher.key_length + Cipher.nonce_length]u8 = [_]u8{0x69} ** (Cipher.key_length + Cipher.nonce_length);
|
||||||
|
var session = Self.init(allocator, &cookies, &secret);
|
||||||
|
defer session.deinit();
|
||||||
|
defer session.data.deinit();
|
||||||
|
|
||||||
|
var data = jetzig.data.Data.init(allocator);
|
||||||
|
defer data.deinit();
|
||||||
|
|
||||||
|
try session.parse();
|
||||||
|
try session.put("foo", data.string("bar"));
|
||||||
|
var value = (try session.get("foo")).?;
|
||||||
|
try std.testing.expectEqualStrings(try value.toString(), "bar");
|
||||||
|
}
|
||||||
|
|
||||||
|
test "get value from parsed/decrypted cookie" {
|
||||||
|
const allocator = std.testing.allocator;
|
||||||
|
var cookies = jetzig.http.Cookies.init(allocator, "_jetzig-session=GIRI22v4C9EwU_mY02_obbnX2QkdnEZenlQz2xs");
|
||||||
|
defer cookies.deinit();
|
||||||
|
try cookies.parse();
|
||||||
|
|
||||||
|
const secret: [Cipher.key_length + Cipher.nonce_length]u8 = [_]u8{0x69} ** (Cipher.key_length + Cipher.nonce_length);
|
||||||
|
var session = Self.init(allocator, &cookies, &secret);
|
||||||
|
defer session.deinit();
|
||||||
|
defer session.data.deinit();
|
||||||
|
|
||||||
|
try session.parse();
|
||||||
|
var value = (try session.get("foo")).?;
|
||||||
|
try std.testing.expectEqualStrings("bar", try value.toString());
|
||||||
|
}
|
@ -1,6 +1,6 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
|
||||||
const root = @import("root");
|
const jetzig = @import("../../jetzig.zig");
|
||||||
|
|
||||||
pub const StatusCode = enum {
|
pub const StatusCode = enum {
|
||||||
ok,
|
ok,
|
||||||
@ -20,13 +20,13 @@ pub fn StatusCodeType(comptime code: []const u8, comptime message: []const u8) t
|
|||||||
const full_message = code ++ " " ++ message;
|
const full_message = code ++ " " ++ message;
|
||||||
|
|
||||||
if (std.mem.startsWith(u8, code, "2")) {
|
if (std.mem.startsWith(u8, code, "2")) {
|
||||||
return root.jetzig.colors.green(full_message);
|
return jetzig.colors.green(full_message);
|
||||||
} else if (std.mem.startsWith(u8, code, "3")) {
|
} else if (std.mem.startsWith(u8, code, "3")) {
|
||||||
return root.jetzig.colors.blue(full_message);
|
return jetzig.colors.blue(full_message);
|
||||||
} else if (std.mem.startsWith(u8, code, "4")) {
|
} else if (std.mem.startsWith(u8, code, "4")) {
|
||||||
return root.jetzig.colors.yellow(full_message);
|
return jetzig.colors.yellow(full_message);
|
||||||
} else if (std.mem.startsWith(u8, code, "5")) {
|
} else if (std.mem.startsWith(u8, code, "5")) {
|
||||||
return root.jetzig.colors.red(full_message);
|
return jetzig.colors.red(full_message);
|
||||||
} else {
|
} else {
|
||||||
return full_message;
|
return full_message;
|
||||||
}
|
}
|
||||||
|
@ -4,15 +4,12 @@ const Self = @This();
|
|||||||
const Timestamp = @import("../types/Timestamp.zig");
|
const Timestamp = @import("../types/Timestamp.zig");
|
||||||
|
|
||||||
allocator: std.mem.Allocator,
|
allocator: std.mem.Allocator,
|
||||||
enabled: bool,
|
|
||||||
|
|
||||||
pub fn init(allocator: std.mem.Allocator, enabled: bool) Self {
|
pub fn init(allocator: std.mem.Allocator) Self {
|
||||||
return .{ .allocator = allocator, .enabled = enabled };
|
return .{ .allocator = allocator };
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn debug(self: *Self, comptime message: []const u8, args: anytype) !void {
|
pub fn debug(self: *Self, comptime message: []const u8, args: anytype) !void {
|
||||||
if (!self.enabled) return;
|
|
||||||
|
|
||||||
const output = try std.fmt.allocPrint(self.allocator, message, args);
|
const output = try std.fmt.allocPrint(self.allocator, message, args);
|
||||||
defer self.allocator.free(output);
|
defer self.allocator.free(output);
|
||||||
const timestamp = Timestamp.init(std.time.timestamp(), self.allocator);
|
const timestamp = Timestamp.init(std.time.timestamp(), self.allocator);
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
const root = @import("root");
|
const root = @import("root");
|
||||||
|
|
||||||
pub const data = @import("views/data.zig");
|
|
||||||
pub const Route = @import("views/Route.zig");
|
pub const Route = @import("views/Route.zig");
|
||||||
pub const View = @import("views/View.zig");
|
pub const View = @import("views/View.zig");
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
|
||||||
const root = @import("root");
|
const jetzig = @import("../../jetzig.zig");
|
||||||
|
|
||||||
const Self = @This();
|
const Self = @This();
|
||||||
|
|
||||||
pub const Action = enum { index, get, post, put, patch, delete };
|
pub const Action = enum { index, get, post, put, patch, delete };
|
||||||
pub const RenderFn = *const fn (Self, *root.jetzig.http.Request) anyerror!root.jetzig.views.View;
|
pub const RenderFn = *const fn (Self, *jetzig.http.Request) anyerror!jetzig.views.View;
|
||||||
|
|
||||||
const ViewWithoutId = *const fn (*root.jetzig.http.Request) anyerror!root.jetzig.views.View;
|
const ViewWithoutId = *const fn (*jetzig.http.Request, *jetzig.data.Data) anyerror!jetzig.views.View;
|
||||||
const ViewWithId = *const fn (id: []const u8, *root.jetzig.http.Request) anyerror!root.jetzig.views.View;
|
const ViewWithId = *const fn (id: []const u8, *jetzig.http.Request, *jetzig.data.Data) anyerror!jetzig.views.View;
|
||||||
|
|
||||||
pub const ViewType = union(Action) {
|
pub const ViewType = union(Action) {
|
||||||
index: ViewWithoutId,
|
index: ViewWithoutId,
|
||||||
@ -25,6 +25,9 @@ view: ViewType,
|
|||||||
render: RenderFn = renderFn,
|
render: RenderFn = renderFn,
|
||||||
|
|
||||||
pub fn templateName(self: Self, allocator: std.mem.Allocator) ![]const u8 {
|
pub fn templateName(self: Self, allocator: std.mem.Allocator) ![]const u8 {
|
||||||
|
if (std.mem.eql(u8, self.name, "app.views.index") and self.action == .index)
|
||||||
|
return try allocator.dupe(u8, "index");
|
||||||
|
|
||||||
const underscored_name = try std.mem.replaceOwned(u8, allocator, self.name, ".", "_");
|
const underscored_name = try std.mem.replaceOwned(u8, allocator, self.name, ".", "_");
|
||||||
defer allocator.free(underscored_name);
|
defer allocator.free(underscored_name);
|
||||||
|
|
||||||
@ -35,22 +38,19 @@ pub fn templateName(self: Self, allocator: std.mem.Allocator) ![]const u8 {
|
|||||||
const suffixed = try std.mem.concat(allocator, u8, &[_][]const u8{
|
const suffixed = try std.mem.concat(allocator, u8, &[_][]const u8{
|
||||||
unprefixed,
|
unprefixed,
|
||||||
"_",
|
"_",
|
||||||
switch (self.action) {
|
@tagName(self.action),
|
||||||
.get => "get_id",
|
|
||||||
else => @tagName(self.action),
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return suffixed;
|
return suffixed;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn renderFn(self: Self, request: *root.jetzig.http.Request) anyerror!root.jetzig.views.View {
|
fn renderFn(self: Self, request: *jetzig.http.Request) anyerror!jetzig.views.View {
|
||||||
switch (self.view) {
|
switch (self.view) {
|
||||||
.index => |view| return try view(request),
|
.index => |view| return try view(request, request.response_data),
|
||||||
.get => |view| return try view(request.resourceId(), request),
|
.get => |view| return try view(request.resourceId(), request, request.response_data),
|
||||||
.post => |view| return try view(request),
|
.post => |view| return try view(request, request.response_data),
|
||||||
.patch => |view| return try view(request.resourceId(), request),
|
.patch => |view| return try view(request.resourceId(), request, request.response_data),
|
||||||
.put => |view| return try view(request.resourceId(), request),
|
.put => |view| return try view(request.resourceId(), request, request.response_data),
|
||||||
.delete => |view| return try view(request.resourceId(), request),
|
.delete => |view| return try view(request.resourceId(), request, request.response_data),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
|
||||||
const root = @import("root");
|
const jetzig = @import("../../jetzig.zig");
|
||||||
|
|
||||||
data: *root.jetzig.views.data.Data,
|
data: *jetzig.data.Data,
|
||||||
status_code: root.jetzig.http.status_codes.StatusCode,
|
status_code: jetzig.http.status_codes.StatusCode,
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
|
||||||
pub const jetzig = @import("jetzig.zig");
|
pub const jetzig = @import("jetzig.zig");
|
||||||
pub const templates = @import("app/views/manifest.zig").templates;
|
pub const templates = @import("app/views/zmpl.manifest.zig").templates;
|
||||||
pub const routes = @import("app/views/routes.zig").routes;
|
pub const routes = @import("app/views/routes.zig").routes;
|
||||||
|
|
||||||
pub fn main() !void {
|
pub fn main() !void {
|
||||||
|
7
src/tests.zig
Normal file
7
src/tests.zig
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
// const Cookies = @import("http/Cookies.zig");
|
||||||
|
|
||||||
|
test {
|
||||||
|
_ = @import("jetzig.zig");
|
||||||
|
_ = @import("zmpl");
|
||||||
|
@import("std").testing.refAllDeclsRecursive(@This());
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user