mirror of
https://github.com/zigzap/zap.git
synced 2025-10-20 23:24:09 +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 = "routes", .src = "examples/routes/routes.zig" },
|
||||||
.{ .name = "serve", .src = "examples/serve/serve.zig" },
|
.{ .name = "serve", .src = "examples/serve/serve.zig" },
|
||||||
.{ .name = "hello_json", .src = "examples/hello_json/hello_json.zig" },
|
.{ .name = "hello_json", .src = "examples/hello_json/hello_json.zig" },
|
||||||
|
.{ .name = "endpoint", .src = "examples/endpoint/main.zig" },
|
||||||
}) |excfg| {
|
}) |excfg| {
|
||||||
const ex_name = excfg.name;
|
const ex_name = excfg.name;
|
||||||
const ex_src = excfg.src;
|
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 std = @import("std");
|
||||||
const zap = @import("zap.zig");
|
const zap = @import("zap.zig");
|
||||||
const Request = zap.SimpleRequest;
|
const Request = zap.SimpleRequest;
|
||||||
const RequestFn = zap.SimpleHttpRequestFn;
|
|
||||||
const ListenerSettings = zap.SimpleHttpListenerSettings;
|
const ListenerSettings = zap.SimpleHttpListenerSettings;
|
||||||
const Listener = zap.SimpleHttpListener;
|
const Listener = zap.SimpleHttpListener;
|
||||||
|
|
||||||
const SimpleEndpointSettings = struct {
|
pub const RequestFn = *const fn (self: *SimpleEndpoint, r: Request) void;
|
||||||
|
pub const SimpleEndpointSettings = struct {
|
||||||
path: []const u8,
|
path: []const u8,
|
||||||
get: ?RequestFn,
|
get: ?RequestFn = null,
|
||||||
post: ?RequestFn,
|
post: ?RequestFn = null,
|
||||||
put: ?RequestFn,
|
put: ?RequestFn = null,
|
||||||
delete: ?RequestFn,
|
delete: ?RequestFn = null,
|
||||||
};
|
};
|
||||||
|
|
||||||
const SimpleEndpoint = struct {
|
pub const SimpleEndpoint = struct {
|
||||||
settings: SimpleEndpointSettings,
|
settings: SimpleEndpointSettings,
|
||||||
|
|
||||||
var Self = @This();
|
const Self = @This();
|
||||||
|
|
||||||
pub fn init(s: SimpleEndpointSettings) Self {
|
pub fn init(s: SimpleEndpointSettings) Self {
|
||||||
return .{
|
return .{
|
||||||
.path = s.path,
|
.settings = .{
|
||||||
.get = s.get orelse &nop,
|
.path = s.path,
|
||||||
.post = s.post orelse &nop,
|
.get = s.get orelse &nop,
|
||||||
.put = s.put orelse &nop,
|
.post = s.post orelse &nop,
|
||||||
.delete = s.delete 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;
|
_ = r;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn onRequest(r: zap.SimpleRequest) void {
|
pub fn onRequest(self: *SimpleEndpoint, r: zap.SimpleRequest) void {
|
||||||
if (r.method) |m| {
|
if (r.method) |m| {
|
||||||
if (std.mem.eql(u8, m, "GET"))
|
if (std.mem.eql(u8, m, "GET"))
|
||||||
// TODO
|
return self.settings.get.?(self, r);
|
||||||
nop();
|
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,
|
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
|
// NOTE: We switch on path.startsWith -> so use endpoints with distinctly
|
||||||
// starting names!!
|
// starting names!!
|
||||||
const SimpleEndpointListener = struct {
|
pub const SimpleEndpointListener = struct {
|
||||||
listener: Listener,
|
listener: Listener,
|
||||||
allocator: std.mem.Allocator,
|
allocator: std.mem.Allocator,
|
||||||
|
|
||||||
var Self = @This();
|
const Self = @This();
|
||||||
|
|
||||||
pub fn init(a: std.mem.Allocator, l: ListenerSettings) Self {
|
pub fn init(a: std.mem.Allocator, l: ListenerSettings) Self {
|
||||||
l.on_request = onRequest;
|
var ls = l;
|
||||||
endpoints = std.StringHashMap(*SimpleEndpoint).init(a);
|
ls.on_request = onRequest;
|
||||||
|
endpoints = std.ArrayList(*SimpleEndpoint).init(a);
|
||||||
return .{
|
return .{
|
||||||
.listener = Listener.init(l),
|
.listener = Listener.init(ls),
|
||||||
.allocator = a,
|
.allocator = a,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn listen(self: *SimpleEndpointListener) !void {
|
||||||
|
try self.listener.listen();
|
||||||
|
}
|
||||||
|
|
||||||
pub fn addEndpoint(self: *SimpleEndpointListener, e: *SimpleEndpoint) !void {
|
pub fn addEndpoint(self: *SimpleEndpointListener, e: *SimpleEndpoint) !void {
|
||||||
var it = endpoints.keyIterator();
|
_ = self;
|
||||||
while (it.next()) |existing_path| {
|
for (endpoints.items) |other| {
|
||||||
if (std.mem.startsWith(
|
if (std.mem.startsWith(
|
||||||
u8,
|
u8,
|
||||||
existing_path,
|
other.settings.path,
|
||||||
e.path,
|
e.settings.path,
|
||||||
) or std.mem.startsWith(
|
) or std.mem.startsWith(
|
||||||
u8,
|
u8,
|
||||||
e.path,
|
e.settings.path,
|
||||||
existing_path,
|
other.settings.path,
|
||||||
)) {
|
)) {
|
||||||
return EndpointListenerError.EndpointPathShadowError;
|
return EndpointListenerError.EndpointPathShadowError;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
try self.endpoints.put(e.path, e);
|
try endpoints.append(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn onRequest(r: Request) void {
|
fn onRequest(r: Request) void {
|
||||||
if (r.path) |p| {
|
if (r.path) |p| {
|
||||||
for (endpoints) |e| {
|
for (endpoints.items) |e| {
|
||||||
if (std.mem.startsWith(u8, p, e.path)) {
|
if (std.mem.startsWith(u8, p, e.settings.path)) {
|
||||||
e.onRequest(r);
|
e.onRequest(r);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
10
src/zap.zig
10
src/zap.zig
|
@ -7,6 +7,8 @@ pub const C = @cImport({
|
||||||
@cInclude("fio.h");
|
@cInclude("fio.h");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
pub usingnamespace @import("endpoint.zig");
|
||||||
|
|
||||||
pub fn fio2str(o: C.FIOBJ) ?[]const u8 {
|
pub fn fio2str(o: C.FIOBJ) ?[]const u8 {
|
||||||
if (o == 0) return null;
|
if (o == 0) return null;
|
||||||
const x: C.fio_str_info_s = C.fiobj_obj2cstr(o);
|
const x: C.fio_str_info_s = C.fiobj_obj2cstr(o);
|
||||||
|
@ -62,6 +64,14 @@ pub const SimpleRequest = struct {
|
||||||
), body.len);
|
), 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 {
|
pub fn setContentType(self: *const Self, c: ContentType) void {
|
||||||
self.setHeader("content-type", switch (c) {
|
self.setHeader("content-type", switch (c) {
|
||||||
.TEXT => "text/plain",
|
.TEXT => "text/plain",
|
||||||
|
|
Loading…
Add table
Reference in a new issue