1
0
Fork 0
mirror of https://github.com/zigzap/zap.git synced 2025-10-20 23:24:09 +00:00

Mega update! SimpleHttpRequest got Parameters!

parseBody() : parse form params
parseQuery() : parse query params
getParamCount() : returns number of parsed params
parametersToOwnedStrList() : get params as kv pairs of strings
parametersToOwnedList() : get params as kv list
getParamStr() : get parameter by name
This commit is contained in:
Rene Schallner 2023-05-06 02:38:35 +02:00
parent e679039bae
commit d0be271337
11 changed files with 647 additions and 31 deletions

View file

@ -26,5 +26,7 @@ jobs:
run: zig version run: zig version
- name: Build simple endpoint example - name: Build simple endpoint example
run: zig build endpoint run: zig build endpoint
- name: Build tests - name: Build authentication tests
run: zig build test run: zig build test-authentication
- name: Build http parameter tests
run: zig build test-httpparams

1
.gitignore vendored
View file

@ -6,3 +6,4 @@ tmp/
**/target/* **/target/*
**/__pycache__/* **/__pycache__/*
wrk/go/main wrk/go/main
scratch

View file

@ -41,7 +41,9 @@ Here's what works:
- **[mustache](examples/mustache/)**: a simple example using - **[mustache](examples/mustache/)**: a simple example using
[mustache](https://mustache.github.io/) templating. [mustache](https://mustache.github.io/) templating.
- **[endpoint authentication](examples/endpoint_auth/)**: a simple authenticated - **[endpoint authentication](examples/endpoint_auth/)**: a simple authenticated
endpoint. Read more about authentication [here](./doc/authentication.md) endpoint. Read more about authentication [here](./doc/authentication.md).
- **[http parameters](examples/http_parameters/)**: a simple example sending
itself query parameters of all supported types.
I'll continue wrapping more of facil.io's functionality and adding stuff to zap I'll continue wrapping more of facil.io's functionality and adding stuff to zap

View file

@ -50,6 +50,7 @@ pub fn build(b: *std.build.Builder) !void {
.{ .name = "wrk_zigstd", .src = "wrk/zigstd/main.zig" }, .{ .name = "wrk_zigstd", .src = "wrk/zigstd/main.zig" },
.{ .name = "mustache", .src = "examples/mustache/mustache.zig" }, .{ .name = "mustache", .src = "examples/mustache/mustache.zig" },
.{ .name = "endpoint_auth", .src = "examples/endpoint_auth/endpoint_auth.zig" }, .{ .name = "endpoint_auth", .src = "examples/endpoint_auth/endpoint_auth.zig" },
.{ .name = "http_params", .src = "examples/http_params/http_params.zig" },
}) |excfg| { }) |excfg| {
const ex_name = excfg.name; const ex_name = excfg.name;
const ex_src = excfg.src; const ex_src = excfg.src;
@ -93,11 +94,20 @@ pub fn build(b: *std.build.Builder) !void {
// //
// TOOLS & TESTING // TOOLS & TESTING
// //
// n.b.: tests run in parallel, so we need all tests that use the network
// to run sequentially, since zap doesn't like to be started multiple
// times on different threads
//
// TODO: for some reason, tests aren't run more than once unless
// dependencies have changed.
// So, for now, we just force the exe to be built, so in order that
// we can call it again when needed.
// tests // authentication tests
// //
const auth_tests = b.addTest(.{ const auth_tests = b.addTest(.{
.root_source_file = .{ .path = "src/test_auth.zig" }, .name = "auth_tests",
.root_source_file = .{ .path = "src/tests/test_auth.zig" },
.target = target, .target = target,
.optimize = optimize, .optimize = optimize,
}); });
@ -105,15 +115,33 @@ pub fn build(b: *std.build.Builder) !void {
auth_tests.addModule("zap", zap_module); auth_tests.addModule("zap", zap_module);
const run_auth_tests = b.addRunArtifact(auth_tests); const run_auth_tests = b.addRunArtifact(auth_tests);
const install_auth_tests = b.addInstallArtifact(auth_tests);
// http paramters (qyery, body) tests
const httpparams_tests = b.addTest(.{
.name = "http_params_tests",
.root_source_file = .{ .path = "src/tests/test_http_params.zig" },
.target = target,
.optimize = optimize,
});
httpparams_tests.linkLibrary(facil_dep.artifact("facil.io"));
httpparams_tests.addModule("zap", zap_module);
const run_httpparams_tests = b.addRunArtifact(httpparams_tests);
// TODO: for some reason, tests aren't run more than once unless // TODO: for some reason, tests aren't run more than once unless
// dependencies have changed. // dependencies have changed.
// So, for now, we just force the exe to be built, so in order that // So, for now, we just force the exe to be built, so in order that
// we can call it again when needed. // we can call it again when needed.
const install_auth_tests = b.addInstallArtifact(auth_tests); const install_httpparams_tests = b.addInstallArtifact(httpparams_tests);
const test_step = b.step("test", "Run unit tests [REMOVE zig-cache!]"); // test commands
test_step.dependOn(&run_auth_tests.step); const run_auth_test_step = b.step("test-authentication", "Run auth unit tests [REMOVE zig-cache!]");
test_step.dependOn(&install_auth_tests.step); run_auth_test_step.dependOn(&run_auth_tests.step);
run_auth_test_step.dependOn(&install_auth_tests.step);
const run_httpparams_test_step = b.step("test-httpparams", "Run http param unit tests [REMOVE zig-cache!]");
run_httpparams_test_step.dependOn(&run_httpparams_tests.step);
run_httpparams_test_step.dependOn(&install_httpparams_tests.step);
// pkghash // pkghash
// //

View file

@ -1,6 +1,6 @@
.{ .{
.name = "zap", .name = "zap",
.version = "0.0.13", .version = "0.0.14",
.dependencies = .{ .dependencies = .{
.@"facil.io" = .{ .@"facil.io" = .{

View file

@ -0,0 +1,120 @@
const std = @import("std");
const zap = @import("zap");
// We send ourselves a request
fn makeRequest(a: std.mem.Allocator, url: []const u8) !void {
const uri = try std.Uri.parse(url);
var h = std.http.Headers{ .allocator = a };
defer h.deinit();
var http_client: std.http.Client = .{ .allocator = a };
defer http_client.deinit();
var req = try http_client.request(.GET, uri, h, .{});
defer req.deinit();
try req.start();
try req.wait();
}
fn makeRequestThread(a: std.mem.Allocator, url: []const u8) !std.Thread {
return try std.Thread.spawn(.{}, makeRequest, .{ a, url });
}
// here we go
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{
.thread_safe = true,
}){};
var allocator = gpa.allocator();
const Handler = struct {
var alloc: std.mem.Allocator = undefined;
pub fn on_request(r: zap.SimpleRequest) void {
std.debug.print("\n=====================================================\n", .{});
defer std.debug.print("=====================================================\n\n", .{});
// check for FORM parameters
r.parseBody() catch |err| {
std.log.err("Parse Body error: {any}. Expected if body is empty", .{err});
};
// check for query parameters
r.parseQuery();
var param_count = r.getParamCount();
std.log.info("param_count: {}", .{param_count});
// iterate over all params as strings
var strparams = r.parametersToOwnedStrList(alloc, false) catch unreachable;
defer strparams.deinit();
std.debug.print("\n", .{});
for (strparams.items) |kv| {
std.log.info("ParamStr `{s}` is `{s}`", .{ kv.key.str, kv.value.str });
}
std.debug.print("\n", .{});
// iterate over all params
const params = r.parametersToOwnedList(alloc, false) catch unreachable;
defer params.deinit();
for (params.items) |kv| {
std.log.info("Param `{s}` is {any}", .{ kv.key.str, kv.value });
}
// let's get param "one" by name
std.debug.print("\n", .{});
if (r.getParamStr("one", alloc, false)) |maybe_str| {
if (maybe_str) |*s| {
defer s.deinit();
std.log.info("Param one = {s}", .{s.str});
} else {
std.log.info("Param one not found!", .{});
}
}
// since we provided "false" for duplicating strings in the call
// to getParamStr(), there won't be an allocation error
else |err| {
std.log.err("cannot check for `one` param: {any}\n", .{err});
}
// check if we received a terminate=true parameter
if (r.getParamStr("terminate", alloc, false)) |maybe_str| {
if (maybe_str) |*s| {
defer s.deinit();
if (std.mem.eql(u8, s.str, "true")) {
zap.fio_stop();
}
}
} else |err| {
std.log.err("cannot check for terminate param: {any}\n", .{err});
}
}
};
Handler.alloc = allocator;
// setup listener
var listener = zap.SimpleHttpListener.init(
.{
.port = 3000,
.on_request = Handler.on_request,
.log = false,
.max_clients = 10,
.max_body_size = 1 * 1024,
},
);
zap.enableDebugLog();
try listener.listen();
std.log.info("\n\nTerminate with CTRL+C or by sending query param terminate=true\n", .{});
const thread = try makeRequestThread(allocator, "http://127.0.0.1:3000/?one=1&two=2&string=hello+world&float=6.28&bool=true");
defer thread.join();
zap.start(.{
.threads = 1,
.workers = 0,
});
}

View file

@ -74,10 +74,22 @@ pub const struct_fio_str_info_s = extern struct {
}; };
pub const fio_str_info_s = struct_fio_str_info_s; pub const fio_str_info_s = struct_fio_str_info_s;
pub extern fn http_send_body(h: [*c]http_s, data: ?*anyopaque, length: usize) c_int; pub extern fn http_send_body(h: [*c]http_s, data: ?*anyopaque, length: usize) c_int;
pub fn fiobj_each1(arg_o: FIOBJ, arg_start_at: usize, arg_task: ?*const fn (FIOBJ, ?*anyopaque) callconv(.C) c_int, arg_arg: ?*anyopaque) callconv(.C) usize {
var o = arg_o;
var start_at = arg_start_at;
var task = arg_task;
var arg = arg_arg;
if ((((o != 0) and ((o & @bitCast(c_ulong, @as(c_long, @as(c_int, 1)))) == @bitCast(c_ulong, @as(c_long, @as(c_int, 0))))) and ((o & @bitCast(c_ulong, @as(c_long, @as(c_int, 6)))) != @bitCast(c_ulong, @as(c_long, @as(c_int, 6))))) and (fiobj_type_vtable(o).*.each != null)) return fiobj_type_vtable(o).*.each.?(o, start_at, task, arg);
return 0;
}
pub extern fn fiobj_hash_new() FIOBJ; pub extern fn fiobj_hash_new() FIOBJ;
pub extern fn fiobj_hash_set(hash: FIOBJ, key: FIOBJ, obj: FIOBJ) c_int; pub extern fn fiobj_hash_set(hash: FIOBJ, key: FIOBJ, obj: FIOBJ) c_int;
pub extern fn fiobj_hash_get(hash: FIOBJ, key: FIOBJ) FIOBJ; pub extern fn fiobj_hash_get(hash: FIOBJ, key: FIOBJ) FIOBJ;
pub extern fn fiobj_hash_pop(hash: FIOBJ, key: [*c]FIOBJ) FIOBJ;
pub extern fn fiobj_hash_count(hash: FIOBJ) usize;
pub extern fn fiobj_hash_key_in_loop() FIOBJ;
pub extern fn fiobj_hash_haskey(hash: FIOBJ, key: FIOBJ) c_int;
pub extern fn fiobj_ary_push(ary: FIOBJ, obj: FIOBJ) void; pub extern fn fiobj_ary_push(ary: FIOBJ, obj: FIOBJ) void;
pub extern fn fiobj_float_new(num: f64) FIOBJ; pub extern fn fiobj_float_new(num: f64) FIOBJ;
pub extern fn fiobj_num_new_bignum(num: isize) FIOBJ; pub extern fn fiobj_num_new_bignum(num: isize) FIOBJ;
@ -210,6 +222,22 @@ pub fn fiobj_type_vtable(arg_o: FIOBJ) callconv(.C) [*c]const fiobj_object_vtabl
} }
return null; return null;
} }
pub fn fiobj_obj2num(o: FIOBJ) callconv(.C) isize {
if ((o & @bitCast(c_ulong, @as(c_long, @as(c_int, 1)))) != 0) {
const sign: usize = if ((o & ~(~@bitCast(usize, @as(c_long, @as(c_int, 0))) >> @intCast(@import("std").math.Log2Int(usize), 1))) != 0) ~(~@bitCast(usize, @as(c_long, @as(c_int, 0))) >> @intCast(@import("std").math.Log2Int(usize), 1)) | (~(~@bitCast(usize, @as(c_long, @as(c_int, 0))) >> @intCast(@import("std").math.Log2Int(usize), 1)) >> @intCast(@import("std").math.Log2Int(usize), 1)) else @bitCast(c_ulong, @as(c_long, @as(c_int, 0)));
return @bitCast(isize, ((o & (~@bitCast(usize, @as(c_long, @as(c_int, 0))) >> @intCast(@import("std").math.Log2Int(usize), 1))) >> @intCast(@import("std").math.Log2Int(c_ulong), 1)) | sign);
}
if (!(o != 0) or !(((o != 0) and ((o & @bitCast(c_ulong, @as(c_long, @as(c_int, 1)))) == @bitCast(c_ulong, @as(c_long, @as(c_int, 0))))) and ((o & @bitCast(c_ulong, @as(c_long, @as(c_int, 6)))) != @bitCast(c_ulong, @as(c_long, @as(c_int, 6)))))) return @bitCast(isize, @as(c_long, @boolToInt(o == @bitCast(c_ulong, @as(c_long, FIOBJ_T_TRUE)))));
return fiobj_type_vtable(o).*.to_i.?(o);
}
pub fn fiobj_obj2float(o: FIOBJ) callconv(.C) f64 {
if ((o & @bitCast(c_ulong, @as(c_long, @as(c_int, 1)))) != 0) return @intToFloat(f64, fiobj_obj2num(o));
// the below doesn't parse and we don't support ints here anyway
// if (!(o != 0) or ((o & @bitCast(c_ulong, @as(c_long, @as(c_int, 6)))) == @bitCast(c_ulong, @as(c_long, @as(c_int, 6))))) return @intToFloat(f64, o == @bitCast(c_ulong, @as(c_long, FIOBJ_T_TRUE)));
return fiobj_type_vtable(o).*.to_f.?(o);
}
pub extern fn fio_ltocstr(c_long) fio_str_info_s; pub extern fn fio_ltocstr(c_long) fio_str_info_s;
pub fn fiobj_obj2cstr(o: FIOBJ) callconv(.C) fio_str_info_s { pub fn fiobj_obj2cstr(o: FIOBJ) callconv(.C) fio_str_info_s {
if (!(o != 0)) { if (!(o != 0)) {

View file

@ -1,9 +1,13 @@
const std = @import("std"); const std = @import("std");
const Authenticators = @import("http_auth.zig"); const zap = @import("zap");
const zap = @import("zap.zig"); // const Authenticators = @import("http_auth.zig");
const Endpoints = @import("endpoint.zig"); const Authenticators = zap;
const fio = @import("fio.zig"); const Endpoints = zap;
const util = @import("util.zig"); // const Endpoints = @import("endpoint.zig");
const fio = zap;
// const fio = @import("fio.zig");
const util = zap;
// const util = @import("util.zig");
test "BearerAuthSingle authenticate" { test "BearerAuthSingle authenticate" {
const a = std.testing.allocator; const a = std.testing.allocator;

View file

@ -0,0 +1,166 @@
const std = @import("std");
const zap = @import("zap");
fn makeRequest(a: std.mem.Allocator, url: []const u8) !void {
const uri = try std.Uri.parse(url);
var h = std.http.Headers{ .allocator = a };
defer h.deinit();
var http_client: std.http.Client = .{ .allocator = a };
defer http_client.deinit();
var req = try http_client.request(.GET, uri, h, .{});
defer req.deinit();
try req.start();
try req.wait();
zap.fio_stop();
}
fn makeRequestThread(a: std.mem.Allocator, url: []const u8) !std.Thread {
return try std.Thread.spawn(.{}, makeRequest, .{ a, url });
}
test "http parameters" {
var allocator = std.testing.allocator;
const Handler = struct {
var alloc: std.mem.Allocator = undefined;
var ran: bool = false;
var param_count: isize = 0;
var strParams: ?zap.HttpParamStrKVList = null;
var params: ?zap.HttpParamKVList = null;
var paramOneStr: ?zap.FreeOrNot = null;
pub fn on_request(r: zap.SimpleRequest) void {
ran = true;
r.parseQuery();
param_count = r.getParamCount();
// true -> make copies of temp strings
strParams = r.parametersToOwnedStrList(alloc, true) catch unreachable;
// true -> make copies of temp strings
params = r.parametersToOwnedList(alloc, true) catch unreachable;
var maybe_str = r.getParamStr("one", alloc, true) catch unreachable;
if (maybe_str) |*s| {
paramOneStr = s.*;
}
}
};
Handler.alloc = allocator;
// setup listener
var listener = zap.SimpleHttpListener.init(
.{
.port = 3001,
.on_request = Handler.on_request,
.log = false,
.max_clients = 10,
.max_body_size = 1 * 1024,
},
);
zap.enableDebugLog();
try listener.listen();
const thread = try makeRequestThread(allocator, "http://127.0.0.1:3001/?one=1&two=2&string=hello+world&float=6.28&bool=true");
defer thread.join();
zap.start(.{
.threads = 1,
.workers = 0,
});
defer {
if (Handler.strParams) |*p| {
p.deinit();
}
if (Handler.params) |*p| {
p.deinit();
}
if (Handler.paramOneStr) |*p| {
// allocator.free(p);
p.deinit();
}
}
try std.testing.expectEqual(Handler.ran, true);
try std.testing.expectEqual(Handler.param_count, 5);
try std.testing.expect(Handler.paramOneStr != null);
try std.testing.expectEqualStrings(Handler.paramOneStr.?.str, "1");
try std.testing.expect(Handler.strParams != null);
for (Handler.strParams.?.items, 0..) |kv, i| {
switch (i) {
0 => {
try std.testing.expectEqualStrings(kv.key.str, "one");
try std.testing.expectEqualStrings(kv.value.str, "1");
},
1 => {
try std.testing.expectEqualStrings(kv.key.str, "two");
try std.testing.expectEqualStrings(kv.value.str, "2");
},
2 => {
try std.testing.expectEqualStrings(kv.key.str, "string");
try std.testing.expectEqualStrings(kv.value.str, "hello world");
},
3 => {
try std.testing.expectEqualStrings(kv.key.str, "float");
try std.testing.expectEqualStrings(kv.value.str, "6.28");
},
4 => {
try std.testing.expectEqualStrings(kv.key.str, "bool");
try std.testing.expectEqualStrings(kv.value.str, "true");
},
else => return error.TooManyArgs,
}
}
for (Handler.params.?.items, 0..) |kv, i| {
switch (i) {
0 => {
try std.testing.expectEqualStrings(kv.key.str, "one");
try std.testing.expect(kv.value != null);
switch (kv.value.?) {
.Int => |n| try std.testing.expectEqual(n, 1),
else => return error.InvalidHttpParamType,
}
},
1 => {
try std.testing.expectEqualStrings(kv.key.str, "two");
try std.testing.expect(kv.value != null);
switch (kv.value.?) {
.Int => |n| try std.testing.expectEqual(n, 2),
else => return error.InvalidHttpParamType,
}
},
2 => {
try std.testing.expectEqualStrings(kv.key.str, "string");
try std.testing.expect(kv.value != null);
switch (kv.value.?) {
.String => |s| try std.testing.expectEqualStrings(s.str, "hello world"),
else => return error.InvalidHttpParamType,
}
},
3 => {
try std.testing.expectEqualStrings(kv.key.str, "float");
try std.testing.expect(kv.value != null);
switch (kv.value.?) {
.Float => |f| try std.testing.expectEqual(f, 6.28),
else => return error.InvalidHttpParamType,
}
},
4 => {
try std.testing.expectEqualStrings(kv.key.str, "bool");
try std.testing.expect(kv.value != null);
switch (kv.value.?) {
.Bool => |b| try std.testing.expectEqual(b, true),
else => return error.InvalidHttpParamType,
}
},
else => return error.TooManyArgs,
}
}
}

View file

@ -1,6 +1,9 @@
const std = @import("std"); const std = @import("std");
const fio = @import("fio.zig"); const fio = @import("fio.zig");
/// note: since this is called from within request functions, we don't make
/// copies. Also, we return temp memory from fio. -> don't hold on to it outside
/// of a request function
pub fn fio2str(o: fio.FIOBJ) ?[]const u8 { pub fn fio2str(o: fio.FIOBJ) ?[]const u8 {
if (o == 0) return null; if (o == 0) return null;
const x: fio.fio_str_info_s = fio.fiobj_obj2cstr(o); const x: fio.fio_str_info_s = fio.fiobj_obj2cstr(o);
@ -9,6 +12,32 @@ pub fn fio2str(o: fio.FIOBJ) ?[]const u8 {
return std.mem.span(x.data); return std.mem.span(x.data);
} }
pub const FreeOrNot = struct {
str: []const u8,
freeme: bool,
allocator: ?std.mem.Allocator = null,
pub fn deinit(self: *const @This()) void {
if (self.freeme) {
self.allocator.?.free(self.str);
}
}
};
pub fn fio2strAllocOrNot(o: fio.FIOBJ, a: std.mem.Allocator, always_alloc: bool) !FreeOrNot {
if (o == 0) return .{ .str = "null", .freeme = false };
if (o == fio.FIOBJ_INVALID) return .{ .str = "null", .freeme = false };
return switch (fio.fiobj_type(o)) {
fio.FIOBJ_T_TRUE => .{ .str = "true", .freeme = false },
fio.FIOBJ_T_FALSE => .{ .str = "false", .freeme = false },
// according to fio2str above, the orelse should never happen
fio.FIOBJ_T_NUMBER => .{ .str = try a.dupe(u8, fio2str(o) orelse "null"), .freeme = true, .allocator = a },
fio.FIOBJ_T_FLOAT => .{ .str = try a.dupe(u8, fio2str(o) orelse "null"), .freeme = true, .allocator = a },
// the string comes out of the request, so it is safe to not make a copy
fio.FIOBJ_T_STRING => .{ .str = if (always_alloc) try a.dupe(u8, fio2str(o) orelse "") else fio2str(o) orelse "", .freeme = if (always_alloc) true else false, .allocator = a },
else => .{ .str = "null", .freeme = false },
};
}
pub fn str2fio(s: []const u8) fio.fio_str_info_s { pub fn str2fio(s: []const u8) fio.fio_str_info_s {
return .{ return .{
.data = toCharPtr(s), .data = toCharPtr(s),

View file

@ -48,11 +48,8 @@ pub const HttpError = error{
HttpSendBody, HttpSendBody,
HttpSetContentType, HttpSetContentType,
HttpSetHeader, HttpSetHeader,
}; HttpParseBody,
HttpIterParams,
pub const HttpParam = struct {
key: []const u8,
value: []const u8,
}; };
pub const ContentType = enum { pub const ContentType = enum {
@ -153,10 +150,6 @@ pub const SimpleRequest = struct {
} }
debug("setHeader: ret = {}\n", .{ret}); debug("setHeader: ret = {}\n", .{ret});
// Note to self:
// const new_fiobj_str = fio.fiobj_str_new(name.ptr, name.len);
// fio.fiobj_free(new_fiobj_str);
if (ret == 0) return; if (ret == 0) return;
return error.HttpSetHeader; return error.HttpSetHeader;
} }
@ -169,20 +162,263 @@ pub const SimpleRequest = struct {
self.h.*.status = @intCast(usize, @enumToInt(status)); self.h.*.status = @intCast(usize, @enumToInt(status));
} }
pub fn nextParam(self: *const Self) ?HttpParam { /// Attempts to decode the request's body.
/// This should be called BEFORE parseQuery
/// Result is accessible via parametersToOwnedSlice(), parametersToOwnedStrSlice()
///
/// Supported body types:
/// - application/x-www-form-urlencoded
/// - application/json
/// - multipart/form-data
pub fn parseBody(self: *const Self) HttpError!void {
if (fio.http_parse_body(self.h) == -1) return error.HttpParseBody;
}
/// Parses the query part of an HTTP request
/// This should be called AFTER parseBody(), just in case the body is a JSON
/// object that doesn't have a hash map at its root.
///
/// Result is accessible via parametersToOwnedSlice(), parametersToOwnedStrSlice()
pub fn parseQuery(self: *const Self) void {
return fio.http_parse_query(self.h);
}
/// not implemented.
pub fn parseCookies() !void {}
/// not implemented.
pub fn getCookie(name: []const u8) ?[]const u8 {
_ = name;
}
/// Returns the number of parameters after parsing.
///
/// Parse with parseBody() and / or parseQuery()
pub fn getParamCount(self: *const Self) isize {
if (self.h.*.params == 0) return 0;
return fio.fiobj_obj2num(self.h.*.params);
}
/// Returns the query / body parameters as key/value pairs, as strings.
/// Supported param types that will be converted:
///
/// - Bool
/// - Int
/// - Float
/// - String
///
/// At the moment, no fio ARRAYs are supported as well as HASH maps.
/// So, for JSON body payloads: parse the body instead.
///
/// Requires parseBody() and/or parseQuery() have been called.
/// Returned list needs to be deinited.
pub fn parametersToOwnedStrList(self: *const Self, a: std.mem.Allocator, always_alloc: bool) anyerror!HttpParamStrKVList {
var params = try std.ArrayList(HttpParamStrKV).initCapacity(a, @intCast(usize, self.getParamCount()));
var context: _parametersToOwnedStrSliceContext = .{
.params = &params,
.allocator = a,
.always_alloc = always_alloc,
};
const howmany = fio.fiobj_each1(self.h.*.params, 0, _each_nextParamStr, &context);
if (howmany != self.getParamCount()) {
return error.HttpIterParams;
}
return .{ .items = try params.toOwnedSlice(), .allocator = a };
}
const _parametersToOwnedStrSliceContext = struct {
allocator: std.mem.Allocator,
params: *std.ArrayList(HttpParamStrKV),
last_error: ?anyerror = null,
always_alloc: bool,
};
fn _each_nextParamStr(fiobj_value: fio.FIOBJ, context: ?*anyopaque) callconv(.C) c_int {
const ctx: *_parametersToOwnedStrSliceContext = @ptrCast(*_parametersToOwnedStrSliceContext, @alignCast(@alignOf(*_parametersToOwnedStrSliceContext), context));
// this is thread-safe, guaranteed by fio
var fiobj_key: fio.FIOBJ = fio.fiobj_hash_key_in_loop();
ctx.params.append(.{
.key = util.fio2strAllocOrNot(fiobj_key, ctx.allocator, ctx.always_alloc) catch |err| {
ctx.last_error = err;
return -1;
},
.value = util.fio2strAllocOrNot(fiobj_value, ctx.allocator, ctx.always_alloc) catch |err| {
ctx.last_error = err;
return -1;
},
}) catch |err| {
// what to do?
// signal the caller that an error occured by returning -1
// also, set the error
ctx.last_error = err;
return -1;
};
return 0;
}
/// Returns the query / body parameters as key/value pairs
/// Supported param types that will be converted:
///
/// - Bool
/// - Int
/// - Float
/// - String
///
/// At the moment, no fio ARRAYs are supported as well as HASH maps.
/// So, for JSON body payloads: parse the body instead.
///
/// Requires parseBody() and/or parseQuery() have been called.
/// Returned slice needs to be freed.
pub fn parametersToOwnedList(self: *const Self, a: std.mem.Allocator, dupe_strings: bool) !HttpParamKVList {
var params = try std.ArrayList(HttpParamKV).initCapacity(a, @intCast(usize, self.getParamCount()));
var context: _parametersToOwnedSliceContext = .{ .params = &params, .allocator = a, .dupe_strings = dupe_strings };
const howmany = fio.fiobj_each1(self.h.*.params, 0, _each_nextParam, &context);
if (howmany != self.getParamCount()) {
return error.HttpIterParams;
}
return .{ .items = try params.toOwnedSlice(), .allocator = a };
}
const _parametersToOwnedSliceContext = struct {
params: *std.ArrayList(HttpParamKV),
last_error: ?anyerror = null,
allocator: std.mem.Allocator,
dupe_strings: bool,
};
fn _each_nextParam(fiobj_value: fio.FIOBJ, context: ?*anyopaque) callconv(.C) c_int {
const ctx: *_parametersToOwnedSliceContext = @ptrCast(*_parametersToOwnedSliceContext, @alignCast(@alignOf(*_parametersToOwnedSliceContext), context));
// this is thread-safe, guaranteed by fio
var fiobj_key: fio.FIOBJ = fio.fiobj_hash_key_in_loop();
ctx.params.append(.{
.key = util.fio2strAllocOrNot(fiobj_key, ctx.allocator, ctx.dupe_strings) catch |err| {
ctx.last_error = err;
return -1;
},
.value = Fiobj2HttpParam(fiobj_value, ctx.allocator, ctx.dupe_strings) catch |err| {
ctx.last_error = err;
return -1;
},
}) catch |err| {
// what to do?
// signal the caller that an error occured by returning -1
// also, set the error
ctx.last_error = err;
return -1;
};
return 0;
}
/// get named parameter as string
/// Supported param types that will be converted:
///
/// - Bool
/// - Int
/// - Float
/// - String
///
/// At the moment, no fio ARRAYs are supported as well as HASH maps.
/// So, for JSON body payloads: parse the body instead.
///
/// Requires parseBody() and/or parseQuery() have been called.
/// The returned string needs to be deinited with .deinit()
pub fn getParamStr(self: *const Self, name: []const u8, a: std.mem.Allocator, always_alloc: bool) !?util.FreeOrNot {
if (self.h.*.params == 0) return null; if (self.h.*.params == 0) return null;
var key: fio.FIOBJ = undefined; const key = fio.fiobj_str_new(name.ptr, name.len);
const value = fio.fiobj_hash_pop(self.h.*.params, &key); defer fio.fiobj_free_wrapped(key);
const value = fio.fiobj_hash_get(self.h.*.params, key);
if (value == fio.FIOBJ_INVALID) { if (value == fio.FIOBJ_INVALID) {
return null; return null;
} }
return HttpParam{ return try util.fio2strAllocOrNot(value, a, always_alloc);
.key = util.fio2str(key).?,
.value = util.fio2str(value).?,
};
} }
}; };
/// Key value pair of strings from HTTP parameters
pub const HttpParamStrKV = struct {
key: util.FreeOrNot,
value: util.FreeOrNot,
pub fn deinit(self: *@This()) void {
self.key.deinit();
self.value.deinit();
}
};
pub const HttpParamStrKVList = struct {
items: []HttpParamStrKV,
allocator: std.mem.Allocator,
pub fn deinit(self: *@This()) void {
for (self.items) |*item| {
item.deinit();
}
self.allocator.free(self.items);
}
};
pub const HttpParamKVList = struct {
items: []HttpParamKV,
allocator: std.mem.Allocator,
pub fn deinit(self: *const @This()) void {
for (self.items) |*item| {
item.deinit();
}
self.allocator.free(self.items);
}
};
pub const HttpParamValueType = enum {
// Null,
Bool,
Int,
Float,
String,
Unsupported,
Unsupported_Hash,
Unsupported_Array,
};
pub const HttpParam = union(HttpParamValueType) {
Bool: bool,
Int: isize,
Float: f64,
/// we don't do writable strings here
String: util.FreeOrNot,
/// value will always be null
Unsupported: ?void,
/// value will always be null
Unsupported_Hash: ?void,
/// value will always be null
Unsupported_Array: ?void,
};
pub const HttpParamKV = struct {
key: util.FreeOrNot,
value: ?HttpParam,
pub fn deinit(self: *@This()) void {
self.key.deinit();
if (self.value) |p| {
switch (p) {
.String => |*s| s.deinit(),
else => {},
}
}
}
};
pub fn Fiobj2HttpParam(o: fio.FIOBJ, a: std.mem.Allocator, dupe_string: bool) !?HttpParam {
return switch (fio.fiobj_type(o)) {
fio.FIOBJ_T_NULL => null,
fio.FIOBJ_T_TRUE => .{ .Bool = true },
fio.FIOBJ_T_FALSE => .{ .Bool = false },
fio.FIOBJ_T_NUMBER => .{ .Int = fio.fiobj_obj2num(o) },
fio.FIOBJ_T_FLOAT => .{ .Float = fio.fiobj_obj2float(o) },
fio.FIOBJ_T_STRING => .{ .String = try util.fio2strAllocOrNot(o, a, dupe_string) },
fio.FIOBJ_T_ARRAY => .{ .Unsupported_Array = null },
fio.FIOBJ_T_HASH => .{ .Unsupported_Hash = null },
else => .{ .Unsupported = null },
};
}
pub const HttpRequestFn = *const fn (r: [*c]fio.http_s) callconv(.C) void; pub const HttpRequestFn = *const fn (r: [*c]fio.http_s) callconv(.C) void;
pub const SimpleHttpRequestFn = *const fn (SimpleRequest) void; pub const SimpleHttpRequestFn = *const fn (SimpleRequest) void;