mirror of
https://github.com/jetzig-framework/jetzig.git
synced 2025-05-14 22:16:08 +00:00
Provide tooling for running Jetzig in deployment
This commit is contained in:
parent
adba4756c5
commit
87bcc4c9e0
@ -39,7 +39,7 @@ pub fn build(b: *std.Build) !void {
|
|||||||
|
|
||||||
lib.root_module.addImport("zmpl", zmpl_dep.module("zmpl"));
|
lib.root_module.addImport("zmpl", zmpl_dep.module("zmpl"));
|
||||||
jetzig_module.addImport("zmpl", zmpl_dep.module("zmpl"));
|
jetzig_module.addImport("zmpl", zmpl_dep.module("zmpl"));
|
||||||
lib.root_module.addImport("args", zig_args_dep.module("args"));
|
jetzig_module.addImport("args", zig_args_dep.module("args"));
|
||||||
|
|
||||||
// This is the way to make it look nice in the zig build script
|
// This is the way to make it look nice in the zig build script
|
||||||
// If we would do it the other way around, we would have to do
|
// If we would do it the other way around, we would have to do
|
||||||
|
@ -7,8 +7,8 @@
|
|||||||
.hash = "1220e5ede084ca6b94defd466a8f8779aab151d37bf688fefb928fded6f02cde4135",
|
.hash = "1220e5ede084ca6b94defd466a8f8779aab151d37bf688fefb928fded6f02cde4135",
|
||||||
},
|
},
|
||||||
.args = .{
|
.args = .{
|
||||||
.url = "https://github.com/MasterQ32/zig-args/archive/89f18a104d9c13763b90e97d6b4ce133da8a3e2b.tar.gz",
|
.url = "https://github.com/bobf/zig-args/archive/e827c93f00e8bd95bd4b970c59593f393a6b08d5.tar.gz",
|
||||||
.hash = "12203ded54c85878eea7f12744066dcb4397177395ac49a7b2aa365bf6047b623829",
|
.hash = "12208a1de366740d11de525db7289345949f5fd46527db3f89eecc7bb49b012c0732",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -5,8 +5,8 @@
|
|||||||
|
|
||||||
.dependencies = .{
|
.dependencies = .{
|
||||||
.args = .{
|
.args = .{
|
||||||
.url = "https://github.com/MasterQ32/zig-args/archive/89f18a104d9c13763b90e97d6b4ce133da8a3e2b.tar.gz",
|
.url = "https://github.com/bobf/zig-args/archive/e827c93f00e8bd95bd4b970c59593f393a6b08d5.tar.gz",
|
||||||
.hash = "12203ded54c85878eea7f12744066dcb4397177395ac49a7b2aa365bf6047b623829",
|
.hash = "12208a1de366740d11de525db7289345949f5fd46527db3f89eecc7bb49b012c0732",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
.paths = .{
|
.paths = .{
|
||||||
|
@ -4,12 +4,13 @@ const view = @import("generate/view.zig");
|
|||||||
const partial = @import("generate/partial.zig");
|
const partial = @import("generate/partial.zig");
|
||||||
const layout = @import("generate/layout.zig");
|
const layout = @import("generate/layout.zig");
|
||||||
const middleware = @import("generate/middleware.zig");
|
const middleware = @import("generate/middleware.zig");
|
||||||
|
const secret = @import("generate/secret.zig");
|
||||||
const util = @import("../util.zig");
|
const util = @import("../util.zig");
|
||||||
|
|
||||||
/// Command line options for the `generate` command.
|
/// Command line options for the `generate` command.
|
||||||
pub const Options = struct {
|
pub const Options = struct {
|
||||||
pub const meta = .{
|
pub const meta = .{
|
||||||
.usage_summary = "[view|partial|layout|middleware] [options]",
|
.usage_summary = "[view|partial|layout|middleware|secret] [options]",
|
||||||
.full_text =
|
.full_text =
|
||||||
\\Generates scaffolding for views, middleware, and other objects in future.
|
\\Generates scaffolding for views, middleware, and other objects in future.
|
||||||
\\
|
\\
|
||||||
@ -44,7 +45,7 @@ pub fn run(
|
|||||||
try args.printHelp(Options, "jetzig generate", writer);
|
try args.printHelp(Options, "jetzig generate", writer);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var generate_type: ?enum { view, partial, layout, middleware } = null;
|
var generate_type: ?enum { view, partial, layout, middleware, secret } = null;
|
||||||
var sub_args = std.ArrayList([]const u8).init(allocator);
|
var sub_args = std.ArrayList([]const u8).init(allocator);
|
||||||
defer sub_args.deinit();
|
defer sub_args.deinit();
|
||||||
|
|
||||||
@ -57,6 +58,8 @@ pub fn run(
|
|||||||
generate_type = .layout;
|
generate_type = .layout;
|
||||||
} else if (generate_type == null and std.mem.eql(u8, arg, "middleware")) {
|
} else if (generate_type == null and std.mem.eql(u8, arg, "middleware")) {
|
||||||
generate_type = .middleware;
|
generate_type = .middleware;
|
||||||
|
} else if (generate_type == null and std.mem.eql(u8, arg, "secret")) {
|
||||||
|
generate_type = .secret;
|
||||||
} else if (generate_type == null) {
|
} else if (generate_type == null) {
|
||||||
std.debug.print("Unknown generator command: {s}\n", .{arg});
|
std.debug.print("Unknown generator command: {s}\n", .{arg});
|
||||||
return error.JetzigCommandError;
|
return error.JetzigCommandError;
|
||||||
@ -71,6 +74,7 @@ pub fn run(
|
|||||||
.partial => partial.run(allocator, cwd, sub_args.items),
|
.partial => partial.run(allocator, cwd, sub_args.items),
|
||||||
.layout => layout.run(allocator, cwd, sub_args.items),
|
.layout => layout.run(allocator, cwd, sub_args.items),
|
||||||
.middleware => middleware.run(allocator, cwd, sub_args.items),
|
.middleware => middleware.run(allocator, cwd, sub_args.items),
|
||||||
|
.secret => secret.run(allocator, cwd, sub_args.items),
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
std.debug.print("Missing sub-command. Expected: [view|partial|layout|middleware]\n", .{});
|
std.debug.print("Missing sub-command. Expected: [view|partial|layout|middleware]\n", .{});
|
||||||
|
16
cli/commands/generate/secret.zig
Normal file
16
cli/commands/generate/secret.zig
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
/// Generate a secure random secret and output to stdout.
|
||||||
|
pub fn run(allocator: std.mem.Allocator, cwd: std.fs.Dir, args: [][]const u8) !void {
|
||||||
|
_ = allocator;
|
||||||
|
_ = args;
|
||||||
|
_ = cwd;
|
||||||
|
const chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
|
||||||
|
var secret: [44]u8 = undefined;
|
||||||
|
|
||||||
|
for (0..44) |index| {
|
||||||
|
secret[index] = chars[std.crypto.random.intRangeAtMost(u8, 0, chars.len)];
|
||||||
|
}
|
||||||
|
|
||||||
|
std.debug.print("{s}\n", .{secret});
|
||||||
|
}
|
@ -33,14 +33,17 @@ pub fn init(request: *jetzig.http.Request) !*Self {
|
|||||||
/// Any calls to `request.render` or `request.redirect` will prevent further processing of the
|
/// Any calls to `request.render` or `request.redirect` will prevent further processing of the
|
||||||
/// request, including any other middleware in the chain.
|
/// request, including any other middleware in the chain.
|
||||||
pub fn afterRequest(self: *Self, request: *jetzig.http.Request) !void {
|
pub fn afterRequest(self: *Self, request: *jetzig.http.Request) !void {
|
||||||
request.server.logger.debug("[DemoMiddleware:afterRequest] my_custom_value: {s}", .{self.my_custom_value});
|
try request.server.logger.DEBUG(
|
||||||
|
"[DemoMiddleware:afterRequest] my_custom_value: {s}",
|
||||||
|
.{self.my_custom_value},
|
||||||
|
);
|
||||||
self.my_custom_value = @tagName(request.method);
|
self.my_custom_value = @tagName(request.method);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Invoked immediately before the response renders to the client.
|
/// Invoked immediately before the response renders to the client.
|
||||||
/// The response can be modified here if needed.
|
/// The response can be modified here if needed.
|
||||||
pub fn beforeResponse(self: *Self, request: *jetzig.http.Request, response: *jetzig.http.Response) !void {
|
pub fn beforeResponse(self: *Self, request: *jetzig.http.Request, response: *jetzig.http.Response) !void {
|
||||||
request.server.logger.debug(
|
try request.server.logger.DEBUG(
|
||||||
"[DemoMiddleware:beforeResponse] my_custom_value: {s}, response status: {s}",
|
"[DemoMiddleware:beforeResponse] my_custom_value: {s}, response status: {s}",
|
||||||
.{ self.my_custom_value, @tagName(response.status_code) },
|
.{ self.my_custom_value, @tagName(response.status_code) },
|
||||||
);
|
);
|
||||||
@ -51,7 +54,7 @@ pub fn beforeResponse(self: *Self, request: *jetzig.http.Request, response: *jet
|
|||||||
pub fn afterResponse(self: *Self, request: *jetzig.http.Request, response: *jetzig.http.Response) !void {
|
pub fn afterResponse(self: *Self, request: *jetzig.http.Request, response: *jetzig.http.Response) !void {
|
||||||
_ = self;
|
_ = self;
|
||||||
_ = response;
|
_ = response;
|
||||||
request.server.logger.debug("[DemoMiddleware:afterResponse] response completed", .{});
|
try request.server.logger.DEBUG("[DemoMiddleware:afterResponse] response completed", .{});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Invoked after `afterResponse` is called. Use this function to do any clean-up.
|
/// Invoked after `afterResponse` is called. Use this function to do any clean-up.
|
||||||
|
@ -5,16 +5,19 @@ pub const zmpl = @import("zmpl").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 data = @import("jetzig/data.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 middleware = @import("jetzig/middleware.zig");
|
pub const middleware = @import("jetzig/middleware.zig");
|
||||||
pub const util = @import("jetzig/util.zig");
|
pub const util = @import("jetzig/util.zig");
|
||||||
|
pub const types = @import("jetzig/types.zig");
|
||||||
|
|
||||||
/// The primary interface for a Jetzig application. Create an `App` in your application's
|
/// The primary interface for a Jetzig application. Create an `App` in your application's
|
||||||
/// `src/main.zig` and call `start` to launch the application.
|
/// `src/main.zig` and call `start` to launch the application.
|
||||||
pub const App = @import("jetzig/App.zig");
|
pub const App = @import("jetzig/App.zig");
|
||||||
|
|
||||||
|
/// Configuration options for the application server with command-line argument parsing.
|
||||||
|
pub const Environment = @import("jetzig/Environment.zig");
|
||||||
|
|
||||||
/// An HTTP request which is passed to (dynamic) view functions and provides access to params,
|
/// An HTTP request which is passed to (dynamic) view functions and provides access to params,
|
||||||
/// headers, and functions to render a response.
|
/// headers, and functions to render a response.
|
||||||
pub const Request = http.Request;
|
pub const Request = http.Request;
|
||||||
@ -43,36 +46,11 @@ pub fn init(allocator: std.mem.Allocator) !App {
|
|||||||
const args = try std.process.argsAlloc(allocator);
|
const args = try std.process.argsAlloc(allocator);
|
||||||
defer std.process.argsFree(allocator, args);
|
defer std.process.argsFree(allocator, args);
|
||||||
|
|
||||||
const host: []const u8 = if (args.len > 1)
|
const environment = Environment.init(allocator);
|
||||||
try allocator.dupe(u8, args[1])
|
|
||||||
else
|
|
||||||
try allocator.dupe(u8, "127.0.0.1");
|
|
||||||
|
|
||||||
// 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 use_cache: bool = args.len > 3 and std.mem.eql(u8, args[3], "--cache");
|
|
||||||
const server_cache = switch (use_cache) {
|
|
||||||
true => caches.Cache{ .memory_cache = caches.MemoryCache.init(allocator) },
|
|
||||||
false => caches.Cache{ .null_cache = caches.NullCache.init(allocator) },
|
|
||||||
};
|
|
||||||
var logger = loggers.Logger{ .development_logger = loggers.DevelopmentLogger.init(allocator) };
|
|
||||||
const secret = try generateSecret(allocator);
|
|
||||||
logger.debug(
|
|
||||||
"Running in development mode, using auto-generated cookie encryption key:\n {s}",
|
|
||||||
.{secret},
|
|
||||||
);
|
|
||||||
|
|
||||||
const server_options = http.Server.ServerOptions{
|
|
||||||
.cache = server_cache,
|
|
||||||
.logger = logger,
|
|
||||||
.secret = secret,
|
|
||||||
};
|
|
||||||
|
|
||||||
return .{
|
return .{
|
||||||
.server_options = server_options,
|
.server_options = try environment.getServerOptions(),
|
||||||
.allocator = allocator,
|
.allocator = allocator,
|
||||||
.host = host,
|
|
||||||
.port = port,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -154,36 +132,3 @@ pub fn route(comptime routes: anytype) []views.Route {
|
|||||||
|
|
||||||
return &detected;
|
return &detected;
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
|
||||||
|
const args = @import("args");
|
||||||
|
|
||||||
const jetzig = @import("../jetzig.zig");
|
const jetzig = @import("../jetzig.zig");
|
||||||
const mime_types = @import("mime_types").mime_types; // Generated at build time.
|
const mime_types = @import("mime_types").mime_types; // Generated at build time.
|
||||||
|
|
||||||
@ -7,8 +9,6 @@ const Self = @This();
|
|||||||
|
|
||||||
server_options: jetzig.http.Server.ServerOptions,
|
server_options: jetzig.http.Server.ServerOptions,
|
||||||
allocator: std.mem.Allocator,
|
allocator: std.mem.Allocator,
|
||||||
host: []const u8,
|
|
||||||
port: u16,
|
|
||||||
|
|
||||||
pub fn deinit(self: Self) void {
|
pub fn deinit(self: Self) void {
|
||||||
_ = self;
|
_ = self;
|
||||||
@ -48,30 +48,40 @@ pub fn start(self: Self, comptime_routes: []jetzig.views.Route) !void {
|
|||||||
self.allocator.destroy(route);
|
self.allocator.destroy(route);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (self.server_options.detach) {
|
||||||
|
const argv = try std.process.argsAlloc(self.allocator);
|
||||||
|
defer std.process.argsFree(self.allocator, argv);
|
||||||
|
var child_argv = std.ArrayList([]const u8).init(self.allocator);
|
||||||
|
for (argv) |arg| {
|
||||||
|
if (!std.mem.eql(u8, "-d", arg) and !std.mem.eql(u8, "--detach", arg)) {
|
||||||
|
try child_argv.append(arg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var child = std.process.Child.init(child_argv.items, self.allocator);
|
||||||
|
try child.spawn();
|
||||||
|
std.debug.print("Spawned child process. PID: {}. Exiting.\n", .{child.id});
|
||||||
|
std.process.exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
var server = jetzig.http.Server.init(
|
var server = jetzig.http.Server.init(
|
||||||
self.allocator,
|
self.allocator,
|
||||||
self.host,
|
|
||||||
self.port,
|
|
||||||
self.server_options,
|
self.server_options,
|
||||||
routes.items,
|
routes.items,
|
||||||
&mime_map,
|
&mime_map,
|
||||||
);
|
);
|
||||||
|
|
||||||
defer server.deinit();
|
defer server.deinit();
|
||||||
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) {
|
||||||
error.AddressInUse => {
|
error.AddressInUse => {
|
||||||
server.logger.debug(
|
try server.logger.ERROR(
|
||||||
"Socket unavailable: {s}:{} - unable to start server.\n",
|
"Socket unavailable: {s}:{} - unable to start server.\n",
|
||||||
.{ self.host, self.port },
|
.{ self.server_options.bind, self.server_options.port },
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
},
|
},
|
||||||
else => {
|
else => {
|
||||||
server.logger.debug("Encountered error: {}\nExiting.\n", .{err});
|
try server.logger.ERROR("Encountered error: {}\nExiting.\n", .{err});
|
||||||
return err;
|
return err;
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
124
src/jetzig/Environment.zig
Normal file
124
src/jetzig/Environment.zig
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
const args = @import("args");
|
||||||
|
|
||||||
|
const jetzig = @import("../jetzig.zig");
|
||||||
|
|
||||||
|
const Environment = @This();
|
||||||
|
|
||||||
|
allocator: std.mem.Allocator,
|
||||||
|
|
||||||
|
const Options = struct {
|
||||||
|
help: bool = false,
|
||||||
|
bind: []const u8 = "127.0.0.1",
|
||||||
|
port: u16 = 8080,
|
||||||
|
environment: []const u8 = "development",
|
||||||
|
log: []const u8 = "-",
|
||||||
|
@"log-error": []const u8 = "-",
|
||||||
|
@"log-level": jetzig.loggers.LogLevel = .DEBUG,
|
||||||
|
detach: bool = false,
|
||||||
|
|
||||||
|
pub const shorthands = .{
|
||||||
|
.h = "help",
|
||||||
|
.b = "bind",
|
||||||
|
.p = "port",
|
||||||
|
.e = "environment",
|
||||||
|
.d = "detach",
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const wrap_len = 80;
|
||||||
|
|
||||||
|
pub const meta = .{
|
||||||
|
.option_docs = .{
|
||||||
|
.bind = "IP address/hostname to bind to (default: 127.0.0.1)",
|
||||||
|
.port = "Port to listen on (default: 8080)",
|
||||||
|
.environment = "Load an environment configuration from src/app/environments/<environment>.zig",
|
||||||
|
.log = "Path to log file. Use '-' for stdout (default: -)",
|
||||||
|
.@"log-error" =
|
||||||
|
\\Optional path to separate error log file. Use '-' for stdout. If omitted, errors are logged to the location specified by the `log` option.
|
||||||
|
,
|
||||||
|
.@"log-level" =
|
||||||
|
\\Specify the minimum log level. Log events below the given level are ignored. Must be one of: TRACE, DEBUG, INFO, WARN, ERROR, FATAL (default: DEBUG)
|
||||||
|
,
|
||||||
|
.detach =
|
||||||
|
\\Run the server in the background. Must be used in conjunction with --log (default: false)
|
||||||
|
,
|
||||||
|
.help = "Print help and exit",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn init(allocator: std.mem.Allocator) Environment {
|
||||||
|
return .{ .allocator = allocator };
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generate server initialization options using command line args with defaults.
|
||||||
|
pub fn getServerOptions(self: Environment) !jetzig.http.Server.ServerOptions {
|
||||||
|
const options = try args.parseForCurrentProcess(Options, self.allocator, .print);
|
||||||
|
|
||||||
|
if (options.options.help) {
|
||||||
|
const writer = std.io.getStdErr().writer();
|
||||||
|
try args.printHelp(Options, options.executable_name orelse "<app-name>", writer);
|
||||||
|
std.process.exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
var logger = jetzig.loggers.Logger{
|
||||||
|
.development_logger = jetzig.loggers.DevelopmentLogger.init(
|
||||||
|
self.allocator,
|
||||||
|
try getLogFile(options.options.log),
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
if (options.options.detach and std.mem.eql(u8, options.options.log, "-")) {
|
||||||
|
try logger.ERROR("Must pass `--log` when using `--detach`.", .{});
|
||||||
|
std.process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Generate nonce per session - do research to confirm correct best practice.
|
||||||
|
const secret_len = jetzig.http.Session.Cipher.key_length + jetzig.http.Session.Cipher.nonce_length;
|
||||||
|
const secret = try self.getSecret(&logger, secret_len);
|
||||||
|
|
||||||
|
if (secret.len != secret_len) {
|
||||||
|
try logger.ERROR("Expected secret length: {}, found: {}.", .{ secret_len, secret.len });
|
||||||
|
try logger.ERROR("Use `jetzig generate secret` to create a secure secret value.", .{});
|
||||||
|
std.process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return .{
|
||||||
|
.logger = logger,
|
||||||
|
.secret = secret,
|
||||||
|
.bind = try self.allocator.dupe(u8, options.options.bind),
|
||||||
|
.port = options.options.port,
|
||||||
|
.detach = options.options.detach,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn getLogFile(path: []const u8) !std.fs.File {
|
||||||
|
if (std.mem.eql(u8, path, "-")) return std.io.getStdOut();
|
||||||
|
|
||||||
|
const file = try std.fs.createFileAbsolute(path, .{ .truncate = false });
|
||||||
|
try file.seekFromEnd(0);
|
||||||
|
return file;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn getSecret(self: Environment, logger: *jetzig.loggers.Logger, comptime len: u10) ![]const u8 {
|
||||||
|
return std.process.getEnvVarOwned(self.allocator, "JETZIG_SECRET") catch |err| {
|
||||||
|
switch (err) {
|
||||||
|
error.EnvironmentVariableNotFound => {
|
||||||
|
// TODO: Make this a failure when running in non-development mode.
|
||||||
|
const secret = try jetzig.util.generateSecret(self.allocator, len);
|
||||||
|
try logger.WARN(
|
||||||
|
"Running in development mode, using auto-generated cookie encryption key: {s}",
|
||||||
|
.{secret},
|
||||||
|
);
|
||||||
|
try logger.WARN(
|
||||||
|
"Run `jetzig generate secret` and set `JETZIG_SECRET` to remove this warning.",
|
||||||
|
.{},
|
||||||
|
);
|
||||||
|
|
||||||
|
return secret;
|
||||||
|
},
|
||||||
|
else => return err,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
@ -1,30 +0,0 @@
|
|||||||
const std = @import("std");
|
|
||||||
|
|
||||||
const http = @import("http.zig");
|
|
||||||
|
|
||||||
pub const Result = @import("caches/Result.zig");
|
|
||||||
pub const MemoryCache = @import("caches/MemoryCache.zig");
|
|
||||||
pub const NullCache = @import("caches/NullCache.zig");
|
|
||||||
|
|
||||||
pub const Cache = union(enum) {
|
|
||||||
memory_cache: MemoryCache,
|
|
||||||
null_cache: NullCache,
|
|
||||||
|
|
||||||
pub fn deinit(self: *Cache) void {
|
|
||||||
switch (self.*) {
|
|
||||||
inline else => |*case| case.deinit(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get(self: *Cache, key: []const u8) ?Result {
|
|
||||||
return switch (self.*) {
|
|
||||||
inline else => |*case| case.get(key),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn put(self: *Cache, key: []const u8, value: http.Response) !Result {
|
|
||||||
return switch (self.*) {
|
|
||||||
inline else => |*case| case.put(key, value),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
@ -1,5 +0,0 @@
|
|||||||
const std = @import("std");
|
|
||||||
|
|
||||||
pub const Result = @import("Result.zig");
|
|
||||||
pub const MemoryCache = @import("MemoryCache.zig");
|
|
||||||
pub const NullCache = @import("NullCache.zig");
|
|
@ -1,39 +0,0 @@
|
|||||||
const std = @import("std");
|
|
||||||
|
|
||||||
const http = @import("../http.zig");
|
|
||||||
const Result = @import("Result.zig");
|
|
||||||
|
|
||||||
allocator: std.mem.Allocator,
|
|
||||||
cache: std.StringHashMap(http.Response),
|
|
||||||
|
|
||||||
const Self = @This();
|
|
||||||
|
|
||||||
pub fn init(allocator: std.mem.Allocator) Self {
|
|
||||||
const cache = std.StringHashMap(http.Response).init(allocator);
|
|
||||||
|
|
||||||
return .{ .allocator = allocator, .cache = cache };
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn deinit(self: *Self) void {
|
|
||||||
var iterator = self.cache.keyIterator();
|
|
||||||
while (iterator.next()) |key| {
|
|
||||||
self.allocator.free(key.*);
|
|
||||||
}
|
|
||||||
self.cache.deinit();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get(self: *Self, key: []const u8) ?Result {
|
|
||||||
if (self.cache.get(key)) |value| {
|
|
||||||
return Result.init(self.allocator, value, true);
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn put(self: *Self, key: []const u8, value: http.Response) !Result {
|
|
||||||
const key_dupe = try self.allocator.dupe(u8, key);
|
|
||||||
const value_dupe = try value.dupe();
|
|
||||||
try self.cache.put(key_dupe, value_dupe);
|
|
||||||
|
|
||||||
return Result.init(self.allocator, value_dupe, true);
|
|
||||||
}
|
|
@ -1,27 +0,0 @@
|
|||||||
const std = @import("std");
|
|
||||||
|
|
||||||
const http = @import("../http.zig");
|
|
||||||
const Result = @import("Result.zig");
|
|
||||||
|
|
||||||
const Self = @This();
|
|
||||||
|
|
||||||
allocator: std.mem.Allocator,
|
|
||||||
|
|
||||||
pub fn init(allocator: std.mem.Allocator) Self {
|
|
||||||
return Self{ .allocator = allocator };
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn deinit(self: *const Self) void {
|
|
||||||
_ = self;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get(self: *const Self, key: []const u8) ?Result {
|
|
||||||
_ = key;
|
|
||||||
_ = self;
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn put(self: *const Self, key: []const u8, value: http.Response) !Result {
|
|
||||||
_ = key;
|
|
||||||
return Result{ .value = value, .cached = false, .allocator = self.allocator };
|
|
||||||
}
|
|
@ -1,17 +0,0 @@
|
|||||||
const std = @import("std");
|
|
||||||
|
|
||||||
const Self = @This();
|
|
||||||
|
|
||||||
const jetzig = @import("../../jetzig.zig");
|
|
||||||
|
|
||||||
value: jetzig.http.Response,
|
|
||||||
cached: bool,
|
|
||||||
allocator: std.mem.Allocator,
|
|
||||||
|
|
||||||
pub fn init(allocator: std.mem.Allocator, value: jetzig.http.Response, cached: bool) Self {
|
|
||||||
return .{ .allocator = allocator, .cached = cached, .value = value };
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn deinit(self: *const Self) void {
|
|
||||||
if (!self.cached) self.value.deinit();
|
|
||||||
}
|
|
@ -101,7 +101,7 @@ pub fn process(self: *Self) !void {
|
|||||||
self.session.parse() catch |err| {
|
self.session.parse() catch |err| {
|
||||||
switch (err) {
|
switch (err) {
|
||||||
error.JetzigInvalidSessionCookie => {
|
error.JetzigInvalidSessionCookie => {
|
||||||
self.server.logger.debug("Invalid session cookie detected. Resetting session.", .{});
|
try self.server.logger.DEBUG("Invalid session cookie detected. Resetting session.", .{});
|
||||||
try self.session.reset();
|
try self.session.reset();
|
||||||
},
|
},
|
||||||
else => return err,
|
else => return err,
|
||||||
@ -310,7 +310,9 @@ pub fn hash(self: *Self) ![]const u8 {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn fmtMethod(self: *Self) []const u8 {
|
pub fn fmtMethod(self: *Self, colorized: bool) []const u8 {
|
||||||
|
if (!colorized) return @tagName(self.method);
|
||||||
|
|
||||||
return switch (self.method) {
|
return switch (self.method) {
|
||||||
.GET => jetzig.colors.cyan("GET"),
|
.GET => jetzig.colors.cyan("GET"),
|
||||||
.PUT => jetzig.colors.yellow("PUT"),
|
.PUT => jetzig.colors.yellow("PUT"),
|
||||||
|
@ -11,37 +11,32 @@ else
|
|||||||
struct {};
|
struct {};
|
||||||
|
|
||||||
pub const ServerOptions = struct {
|
pub const ServerOptions = struct {
|
||||||
cache: jetzig.caches.Cache,
|
|
||||||
logger: jetzig.loggers.Logger,
|
logger: jetzig.loggers.Logger,
|
||||||
|
bind: []const u8,
|
||||||
|
port: u16,
|
||||||
secret: []const u8,
|
secret: []const u8,
|
||||||
|
detach: bool,
|
||||||
};
|
};
|
||||||
|
|
||||||
allocator: std.mem.Allocator,
|
allocator: std.mem.Allocator,
|
||||||
port: u16,
|
|
||||||
host: []const u8,
|
|
||||||
cache: jetzig.caches.Cache,
|
|
||||||
logger: jetzig.loggers.Logger,
|
logger: jetzig.loggers.Logger,
|
||||||
options: ServerOptions,
|
options: ServerOptions,
|
||||||
start_time: i128 = undefined,
|
start_time: i128 = undefined,
|
||||||
routes: []*jetzig.views.Route,
|
routes: []*jetzig.views.Route,
|
||||||
mime_map: *jetzig.http.mime.MimeMap,
|
mime_map: *jetzig.http.mime.MimeMap,
|
||||||
std_net_server: std.net.Server = undefined,
|
std_net_server: std.net.Server = undefined,
|
||||||
|
initialized: bool = false,
|
||||||
|
|
||||||
const Self = @This();
|
const Self = @This();
|
||||||
|
|
||||||
pub fn init(
|
pub fn init(
|
||||||
allocator: std.mem.Allocator,
|
allocator: std.mem.Allocator,
|
||||||
host: []const u8,
|
|
||||||
port: u16,
|
|
||||||
options: ServerOptions,
|
options: ServerOptions,
|
||||||
routes: []*jetzig.views.Route,
|
routes: []*jetzig.views.Route,
|
||||||
mime_map: *jetzig.http.mime.MimeMap,
|
mime_map: *jetzig.http.mime.MimeMap,
|
||||||
) Self {
|
) Self {
|
||||||
return .{
|
return .{
|
||||||
.allocator = allocator,
|
.allocator = allocator,
|
||||||
.host = host,
|
|
||||||
.port = port,
|
|
||||||
.cache = options.cache,
|
|
||||||
.logger = options.logger,
|
.logger = options.logger,
|
||||||
.options = options,
|
.options = options,
|
||||||
.routes = routes,
|
.routes = routes,
|
||||||
@ -50,15 +45,18 @@ pub fn init(
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn deinit(self: *Self) void {
|
pub fn deinit(self: *Self) void {
|
||||||
self.std_net_server.deinit();
|
if (self.initialized) self.std_net_server.deinit();
|
||||||
|
self.allocator.free(self.options.secret);
|
||||||
|
self.allocator.free(self.options.bind);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn listen(self: *Self) !void {
|
pub fn listen(self: *Self) !void {
|
||||||
const address = try std.net.Address.parseIp("127.0.0.1", 8080);
|
const address = try std.net.Address.parseIp(self.options.bind, self.options.port);
|
||||||
self.std_net_server = try address.listen(.{ .reuse_port = true });
|
self.std_net_server = try address.listen(.{ .reuse_port = true });
|
||||||
|
|
||||||
const cache_status = if (self.options.cache == .null_cache) "disabled" else "enabled";
|
self.initialized = true;
|
||||||
self.logger.debug("Listening on http://{s}:{} [cache:{s}]", .{ self.host, self.port, cache_status });
|
|
||||||
|
try self.logger.INFO("Listening on http://{s}:{}", .{ self.options.bind, self.options.port });
|
||||||
try self.processRequests();
|
try self.processRequests();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -77,7 +75,6 @@ fn processRequests(self: *Self) !void {
|
|||||||
|
|
||||||
self.processNextRequest(allocator, &std_http_server) catch |err| {
|
self.processNextRequest(allocator, &std_http_server) catch |err| {
|
||||||
if (isBadHttpError(err)) {
|
if (isBadHttpError(err)) {
|
||||||
std.debug.print("Encountered HTTP error: {s}\n", .{@errorName(err)});
|
|
||||||
std_http_server.connection.stream.close();
|
std_http_server.connection.stream.close();
|
||||||
continue;
|
continue;
|
||||||
} else return err;
|
} else return err;
|
||||||
@ -113,7 +110,7 @@ fn processNextRequest(self: *Self, allocator: std.mem.Allocator, std_http_server
|
|||||||
|
|
||||||
const log_message = try self.requestLogMessage(&request);
|
const log_message = try self.requestLogMessage(&request);
|
||||||
defer self.allocator.free(log_message);
|
defer self.allocator.free(log_message);
|
||||||
self.logger.debug("{s}", .{log_message});
|
try self.logger.INFO("{s}", .{log_message});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn renderResponse(self: *Self, request: *jetzig.http.Request) !void {
|
fn renderResponse(self: *Self, request: *jetzig.http.Request) !void {
|
||||||
@ -210,7 +207,7 @@ fn renderView(
|
|||||||
// `return request.render(.ok)`, but the actual rendered view is stored in
|
// `return request.render(.ok)`, but the actual rendered view is stored in
|
||||||
// `request.rendered_view`.
|
// `request.rendered_view`.
|
||||||
_ = route.render(route.*, request) catch |err| {
|
_ = route.render(route.*, request) catch |err| {
|
||||||
self.logger.debug("Encountered error: {s}", .{@errorName(err)});
|
try self.logger.ERROR("Encountered error: {s}", .{@errorName(err)});
|
||||||
if (isUnhandledError(err)) return err;
|
if (isUnhandledError(err)) return err;
|
||||||
if (isBadRequest(err)) return try self.renderBadRequest(request);
|
if (isBadRequest(err)) return try self.renderBadRequest(request);
|
||||||
return try self.renderInternalServerError(request, err);
|
return try self.renderInternalServerError(request, err);
|
||||||
@ -230,7 +227,7 @@ fn renderView(
|
|||||||
return .{ .view = rendered_view, .content = "" };
|
return .{ .view = rendered_view, .content = "" };
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
self.logger.debug("`request.render` was not invoked. Rendering empty content.", .{});
|
try self.logger.WARN("`request.render` was not invoked. Rendering empty content.", .{});
|
||||||
request.response_data.reset();
|
request.response_data.reset();
|
||||||
return .{
|
return .{
|
||||||
.view = .{ .data = request.response_data, .status_code = .no_content },
|
.view = .{ .data = request.response_data, .status_code = .no_content },
|
||||||
@ -254,7 +251,7 @@ fn renderTemplateWithLayout(
|
|||||||
if (zmpl.manifest.find(prefixed_name)) |layout| {
|
if (zmpl.manifest.find(prefixed_name)) |layout| {
|
||||||
return try template.renderWithLayout(layout, view.data);
|
return try template.renderWithLayout(layout, view.data);
|
||||||
} else {
|
} else {
|
||||||
self.logger.debug("Unknown layout: {s}", .{layout_name});
|
try self.logger.WARN("Unknown layout: {s}", .{layout_name});
|
||||||
return try template.render(view.data);
|
return try template.render(view.data);
|
||||||
}
|
}
|
||||||
} else return try template.render(view.data);
|
} else return try template.render(view.data);
|
||||||
@ -299,7 +296,7 @@ fn renderInternalServerError(self: *Self, request: *jetzig.http.Request, err: an
|
|||||||
try object.put("error", request.response_data.string(@errorName(err)));
|
try object.put("error", request.response_data.string(@errorName(err)));
|
||||||
|
|
||||||
const stack = @errorReturnTrace();
|
const stack = @errorReturnTrace();
|
||||||
if (stack) |capture| try self.logStackTrace(capture, request, object);
|
if (stack) |capture| try self.logStackTrace(capture, request);
|
||||||
|
|
||||||
return .{
|
return .{
|
||||||
.view = jetzig.views.View{ .data = request.response_data, .status_code = .internal_server_error },
|
.view = jetzig.views.View{ .data = request.response_data, .status_code = .internal_server_error },
|
||||||
@ -324,16 +321,13 @@ fn logStackTrace(
|
|||||||
self: *Self,
|
self: *Self,
|
||||||
stack: *std.builtin.StackTrace,
|
stack: *std.builtin.StackTrace,
|
||||||
request: *jetzig.http.Request,
|
request: *jetzig.http.Request,
|
||||||
object: *jetzig.data.Value,
|
|
||||||
) !void {
|
) !void {
|
||||||
_ = self;
|
try self.logger.ERROR("\nStack Trace:\n{}", .{stack});
|
||||||
std.debug.print("\nStack Trace:\n{}", .{stack});
|
var buf = std.ArrayList(u8).init(request.allocator);
|
||||||
var array = std.ArrayList(u8).init(request.allocator);
|
defer buf.deinit();
|
||||||
const writer = array.writer();
|
const writer = buf.writer();
|
||||||
try stack.format("", .{}, writer);
|
try stack.format("", .{}, writer);
|
||||||
// TODO: Generate an array of objects with stack trace in useful data structure instead of
|
try self.logger.ERROR("{s}", .{buf.items});
|
||||||
// dumping the whole formatted backtrace as a JSON string:
|
|
||||||
try object.put("backtrace", request.response_data.string(array.items));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn requestLogMessage(self: *Self, request: *jetzig.http.Request) ![]const u8 {
|
fn requestLogMessage(self: *Self, request: *jetzig.http.Request) ![]const u8 {
|
||||||
@ -345,13 +339,16 @@ fn requestLogMessage(self: *Self, request: *jetzig.http.Request) ![]const u8 {
|
|||||||
),
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
const formatted_duration = try jetzig.colors.duration(self.allocator, self.duration());
|
const formatted_duration = if (self.logger.isColorized())
|
||||||
|
try jetzig.colors.duration(self.allocator, self.duration())
|
||||||
|
else
|
||||||
|
try std.fmt.allocPrint(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}", .{
|
||||||
formatted_duration,
|
formatted_duration,
|
||||||
request.fmtMethod(),
|
request.fmtMethod(self.logger.isColorized()),
|
||||||
status.format(),
|
status.format(self.logger.isColorized()),
|
||||||
request.path.path,
|
request.path.path,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,7 @@ const std = @import("std");
|
|||||||
const jetzig = @import("../../jetzig.zig");
|
const jetzig = @import("../../jetzig.zig");
|
||||||
|
|
||||||
pub const cookie_name = "_jetzig-session";
|
pub const cookie_name = "_jetzig-session";
|
||||||
const Cipher = std.crypto.aead.aes_gcm.Aes256Gcm;
|
pub const Cipher = std.crypto.aead.aes_gcm.Aes256Gcm;
|
||||||
|
|
||||||
allocator: std.mem.Allocator,
|
allocator: std.mem.Allocator,
|
||||||
encryption_key: ?[]const u8,
|
encryption_key: ?[]const u8,
|
||||||
@ -99,7 +99,7 @@ fn save(self: *Self) !void {
|
|||||||
}
|
}
|
||||||
self.encrypted = try self.encrypt(json);
|
self.encrypted = try self.encrypt(json);
|
||||||
|
|
||||||
const encoded = try jetzig.base64Encode(self.allocator, self.encrypted.?);
|
const encoded = try jetzig.util.base64Encode(self.allocator, self.encrypted.?);
|
||||||
defer self.allocator.free(encoded);
|
defer self.allocator.free(encoded);
|
||||||
|
|
||||||
if (self.cookie) |*ptr| self.allocator.free(ptr.*.value);
|
if (self.cookie) |*ptr| self.allocator.free(ptr.*.value);
|
||||||
@ -113,7 +113,7 @@ fn save(self: *Self) !void {
|
|||||||
|
|
||||||
fn parseSessionCookie(self: *Self, cookie_value: []const u8) !void {
|
fn parseSessionCookie(self: *Self, cookie_value: []const u8) !void {
|
||||||
self.data = jetzig.data.Data.init(self.allocator);
|
self.data = jetzig.data.Data.init(self.allocator);
|
||||||
const decoded = try jetzig.base64Decode(self.allocator, cookie_value);
|
const decoded = try jetzig.util.base64Decode(self.allocator, cookie_value);
|
||||||
defer self.allocator.free(decoded);
|
defer self.allocator.free(decoded);
|
||||||
|
|
||||||
const buf = self.decrypt(decoded) catch |err| {
|
const buf = self.decrypt(decoded) catch |err| {
|
||||||
|
@ -73,11 +73,12 @@ pub fn StatusCodeType(comptime code: []const u8, comptime message: []const u8) t
|
|||||||
|
|
||||||
const Self = @This();
|
const Self = @This();
|
||||||
|
|
||||||
pub fn format(self: Self) []const u8 {
|
pub fn format(self: Self, colorized: bool) []const u8 {
|
||||||
_ = self;
|
_ = self;
|
||||||
|
|
||||||
const full_message = code ++ " " ++ message;
|
const full_message = code ++ " " ++ message;
|
||||||
|
|
||||||
|
if (!colorized) return full_message;
|
||||||
|
|
||||||
if (std.mem.startsWith(u8, code, "2")) {
|
if (std.mem.startsWith(u8, code, "2")) {
|
||||||
return 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")) {
|
||||||
@ -158,9 +159,9 @@ pub const TaggedStatusCode = union(StatusCode) {
|
|||||||
|
|
||||||
const Self = @This();
|
const Self = @This();
|
||||||
|
|
||||||
pub fn format(self: Self) []const u8 {
|
pub fn format(self: Self, colorized: bool) []const u8 {
|
||||||
return switch (self) {
|
return switch (self) {
|
||||||
inline else => |capture| capture.format(),
|
inline else => |capture| capture.format(colorized),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -4,18 +4,56 @@ const Self = @This();
|
|||||||
|
|
||||||
pub const DevelopmentLogger = @import("loggers/DevelopmentLogger.zig");
|
pub const DevelopmentLogger = @import("loggers/DevelopmentLogger.zig");
|
||||||
|
|
||||||
const LogLevel = enum {
|
pub const LogLevel = enum { TRACE, DEBUG, INFO, WARN, ERROR, FATAL };
|
||||||
debug,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const Logger = union(enum) {
|
pub const Logger = union(enum) {
|
||||||
development_logger: DevelopmentLogger,
|
development_logger: DevelopmentLogger,
|
||||||
|
|
||||||
pub fn debug(self: *Logger, comptime message: []const u8, args: anytype) void {
|
pub fn isColorized(self: Logger) bool {
|
||||||
|
switch (self) {
|
||||||
|
inline else => |logger| return logger.isColorized(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Log a TRACE level message to the configured logger.
|
||||||
|
pub fn TRACE(self: *const Logger, comptime message: []const u8, args: anytype) !void {
|
||||||
switch (self.*) {
|
switch (self.*) {
|
||||||
inline else => |*case| case.debug(message, args) catch |err| {
|
inline else => |*logger| try logger.TRACE(message, args),
|
||||||
std.debug.print("{}\n", .{err});
|
}
|
||||||
},
|
}
|
||||||
|
|
||||||
|
/// Log a DEBUG level message to the configured logger.
|
||||||
|
pub fn DEBUG(self: *const Logger, comptime message: []const u8, args: anytype) !void {
|
||||||
|
switch (self.*) {
|
||||||
|
inline else => |*logger| try logger.DEBUG(message, args),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Log an INFO level message to the configured logger.
|
||||||
|
pub fn INFO(self: *const Logger, comptime message: []const u8, args: anytype) !void {
|
||||||
|
switch (self.*) {
|
||||||
|
inline else => |*logger| try logger.INFO(message, args),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Log a WARN level message to the configured logger.
|
||||||
|
pub fn WARN(self: *const Logger, comptime message: []const u8, args: anytype) !void {
|
||||||
|
switch (self.*) {
|
||||||
|
inline else => |*logger| try logger.WARN(message, args),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Log an ERROR level message to the configured logger.
|
||||||
|
pub fn ERROR(self: *const Logger, comptime message: []const u8, args: anytype) !void {
|
||||||
|
switch (self.*) {
|
||||||
|
inline else => |*logger| try logger.ERROR(message, args),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Log a FATAL level message to the configured logger.
|
||||||
|
pub fn FATAL(self: *const Logger, comptime message: []const u8, args: anytype) !void {
|
||||||
|
switch (self.*) {
|
||||||
|
inline else => |*logger| try logger.FATAL(message, args),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -1,19 +1,74 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
|
||||||
|
const jetzig = @import("../../jetzig.zig");
|
||||||
|
|
||||||
const Self = @This();
|
const Self = @This();
|
||||||
const Timestamp = @import("../types/Timestamp.zig");
|
|
||||||
|
const Timestamp = jetzig.types.Timestamp;
|
||||||
|
const LogLevel = jetzig.loggers.LogLevel;
|
||||||
|
|
||||||
allocator: std.mem.Allocator,
|
allocator: std.mem.Allocator,
|
||||||
|
file: std.fs.File,
|
||||||
|
colorized: bool,
|
||||||
|
|
||||||
pub fn init(allocator: std.mem.Allocator) Self {
|
pub fn init(allocator: std.mem.Allocator, file: std.fs.File) Self {
|
||||||
return .{ .allocator = allocator };
|
return .{ .allocator = allocator, .file = file, .colorized = file.isTty() };
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn debug(self: *Self, comptime message: []const u8, args: anytype) !void {
|
/// Return true if logger was initialized with colorization (i.e. if log file is a tty)
|
||||||
|
pub fn isColorized(self: Self) bool {
|
||||||
|
return self.colorized;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Log a TRACE level message to the configured logger.
|
||||||
|
pub fn TRACE(self: *const Self, comptime message: []const u8, args: anytype) !void {
|
||||||
|
try self.log(.DEBUG, message, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Log a DEBUG level message to the configured logger.
|
||||||
|
pub fn DEBUG(self: *const Self, comptime message: []const u8, args: anytype) !void {
|
||||||
|
try self.log(.DEBUG, message, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Log an INFO level message to the configured logger.
|
||||||
|
pub fn INFO(self: *const Self, comptime message: []const u8, args: anytype) !void {
|
||||||
|
try self.log(.INFO, message, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Log a WARN level message to the configured logger.
|
||||||
|
pub fn WARN(self: *const Self, comptime message: []const u8, args: anytype) !void {
|
||||||
|
try self.log(.WARN, message, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Log an ERROR level message to the configured logger.
|
||||||
|
pub fn ERROR(self: *const Self, comptime message: []const u8, args: anytype) !void {
|
||||||
|
try self.log(.ERROR, message, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Log a FATAL level message to the configured logger.
|
||||||
|
pub fn FATAL(self: *const Self, comptime message: []const u8, args: anytype) !void {
|
||||||
|
try self.log(.FATAL, message, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn log(self: Self, comptime level: LogLevel, comptime message: []const u8, args: anytype) !void {
|
||||||
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);
|
||||||
const iso8601 = try timestamp.iso8601();
|
const iso8601 = try timestamp.iso8601();
|
||||||
defer self.allocator.free(iso8601);
|
defer self.allocator.free(iso8601);
|
||||||
std.debug.print("[{s}] {s}\n", .{ iso8601, output });
|
const writer = self.file.writer();
|
||||||
|
const level_formatted = if (self.colorized) colorizedLogLevel(level) else @tagName(level);
|
||||||
|
try writer.print("{s: >5} [{s}] {s}\n", .{ level_formatted, iso8601, output });
|
||||||
|
if (!self.file.isTty()) try self.file.sync();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn colorizedLogLevel(comptime level: LogLevel) []const u8 {
|
||||||
|
return switch (level) {
|
||||||
|
.TRACE => jetzig.colors.white(@tagName(level)),
|
||||||
|
.DEBUG => jetzig.colors.cyan(@tagName(level)),
|
||||||
|
.INFO => jetzig.colors.blue(@tagName(level) ++ " "),
|
||||||
|
.WARN => jetzig.colors.yellow(@tagName(level) ++ " "),
|
||||||
|
.ERROR => jetzig.colors.red(@tagName(level)),
|
||||||
|
.FATAL => jetzig.colors.red(@tagName(level)),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
@ -16,7 +16,7 @@ pub fn init(request: *jetzig.http.Request) !*Self {
|
|||||||
pub fn afterRequest(self: *Self, request: *jetzig.http.Request) !void {
|
pub fn afterRequest(self: *Self, request: *jetzig.http.Request) !void {
|
||||||
_ = self;
|
_ = self;
|
||||||
if (request.getHeader("HX-Target")) |target| {
|
if (request.getHeader("HX-Target")) |target| {
|
||||||
request.server.logger.debug(
|
try request.server.logger.DEBUG(
|
||||||
"[middleware-htmx] htmx request detected, disabling layout. (#{s})",
|
"[middleware-htmx] htmx request detected, disabling layout. (#{s})",
|
||||||
.{target},
|
.{target},
|
||||||
);
|
);
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
|
||||||
|
/// Compare two strings with case-insensitive matching.
|
||||||
pub fn equalStringsCaseInsensitive(expected: []const u8, actual: []const u8) bool {
|
pub fn equalStringsCaseInsensitive(expected: []const u8, actual: []const u8) bool {
|
||||||
if (expected.len != actual.len) return false;
|
if (expected.len != actual.len) return false;
|
||||||
for (expected, actual) |expected_char, actual_char| {
|
for (expected, actual) |expected_char, actual_char| {
|
||||||
@ -7,3 +8,39 @@ pub fn equalStringsCaseInsensitive(expected: []const u8, actual: []const u8) boo
|
|||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Encode arbitrary input to Base64.
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Decode arbitrary input from Base64.
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generate a secure random string of `len` characters (for cryptographic purposes).
|
||||||
|
pub fn generateSecret(allocator: std.mem.Allocator, comptime len: u10) ![]const u8 {
|
||||||
|
const chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
|
||||||
|
var secret: [len]u8 = undefined;
|
||||||
|
|
||||||
|
for (0..len) |index| {
|
||||||
|
secret[index] = chars[std.crypto.random.intRangeAtMost(u8, 0, chars.len)];
|
||||||
|
}
|
||||||
|
|
||||||
|
return try allocator.dupe(u8, &secret);
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user