mirror of
https://github.com/zigzap/zap.git
synced 2025-10-20 23:24:09 +00:00
cookies!
This commit is contained in:
parent
00c809876e
commit
b45a7df668
7 changed files with 247 additions and 18 deletions
|
@ -38,12 +38,14 @@ Here's what works:
|
||||||
- **[endpoint](examples/endpoint/)**: a simple JSON REST API example featuring
|
- **[endpoint](examples/endpoint/)**: a simple JSON REST API example featuring
|
||||||
a `/users` endpoint for PUTting/DELETE-ing/GET-ting/POST-ing and listing
|
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.
|
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.
|
[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).
|
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.
|
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
|
I'll continue wrapping more of facil.io's functionality and adding stuff to zap
|
||||||
|
|
|
@ -51,6 +51,7 @@ pub fn build(b: *std.build.Builder) !void {
|
||||||
.{ .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" },
|
.{ .name = "http_params", .src = "examples/http_params/http_params.zig" },
|
||||||
|
.{ .name = "cookies", .src = "examples/cookies/cookies.zig" },
|
||||||
}) |excfg| {
|
}) |excfg| {
|
||||||
const ex_name = excfg.name;
|
const ex_name = excfg.name;
|
||||||
const ex_src = excfg.src;
|
const ex_src = excfg.src;
|
||||||
|
|
|
@ -1,13 +1,9 @@
|
||||||
.{
|
.{
|
||||||
.name = "zap",
|
.name = "zap",
|
||||||
.version = "0.0.14",
|
.version = "0.0.15",
|
||||||
|
|
||||||
.dependencies = .{
|
.dependencies = .{
|
||||||
.@"facil.io" = .{
|
.@"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",
|
.url = "https://github.com/zigzap/facil.io/archive/refs/tags/zap-0.0.7.tar.gz",
|
||||||
.hash = "1220d03e0579bbb726efb8224ea289b26227bc421158b45c1b16a60b31bfa400ab33",
|
.hash = "1220d03e0579bbb726efb8224ea289b26227bc421158b45c1b16a60b31bfa400ab33",
|
||||||
|
|
||||||
|
|
114
examples/cookies/cookies.zig
Normal file
114
examples/cookies/cookies.zig
Normal 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
6
flake.lock
generated
|
@ -166,11 +166,11 @@
|
||||||
"nixpkgs": "nixpkgs_2"
|
"nixpkgs": "nixpkgs_2"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1682641397,
|
"lastModified": 1683288458,
|
||||||
"narHash": "sha256-gt/jGCv21Oju3R8t9uzq5c7l3EagYnWhKGlY+uNNtAM=",
|
"narHash": "sha256-Avc/4kh6wb5C2C+kDcm8S8fnR1gkDOWW7RPulwaw1Tg=",
|
||||||
"owner": "mitchellh",
|
"owner": "mitchellh",
|
||||||
"repo": "zig-overlay",
|
"repo": "zig-overlay",
|
||||||
"rev": "9de49969d69441e2fb50ab891d6fbd0cff0f0b5c",
|
"rev": "9f9baaee29b3615ef0d7bbe6f8367681ea8091cb",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
|
44
src/fio.zig
44
src/fio.zig
|
@ -67,6 +67,47 @@ pub const http_s = extern struct {
|
||||||
body: FIOBJ,
|
body: FIOBJ,
|
||||||
udata: ?*anyopaque,
|
udata: ?*anyopaque,
|
||||||
}; // zig-cache/i/e0c8a6e617497ade13de512cbe191f23/include/http.h:153:12: warning: struct demoted to opaque type - has bitfield
|
}; // 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 {
|
pub const struct_fio_str_info_s = extern struct {
|
||||||
capa: usize,
|
capa: usize,
|
||||||
len: 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);
|
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_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_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;
|
pub extern fn http_set_cookie(h: [*c]http_s, http_cookie_args_s) c_int;
|
||||||
|
|
86
src/zap.zig
86
src/zap.zig
|
@ -50,6 +50,7 @@ pub const HttpError = error{
|
||||||
HttpSetHeader,
|
HttpSetHeader,
|
||||||
HttpParseBody,
|
HttpParseBody,
|
||||||
HttpIterParams,
|
HttpIterParams,
|
||||||
|
SetCookie,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const ContentType = enum {
|
pub const ContentType = enum {
|
||||||
|
@ -180,15 +181,51 @@ pub const SimpleRequest = struct {
|
||||||
///
|
///
|
||||||
/// Result is accessible via parametersToOwnedSlice(), parametersToOwnedStrSlice()
|
/// Result is accessible via parametersToOwnedSlice(), parametersToOwnedStrSlice()
|
||||||
pub fn parseQuery(self: *const Self) void {
|
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(self: *const Self, url_encoded: bool) void {
|
||||||
pub fn parseCookies() !void {}
|
fio.http_parse_cookies(self.h, if (url_encoded) 1 else 0);
|
||||||
|
}
|
||||||
|
|
||||||
/// not implemented.
|
// Set a response cookie
|
||||||
pub fn getCookie(name: []const u8) ?[]const u8 {
|
pub fn setCookie(self: *const Self, args: CookieArgs) HttpError!void {
|
||||||
_ = name;
|
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.
|
/// Returns the number of parameters after parsing.
|
||||||
|
@ -199,6 +236,32 @@ pub const SimpleRequest = struct {
|
||||||
return fio.fiobj_obj2num(self.h.*.params);
|
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 = ¶ms,
|
||||||
|
.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 = ¶ms, .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.
|
/// Returns the query / body parameters as key/value pairs, as strings.
|
||||||
/// Supported param types that will be converted:
|
/// 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 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;
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue