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.
- **[cookies](examples/cookies/cookies.zig)**: a simple example sending
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

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 = "http_params", .src = "examples/http_params/http_params.zig" },
.{ .name = "cookies", .src = "examples/cookies/cookies.zig" },
.{ .name = "websockets", .src = "examples/websockets/websockets.zig" },
}) |excfg| {
const ex_name = excfg.name;
const ex_src = excfg.src;

View file

@ -1,12 +1,11 @@
.{
.name = "zap",
.version = "0.0.15",
.version = "0.0.16",
.dependencies = .{
.@"facil.io" = .{
.url = "https://github.com/zigzap/facil.io/archive/refs/tags/zap-0.0.7.tar.gz",
.hash = "1220d03e0579bbb726efb8224ea289b26227bc421158b45c1b16a60b31bfa400ab33",
.url = "https://github.com/zigzap/facil.io/archive/refs/tags/zap-0.0.8.tar.gz",
.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,
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 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;
@ -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_unsubscribe(ws: ?*ws_s, subscription_id: usize) 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 struct_http_sse_s = extern struct {
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("mustache.zig");
pub usingnamespace @import("http_auth.zig");
pub const WebSockets = @import("websockets.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 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 {
port: usize,
interface: [*c]const u8 = null,
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,
max_clients: ?isize = null,
max_body_size: ?usize = null,
timeout: ?u8 = null,
log: bool = false,
ws_timeout: u8 = 40,
ws_max_msg_size: usize = 262144,
};
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
pub fn theOneAndOnlyRequestCallBack(r: [*c]fio.http_s) callconv(.C) void {
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 {
var pfolder: [*c]const u8 = null;
var pfolder_len: usize = 0;
@ -546,22 +599,23 @@ pub const SimpleHttpListener = struct {
var x: fio.http_settings_s = .{
.on_request = if (self.settings.on_request) |_| Self.theOneAndOnlyRequestCallBack else null,
.on_upgrade = null,
.on_response = self.settings.on_response,
.on_finish = null,
.on_upgrade = if (self.settings.on_upgrade) |_| Self.theOneAndOnlyUpgradeCallBack else null,
.on_response = if (self.settings.on_response) |_| Self.theOneAndOnlyResponseCallBack else null,
.on_finish = if (self.settings.on_finish) |_| Self.theOneAndOnlyFinishCallBack else null,
.udata = null,
.public_folder = pfolder,
.public_folder_length = pfolder_len,
.max_header_size = 32 * 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,
.reserved1 = 0,
.reserved2 = 0,
.reserved3 = 0,
.ws_max_msg_size = 0,
.timeout = self.settings.timeout orelse 5,
.ws_timeout = 0,
.ws_timeout = self.settings.ws_timeout,
.log = if (self.settings.log) 1 else 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 = .{
.on_request = settings.on_request,
.on_upgrade = settings.on_upgrade,
.on_response = settings.on_response orelse null,
.on_response = settings.on_response,
.on_finish = settings.on_finish,
.udata = null,
.public_folder = pfolder,
@ -636,7 +690,7 @@ pub fn listen(port: [*c]const u8, interface: [*c]const u8, settings: ListenSetti
.reserved1 = 0,
.reserved2 = 0,
.reserved3 = 0,
.ws_max_msg_size = 0,
.ws_max_msg_size = settings.ws_max_msg_size,
.timeout = settings.keepalive_timeout_s,
.ws_timeout = 0,
.log = if (settings.log) 1 else 0,