HTTP Server
This example follows the same pattern as the TCP server we built previously - accepting connections and spawning tasks to handle clients - but uses Zig's standard library std.http.Server to handle the HTTP protocol.
You'll see that thanks to the standard std.Io.Reader and std.Io.Writer interfaces, you can use existing Zig libraries that were not written with async I/O in mind.
The Code
Replace the contents of src/main.zig with this:
const std = @import("std");
const zio = @import("zio");
// Maximum size of the request headers
const MAX_REQUEST_HEADER_SIZE = 64 * 1024;
fn handleClient(stream: zio.net.Stream) !void {
defer stream.close();
std.log.info("HTTP client connected from {f}", .{stream.socket.address});
var read_buffer: [MAX_REQUEST_HEADER_SIZE]u8 = undefined;
var reader = stream.reader(&read_buffer);
var write_buffer: [4096]u8 = undefined;
var writer = stream.writer(&write_buffer);
// Initialize HTTP server for this connection
var server = std.http.Server.init(&reader.interface, &writer.interface);
while (true) {
// Receive HTTP request headers
var request = server.receiveHead() catch |err| switch (err) {
error.ReadFailed => |e| return reader.err orelse e,
else => |e| return e,
};
std.log.info("{t} {s}", .{ request.head.method, request.head.target });
// Simple HTML response
const html =
\\<!DOCTYPE html>
\\<html>
\\<head><title>zio HTTP Server</title></head>
\\<body>
\\ <h1>Hello from zio!</h1>
\\ <p>This is a simple HTTP server built with zio async runtime and std.http.Server.</p>
\\</body>
\\</html>
;
try request.respond(html, .{
.status = .ok,
.extra_headers = &.{
.{ .name = "content-type", .value = "text/html; charset=utf-8" },
},
});
// If the client doesn't want keep-alive, close the connection
if (!request.head.keep_alive) {
try stream.shutdown(.both);
break;
}
}
std.log.info("HTTP client disconnected", .{});
}
pub fn main() !void {
const rt = try zio.Runtime.init(std.heap.smp_allocator, .{});
defer rt.deinit();
const addr = try zio.net.IpAddress.parseIp4("127.0.0.1", 8080);
const server = try addr.listen(.{});
defer server.close();
std.log.info("HTTP server listening on {f}", .{server.socket.address});
std.log.info("Visit http://{f} in your browser", .{server.socket.address});
std.log.info("Press Ctrl+C to stop the server", .{});
var group: zio.Group = .init;
defer group.cancel();
while (true) {
const stream = try server.accept();
errdefer stream.close();
try group.spawn(handleClient, .{stream});
}
}
Now build and run it:
$ zig build run
info: HTTP server listening on 127.0.0.1:8080
info: Visit http://127.0.0.1:8080 in your browser
info: Press Ctrl+C to stop the server
Open your browser and visit http://localhost:8080 to see the response.
How It Works
The structure is similar to the TCP server, but instead of reading raw bytes, we use std.http.Server to handle the HTTP protocol.
Setting Up the HTTP Server
For each client connection, we create an HTTP server instance:
// Initialize HTTP server for this connection
var server = std.http.Server.init(&reader.interface, &writer.interface);
The key here is that std.http.Server works with any std.Io.Reader and std.Io.Writer. ZIO's stream reader and writer implement these standard interfaces, so they work seamlessly with existing Zig libraries.
Handling Requests
The request handling loop receives HTTP requests and sends responses:
while (true) {
// Receive HTTP request headers
var request = server.receiveHead() catch |err| switch (err) {
error.ReadFailed => |e| return reader.err orelse e,
else => |e| return e,
};
std.log.info("{t} {s}", .{ request.head.method, request.head.target });
// Simple HTML response
const html =
\\<!DOCTYPE html>
\\<html>
\\<head><title>zio HTTP Server</title></head>
\\<body>
\\ <h1>Hello from zio!</h1>
\\ <p>This is a simple HTTP server built with zio async runtime and std.http.Server.</p>
\\</body>
\\</html>
;
try request.respond(html, .{
.status = .ok,
.extra_headers = &.{
.{ .name = "content-type", .value = "text/html; charset=utf-8" },
},
});
// If the client doesn't want keep-alive, close the connection
if (!request.head.keep_alive) {
try stream.shutdown(.both);
break;
}
}
The server supports HTTP keep-alive, allowing multiple requests over a single connection. When the client doesn't want keep-alive, we shut down the connection.
Ecosystem Integration
This example shows an important aspect of ZIO: because it implements the standard std.Io.Reader and std.Io.Writer interfaces, you can use ZIO with any library from the Zig ecosystem that works with these interfaces. You don't need special "async" versions of libraries - regular Zig libraries just work.
This means you can:
- Use
std.httpfor HTTP (as shown here) - Use
std.crypto.tlsfor TLS connections - Use
std.jsonfor JSON parsing - Use any third-party library that reads/writes data through standard interfaces
The runtime handles the async I/O under the hood, so library authors don't need to know about ZIO for their code to work with it.
For more complex HTTP applications, check out Dusty, a full-featured HTTP client/server library built on top of ZIO.