1
0
Fork 0
mirror of https://github.com/zigzap/zap.git synced 2025-10-20 15:14:08 +00:00
This commit is contained in:
Rene Schallner 2023-05-06 04:02:03 +02:00
parent 00c809876e
commit b45a7df668
7 changed files with 247 additions and 18 deletions

View file

@ -38,12 +38,14 @@ Here's what works:
- **[endpoint](examples/endpoint/)**: a simple JSON REST API example featuring
a `/users` endpoint for PUTting/DELETE-ing/GET-ting/POST-ing and listing
users, together with a static HTML and JavaScript frontend to play with.
- **[mustache](examples/mustache/)**: a simple example using
- **[mustache](examples/mustache/mustache.zig)**: a simple example using
[mustache](https://mustache.github.io/) templating.
- **[endpoint authentication](examples/endpoint_auth/)**: a simple authenticated
- **[endpoint authentication](examples/endpoint_auth/endpoint_auth.zig)**: a simple authenticated
endpoint. Read more about authentication [here](./doc/authentication.md).
- **[http parameters](examples/http_parameters/)**: a simple example sending
- **[http parameters](examples/http_parameters/http_params.zig)**: a simple example sending
itself query parameters of all supported types.
- **[cookies](examples/cookies/cookies.zig)**: a simple example sending
itself a cookie and responding with a session cookie.
I'll continue wrapping more of facil.io's functionality and adding stuff to zap

View file

@ -51,6 +51,7 @@ pub fn build(b: *std.build.Builder) !void {
.{ .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" },
.{ .name = "cookies", .src = "examples/cookies/cookies.zig" },
}) |excfg| {
const ex_name = excfg.name;
const ex_src = excfg.src;

View file

@ -1,13 +1,9 @@
.{
.name = "zap",
.version = "0.0.14",
.version = "0.0.15",
.dependencies = .{
.@"facil.io" = .{
// temp workaround until zig's fetch is fixed, supporting GH's redirects
// .url = "http://localhost:8000/zap-0.0.7.tar.gz",
// this is how it should be:
.url = "https://github.com/zigzap/facil.io/archive/refs/tags/zap-0.0.7.tar.gz",
.hash = "1220d03e0579bbb726efb8224ea289b26227bc421158b45c1b16a60b31bfa400ab33",

View file

@ -0,0 +1,114 @@
const std = @import("std");
const zap = @import("zap");
// We send ourselves a request with a cookie
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.headers.append("cookie", "ZIG_ZAP=awesome");
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", .{});
r.parseCookies(false);
var cookie_count = r.getCookiesCount();
std.log.info("cookie_count: {}", .{cookie_count});
// iterate over all cookies as strings
var strCookies = r.cookiesToOwnedStrList(alloc, false) catch unreachable;
defer strCookies.deinit();
std.debug.print("\n", .{});
for (strCookies.items) |kv| {
std.log.info("CookieStr `{s}` is `{s}`", .{ kv.key.str, kv.value.str });
}
std.debug.print("\n", .{});
// // iterate over all cookies
const cookies = r.cookiesToOwnedList(alloc, false) catch unreachable;
defer cookies.deinit();
for (cookies.items) |kv| {
std.log.info("cookie `{s}` is {any}", .{ kv.key.str, kv.value });
}
// let's get cookie "ZIG_ZAP" by name
std.debug.print("\n", .{});
if (r.getCookieStr("ZIG_ZAP", alloc, false)) |maybe_str| {
if (maybe_str) |*s| {
defer s.deinit();
std.log.info("Cookie ZIG_ZAP = {s}", .{s.str});
} else {
std.log.info("Cookie ZIG_ZAP not found!", .{});
}
}
// since we provided "false" for duplicating strings in the call
// to getCookieStr(), there won't be an allocation error
else |err| {
std.log.err("cannot check for `ZIG_ZAP` cookie: {any}\n", .{err});
}
r.setCookie(.{
.name = "rene",
.value = "rocksai",
// if we leave .max_age_s = 0 -> session cookie
// .max_age_s = 60,
//
// check out other params: domain, path, secure, http_only
}) catch unreachable;
r.sendBody("Hello") catch unreachable;
}
};
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", .{});
const thread = try makeRequestThread(allocator, "http://127.0.0.1:3000");
defer thread.join();
zap.start(.{
.threads = 1,
.workers = 0,
});
}

6
flake.lock generated
View file

@ -166,11 +166,11 @@
"nixpkgs": "nixpkgs_2"
},
"locked": {
"lastModified": 1682641397,
"narHash": "sha256-gt/jGCv21Oju3R8t9uzq5c7l3EagYnWhKGlY+uNNtAM=",
"lastModified": 1683288458,
"narHash": "sha256-Avc/4kh6wb5C2C+kDcm8S8fnR1gkDOWW7RPulwaw1Tg=",
"owner": "mitchellh",
"repo": "zig-overlay",
"rev": "9de49969d69441e2fb50ab891d6fbd0cff0f0b5c",
"rev": "9f9baaee29b3615ef0d7bbe6f8367681ea8091cb",
"type": "github"
},
"original": {

View file

@ -67,6 +67,47 @@ pub const http_s = extern struct {
body: FIOBJ,
udata: ?*anyopaque,
}; // zig-cache/i/e0c8a6e617497ade13de512cbe191f23/include/http.h:153:12: warning: struct demoted to opaque type - has bitfield
// typedef struct {
// /** The cookie's name (Symbol). */
// const char *name;
// /** The cookie's value (leave blank to delete cookie). */
// const char *value;
// /** The cookie's domain (optional). */
// const char *domain;
// /** The cookie's path (optional). */
// const char *path;
// /** The cookie name's size in bytes or a terminating NUL will be assumed.*/
// size_t name_len;
// /** The cookie value's size in bytes or a terminating NUL will be assumed.*/
// size_t value_len;
// /** The cookie domain's size in bytes or a terminating NUL will be assumed.*/
// size_t domain_len;
// /** The cookie path's size in bytes or a terminating NULL will be assumed.*/
// size_t path_len;
// /** Max Age (how long should the cookie persist), in seconds (0 == session).*/
// int max_age;
// /** Limit cookie to secure connections.*/
// unsigned secure : 1;
// /** Limit cookie to HTTP (intended to prevent javascript access/hijacking).*/
// unsigned http_only : 1;
// } http_cookie_args_s;
pub const http_cookie_args_s = extern struct {
name: [*c]u8,
value: [*c]u8,
domain: [*c]u8,
path: [*c]u8,
name_len: isize,
value_len: isize,
domain_len: isize,
path_len: isize,
/// in seconds
max_age: c_int,
secure: c_uint,
http_only: c_uint,
};
pub const struct_fio_str_info_s = extern struct {
capa: usize,
len: usize,
@ -289,7 +330,8 @@ pub fn fiobj_obj2cstr(o: FIOBJ) callconv(.C) fio_str_info_s {
}
return fiobj_type_vtable(o).*.to_str.?(o);
}
pub const http_cookie_args_s = opaque {};
// pub const http_cookie_args_s = opaque {};
pub extern fn http_set_header(h: [*c]http_s, name: FIOBJ, value: FIOBJ) c_int;
pub extern fn http_set_header2(h: [*c]http_s, name: fio_str_info_s, value: fio_str_info_s) c_int;
pub extern fn http_set_cookie(h: [*c]http_s, http_cookie_args_s) c_int;

View file

@ -50,6 +50,7 @@ pub const HttpError = error{
HttpSetHeader,
HttpParseBody,
HttpIterParams,
SetCookie,
};
pub const ContentType = enum {
@ -180,15 +181,51 @@ pub const SimpleRequest = struct {
///
/// Result is accessible via parametersToOwnedSlice(), parametersToOwnedStrSlice()
pub fn parseQuery(self: *const Self) void {
return fio.http_parse_query(self.h);
fio.http_parse_query(self.h);
}
/// not implemented.
pub fn parseCookies() !void {}
pub fn parseCookies(self: *const Self, url_encoded: bool) void {
fio.http_parse_cookies(self.h, if (url_encoded) 1 else 0);
}
/// not implemented.
pub fn getCookie(name: []const u8) ?[]const u8 {
_ = name;
// Set a response cookie
pub fn setCookie(self: *const Self, args: CookieArgs) HttpError!void {
var c: fio.http_cookie_args_s = .{
.name = util.toCharPtr(args.name),
.name_len = @intCast(isize, args.name.len),
.value = util.toCharPtr(args.value),
.value_len = @intCast(isize, args.value.len),
.domain = if (args.domain) |p| util.toCharPtr(p) else null,
.domain_len = if (args.domain) |p| @intCast(isize, p.len) else 0,
.path = if (args.path) |p| util.toCharPtr(p) else null,
.path_len = if (args.path) |p| @intCast(isize, p.len) else 0,
.max_age = args.max_age_s,
.secure = if (args.secure) 1 else 0,
.http_only = if (args.http_only) 1 else 0,
};
if (fio.http_set_cookie(self.h, c) == -1) {
return error.SetCookie;
}
}
/// Returns named cookie. Works like getParamStr()
pub fn getCookieStr(self: *const Self, name: []const u8, a: std.mem.Allocator, always_alloc: bool) !?util.FreeOrNot {
if (self.h.*.cookies == 0) return null;
const key = fio.fiobj_str_new(name.ptr, name.len);
defer fio.fiobj_free_wrapped(key);
const value = fio.fiobj_hash_get(self.h.*.cookies, key);
if (value == fio.FIOBJ_INVALID) {
return null;
}
return try util.fio2strAllocOrNot(value, a, always_alloc);
}
/// Returns the number of parameters after parsing.
///
/// Parse with parseCookies()
pub fn getCookiesCount(self: *const Self) isize {
if (self.h.*.cookies == 0) return 0;
return fio.fiobj_obj2num(self.h.*.cookies);
}
/// Returns the number of parameters after parsing.
@ -199,6 +236,32 @@ pub const SimpleRequest = struct {
return fio.fiobj_obj2num(self.h.*.params);
}
/// Same as parametersToOwnedStrList() but for cookies
pub fn cookiesToOwnedStrList(self: *const Self, a: std.mem.Allocator, always_alloc: bool) anyerror!HttpParamStrKVList {
var params = try std.ArrayList(HttpParamStrKV).initCapacity(a, @intCast(usize, self.getCookiesCount()));
var context: _parametersToOwnedStrSliceContext = .{
.params = &params,
.allocator = a,
.always_alloc = always_alloc,
};
const howmany = fio.fiobj_each1(self.h.*.cookies, 0, _each_nextParamStr, &context);
if (howmany != self.getCookiesCount()) {
return error.HttpIterParams;
}
return .{ .items = try params.toOwnedSlice(), .allocator = a };
}
/// Same as parametersToOwnedList() but for cookies
pub fn cookiesToOwnedList(self: *const Self, a: std.mem.Allocator, dupe_strings: bool) !HttpParamKVList {
var params = try std.ArrayList(HttpParamKV).initCapacity(a, @intCast(usize, self.getCookiesCount()));
var context: _parametersToOwnedSliceContext = .{ .params = &params, .allocator = a, .dupe_strings = dupe_strings };
const howmany = fio.fiobj_each1(self.h.*.cookies, 0, _each_nextParam, &context);
if (howmany != self.getCookiesCount()) {
return error.HttpIterParams;
}
return .{ .items = try params.toOwnedSlice(), .allocator = a };
}
/// Returns the query / body parameters as key/value pairs, as strings.
/// Supported param types that will be converted:
///
@ -419,6 +482,17 @@ pub fn Fiobj2HttpParam(o: fio.FIOBJ, a: std.mem.Allocator, dupe_string: bool) !?
};
}
pub const CookieArgs = struct {
name: []const u8,
value: []const u8,
domain: ?[]const u8 = null,
path: ?[]const u8 = null,
/// max age in seconds. 0 -> session
max_age_s: c_int = 0,
secure: bool = true,
http_only: bool = true,
};
pub const HttpRequestFn = *const fn (r: [*c]fio.http_s) callconv(.C) void;
pub const SimpleHttpRequestFn = *const fn (SimpleRequest) void;