mirror of
https://github.com/zigzap/zap.git
synced 2025-10-20 15:14:08 +00:00
commit
70c0ef1100
5 changed files with 178 additions and 27 deletions
|
@ -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;
|
||||
|
|
76
examples/accept/accept.zig
Normal file
76
examples/accept/accept.zig
Normal file
|
@ -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
src/http.zig
18
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();
|
||||
}
|
||||
|
|
|
@ -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 = .{
|
||||
|
|
27
src/zap.zig
27
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
|
||||
|
|
Loading…
Add table
Reference in a new issue