Closed hajsf closed 11 months ago
Your handler's handle
is called when a message is received from the server. You should not be calling this directly. You don't call ws.onmessage("some data")
in your javascript example.
So you use the client
to write to the server (either from within your handler or outside of it), and handle
is used when you get a message. Keeping your server as-is, the following client code works:
const std = @import("std");
const websocket = @import("./websocket.zig/src/websocket.zig");
const Handler = struct {
client: *websocket.Client,
pub fn handle(_: Handler, message: websocket.Message) !void {
const data = message.data;
std.debug.print("CLIENT GOT: {any}\n", .{data});
}
pub fn close(_: Handler) void {}
};
pub fn main() !void {
var general_purpose_allocator = std.heap.GeneralPurposeAllocator(.{}){};
const allocator = general_purpose_allocator.allocator();
var client = try websocket.connect(allocator, "localhost", 9223, .{});
defer client.deinit();
const path = "/";
try client.handshake(path, .{
.timeout_ms = 5000,
.headers = "host: 127.0.0.1:9223\r\n",
});
const handler = Handler{.client = &client};
// Starts the read-loop which will take messages from the server and calls
// your handle method
const thread = try client.readLoopInNewThread(handler);
var data = try allocator.dupe(u8, "hello world");
try client.write(data);
// blocks until handle returns
// normally, you'd probably call thread.detach(); to have it run in the
// back and not block here.
thread.join();
}
You'll have to ctrl-c this code to end it, since the thread.join()
will block on the read-loop (unless one or the other end closes the connection)
You might be wondering why I had to do:
var data = try allocator.dupe(u8, "hello world");
try client.write(data);
Rather than just doing:
try client.write("hello world");
Technically, it's because write
takes a []u8
and not a []const u8
. This is why your code was crashing. You were passing a []const u8
to handle
and then calling @constCast
on it, which is not valid.
The reason the library takes a []u8
is because, in websocket, all client->server messages are masked. You can see this in action, if you std.debug.print("{s}\n", .{data});
AFTER the call to client.write
, you'll get garbage output. write
mutates the input. That might seem weird, but this masking has to happen. If the library took a []const u8
, then it would HAVE to dupe the value in order to be able to write to it. By making it take a []u8
, I'm letting the caller decide whether the data can be mutated as-is, or whether to dupe the value and pass that dupe into the library. In cases where the value can be mutated as-is, we avoid the expensive cost of duplicating the value.
The example in the readme exists to document the *websocket.Client API. The client is the library code. The handler is your code...and the documentation tries to showcase them separately.
There's nothing stopping you from merging them. Something like this might be more what you were expecting:
const std = @import("std");
const websocket = @import("./websocket.zig/src/websocket.zig");
const Handler = struct {
client: websocket.Client,
pub fn init(allocator: std.mem.Allocator, host: []const u8, port: u16) !Handler {
return .{
.client = try websocket.connect(allocator, host, port, .{}),
};
}
pub fn deinit(self: *Handler) void {
self.client.deinit();
}
pub fn connect(self: *Handler, path: []const u8) !void {
try self.client.handshake(path, .{.timeout_ms = 5000});
const thread = try self.client.readLoopInNewThread(self);
thread.detach();
}
pub fn handle(_: Handler, message: websocket.Message) !void {
const data = message.data;
std.debug.print("CLIENT GOT: {any}\n", .{data});
}
pub fn write(self: *Handler, data: []u8) !void {
return self.client.write(data);
}
pub fn close(_: Handler) void {}
};
pub fn main() !void {
var general_purpose_allocator = std.heap.GeneralPurposeAllocator(.{}){};
const allocator = general_purpose_allocator.allocator();
var handler = try Handler.init(allocator, "127.0.0.1", 9223);
defer handler.deinit();
// spins up a thread to listen to new messages
try handler.connect("/");
var data = try allocator.dupe(u8, "hello world");
try handler.write(data);
// without this, we'll exit immediately without having time to receive the
// echo'd message from the server
std.time.sleep(std.time.ns_per_s);
}
Thanks a lot for the detailed explanation and examples, deeply appreciated.
I'm using zig 0.11.0, and I wrote the below as
server.zig
:And it looks to be wroking fine, as I tested it with the
html
file:And tried to test the same with the zig code, and wrote the below which looks to be wrong:
And I got the below error:
UPDATE I was able to make the client send to the server by changing the
handle
function to the below, but not sure if this is the correct way or not, also I could not fugure how can I recieve the response from the server:I tried making it also as below, but got the same, it looks I do not understand the handler properly, appreciate your explaination:
Appreciate your support so that I can write a function equivalent to the JS function
ws.onmessage