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