Hello everyone! I am a beginner learning Zig. I’m attempting to write a simple HTTP server to help me get started. I found a library called libxev that I like and discovered an example on GitHub to use as a starting point. However, I stumbled when I tried to split the Client struct into another file. There seems to be a circular dependency with the type ClientPool. I’m not sure how to resolve this issue in Zig. Any suggestions would be greatly appreciated. Thank you.
link: xev-http/src/main.zig at master · dylanblokhuis/xev-http · GitHub
Hi @chrischtel Welcome to ziggit
I’ve tried to split into client.zig
, server.zig
and main.zig
.
In version 0.13.0, Build process is successful.
client.zig
const std = @import("std");
const xev = @import("xev");
pub const CompletionPool = std.heap.MemoryPoolExtra(xev.Completion, .{});
pub const ClientPool = std.heap.MemoryPoolExtra(Client, .{});
pub const Client = struct {
id: u32,
socket: xev.TCP,
loop: *xev.Loop,
arena: std.heap.ArenaAllocator,
client_pool: *ClientPool,
completion_pool: *CompletionPool,
read_buf: [4096]u8 = undefined,
const Self = @This();
pub fn work(self: *Self) void {
const c_read = self.completion_pool.create() catch unreachable;
self.socket.read(self.loop, c_read, .{ .slice = &self.read_buf }, Client, self, Client.readCallback);
}
pub fn readCallback(
self_: ?*Client,
l: *xev.Loop,
c: *xev.Completion,
s: xev.TCP,
buf: xev.ReadBuffer,
r: xev.TCP.ReadError!usize,
) xev.CallbackAction {
const self = self_.?;
const n = r catch |err| {
std.log.err("read error {any}", .{err});
s.shutdown(l, c, Client, self, shutdownCallback);
return .disarm;
};
const data = buf.slice[0..n];
std.log.info("{s}", .{data});
const httpOk =
\\HTTP/1.1 200 OK
\\Content-Type: text/plain
\\Server: xev-http
\\Content-Length: {d}
\\Connection: close
\\
\\{s}
;
const content_str =
\\Hello, World! {d}
;
const content = std.fmt.allocPrint(self.arena.allocator(), content_str, .{self.id}) catch unreachable;
const res = std.fmt.allocPrint(self.arena.allocator(), httpOk, .{ content.len, content }) catch unreachable;
self.socket.write(self.loop, c, .{ .slice = res }, Client, self, writeCallback);
return .disarm;
}
fn writeCallback(
self_: ?*Client,
l: *xev.Loop,
c: *xev.Completion,
s: xev.TCP,
buf: xev.WriteBuffer,
r: xev.TCP.WriteError!usize,
) xev.CallbackAction {
_ = buf; // autofix
_ = r catch unreachable;
const self = self_.?;
s.shutdown(l, c, Client, self, shutdownCallback);
return .disarm;
}
fn shutdownCallback(
self_: ?*Client,
l: *xev.Loop,
c: *xev.Completion,
s: xev.TCP,
r: xev.TCP.ShutdownError!void,
) xev.CallbackAction {
_ = r catch {};
const self = self_.?;
s.close(l, c, Client, self, closeCallback);
return .disarm;
}
fn closeCallback(
self_: ?*Client,
l: *xev.Loop,
c: *xev.Completion,
socket: xev.TCP,
r: xev.TCP.CloseError!void,
) xev.CallbackAction {
_ = l;
_ = r catch unreachable;
_ = socket;
var self = self_.?;
self.arena.deinit();
self.completion_pool.destroy(c);
self.client_pool.destroy(self);
return .disarm;
}
pub fn destroy(self: *Self) void {
self.arena.deinit();
self.client_pool.destroy(self);
}
};
server.zig
const std = @import("std");
const xev = @import("xev");
const Allocator = std.mem.Allocator;
const clients = @import("client.zig");
const Client = clients.Client;
const CompletionPool = clients.CompletionPool;
const ClientPool = clients.ClientPool;
pub const Server = struct {
loop: *xev.Loop,
gpa: Allocator,
completion_pool: *CompletionPool,
client_pool: *ClientPool,
conns: u32 = 0,
pub fn acceptCallback(
self_: ?*Server,
l: *xev.Loop,
// we ignore the completion, to keep the accept loop going for new connections
_: *xev.Completion,
r: xev.TCP.AcceptError!xev.TCP,
) xev.CallbackAction {
const self = self_.?;
var client = self.client_pool.create() catch unreachable;
client.* = Client{
.id = self.conns,
.loop = l,
.socket = r catch unreachable,
.arena = std.heap.ArenaAllocator.init(self.gpa),
.client_pool = self.client_pool,
.completion_pool = self.completion_pool,
};
client.work();
self.conns += 1;
return .rearm;
}
};
main.zig
const std = @import("std");
const xev = @import("xev");
const clients = @import("client.zig");
const Client = clients.Client;
const CompletionPool = clients.CompletionPool;
const ClientPool = clients.ClientPool;
const Server = @import("server.zig").Server;
const net = std.net;
const Allocator = std.mem.Allocator;
pub fn main() !void {
var thread_pool = xev.ThreadPool.init(.{});
defer thread_pool.deinit();
defer thread_pool.shutdown();
var loop = try xev.Loop.init(.{
.entries = 4096,
.thread_pool = &thread_pool,
});
defer loop.deinit();
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const alloc = gpa.allocator();
const port = 3000;
const addr = try net.Address.parseIp4("0.0.0.0", port);
var socket = try xev.TCP.init(addr);
std.log.info("Listening on port {}", .{port});
try socket.bind(addr);
try socket.listen(std.os.linux.SOMAXCONN);
var completion_pool = CompletionPool.init(alloc);
defer completion_pool.deinit();
var client_pool = ClientPool.init(alloc);
defer client_pool.deinit();
const c = try completion_pool.create();
var server = Server{
.loop = &loop,
.gpa = alloc,
.completion_pool = &completion_pool,
.client_pool = &client_pool,
};
socket.accept(&loop, c, Server, &server, Server.acceptCallback);
try loop.run(.until_done);
}
For 0.13.0, build.zig
has changed a little.
const exe = b.addExecutable(.{
.name = "xev-http",
- .root_source_file = .{ .path = "src/main.zig" },
+ .root_source_file = .{ .cwd_relative = "src/main.zig" },
.target = target,
.optimize = optimize,
// .use_llvm = false,
// .use_lld = false,
});
2 Likes
Oh cool, thanks!
This is not the correct fix! Please use instead:
const exe = b.addExecutable(.{
.name = "xev-http",
- .root_source_file = .{ .path = "src/main.zig" },
+ .root_source_file = b.path("src/main.zig"),
.target = target,
.optimize = optimize,
// .use_llvm = false,
// .use_lld = false,
});
3 Likes
Thank you for your follow up.
Hi, this an off-topic question, but I don’t think it deserves it’s own topic.
What is the difference between b.path and .{ .path = … } ?
path
field of std.Build.LazyPath is deprecated with version 0.13
https://ziglang.org/documentation/0.12.0/std/#std.Build.LazyPath