init
This commit is contained in:
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
.zig-cache/
|
||||||
|
zig-out/
|
||||||
|
zig-pkg/
|
||||||
44
build.zig
Normal file
44
build.zig
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
pub fn build(b: *std.Build) void {
|
||||||
|
const target = b.standardTargetOptions(.{});
|
||||||
|
const optimize = b.standardOptimizeOption(.{});
|
||||||
|
|
||||||
|
const iface = b.dependency("interface_helper", .{
|
||||||
|
.target = target,
|
||||||
|
.optimize = optimize,
|
||||||
|
});
|
||||||
|
|
||||||
|
const exe = b.addExecutable(.{
|
||||||
|
.name = "zig_webserver",
|
||||||
|
.root_module = b.createModule(.{
|
||||||
|
.root_source_file = b.path("src/main.zig"),
|
||||||
|
.target = target,
|
||||||
|
.optimize = optimize,
|
||||||
|
.imports = &.{},
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
exe.root_module.addImport("interface", iface.module("interface"));
|
||||||
|
b.installArtifact(exe);
|
||||||
|
|
||||||
|
const run_step = b.step("run", "Run the app");
|
||||||
|
|
||||||
|
const run_cmd = b.addRunArtifact(exe);
|
||||||
|
run_step.dependOn(&run_cmd.step);
|
||||||
|
|
||||||
|
run_cmd.step.dependOn(b.getInstallStep());
|
||||||
|
|
||||||
|
if (b.args) |args| {
|
||||||
|
run_cmd.addArgs(args);
|
||||||
|
}
|
||||||
|
|
||||||
|
const exe_tests = b.addTest(.{
|
||||||
|
.root_module = exe.root_module,
|
||||||
|
});
|
||||||
|
|
||||||
|
const run_exe_tests = b.addRunArtifact(exe_tests);
|
||||||
|
|
||||||
|
const test_step = b.step("test", "Run tests");
|
||||||
|
test_step.dependOn(&run_exe_tests.step);
|
||||||
|
}
|
||||||
48
build.zig.zon
Normal file
48
build.zig.zon
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
.{
|
||||||
|
// This is the default name used by packages depending on this one. For
|
||||||
|
// example, when a user runs `zig fetch --save <url>`, this field is used
|
||||||
|
// as the key in the `dependencies` table. Although the user can choose a
|
||||||
|
// different name, most users will stick with this provided value.
|
||||||
|
//
|
||||||
|
// It is redundant to include "zig" in this name because it is already
|
||||||
|
// within the Zig package namespace.
|
||||||
|
.name = .zig_webserver,
|
||||||
|
// This is a [Semantic Version](https://semver.org/).
|
||||||
|
// In a future version of Zig it will be used for package deduplication.
|
||||||
|
.version = "0.0.0",
|
||||||
|
// Together with name, this represents a globally unique package
|
||||||
|
// identifier. This field is generated by the Zig toolchain when the
|
||||||
|
// package is first created, and then *never changes*. This allows
|
||||||
|
// unambiguous detection of one package being an updated version of
|
||||||
|
// another.
|
||||||
|
//
|
||||||
|
// When forking a Zig project, this id should be regenerated (delete the
|
||||||
|
// field and run `zig build`) if the upstream project is still maintained.
|
||||||
|
// Otherwise, the fork is *hostile*, attempting to take control over the
|
||||||
|
// original project's identity. Thus it is recommended to leave the comment
|
||||||
|
// on the following line intact, so that it shows up in code reviews that
|
||||||
|
// modify the field.
|
||||||
|
.fingerprint = 0xf09b7d9dd5d3c2c7, // Changing this has security and trust implications.
|
||||||
|
// Tracks the earliest Zig version that the package considers to be a
|
||||||
|
// supported use case.
|
||||||
|
.minimum_zig_version = "0.16.0",
|
||||||
|
// This field is optional.
|
||||||
|
// Each dependency must either provide a `url` and `hash`, or a `path`.
|
||||||
|
// `zig build --fetch` can be used to fetch all dependencies of a package, recursively.
|
||||||
|
// Once all dependencies are fetched, `zig build` no longer requires
|
||||||
|
// internet connectivity.
|
||||||
|
.dependencies = .{
|
||||||
|
.interface_helper = .{
|
||||||
|
.url = "git+https://git.sirlilpanda.studio/sirlilpanda/zig-interface-helpers#e64b016cfcefe93ede5eddd626fd373454167cbe",
|
||||||
|
.hash = "interface_helper-0.0.0--QUDPAtEAACFt9ZNHCzvbqGvgQgChObxyLSyA2bHWYK_",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
.paths = .{
|
||||||
|
"build.zig",
|
||||||
|
"build.zig.zon",
|
||||||
|
"src",
|
||||||
|
// For example...
|
||||||
|
//"LICENSE",
|
||||||
|
//"README.md",
|
||||||
|
},
|
||||||
|
}
|
||||||
331
src/main.zig
Normal file
331
src/main.zig
Normal file
@@ -0,0 +1,331 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const log = std.log.scoped(.server);
|
||||||
|
const iFace = @import("interface");
|
||||||
|
const LISTEN_ADDR = "127.0.0.1";
|
||||||
|
const LISTEN_PORT = 8000;
|
||||||
|
|
||||||
|
const Route = struct {
|
||||||
|
const Request = *std.http.Server.Request;
|
||||||
|
const Self = @This();
|
||||||
|
|
||||||
|
userdata: *anyopaque,
|
||||||
|
vtable: *const Vtable,
|
||||||
|
|
||||||
|
pub const Vtable = struct {
|
||||||
|
matchPath: *const iFace.VtableFn(fn (*anyopaque, []const u8) bool),
|
||||||
|
GET: *const iFace.VtableFn(fn (*anyopaque, std.Io, Request) anyerror!void),
|
||||||
|
POST: *const iFace.VtableFn(fn (*anyopaque, std.Io, Request) anyerror!void),
|
||||||
|
PUT: *const iFace.VtableFn(fn (*anyopaque, std.Io, Request) anyerror!void),
|
||||||
|
DELETE: *const iFace.VtableFn(fn (*anyopaque, std.Io, Request) anyerror!void),
|
||||||
|
HEAD: *const iFace.VtableFn(fn (*anyopaque, std.Io, Request) anyerror!void),
|
||||||
|
CONNECT: *const iFace.VtableFn(fn (*anyopaque, std.Io, Request) anyerror!void),
|
||||||
|
OPTIONS: *const iFace.VtableFn(fn (*anyopaque, std.Io, Request) anyerror!void),
|
||||||
|
TRACE: *const iFace.VtableFn(fn (*anyopaque, std.Io, Request) anyerror!void),
|
||||||
|
PATCH: *const iFace.VtableFn(fn (*anyopaque, std.Io, Request) anyerror!void),
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn init(route: anytype) Self {
|
||||||
|
return Self{
|
||||||
|
.userdata = @ptrCast(route),
|
||||||
|
.vtable = &Vtable{
|
||||||
|
.matchPath = iFace.ToVtableFn(@field(@TypeOf(route.*), "matchPath")),
|
||||||
|
.GET = iFace.ToVtableFn(@field(@TypeOf(route.*), "GET")),
|
||||||
|
.POST = iFace.ToVtableFn(@field(@TypeOf(route.*), "POST")),
|
||||||
|
.PUT = iFace.ToVtableFn(@field(@TypeOf(route.*), "PUT")),
|
||||||
|
.DELETE = iFace.ToVtableFn(@field(@TypeOf(route.*), "DELETE")),
|
||||||
|
.HEAD = iFace.ToVtableFn(@field(@TypeOf(route.*), "HEAD")),
|
||||||
|
.CONNECT = iFace.ToVtableFn(@field(@TypeOf(route.*), "CONNECT")),
|
||||||
|
.OPTIONS = iFace.ToVtableFn(@field(@TypeOf(route.*), "OPTIONS")),
|
||||||
|
.TRACE = iFace.ToVtableFn(@field(@TypeOf(route.*), "TRACE")),
|
||||||
|
.PATCH = iFace.ToVtableFn(@field(@TypeOf(route.*), "PATCH")),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn matchPath(self: Self, path: []const u8) bool {
|
||||||
|
return self.vtable.matchPath(.{ self.userdata, path });
|
||||||
|
}
|
||||||
|
pub fn GET(self: Self, io: std.Io, request: Request) anyerror!void {
|
||||||
|
return self.vtable.GET(.{ self.userdata, io, request });
|
||||||
|
}
|
||||||
|
pub fn POST(self: Self, io: std.Io, request: Request) anyerror!void {
|
||||||
|
return self.vtable.POST(.{ self.userdata, io, request });
|
||||||
|
}
|
||||||
|
pub fn PUT(self: Self, io: std.Io, request: Request) anyerror!void {
|
||||||
|
return self.vtable.PUT(.{ self.userdata, io, request });
|
||||||
|
}
|
||||||
|
pub fn DELETE(self: Self, io: std.Io, request: Request) anyerror!void {
|
||||||
|
return self.vtable.DELETE(.{ self.userdata, io, request });
|
||||||
|
}
|
||||||
|
pub fn HEAD(self: Self, io: std.Io, request: Request) anyerror!void {
|
||||||
|
return self.vtable.HEAD(.{ self.userdata, io, request });
|
||||||
|
}
|
||||||
|
pub fn CONNECT(self: Self, io: std.Io, request: Request) anyerror!void {
|
||||||
|
return self.vtable.CONNECT(.{ self.userdata, io, request });
|
||||||
|
}
|
||||||
|
pub fn OPTIONS(self: Self, io: std.Io, request: Request) anyerror!void {
|
||||||
|
return self.vtable.OPTIONS(.{ self.userdata, io, request });
|
||||||
|
}
|
||||||
|
pub fn TRACE(self: Self, io: std.Io, request: Request) anyerror!void {
|
||||||
|
return self.vtable.TRACE(.{ self.userdata, io, request });
|
||||||
|
}
|
||||||
|
pub fn PATCH(self: Self, io: std.Io, request: Request) anyerror!void {
|
||||||
|
return self.vtable.PATCH(.{ self.userdata, io, request });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const UsersRoute = struct {
|
||||||
|
const Self = @This();
|
||||||
|
const UsersRouteLog = std.log.scoped(.userRoute);
|
||||||
|
|
||||||
|
users: std.ArrayList([]const u8),
|
||||||
|
mutex: std.Io.Mutex, // dont forget the mutex
|
||||||
|
|
||||||
|
pub fn init() !Self {
|
||||||
|
return Self{
|
||||||
|
.users = .empty,
|
||||||
|
.mutex = undefined,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn matchPath(self: Self, path: []const u8) bool {
|
||||||
|
_ = self;
|
||||||
|
return std.mem.startsWith(u8, "/users/", path);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn GET(self: *Self, io: std.Io, request: *std.http.Server.Request) !void {
|
||||||
|
_ = self;
|
||||||
|
_ = io;
|
||||||
|
UsersRouteLog.info("got GET request", .{});
|
||||||
|
try request.respond("", .{ .status = .not_found });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn PUT(self: Self, io: std.Io, request: *std.http.Server.Request) !void {
|
||||||
|
_ = self;
|
||||||
|
_ = io;
|
||||||
|
UsersRouteLog.info("got PUT request", .{});
|
||||||
|
try request.respond("", .{ .status = .not_found });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn DELETE(self: Self, io: std.Io, request: *std.http.Server.Request) !void {
|
||||||
|
_ = self;
|
||||||
|
_ = io;
|
||||||
|
UsersRouteLog.info("got DELETE request", .{});
|
||||||
|
try request.respond("", .{ .status = .not_found });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn POST(self: Self, io: std.Io, request: *std.http.Server.Request) !void {
|
||||||
|
_ = self;
|
||||||
|
_ = io;
|
||||||
|
UsersRouteLog.info("got POST request", .{});
|
||||||
|
try request.respond("", .{ .status = .not_found });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
pub fn HEAD(self: Self, io: std.Io, request: *std.http.Server.Request) !void {
|
||||||
|
_ = self;
|
||||||
|
_ = io;
|
||||||
|
UsersRouteLog.info("got HEAD request", .{});
|
||||||
|
try request.respond("", .{ .status = .not_found });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
pub fn CONNECT(self: Self, io: std.Io, request: *std.http.Server.Request) !void {
|
||||||
|
_ = self;
|
||||||
|
_ = io;
|
||||||
|
UsersRouteLog.info("got CONNECT request", .{});
|
||||||
|
try request.respond("", .{ .status = .not_found });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
pub fn OPTIONS(self: Self, io: std.Io, request: *std.http.Server.Request) !void {
|
||||||
|
_ = self;
|
||||||
|
_ = io;
|
||||||
|
UsersRouteLog.info("got OPTIONS request", .{});
|
||||||
|
try request.respond("", .{ .status = .not_found });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
pub fn TRACE(self: Self, io: std.Io, request: *std.http.Server.Request) !void {
|
||||||
|
_ = self;
|
||||||
|
_ = io;
|
||||||
|
UsersRouteLog.info("got TRACE request", .{});
|
||||||
|
try request.respond("", .{ .status = .not_found });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
pub fn PATCH(self: Self, io: std.Io, request: *std.http.Server.Request) !void {
|
||||||
|
_ = self;
|
||||||
|
_ = io;
|
||||||
|
UsersRouteLog.info("got PATCH request", .{});
|
||||||
|
try request.respond("", .{ .status = .not_found });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn ERR(self: Self, io: std.Io, request: *std.http.Server.Request) !void {
|
||||||
|
_ = self;
|
||||||
|
_ = io;
|
||||||
|
try request.respond("", .{ .status = .not_found });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const HomeRoute = struct {
|
||||||
|
const Self = @This();
|
||||||
|
const homeRouteLog = std.log.scoped(.homeRoute);
|
||||||
|
site_name: []const u8,
|
||||||
|
|
||||||
|
pub fn init(name: []const u8) !Self {
|
||||||
|
return Self{
|
||||||
|
.site_name = name,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn matchPath(self: Self, path: []const u8) bool {
|
||||||
|
_ = self;
|
||||||
|
return std.mem.eql(u8, "/", path);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn GET(self: *Self, io: std.Io, request: *std.http.Server.Request) !void {
|
||||||
|
_ = self;
|
||||||
|
_ = io;
|
||||||
|
homeRouteLog.info("got GET request", .{});
|
||||||
|
try request.respond("", .{ .status = .not_found });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
pub fn PUT(self: Self, io: std.Io, request: *std.http.Server.Request) !void {
|
||||||
|
_ = self;
|
||||||
|
_ = io;
|
||||||
|
homeRouteLog.info("got PUT request", .{});
|
||||||
|
try request.respond("", .{ .status = .not_found });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
pub fn DELETE(self: Self, io: std.Io, request: *std.http.Server.Request) !void {
|
||||||
|
_ = self;
|
||||||
|
_ = io;
|
||||||
|
homeRouteLog.info("got DELETE request", .{});
|
||||||
|
try request.respond("", .{ .status = .not_found });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
pub fn POST(self: Self, io: std.Io, request: *std.http.Server.Request) !void {
|
||||||
|
_ = self;
|
||||||
|
_ = io;
|
||||||
|
homeRouteLog.info("got POST request", .{});
|
||||||
|
try request.respond("", .{ .status = .not_found });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
pub fn HEAD(self: Self, io: std.Io, request: *std.http.Server.Request) !void {
|
||||||
|
_ = self;
|
||||||
|
_ = io;
|
||||||
|
homeRouteLog.info("got HEAD request", .{});
|
||||||
|
try request.respond("", .{ .status = .not_found });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
pub fn CONNECT(self: Self, io: std.Io, request: *std.http.Server.Request) !void {
|
||||||
|
_ = self;
|
||||||
|
_ = io;
|
||||||
|
homeRouteLog.info("got CONNECT request", .{});
|
||||||
|
try request.respond("", .{ .status = .not_found });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
pub fn OPTIONS(self: Self, io: std.Io, request: *std.http.Server.Request) !void {
|
||||||
|
_ = self;
|
||||||
|
_ = io;
|
||||||
|
homeRouteLog.info("got OPTIONS request", .{});
|
||||||
|
try request.respond("", .{ .status = .not_found });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
pub fn TRACE(self: Self, io: std.Io, request: *std.http.Server.Request) !void {
|
||||||
|
_ = self;
|
||||||
|
_ = io;
|
||||||
|
homeRouteLog.info("got TRACE request", .{});
|
||||||
|
try request.respond("", .{ .status = .not_found });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
pub fn PATCH(self: Self, io: std.Io, request: *std.http.Server.Request) !void {
|
||||||
|
_ = self;
|
||||||
|
_ = io;
|
||||||
|
homeRouteLog.info("got PATCH request", .{});
|
||||||
|
try request.respond("", .{ .status = .not_found });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
pub fn ERR(self: Self, io: std.Io, request: *std.http.Server.Request) !void {
|
||||||
|
_ = self;
|
||||||
|
_ = io;
|
||||||
|
try request.respond("", .{ .status = .not_found });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const Router = struct {
|
||||||
|
const Self = @This();
|
||||||
|
|
||||||
|
// will change this later to be just an array
|
||||||
|
// with some custom path parsing code
|
||||||
|
routes: []Route,
|
||||||
|
|
||||||
|
pub fn processConnection(self: *Self, io: std.Io, stream: *std.Io.net.Stream) !void {
|
||||||
|
// Wrap the raw stream in buffered Io.Reader / Io.Writer
|
||||||
|
errdefer stream.close(io);
|
||||||
|
|
||||||
|
var read_buffer: [1024]u8 = undefined;
|
||||||
|
var write_buffer: [1024]u8 = undefined;
|
||||||
|
var reader = stream.reader(io, &read_buffer);
|
||||||
|
var writer = stream.writer(io, &write_buffer);
|
||||||
|
|
||||||
|
// HTTP layer: parse the byte stream at HTTP/1.1
|
||||||
|
var http_server = std.http.Server.init(&reader.interface, &writer.interface);
|
||||||
|
|
||||||
|
var req = try http_server.receiveHead();
|
||||||
|
log.info("method : {s} target : \"{s}\"", .{ @tagName(req.head.method), req.head.target });
|
||||||
|
|
||||||
|
for (self.routes) |route| {
|
||||||
|
if (route.matchPath(req.head.target)) {
|
||||||
|
switch (req.head.method) {
|
||||||
|
.GET => try route.GET(io, &req),
|
||||||
|
.HEAD => try route.HEAD(io, &req),
|
||||||
|
.POST => try route.POST(io, &req),
|
||||||
|
.PUT => try route.PUT(io, &req),
|
||||||
|
.DELETE => try route.DELETE(io, &req),
|
||||||
|
.CONNECT => try route.CONNECT(io, &req),
|
||||||
|
.OPTIONS => try route.OPTIONS(io, &req),
|
||||||
|
.TRACE => try route.TRACE(io, &req),
|
||||||
|
.PATCH => try route.PATCH(io, &req),
|
||||||
|
}
|
||||||
|
log.info("Response sent, closing connection", .{});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info("no path found, closing connection", .{});
|
||||||
|
try req.respond("", .{ .status = .not_found });
|
||||||
|
stream.close(io);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn main(init: std.process.Init) !void {
|
||||||
|
var home_route = try HomeRoute.init("sir-pandas-site");
|
||||||
|
var user_route = try UsersRoute.init();
|
||||||
|
|
||||||
|
// const home_route_route = home_route.toRoute();
|
||||||
|
var routes = [_]Route{
|
||||||
|
Route.init(&home_route),
|
||||||
|
Route.init(&user_route),
|
||||||
|
};
|
||||||
|
|
||||||
|
var router = Router{ .routes = routes[0..] };
|
||||||
|
|
||||||
|
// std.debug.print("route {any}\n", .{home_route_route});
|
||||||
|
log.info("Listening on http://{s}:{d}", .{ LISTEN_ADDR, LISTEN_PORT });
|
||||||
|
|
||||||
|
// set the address and port
|
||||||
|
const addr = std.Io.net.IpAddress.parseIp4(LISTEN_ADDR, LISTEN_PORT) catch unreachable;
|
||||||
|
|
||||||
|
// TCP layer: bind the port and accept the raw streams
|
||||||
|
var server = try addr.listen(init.io, .{ .reuse_address = true });
|
||||||
|
defer server.deinit(init.io);
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
log.info("Waiting for connection...", .{});
|
||||||
|
var stream = try server.accept(init.io);
|
||||||
|
log.info("TCP connection established", .{});
|
||||||
|
|
||||||
|
_ = try init.io.concurrent(Router.processConnection, .{ &router, init.io, &stream });
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user