-
-
Notifications
You must be signed in to change notification settings - Fork 84
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Parse Accept #100
Parse Accept #100
Changes from 4 commits
583ad97
698c06d
6d646b6
4988feb
c79d62c
5e347ce
31ba05d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
const std = @import("std"); | ||
const zap = @import("zap"); | ||
|
||
var gpa = std.heap.GeneralPurposeAllocator(.{ | ||
.thread_safe = true, | ||
}){}; | ||
|
||
fn on_request_verbose(r: zap.Request) void { | ||
const allocator = gpa.allocator(); | ||
const content_type: zap.ContentType = content_type: { | ||
var accept_list = std.ArrayList(zap.Request.AcceptItem).init(allocator); | ||
defer accept_list.deinit(); | ||
r.parseAccept(&accept_list) catch break :content_type .HTML; | ||
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, | ||
}); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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,69 @@ 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; | ||
} | ||
}; | ||
|
||
/// Parses `Accept:` http header into `list`, ordered from highest q factor to lowest | ||
pub fn parseAccept(self: *const Self, list: *std.ArrayList(AcceptItem)) !void { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe it makes sense to make a type alias for the ArrayList? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. And taking it one step further: instead of passing the ArrayList, let the user just pass an allocator and do the init within parseAccept. I think that would look very zig-like, so I propose: pub fn parseAccept(self: *const Self, allocator: std.mem.Allocator) !std.ArrayList(AcceptItem) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Now, regarding naming: What do you think of the more verbose pub fn parseAcceptHeaders(self: *const Self, allocator: std.mem.Allocator) !std.ArrayList(AcceptItem) |
||
const accept_str = self.getHeaderCommon(.accept) orelse return error.NoAccept; | ||
|
||
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)) { | ||
try list.insert(i, new_item); | ||
break; | ||
} | ||
} else { | ||
try list.append(new_item); | ||
} | ||
} | ||
} | ||
|
||
/// Set a response cookie | ||
pub fn setCookie(self: *const Self, args: CookieArgs) HttpError!void { | ||
const c: fio.http_cookie_args_s = .{ | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wouldn't it make sense to use a FixedBufferAllocator here? Maybe with a 1kB buffer?