mirror of
https://github.com/zigzap/zap.git
synced 2025-10-20 15:14:08 +00:00
endpoint example: the beginnings
This commit is contained in:
parent
becbeb2a3d
commit
9186bb58ec
6 changed files with 314 additions and 33 deletions
|
@ -23,6 +23,7 @@ pub fn build(b: *std.build.Builder) !void {
|
|||
.{ .name = "routes", .src = "examples/routes/routes.zig" },
|
||||
.{ .name = "serve", .src = "examples/serve/serve.zig" },
|
||||
.{ .name = "hello_json", .src = "examples/hello_json/hello_json.zig" },
|
||||
.{ .name = "endpoint", .src = "examples/endpoint/main.zig" },
|
||||
}) |excfg| {
|
||||
const ex_name = excfg.name;
|
||||
const ex_src = excfg.src;
|
||||
|
|
103
examples/endpoint/endpoints.zig
Normal file
103
examples/endpoint/endpoints.zig
Normal file
|
@ -0,0 +1,103 @@
|
|||
const std = @import("std");
|
||||
const zap = @import("zap");
|
||||
const Users = @import("users.zig");
|
||||
|
||||
// the Endpoints
|
||||
|
||||
pub const Self = @This();
|
||||
|
||||
var alloc: std.mem.Allocator = undefined;
|
||||
var endpoint: zap.SimpleEndpoint = undefined;
|
||||
var list_endpoint: zap.SimpleEndpoint = undefined;
|
||||
var users: Users = undefined;
|
||||
|
||||
pub fn init(
|
||||
a: std.mem.Allocator,
|
||||
user_path: []const u8,
|
||||
userlist_path: []const u8,
|
||||
) void {
|
||||
users = Users.init(a);
|
||||
alloc = a;
|
||||
endpoint = zap.SimpleEndpoint.init(.{
|
||||
.path = user_path,
|
||||
.get = getUser,
|
||||
.post = null,
|
||||
.put = null,
|
||||
.delete = null,
|
||||
});
|
||||
list_endpoint = zap.SimpleEndpoint.init(.{
|
||||
.path = userlist_path,
|
||||
.get = listUsers,
|
||||
.post = null,
|
||||
.put = null,
|
||||
.delete = null,
|
||||
});
|
||||
}
|
||||
|
||||
pub fn getUsers() *Users {
|
||||
return &users;
|
||||
}
|
||||
|
||||
pub fn getUserEndpoint() *zap.SimpleEndpoint {
|
||||
return &endpoint;
|
||||
}
|
||||
|
||||
pub fn getUserListEndpoint() *zap.SimpleEndpoint {
|
||||
return &list_endpoint;
|
||||
}
|
||||
|
||||
fn userIdFromPath(path: []const u8) ?usize {
|
||||
if (path.len >= endpoint.settings.path.len + 2) {
|
||||
if (path[endpoint.settings.path.len] != '/') {
|
||||
std.debug.print("no slash\n", .{});
|
||||
return null;
|
||||
}
|
||||
const idstr = path[endpoint.settings.path.len + 1 ..];
|
||||
std.debug.print("idstr={s}\n", .{idstr});
|
||||
return std.fmt.parseUnsigned(usize, idstr, 10) catch null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
var jsonbuf: [1024]u8 = undefined;
|
||||
fn stringify(value: anytype, options: std.json.StringifyOptions) ?[]const u8 {
|
||||
var fba = std.heap.FixedBufferAllocator.init(&jsonbuf);
|
||||
var string = std.ArrayList(u8).init(fba.allocator());
|
||||
if (std.json.stringify(value, options, string.writer())) {
|
||||
return string.items;
|
||||
} else |_| { // error
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn getUser(e: *zap.SimpleEndpoint, r: zap.SimpleRequest) void {
|
||||
_ = e;
|
||||
std.debug.print("getUser()\n", .{});
|
||||
if (r.path) |path| {
|
||||
std.debug.print("getUser({s})\n", .{path});
|
||||
if (userIdFromPath(path)) |id| {
|
||||
std.debug.print("getUser({})\n", .{id});
|
||||
if (users.get(id)) |user| {
|
||||
std.debug.print("getUser(): {}\n", .{user});
|
||||
if (stringify(user, .{})) |json| {
|
||||
_ = r.sendJson(json);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
std.debug.print("User not found\n", .{});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn listUsers(e: *zap.SimpleEndpoint, r: zap.SimpleRequest) void {
|
||||
std.debug.print("listUsers()\n", .{});
|
||||
_ = r;
|
||||
_ = e;
|
||||
var l = std.ArrayList(Users.User).init(alloc);
|
||||
if (users.list(&l)) {} else |_| {
|
||||
return;
|
||||
}
|
||||
// if (stringify(l, .{})) |json| {
|
||||
// _ = r.sendJson(json);
|
||||
// }
|
||||
}
|
40
examples/endpoint/main.zig
Normal file
40
examples/endpoint/main.zig
Normal file
|
@ -0,0 +1,40 @@
|
|||
const std = @import("std");
|
||||
const zap = @import("zap");
|
||||
const Endpoints = @import("endpoints.zig");
|
||||
|
||||
pub fn main() !void {
|
||||
const allocator = std.heap.page_allocator;
|
||||
// setup listener
|
||||
var listener = zap.SimpleEndpointListener.init(
|
||||
allocator,
|
||||
.{
|
||||
.port = 3000,
|
||||
.on_request = null,
|
||||
.log = true,
|
||||
},
|
||||
);
|
||||
|
||||
Endpoints.init(allocator, "/user", "/list");
|
||||
|
||||
// add endpoints
|
||||
try listener.addEndpoint(Endpoints.getUserEndpoint());
|
||||
try listener.addEndpoint(Endpoints.getUserListEndpoint());
|
||||
|
||||
// fake some users
|
||||
var uid: usize = undefined;
|
||||
uid = try Endpoints.getUsers().addByName("renerocksai", null);
|
||||
std.debug.print("Added user {}\n", .{uid});
|
||||
uid = try Endpoints.getUsers().addByName("renerocksai", "your mom");
|
||||
std.debug.print("Added user {}\n", .{uid});
|
||||
|
||||
// listen
|
||||
try listener.listen();
|
||||
|
||||
std.debug.print("Listening on 0.0.0.0:3000\n", .{});
|
||||
|
||||
// and run
|
||||
zap.start(.{
|
||||
.threads = 2,
|
||||
.workers = 2,
|
||||
});
|
||||
}
|
113
examples/endpoint/users.zig
Normal file
113
examples/endpoint/users.zig
Normal file
|
@ -0,0 +1,113 @@
|
|||
const std = @import("std");
|
||||
|
||||
alloc: std.mem.Allocator = undefined,
|
||||
users: std.AutoHashMap(usize, InternalUser) = undefined,
|
||||
|
||||
pub const Self = @This();
|
||||
|
||||
const InternalUser = struct {
|
||||
id: usize = 0,
|
||||
firstnamebuf: [64]u8,
|
||||
firstnamelen: usize,
|
||||
lastnamebuf: [64]u8,
|
||||
lastnamelen: usize,
|
||||
};
|
||||
|
||||
pub const User = struct {
|
||||
first_name: []const u8,
|
||||
last_name: []const u8,
|
||||
};
|
||||
|
||||
pub fn init(a: std.mem.Allocator) Self {
|
||||
return .{
|
||||
.alloc = a,
|
||||
.users = std.AutoHashMap(usize, InternalUser).init(a),
|
||||
};
|
||||
}
|
||||
|
||||
/// the request will be freed (and reused by facilio) when it's
|
||||
/// completed, so we take copies of the names
|
||||
pub fn addByName(self: *Self, first: ?[]const u8, last: ?[]const u8) !usize {
|
||||
var created = try self.alloc.alloc(InternalUser, 1);
|
||||
var user = created[0];
|
||||
user.id = self.users.count() + 1;
|
||||
user.firstnamelen = 0;
|
||||
user.lastnamelen = 0;
|
||||
if (first) |firstname| {
|
||||
std.mem.copy(u8, user.firstnamebuf[0..], firstname);
|
||||
user.firstnamelen = firstname.len;
|
||||
}
|
||||
if (last) |lastname| {
|
||||
std.mem.copy(u8, user.lastnamebuf[0..], lastname);
|
||||
user.lastnamelen = lastname.len;
|
||||
}
|
||||
try self.users.put(user.id, user);
|
||||
return user.id;
|
||||
}
|
||||
|
||||
pub fn delete(self: *Self, id: usize) bool {
|
||||
if (self.users.get(id)) |pUser| {
|
||||
self.alloc.free(pUser);
|
||||
return self.users.remove(id);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
pub fn get(self: *Self, id: usize) ?User {
|
||||
if (self.users.get(id)) |pUser| {
|
||||
return .{
|
||||
.first_name = pUser.firstnamebuf[0..pUser.firstnamelen],
|
||||
.last_name = pUser.lastnamebuf[0..pUser.lastnamelen],
|
||||
};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
pub fn update(self: *Self, id: usize, first: ?[]const u8, last: ?[]const u8) bool {
|
||||
if (self.users.get(id)) |pUser| {
|
||||
pUser.firstnamelen = 0;
|
||||
pUser.lastnamelen = 0;
|
||||
if (first) |firstname| {
|
||||
std.mem.copy(u8, pUser.firstnamebuf[0..], firstname);
|
||||
pUser.firstnamelen = firstname.len;
|
||||
}
|
||||
if (last) |lastname| {
|
||||
std.mem.copy(u8, pUser.lastname[0..], lastname);
|
||||
pUser.lastnamelen = lastname.len;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// populate the list
|
||||
pub fn list(self: *Self, out: *std.ArrayList(User)) !void {
|
||||
var it = JsonUserIterator.init(&self.users);
|
||||
while (it.next()) |user| {
|
||||
try out.append(user);
|
||||
}
|
||||
}
|
||||
|
||||
const JsonUserIterator = struct {
|
||||
it: std.AutoHashMap(usize, InternalUser).ValueIterator = undefined,
|
||||
const This = @This();
|
||||
|
||||
// careful:
|
||||
// - Self refers to the file's struct
|
||||
// - This refers to the JsonUserIterator struct
|
||||
pub fn init(internal_users: *std.AutoHashMap(usize, InternalUser)) This {
|
||||
return .{
|
||||
.it = internal_users.valueIterator(),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn next(this: *This) ?User {
|
||||
if (this.it.next()) |pUser| {
|
||||
return User{
|
||||
.first_name = pUser.firstnamebuf[0..pUser.firstnamelen],
|
||||
.last_name = pUser.lastnamebuf[0..pUser.lastnamelen],
|
||||
};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
};
|
|
@ -1,91 +1,105 @@
|
|||
const std = @import("std");
|
||||
const zap = @import("zap.zig");
|
||||
const Request = zap.SimpleRequest;
|
||||
const RequestFn = zap.SimpleHttpRequestFn;
|
||||
const ListenerSettings = zap.SimpleHttpListenerSettings;
|
||||
const Listener = zap.SimpleHttpListener;
|
||||
|
||||
const SimpleEndpointSettings = struct {
|
||||
pub const RequestFn = *const fn (self: *SimpleEndpoint, r: Request) void;
|
||||
pub const SimpleEndpointSettings = struct {
|
||||
path: []const u8,
|
||||
get: ?RequestFn,
|
||||
post: ?RequestFn,
|
||||
put: ?RequestFn,
|
||||
delete: ?RequestFn,
|
||||
get: ?RequestFn = null,
|
||||
post: ?RequestFn = null,
|
||||
put: ?RequestFn = null,
|
||||
delete: ?RequestFn = null,
|
||||
};
|
||||
|
||||
const SimpleEndpoint = struct {
|
||||
pub const SimpleEndpoint = struct {
|
||||
settings: SimpleEndpointSettings,
|
||||
|
||||
var Self = @This();
|
||||
const Self = @This();
|
||||
|
||||
pub fn init(s: SimpleEndpointSettings) Self {
|
||||
return .{
|
||||
.path = s.path,
|
||||
.get = s.get orelse &nop,
|
||||
.post = s.post orelse &nop,
|
||||
.put = s.put orelse &nop,
|
||||
.delete = s.delete orelse &nop,
|
||||
.settings = .{
|
||||
.path = s.path,
|
||||
.get = s.get orelse &nop,
|
||||
.post = s.post orelse &nop,
|
||||
.put = s.put orelse &nop,
|
||||
.delete = s.delete orelse &nop,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
fn nop(r: Request) void {
|
||||
fn nop(self: *SimpleEndpoint, r: Request) void {
|
||||
_ = self;
|
||||
_ = r;
|
||||
}
|
||||
|
||||
pub fn onRequest(r: zap.SimpleRequest) void {
|
||||
pub fn onRequest(self: *SimpleEndpoint, r: zap.SimpleRequest) void {
|
||||
if (r.method) |m| {
|
||||
if (std.mem.eql(u8, m, "GET"))
|
||||
// TODO
|
||||
nop();
|
||||
return self.settings.get.?(self, r);
|
||||
if (std.mem.eql(u8, m, "POST"))
|
||||
return self.settings.post.?(self, r);
|
||||
if (std.mem.eql(u8, m, "PUT"))
|
||||
return self.settings.put.?(self, r);
|
||||
if (std.mem.eql(u8, m, "DELETE"))
|
||||
return self.settings.delete.?(self, r);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const EndpointListenerError = error{
|
||||
pub const EndpointListenerError = error{
|
||||
EndpointPathShadowError,
|
||||
};
|
||||
|
||||
var endpoints: std.StringHashMap(*SimpleEndpoint) = undefined;
|
||||
// var endpoints: std.StringHashMap(*SimpleEndpoint) = undefined;
|
||||
var endpoints: std.ArrayList(*SimpleEndpoint) = undefined;
|
||||
|
||||
// NOTE: We switch on path.startsWith -> so use endpoints with distinctly
|
||||
// starting names!!
|
||||
const SimpleEndpointListener = struct {
|
||||
pub const SimpleEndpointListener = struct {
|
||||
listener: Listener,
|
||||
allocator: std.mem.Allocator,
|
||||
|
||||
var Self = @This();
|
||||
const Self = @This();
|
||||
|
||||
pub fn init(a: std.mem.Allocator, l: ListenerSettings) Self {
|
||||
l.on_request = onRequest;
|
||||
endpoints = std.StringHashMap(*SimpleEndpoint).init(a);
|
||||
var ls = l;
|
||||
ls.on_request = onRequest;
|
||||
endpoints = std.ArrayList(*SimpleEndpoint).init(a);
|
||||
return .{
|
||||
.listener = Listener.init(l),
|
||||
.listener = Listener.init(ls),
|
||||
.allocator = a,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn listen(self: *SimpleEndpointListener) !void {
|
||||
try self.listener.listen();
|
||||
}
|
||||
|
||||
pub fn addEndpoint(self: *SimpleEndpointListener, e: *SimpleEndpoint) !void {
|
||||
var it = endpoints.keyIterator();
|
||||
while (it.next()) |existing_path| {
|
||||
_ = self;
|
||||
for (endpoints.items) |other| {
|
||||
if (std.mem.startsWith(
|
||||
u8,
|
||||
existing_path,
|
||||
e.path,
|
||||
other.settings.path,
|
||||
e.settings.path,
|
||||
) or std.mem.startsWith(
|
||||
u8,
|
||||
e.path,
|
||||
existing_path,
|
||||
e.settings.path,
|
||||
other.settings.path,
|
||||
)) {
|
||||
return EndpointListenerError.EndpointPathShadowError;
|
||||
}
|
||||
}
|
||||
try self.endpoints.put(e.path, e);
|
||||
try endpoints.append(e);
|
||||
}
|
||||
|
||||
fn onRequest(r: Request) void {
|
||||
if (r.path) |p| {
|
||||
for (endpoints) |e| {
|
||||
if (std.mem.startsWith(u8, p, e.path)) {
|
||||
for (endpoints.items) |e| {
|
||||
if (std.mem.startsWith(u8, p, e.settings.path)) {
|
||||
e.onRequest(r);
|
||||
return;
|
||||
}
|
||||
|
|
10
src/zap.zig
10
src/zap.zig
|
@ -7,6 +7,8 @@ pub const C = @cImport({
|
|||
@cInclude("fio.h");
|
||||
});
|
||||
|
||||
pub usingnamespace @import("endpoint.zig");
|
||||
|
||||
pub fn fio2str(o: C.FIOBJ) ?[]const u8 {
|
||||
if (o == 0) return null;
|
||||
const x: C.fio_str_info_s = C.fiobj_obj2cstr(o);
|
||||
|
@ -62,6 +64,14 @@ pub const SimpleRequest = struct {
|
|||
), body.len);
|
||||
}
|
||||
|
||||
pub fn sendJson(self: *const Self, json: []const u8) c_int {
|
||||
self.setContentType(.JSON);
|
||||
return C.http_send_body(self.h, @intToPtr(
|
||||
*anyopaque,
|
||||
@ptrToInt(json.ptr),
|
||||
), json.len);
|
||||
}
|
||||
|
||||
pub fn setContentType(self: *const Self, c: ContentType) void {
|
||||
self.setHeader("content-type", switch (c) {
|
||||
.TEXT => "text/plain",
|
||||
|
|
Loading…
Add table
Reference in a new issue