1
0
Fork 0
mirror of https://github.com/zigzap/zap.git synced 2025-10-20 15:14:08 +00:00

websockets + example

This commit is contained in:
Rene Schallner 2023-05-07 04:01:05 +02:00
parent ab8294b2a1
commit 00291117d8
10 changed files with 775 additions and 12 deletions

View file

@ -46,6 +46,8 @@ Here's what works:
itself query parameters of all supported types. itself query parameters of all supported types.
- **[cookies](examples/cookies/cookies.zig)**: a simple example sending - **[cookies](examples/cookies/cookies.zig)**: a simple example sending
itself a cookie and responding with a session cookie. itself a cookie and responding with a session cookie.
- **[websockets](examples/websockets/websockets.zig)**: a simple websockets chat
for the browser.
I'll continue wrapping more of facil.io's functionality and adding stuff to zap I'll continue wrapping more of facil.io's functionality and adding stuff to zap

View file

@ -52,6 +52,7 @@ pub fn build(b: *std.build.Builder) !void {
.{ .name = "endpoint_auth", .src = "examples/endpoint_auth/endpoint_auth.zig" }, .{ .name = "endpoint_auth", .src = "examples/endpoint_auth/endpoint_auth.zig" },
.{ .name = "http_params", .src = "examples/http_params/http_params.zig" }, .{ .name = "http_params", .src = "examples/http_params/http_params.zig" },
.{ .name = "cookies", .src = "examples/cookies/cookies.zig" }, .{ .name = "cookies", .src = "examples/cookies/cookies.zig" },
.{ .name = "websockets", .src = "examples/websockets/websockets.zig" },
}) |excfg| { }) |excfg| {
const ex_name = excfg.name; const ex_name = excfg.name;
const ex_src = excfg.src; const ex_src = excfg.src;

View file

@ -1,12 +1,11 @@
.{ .{
.name = "zap", .name = "zap",
.version = "0.0.15", .version = "0.0.16",
.dependencies = .{ .dependencies = .{
.@"facil.io" = .{ .@"facil.io" = .{
.url = "https://github.com/zigzap/facil.io/archive/refs/tags/zap-0.0.7.tar.gz", .url = "https://github.com/zigzap/facil.io/archive/refs/tags/zap-0.0.8.tar.gz",
.hash = "1220d03e0579bbb726efb8224ea289b26227bc421158b45c1b16a60b31bfa400ab33", .hash = "122071fcc675e114941331726291ca1f0c0c33751d992782c6abf1f0f2ddddc5734d",
} }
} }
} }

View file

@ -0,0 +1,209 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<script src="showdown.min.js"></script>
<title>ZAP-Chat</title>
<style>
* {
box-sizing: border-box;
}
body {
font-family: 'Arial', sans-serif;
background-color: #f0f0f0;
margin: 0;
padding: 0;
padding-bottom: 80px;
}
.container {
max-width: 960px;
margin: 0 auto;
}
header {
background-color: #cd0f0d;
color: white;
text-align: center;
padding: 1.5rem;
}
header h1 {
font-size: 2rem;
margin: 0;
}
.chat-container {
display: flex;
flex-direction: column;
justify-content: space-between;
background-color: white;
border-radius: 10px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
padding: 2rem;
/* height: 500px; */
height: calc(100vh - 260px);
margin-top: 2rem;
overflow-y: auto;
}
.message {
max-width: 80%;
padding: 0.5rem;
margin-bottom: 1rem;
border-radius: 10px;
/* font-size: 1.2rem; */
}
.bot {
align-self: flex-start;
background-color: #e0e0e0;
}
.user {
align-self: flex-end;
background-color: #cd0f0d;;
color: white;
}
.busy-indicator {
display: inline-block;
width: 20px;
height: 20px;
border: 3px solid rgba(255, 255, 255, 0.3);
border-radius: 50%;
border-top-color: #ffffff;
animation: spin 1s linear infinite;
}
@keyframes spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
.input-container {
display: flex;
justify-content: space-between;
position: fixed;
bottom: 0px;
left: 0;
right: 0;
background-color: #f0f0f0;
padding-left: 1rem;
padding-right: 1rem;
padding-bottom: 0.8rem;
padding-top: 1rem;
max-width: 960px;
margin-left: auto;
margin-right: auto;
/* box-shadow: 0 -2px 4px rgba(0, 0, 0, 0.1); */
}
.input-container input {
flex-grow: 1;
padding: 1rem;
border-radius: 10px;
border: 2px solid #cd0f0d;
outline: none;
/* font-size: 1.2rem; */
}
.input-container button {
background-color: #cd0f0d;;
color: white;
border: none;
border-radius: 10px;
padding: 1rem 2rem;
cursor: pointer;
margin-left: 1rem;
outline: none;
transition: 0.3s;
}
.input-container button:hover {
background-color: #A20000;
}
.status-bar {
background-color: #8B0000;
color: white;
text-align: center;
padding: 0.5rem;
font-size: 0.9rem;
position: fixed;
/* bottom: 60px; */
left: 0;
right: 0;
max-width: 960px;
margin-left: auto;
margin-right: auto;
/* border-top-left-radius: 10px; */
/* border-top-right-radius: 10px; */
border-bottom-left-radius: 10px;
border-bottom-right-radius: 10px;
box-shadow: 0 -2px 4px rgba(0, 0, 0, 0.1);
}
.status-online {
/* Green, MediumSeaGreen */
background-color: #3CB371;
}
.status-busy {
/* Blue, DodgerBlue */
background-color: #1E90FF;
}
.status-thinking {
/* Light Gray, LightSlateGray */
background-color: #778899;
}
.status-offline {
/* Red, Tomato */
background-color: #FF6347;
}
.status-warning {
/* Yellow, Goldenrod */
background-color: #DAA520;
}
.status-error {
/* Dark-Red, Firebrick */
background-color: #B22222;
}
</style>
</head>
<body>
<div class="container">
<header>
<h1>ZAP-Chat</h1>
</header>
<div class="chat-container" id="chat-container">
<!-- ... -->
<!-- ... -->
</div>
<div class="input-container">
<input id="prompt-input" type="text" placeholder="Type your message...">
<button id="send-button">Send</button>
</div>
</div>
<!-- Load the script and start executing -->
<script>
var header = document.getElementById('header');
var scriptTag = document.createElement("script");
scriptTag.src = "index.js?version=0";
scriptTag.type = "module";
document.head.appendChild(scriptTag);
</script>
</body>
</html>

View file

@ -0,0 +1,69 @@
// import { show_welcome } from "./screens/welcome_screen.js?version=0";
var chatContainer = document.getElementById("chat-container");
var promptInput = document.getElementById("prompt-input");
var sendButton = document.getElementById("send-button");
// var ws;
function addBotMessage(message) {
var msg = document.createElement("DIV");
msg.classList.add("message");
msg.classList.add("bot");
let converter = new showdown.Converter();
msg.innerHTML = converter.makeHtml(message);
chatContainer.appendChild(msg);
chatContainer.scrollTop = chatContainer.scrollHeight;
}
function addUserMessage(message) {
var msg = document.createElement("DIV");
msg.classList.add("message");
msg.classList.add("user");
let converter = new showdown.Converter();
msg.innerHTML = converter.makeHtml(message);
chatContainer.appendChild(msg);
chatContainer.scrollTop = chatContainer.scrollHeight;
}
function onSend() {
let prompt = promptInput.value;
console.log(prompt);
// addUserMessage(prompt);
promptInput.value = "";
ws.send(prompt);
promptInput.focus();
}
function show_markdown_body(container, task, body) {
let converter = new showdown.Converter();
let m = document.createElement("DIV");
m.innerHTML = converter.makeHtml(body);
m.classList.add("message");
container.appendChild(m);
}
function init() {
sendButton.onclick = onSend;
promptInput.addEventListener("keypress", function(event) {
if(event.key === "Enter") {
event.preventDefault();
sendButton.click();
}
});
promptInput.focus();
}
init();
var ws = new WebSocket("ws://localhost:3010/chat");
ws.onmessage = function(e) { addBotMessage(e.data); return false;};
ws.onclose = function(e) { console.log("closed"); };
ws.onopen = function(e) { /*e.target.send("Yo!");*/ return false;};

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,180 @@
const std = @import("std");
const zap = @import("zap");
const WebSockets = zap.WebSockets;
const Context = struct {
userName: []const u8,
channel: []const u8,
// we need to hold on to them and just re-use them for every incoming connection
subscribeArgs: WebsocketHandler.SubscribeArgs,
settings: WebsocketHandler.WebSocketSettings,
};
const ContextList = std.ArrayList(*Context);
const ContextManager = struct {
allocator: std.mem.Allocator,
channel: []const u8,
usernamePrefix: []const u8,
lock: std.Thread.Mutex = .{},
contexts: ContextList = undefined,
const Self = @This();
pub fn init(allocator: std.mem.Allocator, channelName: []const u8, usernamePrefix: []const u8) Self {
return .{
.allocator = allocator,
.channel = channelName,
.usernamePrefix = usernamePrefix,
.contexts = ContextList.init(allocator),
};
}
pub fn deinit(self: *Self) void {
for (self.contexts.items) |ctx| {
self.allocator.free(ctx.userName);
}
self.contexts.deinit();
}
pub fn newContext(self: *Self) !*Context {
self.lock.lock();
defer self.lock.unlock();
var ctx = try self.allocator.create(Context);
var userName = try std.fmt.allocPrint(self.allocator, "{s}{d}", .{ self.usernamePrefix, self.contexts.items.len });
ctx.* = .{
.userName = userName,
.channel = self.channel,
// used in subscribe()
.subscribeArgs = .{
.channel = self.channel,
.force_text = true,
.context = ctx,
},
// used in upgrade()
.settings = .{
.on_open = on_open_websocket,
.on_close = on_close_websocket,
.on_message = handle_websocket_message,
.context = ctx,
},
};
try self.contexts.append(ctx);
return ctx;
}
};
//
// Websocket Callbacks
//
fn on_open_websocket(context: ?*Context, handle: WebSockets.WsHandle) void {
if (context) |ctx| {
_ = WebsocketHandler.subscribe(handle, &ctx.subscribeArgs) catch |err| {
std.log.err("Error opening websocket: {any}", .{err});
return;
};
// say hello
var buf: [128]u8 = undefined;
const message = std.fmt.bufPrint(&buf, "{s} joined the chat.", .{ctx.userName}) catch unreachable;
// send notification to all others
WebsocketHandler.publish(.{ .channel = ctx.channel, .message = message });
std.log.info("new websocket opened: {s}", .{message});
}
}
fn on_close_websocket(context: ?*Context, uuid: isize) void {
_ = uuid;
if (context) |ctx| {
// say goodbye
var buf: [128]u8 = undefined;
const message = std.fmt.bufPrint(&buf, "{s} left the chat.", .{ctx.userName}) catch unreachable;
// send notification to all others
WebsocketHandler.publish(.{ .channel = ctx.channel, .message = message });
std.log.info("websocket closed: {s}", .{message});
}
}
fn handle_websocket_message(context: ?*Context, handle: WebSockets.WsHandle, message: []const u8, is_text: bool) void {
_ = is_text;
_ = handle;
if (context) |ctx| {
// say goodbye
var buf: [128]u8 = undefined;
const chat_message = std.fmt.bufPrint(&buf, "{s}: {s}", .{ ctx.userName, message }) catch unreachable;
// send notification to all others
WebsocketHandler.publish(.{ .channel = ctx.channel, .message = chat_message });
std.log.info("{s}", .{chat_message});
}
}
//
// HTTP stuff
//
fn on_request(r: zap.SimpleRequest) void {
r.setHeader("Server", "zap.example") catch unreachable;
r.sendBody("<html><body><h1>This is a simple Websocket chatroom example</h1></body></html>") catch return;
}
fn on_upgrade(r: zap.SimpleRequest, target_protocol: []const u8) void {
// make sure we're talking the right protocol
if (!std.mem.eql(u8, target_protocol, "websocket")) {
std.log.warn("received illegal target protocol: {s}", .{target_protocol});
r.setStatus(.bad_request);
r.sendBody("400 - BAD REQUEST") catch unreachable;
return;
}
var context = GlobalContextManager.newContext() catch |err| {
std.log.err("Error creating context: {any}", .{err});
return;
};
WebsocketHandler.upgrade(r.h, &context.settings) catch |err| {
std.log.err("Error in websocketUpgrade(): {any}", .{err});
return;
};
std.log.info("connection upgrade OK", .{});
}
// global variables, yeah!
var GlobalContextManager: ContextManager = undefined;
const WebsocketHandler = WebSockets.Handler(Context);
var handler_instance: WebsocketHandler = .{};
// here we go
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{
.thread_safe = true,
}){};
var allocator = gpa.allocator();
GlobalContextManager = ContextManager.init(allocator, "chatroom", "user-");
defer GlobalContextManager.deinit();
// setup listener
var listener = zap.SimpleHttpListener.init(
.{
.port = 3010,
.on_request = on_request,
.on_upgrade = on_upgrade,
.max_clients = 1000,
.max_body_size = 1 * 1024,
.public_folder = "examples/websockets/frontend",
.log = true,
},
);
try listener.listen();
std.log.info("", .{});
std.log.info("Connect with browser to http://localhost:3010.", .{});
std.log.info("Connect to websocket on ws://localhost:3010.", .{});
std.log.info("Terminate with CTRL+C", .{});
zap.start(.{
.threads = 1,
.workers = 1,
});
}

View file

@ -362,6 +362,32 @@ pub const websocket_settings_s = extern struct {
on_close: ?*const fn (isize, ?*anyopaque) callconv(.C) void, on_close: ?*const fn (isize, ?*anyopaque) callconv(.C) void,
udata: ?*anyopaque, udata: ?*anyopaque,
}; };
// struct websocket_subscribe_s_zigcompat {
// ws_s *ws;
// fio_str_info_s channel;
// void (*on_message)(ws_s *ws, fio_str_info_s channel, fio_str_info_s msg, void *udata);
// void (*on_unsubscribe)(void *udata);
// void *udata;
// fio_match_fn match;
// unsigned char force_binary;
// unsigned char force_text;
// };
pub const websocket_subscribe_s_zigcompat = extern struct {
ws: ?*ws_s,
channel: fio_str_info_s,
on_message: ?*const fn (?*ws_s, fio_str_info_s, fio_str_info_s, ?*anyopaque) callconv(.C) void,
on_unsubscribe: ?*const fn (?*anyopaque) callconv(.C) void,
udata: ?*anyopaque,
match: fio_match_fn,
force_binary: u8,
force_text: u8,
};
/// 0 on failure
pub extern fn websocket_subscribe_zigcompat(websocket_subscribe_s_zigcompat) callconv(.C) usize;
pub extern fn http_upgrade2ws(http: [*c]http_s, websocket_settings_s) c_int; pub extern fn http_upgrade2ws(http: [*c]http_s, websocket_settings_s) c_int;
pub extern fn websocket_connect(url: [*c]const u8, settings: websocket_settings_s) c_int; pub extern fn websocket_connect(url: [*c]const u8, settings: websocket_settings_s) c_int;
pub extern fn websocket_attach(uuid: isize, http_settings: [*c]http_settings_s, args: [*c]websocket_settings_s, data: ?*anyopaque, length: usize) void; pub extern fn websocket_attach(uuid: isize, http_settings: [*c]http_settings_s, args: [*c]websocket_settings_s, data: ?*anyopaque, length: usize) void;
@ -375,6 +401,19 @@ pub const struct_websocket_subscribe_s = opaque {};
pub extern fn websocket_subscribe(args: struct_websocket_subscribe_s) usize; pub extern fn websocket_subscribe(args: struct_websocket_subscribe_s) usize;
pub extern fn websocket_unsubscribe(ws: ?*ws_s, subscription_id: usize) void; pub extern fn websocket_unsubscribe(ws: ?*ws_s, subscription_id: usize) void;
pub extern fn websocket_optimize4broadcasts(@"type": isize, enable: c_int) void; pub extern fn websocket_optimize4broadcasts(@"type": isize, enable: c_int) void;
pub extern fn fio_publish(args: fio_publish_args_s) void;
pub const fio_publish_args_s = struct_fio_publish_args_s;
pub const struct_fio_publish_args_s = extern struct {
engine: ?*anyopaque = null,
// we don't support engines other than default
// engine: [*c]const fio_pubsub_engine_s,
filter: i32 = 0,
channel: fio_str_info_s,
message: fio_str_info_s,
is_json: u8,
};
pub const http_sse_s = struct_http_sse_s; pub const http_sse_s = struct_http_sse_s;
pub const struct_http_sse_s = extern struct { pub const struct_http_sse_s = extern struct {
on_open: ?*const fn ([*c]http_sse_s) callconv(.C) void, on_open: ?*const fn ([*c]http_sse_s) callconv(.C) void,

207
src/websockets.zig Normal file
View file

@ -0,0 +1,207 @@
const std = @import("std");
const zap = @import("zap.zig");
const fio = @import("fio.zig");
const util = @import("util.zig");
pub const WsHandle = ?*fio.ws_s;
pub fn Handler(comptime ContextType: type) type {
return struct {
/// OnMessage Callback on a websocket
pub const WsOnMessageFn = *const fn (
/// user-provided context, passed in from websocketHttpUpgrade()
context: ?*ContextType,
/// websocket handle, used to identify the websocket internally
handle: WsHandle,
/// the received message
message: []const u8,
/// indicator if message is text or binary
is_text: bool,
) void;
/// Callback when websocket is closed. uuid is a connection identifier,
/// it is -1 if a connection could not be established
pub const WsOnCloseFn = *const fn (context: ?*ContextType, uuid: isize) void;
/// A websocket callback function. provides the context passed in at
/// websocketHttpUpgrade().
pub const WsFn = *const fn (context: ?*ContextType, handle: WsHandle) void;
pub const WebSocketSettings = struct {
/// on_message(context, handle, message, is_text)
on_message: ?WsOnMessageFn = null,
/// on_open(context)
on_open: ?WsFn = null,
/// on_ready(context)
on_ready: ?WsFn = null,
/// on_shutdown(context, uuid)
on_shutdown: ?WsFn = null,
/// on_close(context)
on_close: ?WsOnCloseFn = null,
/// passed-in user-defined context
context: ?*ContextType = null,
};
/// This function will end the HTTP stage of the connection and attempt to "upgrade" to a WebSockets connection.
pub fn upgrade(h: [*c]fio.http_s, settings: *WebSocketSettings) WebSocketError!void {
var fio_settings: fio.websocket_settings_s = .{
.on_message = internal_on_message,
.on_open = internal_on_open,
.on_ready = internal_on_ready,
.on_shutdown = internal_on_shutdown,
.on_close = internal_on_close,
.udata = settings,
};
if (fio.http_upgrade2ws(h, fio_settings) != 0) {
return error.UpgradeError;
}
}
fn internal_on_message(handle: WsHandle, msg: fio.fio_str_info_s, is_text: u8) callconv(.C) void {
var user_provided_settings: ?*WebSocketSettings = @ptrCast(?*WebSocketSettings, @alignCast(@alignOf(?*WebSocketSettings), fio.websocket_udata_get(handle)));
var message = msg.data[0..msg.len];
if (user_provided_settings) |settings| {
if (settings.on_message) |on_message| {
on_message(settings.context, handle, message, is_text == 1);
}
}
}
fn internal_on_open(handle: WsHandle) callconv(.C) void {
var user_provided_settings: ?*WebSocketSettings = @ptrCast(?*WebSocketSettings, @alignCast(@alignOf(?*WebSocketSettings), fio.websocket_udata_get(handle)));
if (user_provided_settings) |settings| {
if (settings.on_open) |on_open| {
on_open(settings.context, handle);
}
}
}
fn internal_on_ready(handle: WsHandle) callconv(.C) void {
var user_provided_settings: ?*WebSocketSettings = @ptrCast(?*WebSocketSettings, @alignCast(@alignOf(?*WebSocketSettings), fio.websocket_udata_get(handle)));
if (user_provided_settings) |settings| {
if (settings.on_ready) |on_ready| {
on_ready(settings.context, handle);
}
}
}
fn internal_on_shutdown(handle: WsHandle) callconv(.C) void {
var user_provided_settings: ?*WebSocketSettings = @ptrCast(?*WebSocketSettings, @alignCast(@alignOf(?*WebSocketSettings), fio.websocket_udata_get(handle)));
if (user_provided_settings) |settings| {
if (settings.on_shutdown) |on_shutdown| {
on_shutdown(settings.context, handle);
}
}
}
fn internal_on_close(uuid: isize, udata: ?*anyopaque) callconv(.C) void {
var user_provided_settings: ?*WebSocketSettings = @ptrCast(?*WebSocketSettings, @alignCast(@alignOf(?*WebSocketSettings), udata));
if (user_provided_settings) |settings| {
if (settings.on_close) |on_close| {
on_close(settings.context, uuid);
}
}
}
const WebSocketError = error{
WriteError,
UpgradeError,
SubscribeError,
};
pub inline fn write(handle: WsHandle, message: []const u8, is_text: bool) WebSocketError!void {
if (fio.websocket_write(
handle,
fio.str2fio(message),
if (is_text) 1 else 0,
) != 0) {
return error.WriteError;
}
}
pub fn udataToContext(udata: *anyopaque) *ContextType {
return @ptrCast(*ContextType, @alignCast(@alignOf(*ContextType), udata));
}
pub inline fn close(handle: WsHandle) void {
fio.websocket_close(handle);
}
const PublishArgs = struct {
channel: []const u8,
message: []const u8,
is_json: bool = false,
};
/// publish a message in a channel
pub inline fn publish(args: PublishArgs) void {
fio.fio_publish(.{
.channel = util.str2fio(args.channel),
.message = util.str2fio(args.message),
.is_json = if (args.is_json) 1 else 0,
});
}
pub const SubscriptionOnMessageFn = *const fn (context: ?*ContextType, handle: WsHandle, channel: []const u8, message: []const u8) void;
pub const SubscriptionOnUnsubscribeFn = *const fn (context: ?*ContextType) void;
pub const SubscribeArgs = struct {
channel: []const u8,
on_message: ?SubscriptionOnMessageFn = null,
on_unsubscribe: ?SubscriptionOnUnsubscribeFn = null,
/// this is not wrapped nicely yet
match: fio.fio_match_fn = null,
/// When using direct message forwarding (no on_message callback), this indicates if
/// messages should be sent to the client as binary blobs, which is the safest approach.
/// By default, facil.io will test for UTF-8 data validity and send the data as text if
/// it's a valid UTF-8. Messages above ~32Kb might be assumed to be binary rather than
/// tested.
force_binary: bool = false,
/// When using direct message forwarding (no on_message callback), this indicates if
/// messages should be sent to the client as UTF-8 text. By default, facil.io will test
/// for UTF-8 data validity and send the data as text if it's a valid UTF-8. Messages
/// above ~32Kb might be assumed to be binary rather than tested. force_binary has
/// precedence over force_text.
force_text: bool = false,
context: ?*ContextType = null,
};
/// Returns a subscription ID on success and 0 on failure.
/// we copy the pointer so make sure the struct stays valid.
/// we need it to look up the ziggified callbacks.
pub inline fn subscribe(handle: WsHandle, args: *SubscribeArgs) WebSocketError!usize {
if (handle == null) return error.SubscribeError;
var fio_args: fio.websocket_subscribe_s_zigcompat = .{
.ws = handle.?,
.channel = util.str2fio(args.channel),
.on_message = if (args.on_message) |_| internal_subscription_on_message else null,
.on_unsubscribe = if (args.on_unsubscribe) |_| internal_subscription_on_unsubscribe else null,
.match = args.match,
.force_binary = if (args.force_binary) 1 else 0,
.force_text = if (args.force_text) 1 else 0,
.udata = args,
};
const ret = fio.websocket_subscribe_zigcompat(fio_args);
if (ret == 0) {
return error.SubscribeError;
}
return ret;
}
pub fn internal_subscription_on_message(handle: WsHandle, channel: fio.fio_str_info_s, message: fio.fio_str_info_s, udata: ?*anyopaque) callconv(.C) void {
if (udata) |p| {
const args = @ptrCast(*SubscribeArgs, @alignCast(@alignOf(*SubscribeArgs), p));
if (args.on_message) |on_message| {
on_message(args.context, handle, channel.data[0..channel.len], message.data[0..message.len]);
}
}
}
pub fn internal_subscription_on_unsubscribe(udata: ?*anyopaque) callconv(.C) void {
if (udata) |p| {
const args = @ptrCast(*SubscribeArgs, @alignCast(@alignOf(*SubscribeArgs), p));
if (args.on_unsubscribe) |on_unsubscribe| {
on_unsubscribe(args.context);
}
}
}
};
}

View file

@ -10,6 +10,7 @@ pub usingnamespace @import("util.zig");
pub usingnamespace @import("http.zig"); pub usingnamespace @import("http.zig");
pub usingnamespace @import("mustache.zig"); pub usingnamespace @import("mustache.zig");
pub usingnamespace @import("http_auth.zig"); pub usingnamespace @import("http_auth.zig");
pub const WebSockets = @import("websockets.zig");
pub const Log = @import("log.zig"); pub const Log = @import("log.zig");
@ -496,16 +497,32 @@ pub const CookieArgs = struct {
pub const HttpRequestFn = *const fn (r: [*c]fio.http_s) callconv(.C) void; pub const HttpRequestFn = *const fn (r: [*c]fio.http_s) callconv(.C) void;
pub const SimpleHttpRequestFn = *const fn (SimpleRequest) void; pub const SimpleHttpRequestFn = *const fn (SimpleRequest) void;
/// websocket connection upgrade
/// fn(request, targetstring)
pub const SimpleHttpUpgradeFn = *const fn (r: SimpleRequest, target_protocol: []const u8) void;
/// http finish, called when zap finishes. You get your udata back in the
/// struct.
pub const SimpleHttpFinishSettings = [*c]fio.struct_http_settings_s;
pub const SimpleHttpFinishFn = *const fn (SimpleHttpFinishSettings) void;
pub const SimpleHttpListenerSettings = struct { pub const SimpleHttpListenerSettings = struct {
port: usize, port: usize,
interface: [*c]const u8 = null, interface: [*c]const u8 = null,
on_request: ?SimpleHttpRequestFn, on_request: ?SimpleHttpRequestFn,
on_response: ?*const fn ([*c]fio.http_s) callconv(.C) void = null, on_response: ?SimpleHttpRequestFn = null,
on_upgrade: ?SimpleHttpUpgradeFn = null,
on_finish: ?SimpleHttpFinishFn = null,
// provide any pointer in there for "user data". it will be passed pack in
// on_finish()'s copy of the struct_http_settings_s
udata: ?*anyopaque = null,
public_folder: ?[]const u8 = null, public_folder: ?[]const u8 = null,
max_clients: ?isize = null, max_clients: ?isize = null,
max_body_size: ?usize = null, max_body_size: ?usize = null,
timeout: ?u8 = null, timeout: ?u8 = null,
log: bool = false, log: bool = false,
ws_timeout: u8 = 40,
ws_max_msg_size: usize = 262144,
}; };
pub const SimpleHttpListener = struct { pub const SimpleHttpListener = struct {
@ -520,6 +537,9 @@ pub const SimpleHttpListener = struct {
}; };
} }
// on_upgrade: ?*const fn ([*c]fio.http_s, [*c]u8, usize) callconv(.C) void = null,
// on_finish: ?*const fn ([*c]fio.struct_http_settings_s) callconv(.C) void = null,
// we could make it dynamic by passing a SimpleHttpListener via udata // we could make it dynamic by passing a SimpleHttpListener via udata
pub fn theOneAndOnlyRequestCallBack(r: [*c]fio.http_s) callconv(.C) void { pub fn theOneAndOnlyRequestCallBack(r: [*c]fio.http_s) callconv(.C) void {
if (the_one_and_only_listener) |l| { if (the_one_and_only_listener) |l| {
@ -534,6 +554,39 @@ pub const SimpleHttpListener = struct {
} }
} }
pub fn theOneAndOnlyResponseCallBack(r: [*c]fio.http_s) callconv(.C) void {
if (the_one_and_only_listener) |l| {
var req: SimpleRequest = .{
.path = util.fio2str(r.*.path),
.query = util.fio2str(r.*.query),
.body = util.fio2str(r.*.body),
.method = util.fio2str(r.*.method),
.h = r,
};
l.settings.on_response.?(req);
}
}
pub fn theOneAndOnlyUpgradeCallBack(r: [*c]fio.http_s, target: [*c]u8, target_len: usize) callconv(.C) void {
if (the_one_and_only_listener) |l| {
var req: SimpleRequest = .{
.path = util.fio2str(r.*.path),
.query = util.fio2str(r.*.query),
.body = util.fio2str(r.*.body),
.method = util.fio2str(r.*.method),
.h = r,
};
var zigtarget: []u8 = target[0..target_len];
l.settings.on_upgrade.?(req, zigtarget);
}
}
pub fn theOneAndOnlyFinishCallBack(s: [*c]fio.struct_http_settings_s) callconv(.C) void {
if (the_one_and_only_listener) |l| {
l.settings.on_finish.?(s);
}
}
pub fn listen(self: *Self) !void { pub fn listen(self: *Self) !void {
var pfolder: [*c]const u8 = null; var pfolder: [*c]const u8 = null;
var pfolder_len: usize = 0; var pfolder_len: usize = 0;
@ -546,22 +599,23 @@ pub const SimpleHttpListener = struct {
var x: fio.http_settings_s = .{ var x: fio.http_settings_s = .{
.on_request = if (self.settings.on_request) |_| Self.theOneAndOnlyRequestCallBack else null, .on_request = if (self.settings.on_request) |_| Self.theOneAndOnlyRequestCallBack else null,
.on_upgrade = null, .on_upgrade = if (self.settings.on_upgrade) |_| Self.theOneAndOnlyUpgradeCallBack else null,
.on_response = self.settings.on_response, .on_response = if (self.settings.on_response) |_| Self.theOneAndOnlyResponseCallBack else null,
.on_finish = null, .on_finish = if (self.settings.on_finish) |_| Self.theOneAndOnlyFinishCallBack else null,
.udata = null, .udata = null,
.public_folder = pfolder, .public_folder = pfolder,
.public_folder_length = pfolder_len, .public_folder_length = pfolder_len,
.max_header_size = 32 * 1024, .max_header_size = 32 * 1024,
.max_body_size = self.settings.max_body_size orelse 50 * 1024 * 1024, .max_body_size = self.settings.max_body_size orelse 50 * 1024 * 1024,
.max_clients = self.settings.max_clients orelse 100, // fio provides good default:
.max_clients = self.settings.max_clients orelse 0,
.tls = null, .tls = null,
.reserved1 = 0, .reserved1 = 0,
.reserved2 = 0, .reserved2 = 0,
.reserved3 = 0, .reserved3 = 0,
.ws_max_msg_size = 0, .ws_max_msg_size = 0,
.timeout = self.settings.timeout orelse 5, .timeout = self.settings.timeout orelse 5,
.ws_timeout = 0, .ws_timeout = self.settings.ws_timeout,
.log = if (self.settings.log) 1 else 0, .log = if (self.settings.log) 1 else 0,
.is_client = 0, .is_client = 0,
}; };
@ -624,7 +678,7 @@ pub fn listen(port: [*c]const u8, interface: [*c]const u8, settings: ListenSetti
var x: fio.http_settings_s = .{ var x: fio.http_settings_s = .{
.on_request = settings.on_request, .on_request = settings.on_request,
.on_upgrade = settings.on_upgrade, .on_upgrade = settings.on_upgrade,
.on_response = settings.on_response orelse null, .on_response = settings.on_response,
.on_finish = settings.on_finish, .on_finish = settings.on_finish,
.udata = null, .udata = null,
.public_folder = pfolder, .public_folder = pfolder,
@ -636,7 +690,7 @@ pub fn listen(port: [*c]const u8, interface: [*c]const u8, settings: ListenSetti
.reserved1 = 0, .reserved1 = 0,
.reserved2 = 0, .reserved2 = 0,
.reserved3 = 0, .reserved3 = 0,
.ws_max_msg_size = 0, .ws_max_msg_size = settings.ws_max_msg_size,
.timeout = settings.keepalive_timeout_s, .timeout = settings.keepalive_timeout_s,
.ws_timeout = 0, .ws_timeout = 0,
.log = if (settings.log) 1 else 0, .log = if (settings.log) 1 else 0,