srctree

Gregory Mullen parent 833b40b6 82dbd6ab
add ability to set cookies in the response

examples/cookies.zig added: 104, removed: 45, total 59
@@ -4,6 +4,10 @@ const Router = Verse.Router;
const BuildFn = Router.BuildFn;
const print = std.fmt.bufPrint;
 
const Cookie = Verse.Cookies.Cookie;
var Random = std.Random.DefaultPrng.init(1337);
var random = Random.random();
 
const routes = [_]Router.Match{
Router.GET("", index),
};
@@ -28,6 +32,19 @@ fn route(verse: *Verse) Router.Error!BuildFn {
fn index(verse: *Verse) Router.Error!void {
var buffer: [2048]u8 = undefined;
const found = try print(&buffer, "{} cookies found by the server\n", .{verse.request.cookie_jar.cookies.items.len});
 
const random_cookie = @tagName(random.enumValue(enum {
chocolate_chip,
sugar,
oatmeal,
peanut_butter,
ginger_snap,
}));
 
try verse.response.cookie_jar.add(Cookie{
.name = "best-flavor",
.value = random_cookie,
});
try verse.quickStart();
try verse.sendRawSlice(found);
}
 
src/cookies.zig added: 104, removed: 45, total 59
@@ -63,8 +63,11 @@ pub const Cookie = struct {
};
}
 
pub fn format(c: Cookie, comptime _: []const u8, _: fmt.FormatOptions, w: anytype) !void {
try w.print("Set-Cookie: {s}={s}{}", .{ c.name, c.value, c.attr });
pub fn format(c: Cookie, comptime fstr: []const u8, _: fmt.FormatOptions, w: anytype) !void {
if (comptime eql(u8, fstr, "header")) {
try w.writeAll("Set-Cookie: ");
}
try w.print("{s}={s}{}", .{ c.name, c.value, c.attr });
}
};
 
@@ -86,7 +89,7 @@ test Cookie {
};
 
for (expected, cookies) |expect, cookie| {
const res = try fmt.bufPrint(&buffer, "{}", .{cookie});
const res = try fmt.bufPrint(&buffer, "{header}", .{cookie});
try std.testing.expectEqualStrings(expect, res);
}
}
@@ -144,6 +147,18 @@ pub const Jar = struct {
return found;
}
 
/// Caller owns the array, and each name and value string
pub fn toHeaderSlice(jar: *Jar, a: Allocator) ![]Headers.Header {
const slice = try a.alloc(Headers.Header, jar.cookies.items.len);
for (slice, jar.cookies.items) |*s, cookie| {
s.* = .{
.name = try a.dupe(u8, "Set-Cookie"),
.value = try fmt.allocPrint(a, "{}", .{cookie}),
};
}
return slice;
}
 
/// Reduces size of cookies to it's current length.
pub fn shrink(jar: *Jar) void {
jar.cookies.shrinkAndFree(jar.alloc, jar.cookies.len);
 
src/headers.zig added: 104, removed: 45, total 59
@@ -4,7 +4,7 @@ const Allocator = std.mem.Allocator;
 
pub const Headers = @This();
 
const Header = struct {
pub const Header = struct {
name: []const u8,
value: []const u8,
};
@@ -110,6 +110,19 @@ pub const Iterator = struct {
}
};
 
pub fn toSlice(h: Headers, a: Allocator) ![]Header {
var itr = h.iterator();
var count: usize = 0;
while (itr.next()) |_| count += 1;
 
const slice = try a.alloc(Header, count);
itr = h.iterator();
for (slice) |*s| {
s.* = itr.next() orelse unreachable;
}
return slice;
}
 
pub fn format(h: Headers, comptime _: []const u8, _: std.fmt.FormatOptions, out: anytype) !void {
_ = h;
_ = out;
 
src/response.zig added: 104, removed: 45, total 59
@@ -1,6 +1,8 @@
const std = @import("std");
const bufPrint = std.fmt.bufPrint;
const Allocator = std.mem.Allocator;
const AnyWriter = std.io.AnyWriter;
const Stream = std.net.Stream;
 
const Request = @import("request.zig");
const Headers = @import("headers.zig");
@@ -37,40 +39,40 @@ const Error = error{
 
pub const Writer = std.io.Writer(*Response, Error, write);
 
alloc: Allocator,
headers: Headers,
tranfer_mode: TransferMode = .static,
// This is just bad code, but I need to give the sane implementation more thought
http_response: ?std.http.Server.Response = null,
stdhttp: struct {
request: ?*std.http.Server.Request = null,
response: ?std.http.Server.Response = null,
} = .{},
downstream: union(Downstream) {
buffer: std.io.BufferedWriter(ONESHOT_SIZE, std.net.Stream.Writer),
zwsgi: std.net.Stream.Writer,
buffer: std.io.BufferedWriter(ONESHOT_SIZE, Stream.Writer),
zwsgi: Stream.Writer,
http: std.io.AnyWriter,
},
cookie_jar: Cookies.Jar,
status: ?std.http.Status = null,
 
pub fn init(a: Allocator, req: *const Request) !Response {
var res = Response{
var self = Response{
.alloc = a,
.headers = Headers.init(a),
.http_response = switch (req.raw) {
.zwsgi => null,
.http => |h| h.*.respondStreaming(.{
.send_buffer = try a.alloc(u8, 0xffff),
.respond_options = .{
.transfer_encoding = .chunked,
.keep_alive = false,
},
}),
},
.downstream = switch (req.raw) {
.zwsgi => |z| .{ .zwsgi = z.*.acpt.stream.writer() },
.http => .{ .http = undefined },
},
.cookie_jar = try Cookies.Jar.init(a),
};
if (res.http_response) |*h| res.downstream.http = h.writer();
res.headersInit() catch @panic("unable to create Response obj");
return res;
switch (req.raw) {
.http => |h| {
self.stdhttp.request = h;
},
else => {},
}
self.headersInit() catch @panic("unable to create Response obj");
return self;
}
 
fn headersInit(res: *Response) !void {
@@ -84,15 +86,30 @@ pub fn headersAdd(res: *Response, comptime name: []const u8, value: []const u8)
 
pub fn start(res: *Response) !void {
if (res.status == null) res.status = .ok;
 
switch (res.downstream) {
.http => {
res.stdhttp.response = res.stdhttp.request.?.*.respondStreaming(.{
.send_buffer = try res.alloc.alloc(u8, 0xffffff),
.respond_options = .{
.transfer_encoding = .chunked,
.keep_alive = false,
.extra_headers = @ptrCast(try res.cookie_jar.toHeaderSlice(res.alloc)),
},
});
// I don't know why/where the writer goes invalid, but I'll probably
// fix it later?
if (res.http_response) |*h| res.downstream.http = h.writer();
if (res.stdhttp.response) |*h| res.downstream.http = h.writer();
try res.sendHeaders();
},
else => {
try res.sendHeaders();
for (res.cookie_jar.cookies.items) |cookie| {
var buffer: [1024]u8 = undefined;
const cookie_str = try bufPrint(&buffer, "{header}\r\n", .{cookie});
_ = try res.write(cookie_str);
}
 
_ = try res.write("\r\n");
},
}
@@ -112,7 +129,7 @@ fn sendHTTPHeader(res: *Response) !void {
 
pub fn sendHeaders(res: *Response) !void {
switch (res.downstream) {
.http => try res.http_response.?.flush(),
.http => try res.stdhttp.response.?.flush(),
.zwsgi, .buffer => {
try res.sendHTTPHeader();
var itr = res.headers.headers.iterator();
@@ -152,20 +169,20 @@ pub fn send(res: *Response, data: []const u8) !void {
pub fn writer(res: *const Response) AnyWriter {
return .{
.writeFn = typeErasedWrite,
.context = res,
.context = @ptrCast(&res),
};
}
 
pub fn writeChunk(res: *const Response, data: []const u8) !void {
pub fn writeChunk(res: Response, data: []const u8) !void {
comptime unreachable;
var size: [0xff]u8 = undefined;
const chunk = try std.fmt.bufPrint(&size, "{x}\r\n", .{data.len});
var size: [19]u8 = undefined;
const chunk = try bufPrint(&size, "{x}\r\n", .{data.len});
try res.writeAll(chunk);
try res.writeAll(data);
try res.writeAll("\r\n");
}
 
pub fn writeAll(res: *const Response, data: []const u8) !void {
pub fn writeAll(res: Response, data: []const u8) !void {
var index: usize = 0;
while (index < data.len) {
index += try write(res, data[index..]);
@@ -173,23 +190,20 @@ pub fn writeAll(res: *const Response, data: []const u8) !void {
}
 
pub fn typeErasedWrite(opq: *const anyopaque, data: []const u8) anyerror!usize {
const cast: *const Response = @alignCast(@ptrCast(opq));
return try write(cast, data);
const ptr: *const Response = @alignCast(@ptrCast(opq));
return try write(ptr.*, data);
}
 
/// Raw writer, use with caution! To use phase checking, use send();
pub fn write(res: *const Response, data: []const u8) !usize {
pub fn write(res: Response, data: []const u8) !usize {
return switch (res.downstream) {
.zwsgi => |*w| try w.write(data),
.http => |*w| return try w.write(data),
.buffer => {
var bff: *Response = @constCast(res);
return try bff.write(data);
},
.buffer => return try res.write(data),
};
}
 
fn flush(res: *Response) !void {
fn flush(res: Response) !void {
switch (res.downstream) {
.buffer => |*w| try w.flush(),
.http => |*h| h.flush(),
@@ -200,9 +214,8 @@ fn flush(res: *Response) !void {
pub fn finish(res: *Response) !void {
switch (res.downstream) {
.http => {
if (res.http_response) |*h| try h.endChunked(.{});
if (res.stdhttp.response) |*h| try h.endChunked(.{});
},
//.zwsgi => |*w| _ = try w.write("0\r\n\r\n"),
else => {},
}
}
 
src/verse.zig added: 104, removed: 45, total 59
@@ -13,6 +13,7 @@ pub const Router = @import("router.zig");
pub const UriIter = Router.UriIter;
 
pub const Auth = @import("auth.zig");
pub const Cookies = @import("cookies.zig");
 
const Error = @import("errors.zig").Error;
const NetworkError = @import("errors.zig").NetworkError;
@@ -76,13 +77,13 @@ pub fn sendRawSlice(vrs: *Verse, slice: []const u8) NetworkError!void {
}
 
/// Helper function to return a default error page for a given http status code.
pub fn sendError(vrs: *Verse, comptime code: std.http.Status) NetworkError!void {
pub fn sendError(vrs: *Verse, comptime code: std.http.Status) !void {
return Router.defaultResponse(code)(vrs);
}
 
/// Takes a any object, that can be represented by json, converts it into a
/// json string, and sends to the client.
pub fn sendJSON(vrs: *Verse, json: anytype) NetworkError!void {
pub fn sendJSON(vrs: *Verse, json: anytype) !void {
try vrs.quickStart();
const data = std.json.stringifyAlloc(vrs.alloc, json, .{
.emit_null_optional_fields = false,