1
0
Fork 0
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:
Rene Schallner 2023-01-14 16:13:14 +01:00
parent becbeb2a3d
commit 9186bb58ec
6 changed files with 314 additions and 33 deletions

View file

@ -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;

View 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);
// }
}

View 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
View 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;
}
};

View file

@ -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;
}

View file

@ -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",