-
Notifications
You must be signed in to change notification settings - Fork 58
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
49755ee
commit 0872ba1
Showing
12 changed files
with
368 additions
and
26 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
const std = @import("std"); | ||
const httpz = @import("httpz"); | ||
const Allocator = std.mem.Allocator; | ||
|
||
const PORT = 8800; | ||
|
||
// This example demonstrates basic httpz usage, with focus on using the | ||
// httpz.Request and httpz.Response objects. | ||
|
||
pub fn main() !void { | ||
var gpa = std.heap.GeneralPurposeAllocator(.{}){}; | ||
const allocator = gpa.allocator(); | ||
|
||
// We pass a "void" handler. This is the simplest, but limits what we can do | ||
// The last parameter is an instance of our handler. Since we have | ||
// a void handler, we pass a void value: i.e. {}. | ||
var server = try httpz.Server(void).init(allocator, .{ | ||
.port = PORT, | ||
.request = .{ | ||
// httpz has a number of tweakable configuration settings (see readme) | ||
// by default, it won't read form data. We need to configure a max | ||
// field count (since one of our examples reads form data) | ||
.max_form_count = 20, | ||
}, | ||
}, {}); | ||
defer server.deinit(); | ||
|
||
var router = server.router(); | ||
|
||
// Register routes. The last parameter is a Route Config. For these basic | ||
// examples, we aren't using it. | ||
// Other support methods: post, put, delete, head, trace, options and all | ||
router.get("/", index, .{}); | ||
router.get("/hello", hello, .{}); | ||
router.get("/json/hello/:name", json, .{}); | ||
router.get("/writer/hello/:name", writer, .{}); | ||
router.get("/metrics", metrics, .{}); | ||
router.get("/form_data", formShow, .{}); | ||
router.post("/form_data", formPost, .{}); | ||
|
||
std.debug.print("listening http://localhost:{d}/\n", .{PORT}); | ||
// Starts the server, this is blocking. | ||
try server.listen(); | ||
} | ||
|
||
fn index(_: *httpz.Request, res: *httpz.Response) !void { | ||
res.body = | ||
\\<!DOCTYPE html> | ||
\\ <ul> | ||
\\ <li><a href="/hello?name=Teg">Querystring + text output</a> | ||
\\ <li><a href="/writer/hello/Ghanima">Path parameter + serialize json object</a> | ||
\\ <li><a href="/json/hello/Duncan">Path parameter + json writer</a> | ||
\\ <li><a href="/metrics">Internal metrics</a> | ||
\\ <li><a href="/form_data">Form Data</a> | ||
; | ||
} | ||
|
||
fn hello(req: *httpz.Request, res: *httpz.Response) !void { | ||
const query = try req.query(); | ||
const name = query.get("name") orelse "stranger"; | ||
|
||
// Could also see res.writer(), see the writer endpoint for an example | ||
res.body = try std.fmt.allocPrint(res.arena, "Hello {s}", .{name}); | ||
} | ||
|
||
fn json(req: *httpz.Request, res: *httpz.Response) !void { | ||
const name = req.param("name").?; | ||
|
||
// the last parameter to res.json is an std.json.StringifyOptions | ||
try res.json(.{ .hello = name }, .{}); | ||
} | ||
|
||
fn writer(req: *httpz.Request, res: *httpz.Response) !void { | ||
res.content_type = httpz.ContentType.JSON; | ||
|
||
const name = req.param("name").?; | ||
var ws = std.json.writeStream(res.writer(), .{ .whitespace = .indent_4 }); | ||
try ws.beginObject(); | ||
try ws.objectField("name"); | ||
try ws.write(name); | ||
try ws.endObject(); | ||
} | ||
|
||
fn metrics(_: *httpz.Request, res: *httpz.Response) !void { | ||
// httpz exposes some prometheus-style metrics | ||
return httpz.writeMetrics(res.writer()); | ||
} | ||
|
||
fn formShow(_: *httpz.Request, res: *httpz.Response) !void { | ||
res.body = | ||
\\ <html> | ||
\\ <form method=post> | ||
\\ <p><input name=name value=goku></p> | ||
\\ <p><input name=power value=9001></p> | ||
\\ <p><input type=submit value=submit></p> | ||
\\ </form> | ||
; | ||
} | ||
|
||
fn formPost(req: *httpz.Request, res: *httpz.Response) !void { | ||
var it = (try req.formData()).iterator(); | ||
|
||
res.content_type = .TEXT; | ||
|
||
const w = res.writer(); | ||
while (it.next()) |kv| { | ||
try std.fmt.format(w, "{s}={s}\n", .{kv.key, kv.value}); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
const std = @import("std"); | ||
const httpz = @import("httpz"); | ||
const Allocator = std.mem.Allocator; | ||
|
||
const PORT = 8801; | ||
|
||
// This example demonstrates using a custom Handler. It shows how to have | ||
// global state (here we show a counter, but it could be a more complex struct | ||
// including things such as a DB pool) and how to define not found and error | ||
// handlers. | ||
|
||
pub fn main() !void { | ||
|
||
var gpa = std.heap.GeneralPurposeAllocator(.{}){}; | ||
const allocator = gpa.allocator(); | ||
|
||
// We specify our "Handler" and, as the last parameter to init, pass an | ||
// instance of it. | ||
var handler = Handler{}; | ||
var server = try httpz.Server(*Handler).init(allocator, .{.port = PORT}, &handler); | ||
defer server.deinit(); | ||
|
||
var router = server.router(); | ||
|
||
// Register routes. | ||
|
||
router.get("/", index, .{}); | ||
router.get("/hits", hits, .{}); | ||
router.get("/error", @"error", .{}); | ||
|
||
std.debug.print("listening http://localhost:{d}/\n", .{PORT}); | ||
|
||
// Starts the server, this is blocking. | ||
try server.listen(); | ||
} | ||
|
||
const Handler = struct { | ||
_hits: usize = 0, | ||
|
||
// If the handler defines a special "notFound" function, it'll be called | ||
// when a request is made and no route matches. | ||
pub fn notFound(_: *Handler, _: *httpz.Request, res: *httpz.Response) !void { | ||
res.status = 404; | ||
res.body = "NOPE!"; | ||
} | ||
|
||
// If the handler defines the special "uncaughtError" function, it'll be | ||
// called when an action returns an error. | ||
// Note that this function takes an additional parameter (the error) and | ||
// returns a `void` rather than a `!void`. | ||
pub fn uncaughtError(_: *Handler, req: *httpz.Request, res: *httpz.Response, err: anyerror) void { | ||
std.debug.print("uncaught http error at {s}: {}\n", .{req.url.path, err}); | ||
|
||
// Alternative to res.content_type = .TYPE | ||
// useful for dynamic content types, or content types not defined in | ||
// httpz.ContentType | ||
res.headers.add("content-type", "text/html; charset=utf-8"); | ||
|
||
res.status = 505; | ||
res.body = "<!DOCTYPE html>(╯°□°)╯︵ ┻━┻"; | ||
} | ||
}; | ||
|
||
fn index(_: *Handler, _: *httpz.Request, res: *httpz.Response) !void { | ||
res.body = | ||
\\<!DOCTYPE html> | ||
\\ <ul> | ||
\\ <li><a href="/hits">Shared global hit counter</a> | ||
\\ <li><a href="/not_found">Custom not found handler</a> | ||
\\ <li><a href="/error">Custom error handler</a> | ||
; | ||
} | ||
|
||
pub fn hits(h: *Handler, _: *httpz.Request, res: *httpz.Response) !void { | ||
const count = @atomicRmw(usize, &h._hits, .Add, 1, .monotonic); | ||
|
||
// @atomicRmw returns the previous version so we need to +1 it | ||
// to display the count includin this hit | ||
return res.json(.{.hits = count + 1}, .{}); | ||
} | ||
|
||
fn @"error"(_: *Handler, _: *httpz.Request, _: *httpz.Response) !void { | ||
return error.ActionError; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
const std = @import("std"); | ||
const httpz = @import("httpz"); | ||
const Allocator = std.mem.Allocator; | ||
|
||
const PORT = 8802; | ||
|
||
// This example uses a custom dispatch method on our handler for greater control | ||
// in how actions are executed. | ||
|
||
pub fn main() !void { | ||
var gpa = std.heap.GeneralPurposeAllocator(.{}){}; | ||
const allocator = gpa.allocator(); | ||
|
||
var handler = Handler{}; | ||
var server = try httpz.Server(*Handler).init(allocator, .{.port = PORT}, &handler); | ||
defer server.deinit(); | ||
|
||
var router = server.router(); | ||
|
||
router.get("/", index, .{}); | ||
std.debug.print("listening http://localhost:{d}/\n", .{PORT}); | ||
|
||
// Starts the server, this is blocking. | ||
try server.listen(); | ||
} | ||
|
||
const Handler = struct { | ||
// In addition to the special "notFound" and "uncaughtError" shown in example 2 | ||
// the special "dispatch" method can be used to gain more control over request handling. | ||
pub fn dispatch(self: *Handler, action: httpz.Action(*Handler), req: *httpz.Request, res: *httpz.Response) !void { | ||
// Our custom dispatch lets us add a log + timing for every request | ||
// httpz supports middlewares, but in many cases, having a dispatch is good | ||
// enough and is much more straightforward. | ||
|
||
var start = try std.time.Timer.start(); | ||
// We don't _have_ to call the action if we don't want to. For example | ||
// we could do authentication and set the response directly on error. | ||
try action(self, req, res); | ||
|
||
std.debug.print("{d}\t{d}us\t{s}\n", .{std.time.timestamp(), start.lap() / 1000, req.url.path}); | ||
} | ||
}; | ||
|
||
fn index(_: *Handler, _: *httpz.Request, res: *httpz.Response) !void { | ||
res.body = "see the console logs"; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
const std = @import("std"); | ||
const httpz = @import("httpz"); | ||
const Allocator = std.mem.Allocator; | ||
|
||
const PORT = 8803; | ||
|
||
// This example is very similar to 03_dispatch.zig, but shows how the action | ||
// state can be a different type than the handler. | ||
|
||
pub fn main() !void { | ||
var gpa = std.heap.GeneralPurposeAllocator(.{}){}; | ||
const allocator = gpa.allocator(); | ||
|
||
var handler = Handler{}; | ||
var server = try httpz.Server(*Handler).init(allocator, .{.port = PORT}, &handler); | ||
defer server.deinit(); | ||
|
||
var router = server.router(); | ||
|
||
const restricted_route = &RouteData{.restricted = true}; | ||
|
||
// We can register arbitrary data to a route, which we can retrieve | ||
// via req.route_data. This is stored as a `*const anyopaque`. | ||
router.get("/", index, .{}); | ||
router.get("/admin", admin, .{.data = restricted_route}); | ||
|
||
std.debug.print("listening http://localhost:{d}/\n", .{PORT}); | ||
|
||
// Starts the server, this is blocking. | ||
try server.listen(); | ||
} | ||
|
||
const Handler = struct { | ||
// In example_3, our action type was: httpz.Action(*Handler). | ||
// In this example, we've changed it to: httpz.Action(*Env) | ||
// This allows our handler to be a general app-wide "state" while our actions | ||
// received a request-specific context | ||
pub fn dispatch(self: *Handler, action: httpz.Action(*Env), req: *httpz.Request, res: *httpz.Response) !void { | ||
const user = (try req.query()).get("auth"); | ||
|
||
// RouteData can be anything, but since it's stored as a *const anyopaque | ||
// you'll need to restore the type/alignment. | ||
|
||
// (You could also use a per-route handler, or middleware, to achieve | ||
// the same thing. Using route data is a bit ugly due to the type erasure | ||
// but it can be convenient!). | ||
if (req.route_data) |rd| { | ||
const route_data: *const RouteData = @ptrCast(@alignCast(rd)); | ||
if (route_data.restricted and (user == null or user.?.len == 0)) { | ||
res.status = 401; | ||
res.body = "permission denied"; | ||
return; | ||
} | ||
} | ||
|
||
var env = Env{ | ||
.user = user, // todo: this is not very good security! | ||
.handler = self, | ||
}; | ||
|
||
try action(&env, req, res); | ||
} | ||
}; | ||
|
||
const RouteData = struct{ | ||
restricted: bool, | ||
}; | ||
|
||
const Env = struct{ | ||
handler: *Handler, | ||
user: ?[]const u8, | ||
}; | ||
|
||
fn index(_: *Env, _: *httpz.Request, res: *httpz.Response) !void { | ||
res.body = | ||
\\<!DOCTYPE html> | ||
\\ <ul> | ||
\\ <li><a href="/admin?auth=sudo">admin</a> | ||
; | ||
} | ||
|
||
// because of our dispatch method, this can only be called when env.user != null | ||
fn admin(env: *Env, _: *httpz.Request, res: *httpz.Response) !void { | ||
res.body = try std.fmt.allocPrint(res.arena, "Welcome to the admin portal, {s}", .{env.user.?}); | ||
} |
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
Oops, something went wrong.