mirror of
https://github.com/zigzap/zap.git
synced 2025-10-20 15:14:08 +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:
parent
e679039bae
commit
d0be271337
11 changed files with 647 additions and 31 deletions
6
.github/workflows/mastercheck.yml
vendored
6
.github/workflows/mastercheck.yml
vendored
|
@ -26,5 +26,7 @@ jobs:
|
|||
run: zig version
|
||||
- name: Build simple endpoint example
|
||||
run: zig build endpoint
|
||||
- name: Build tests
|
||||
run: zig build test
|
||||
- name: Build authentication tests
|
||||
run: zig build test-authentication
|
||||
- name: Build http parameter tests
|
||||
run: zig build test-httpparams
|
||||
|
|
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -6,3 +6,4 @@ tmp/
|
|||
**/target/*
|
||||
**/__pycache__/*
|
||||
wrk/go/main
|
||||
scratch
|
||||
|
|
|
@ -41,7 +41,9 @@ Here's what works:
|
|||
- **[mustache](examples/mustache/)**: a simple example using
|
||||
[mustache](https://mustache.github.io/) templating.
|
||||
- **[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
|
||||
|
|
40
build.zig
40
build.zig
|
@ -50,6 +50,7 @@ pub fn build(b: *std.build.Builder) !void {
|
|||
.{ .name = "wrk_zigstd", .src = "wrk/zigstd/main.zig" },
|
||||
.{ .name = "mustache", .src = "examples/mustache/mustache.zig" },
|
||||
.{ .name = "endpoint_auth", .src = "examples/endpoint_auth/endpoint_auth.zig" },
|
||||
.{ .name = "http_params", .src = "examples/http_params/http_params.zig" },
|
||||
}) |excfg| {
|
||||
const ex_name = excfg.name;
|
||||
const ex_src = excfg.src;
|
||||
|
@ -93,11 +94,20 @@ pub fn build(b: *std.build.Builder) !void {
|
|||
//
|
||||
// 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(.{
|
||||
.root_source_file = .{ .path = "src/test_auth.zig" },
|
||||
.name = "auth_tests",
|
||||
.root_source_file = .{ .path = "src/tests/test_auth.zig" },
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
});
|
||||
|
@ -105,15 +115,33 @@ pub fn build(b: *std.build.Builder) !void {
|
|||
auth_tests.addModule("zap", zap_module);
|
||||
|
||||
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
|
||||
// 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.
|
||||
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_step.dependOn(&run_auth_tests.step);
|
||||
test_step.dependOn(&install_auth_tests.step);
|
||||
// test commands
|
||||
const run_auth_test_step = b.step("test-authentication", "Run auth unit tests [REMOVE zig-cache!]");
|
||||
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
|
||||
//
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
.{
|
||||
.name = "zap",
|
||||
.version = "0.0.13",
|
||||
.version = "0.0.14",
|
||||
|
||||
.dependencies = .{
|
||||
.@"facil.io" = .{
|
||||
|
|
120
examples/http_params/http_params.zig
Normal file
120
examples/http_params/http_params.zig
Normal 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,
|
||||
});
|
||||
}
|
28
src/fio.zig
28
src/fio.zig
|
@ -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 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_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_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_float_new(num: f64) 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;
|
||||
}
|
||||
|
||||
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 fn fiobj_obj2cstr(o: FIOBJ) callconv(.C) fio_str_info_s {
|
||||
if (!(o != 0)) {
|
||||
|
|
|
@ -1,9 +1,13 @@
|
|||
const std = @import("std");
|
||||
const Authenticators = @import("http_auth.zig");
|
||||
const zap = @import("zap.zig");
|
||||
const Endpoints = @import("endpoint.zig");
|
||||
const fio = @import("fio.zig");
|
||||
const util = @import("util.zig");
|
||||
const zap = @import("zap");
|
||||
// const Authenticators = @import("http_auth.zig");
|
||||
const Authenticators = zap;
|
||||
const Endpoints = zap;
|
||||
// const Endpoints = @import("endpoint.zig");
|
||||
const fio = zap;
|
||||
// const fio = @import("fio.zig");
|
||||
const util = zap;
|
||||
// const util = @import("util.zig");
|
||||
|
||||
test "BearerAuthSingle authenticate" {
|
||||
const a = std.testing.allocator;
|
166
src/tests/test_http_params.zig
Normal file
166
src/tests/test_http_params.zig
Normal 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,
|
||||
}
|
||||
}
|
||||
}
|
29
src/util.zig
29
src/util.zig
|
@ -1,6 +1,9 @@
|
|||
const std = @import("std");
|
||||
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 {
|
||||
if (o == 0) return null;
|
||||
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);
|
||||
}
|
||||
|
||||
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 {
|
||||
return .{
|
||||
.data = toCharPtr(s),
|
||||
|
|
268
src/zap.zig
268
src/zap.zig
|
@ -48,11 +48,8 @@ pub const HttpError = error{
|
|||
HttpSendBody,
|
||||
HttpSetContentType,
|
||||
HttpSetHeader,
|
||||
};
|
||||
|
||||
pub const HttpParam = struct {
|
||||
key: []const u8,
|
||||
value: []const u8,
|
||||
HttpParseBody,
|
||||
HttpIterParams,
|
||||
};
|
||||
|
||||
pub const ContentType = enum {
|
||||
|
@ -153,10 +150,6 @@ pub const SimpleRequest = struct {
|
|||
}
|
||||
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;
|
||||
return error.HttpSetHeader;
|
||||
}
|
||||
|
@ -169,20 +162,263 @@ pub const SimpleRequest = struct {
|
|||
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 = ¶ms,
|
||||
.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 = ¶ms, .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;
|
||||
var key: fio.FIOBJ = undefined;
|
||||
const value = fio.fiobj_hash_pop(self.h.*.params, &key);
|
||||
const key = fio.fiobj_str_new(name.ptr, name.len);
|
||||
defer fio.fiobj_free_wrapped(key);
|
||||
const value = fio.fiobj_hash_get(self.h.*.params, key);
|
||||
if (value == fio.FIOBJ_INVALID) {
|
||||
return null;
|
||||
}
|
||||
return HttpParam{
|
||||
.key = util.fio2str(key).?,
|
||||
.value = util.fio2str(value).?,
|
||||
};
|
||||
return try util.fio2strAllocOrNot(value, a, always_alloc);
|
||||
}
|
||||
};
|
||||
|
||||
/// 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 SimpleHttpRequestFn = *const fn (SimpleRequest) void;
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue