Getting Started
This guide shows how to write a small HTTP server with Dusty. If you want to use the HTTP client, see Making Requests instead.
Setup
We'll start from an empty project. You need Zig 0.16 or 0.17.
Create the project and add Dusty as a dependency:
TODO
This installs Dusty from the main branch. Switch to a release tag once the next version is out.
Then open build.zig and add the module to your executable, after the exe definition:
const dusty_dep = b.dependency("dusty", .{
.target = target,
.optimize = optimize,
});
exe.root_module.addImport("dusty", dusty_dep.module("dusty"));
See Build Options for how to configure the package.
Hello World
Put this into src/main.zig:
const std = @import("std");
const http = @import("dusty");
const App = struct {
greeting: []const u8,
};
fn index(app: *App, res: *http.Response) !void {
try res.print(.text, "{s}, world!\n", .{app.greeting});
}
fn hello(app: *App, name: http.Param("name", []const u8), res: *http.Response) !void {
try res.print(.text, "{s}, {s}!\n", .{ app.greeting, name.value });
}
pub fn main(init: std.process.Init) !void {
var app: App = .{ .greeting = "Hello" };
var server = http.Server(App).init(init.gpa, init.io, .{
.listeners = &.{.{
.address = .{ .ip = try std.Io.net.IpAddress.parse("127.0.0.1", 8080) },
}},
}, &app);
defer server.deinit();
server.router.get("/", index);
server.router.get("/hello/:name", hello);
try server.run();
}
Now build and run it:
Then try it from another terminal:
In a debug build, the server also logs each request it gets.
Here is what the code does:
Appholds the state of your application. Here it's only a greeting. In a real server it would be things like a database pool or settings loaded at startup.http.Server(App).initcreates the server. It takes an allocator, thestd.Ioimplementation, the configuration, and a pointer to the app.- The configuration has a single listener on
127.0.0.1:8080, so the server can only be reached from your own machine. Use0.0.0.0to accept connections from other machines. server.router.getregisters a handler forGETrequests to a path. In/hello/:name, the:namepart matches one segment of the path.server.run()accepts connections and serves them until the program is stopped.
A handler declares only the parameters it needs. The order doesn't matter. Both handlers take
*App and *http.Response. The *App is the pointer you passed to init. hello also takes
http.Param("name", []const u8), which holds the :name segment in .value. A handler that
needs the request itself, for example to read its headers or body, can also take *http.Request.
res.print formats the body and sets the content type from its first argument. .text sends
text/plain; charset=UTF-8. The response is sent after the handler returns.
Running in Production
The server above runs on init.io, which is std.Io.Threaded from the standard library. It gives
every connection its own OS thread. That's fine for development. See Deployment
for what to use in production.
Next Steps
- Routing covers path parameters, wildcards and groups.
- Handlers lists everything a handler can take and explains how errors become responses.
- Configuration has the listener, timeout and limit settings.
- Making Requests covers the HTTP client.