1
0
Fork 0
mirror of https://github.com/zigzap/zap.git synced 2025-10-20 23:24:09 +00:00
zap/src/websockets.zig

207 lines
9 KiB
Zig

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 = @as(?*WebSocketSettings, @ptrCast(@alignCast(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 = @as(?*WebSocketSettings, @ptrCast(@alignCast(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 = @as(?*WebSocketSettings, @ptrCast(@alignCast(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 = @as(?*WebSocketSettings, @ptrCast(@alignCast(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 = @as(?*WebSocketSettings, @ptrCast(@alignCast(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 @as(*ContextType, @ptrCast(@alignCast(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 = @as(*SubscribeArgs, @ptrCast(@alignCast(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 = @as(*SubscribeArgs, @ptrCast(@alignCast(p)));
if (args.on_unsubscribe) |on_unsubscribe| {
on_unsubscribe(args.context);
}
}
}
};
}