diff --git a/build.zig b/build.zig
index bc08984..d8a260f 100644
--- a/build.zig
+++ b/build.zig
@@ -70,6 +70,7 @@ pub fn build(b: *std.Build) !void {
.{ .name = "middleware_with_endpoint", .src = "examples/middleware_with_endpoint/middleware_with_endpoint.zig" },
.{ .name = "senderror", .src = "examples/senderror/senderror.zig" },
.{ .name = "bindataformpost", .src = "examples/bindataformpost/bindataformpost.zig" },
+ .{ .name = "accept", .src = "examples/accept/accept.zig" },
}) |excfg| {
const ex_name = excfg.name;
const ex_src = excfg.src;
diff --git a/examples/accept/accept.zig b/examples/accept/accept.zig
new file mode 100644
index 0000000..477e973
--- /dev/null
+++ b/examples/accept/accept.zig
@@ -0,0 +1,76 @@
+const std = @import("std");
+const zap = @import("zap");
+
+var gpa = std.heap.GeneralPurposeAllocator(.{
+ .thread_safe = true,
+}){};
+
+fn on_request_verbose(r: zap.Request) void {
+ // use a local buffer for the parsed accept headers
+ var accept_buffer: [1024]u8 = undefined;
+ var fba = std.heap.FixedBufferAllocator.init(&accept_buffer);
+ const accept_allocator = fba.allocator();
+
+ const content_type: zap.ContentType = content_type: {
+ var accept_list = r.parseAcceptHeaders(accept_allocator) catch break :content_type .HTML;
+ defer accept_list.deinit();
+
+ for (accept_list.items) |accept| {
+ break :content_type accept.toContentType() orelse continue;
+ }
+ break :content_type .HTML;
+ };
+
+ r.setContentType(content_type) catch return;
+ switch (content_type) {
+ .TEXT => {
+ r.sendBody("Hello from ZAP!!!") catch return;
+ },
+ .HTML => {
+ r.sendBody("
Hello from ZAP!!!
") catch return;
+ },
+ .XML => {
+ r.sendBody(
+ \\
+ \\
+ \\
+ \\ Hello from ZAP!!!
+ \\
+ \\
+ ) catch return;
+ },
+ .JSON => {
+ var buffer: [128]u8 = undefined;
+ const json = zap.stringifyBuf(&buffer, .{ .message = "Hello from ZAP!!!" }, .{}) orelse return;
+ r.sendJson(json) catch return;
+ },
+ .XHTML => {
+ r.sendBody(
+ \\
+ \\
+ \\
+ \\ Hello from ZAP!!!
+ \\
+ \\
+ ) catch return;
+ },
+ }
+}
+
+pub fn main() !void {
+ var listener = zap.HttpListener.init(.{
+ .port = 3000,
+ .on_request = on_request_verbose,
+ .log = true,
+ .max_clients = 100000,
+ });
+ try listener.listen();
+
+ std.debug.print("Listening on 0.0.0.0:3000\n", .{});
+
+ // start worker threads
+ zap.start(.{
+ .threads = 2,
+ .workers = 2,
+ });
+}
diff --git a/src/http.zig b/src/http.zig
index 8c25558..6c2ef45 100644
--- a/src/http.zig
+++ b/src/http.zig
@@ -1,5 +1,4 @@
const std = @import("std");
-const fio = @import("fio.zig");
/// HTTP Status codes according to `rfc9110`
/// https://datatracker.ietf.org/doc/html/rfc9110#name-status-codes
@@ -149,20 +148,3 @@ pub fn methodToEnum(method: ?[]const u8) Method {
return Method.UNKNOWN;
}
}
-
-/// Registers a new mimetype to be used for files ending with the given extension.
-pub fn mimetypeRegister(file_extension: []const u8, mime_type_str: []const u8) void {
- // NOTE: facil.io is expecting a non-const pointer to u8 values, but it does not
- // not appear to actually modify the value. Here we do a const cast so
- // that it is easy to pass static strings to http_mimetype_register without
- // needing to allocate a buffer on the heap.
- const extension = @constCast(file_extension);
- const mimetype = fio.fiobj_str_new(mime_type_str.ptr, mime_type_str.len);
-
- fio.http_mimetype_register(extension.ptr, extension.len, mimetype);
-}
-
-/// Clears the Mime-Type registry (it will be empty after this call).
-pub fn mimetypeClear() void {
- fio.http_mimetype_clear();
-}
diff --git a/src/request.zig b/src/request.zig
index 24a02f5..a209531 100644
--- a/src/request.zig
+++ b/src/request.zig
@@ -6,6 +6,8 @@ const fio = @import("fio.zig");
const util = @import("util.zig");
const zap = @import("zap.zig");
+const ContentType = zap.ContentType;
+
pub const HttpError = error{
HttpSendBody,
HttpSetContentType,
@@ -16,15 +18,6 @@ pub const HttpError = error{
SendFile,
};
-/// Http Content Type enum.
-/// Needs some love.
-pub const ContentType = enum {
- TEXT,
- HTML,
- JSON,
- // TODO: more content types
-};
-
/// Key value pair of strings from HTTP parameters
pub const HttpParamStrKV = struct {
key: util.FreeOrNot,
@@ -584,6 +577,78 @@ pub fn parseCookies(self: *const Self, url_encoded: bool) void {
fio.http_parse_cookies(self.h, if (url_encoded) 1 else 0);
}
+pub const AcceptItem = struct {
+ raw: []const u8,
+ type: Fragment,
+ subtype: Fragment,
+ q: f64,
+
+ const Fragment = union(enum) {
+ glob,
+ value: []const u8,
+ };
+
+ pub fn lessThan(_: void, lhs: AcceptItem, rhs: AcceptItem) bool {
+ return lhs.q < rhs.q;
+ }
+
+ pub fn toContentType(item: AcceptItem) ?ContentType {
+ if (ContentType.string_map.get(item.raw)) |common| {
+ return common;
+ }
+ return null;
+ }
+};
+
+/// List holding access headers parsed by parseAcceptHeaders()
+const AcceptHeaderList = std.ArrayList(AcceptItem);
+
+/// Parses `Accept:` http header into `list`, ordered from highest q factor to lowest
+pub fn parseAcceptHeaders(self: *const Self, allocator: std.mem.Allocator) !AcceptHeaderList {
+ const accept_str = self.getHeaderCommon(.accept) orelse return error.NoAcceptHeader;
+
+ const comma_count = std.mem.count(u8, accept_str, ",");
+
+ var list = try AcceptHeaderList.initCapacity(allocator, comma_count + 1);
+ errdefer list.deinit();
+
+ var tok_iter = std.mem.tokenize(u8, accept_str, ", ");
+ while (tok_iter.next()) |tok| {
+ var split_iter = std.mem.split(u8, tok, ";");
+ const mimetype_str = split_iter.next().?;
+ const q_factor = q_factor: {
+ const q_factor_str = split_iter.next() orelse break :q_factor 1;
+ var eq_iter = std.mem.split(u8, q_factor_str, "=");
+ const q = eq_iter.next().?;
+ if (q[0] != 'q') break :q_factor 1;
+ const value = eq_iter.next() orelse break :q_factor 1;
+ const parsed = std.fmt.parseFloat(f64, value) catch break :q_factor 1;
+ break :q_factor parsed;
+ };
+
+ var type_split_iter = std.mem.split(u8, mimetype_str, "/");
+
+ const mimetype_type_str = type_split_iter.next() orelse continue;
+ const mimetype_subtype_str = type_split_iter.next() orelse continue;
+
+ const new_item: AcceptItem = .{
+ .raw = mimetype_str,
+ .type = if (std.mem.eql(u8, "*", mimetype_type_str)) .glob else .{ .value = mimetype_type_str },
+ .subtype = if (std.mem.eql(u8, "*", mimetype_subtype_str)) .glob else .{ .value = mimetype_subtype_str },
+ .q = q_factor,
+ };
+ for (list.items, 1..) |item, i| {
+ if (AcceptItem.lessThan({}, new_item, item)) {
+ list.insertAssumeCapacity(i, new_item);
+ break;
+ }
+ } else {
+ list.appendAssumeCapacity(new_item);
+ }
+ }
+ return list;
+}
+
/// Set a response cookie
pub fn setCookie(self: *const Self, args: CookieArgs) HttpError!void {
const c: fio.http_cookie_args_s = .{
diff --git a/src/zap.zig b/src/zap.zig
index 988e833..a766e70 100644
--- a/src/zap.zig
+++ b/src/zap.zig
@@ -122,6 +122,23 @@ pub fn startWithLogging(args: fio.fio_start_args) void {
fio.fio_start(args);
}
+/// Registers a new mimetype to be used for files ending with the given extension.
+pub fn mimetypeRegister(file_extension: []const u8, mime_type_str: []const u8) void {
+ // NOTE: facil.io is expecting a non-const pointer to u8 values, but it does not
+ // not appear to actually modify the value. Here we do a const cast so
+ // that it is easy to pass static strings to http_mimetype_register without
+ // needing to allocate a buffer on the heap.
+ const extension = @constCast(file_extension);
+ const mimetype = fio.fiobj_str_new(mime_type_str.ptr, mime_type_str.len);
+
+ fio.http_mimetype_register(extension.ptr, extension.len, mimetype);
+}
+
+/// Clears the Mime-Type registry (it will be empty after this call).
+pub fn mimetypeClear() void {
+ fio.http_mimetype_clear();
+}
+
pub const ListenError = error{
AlreadyListening,
ListenError,
@@ -142,8 +159,18 @@ pub const HttpError = error{
pub const ContentType = enum {
TEXT,
HTML,
+ XML,
JSON,
+ XHTML,
// TODO: more content types
+
+ pub const string_map = std.ComptimeStringMap(ContentType, .{
+ .{ "text/plain", .TEXT },
+ .{ "text/html", .HTML },
+ .{ "application/xml", .XML },
+ .{ "application/json", .JSON },
+ .{ "application/xhtml+xml", .XHTML },
+ });
};
/// Used internally: facilio Http request callback function type