Skip to content

Commit

Permalink
Merge pull request #100 from desttinghim/parseAccept
Browse files Browse the repository at this point in the history
Parse Accept
  • Loading branch information
renerocksai authored Apr 29, 2024
2 parents 5712c3b + 31ba05d commit 70c0ef1
Show file tree
Hide file tree
Showing 5 changed files with 178 additions and 27 deletions.
1 change: 1 addition & 0 deletions build.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
76 changes: 76 additions & 0 deletions examples/accept/accept.zig
Original file line number Diff line number Diff line change
@@ -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("<html><body><h1>Hello from ZAP!!!</h1></body></html>") catch return;
},
.XML => {
r.sendBody(
\\<?xml version="1.0" encoding="UTF-8"?>
\\<message>
\\ <warning>
\\ Hello from ZAP!!!
\\ </warning>
\\</message>
) 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(
\\<?xml version="1.0" encoding="UTF-8"?>
\\<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US">
\\ <body>
\\ <h1>Hello from ZAP!!!</h1>
\\ </body>
\\</html>
) 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,
});
}
18 changes: 0 additions & 18 deletions src/http.zig
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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();
}
83 changes: 74 additions & 9 deletions src/request.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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,
Expand Down Expand Up @@ -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 = .{
Expand Down
27 changes: 27 additions & 0 deletions src/zap.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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
Expand Down

0 comments on commit 70c0ef1

Please sign in to comment.