Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion doc/reflections/das2rst.das
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,7 @@ def document_module_dashv(_root : string) {
var mod = get_module("dashv")
var groups <- array<DocGroup>(
group_by_regex("WebSocket client", mod, %regex~(make_web_socket_client|destroy_web_socket_client|open|close|is_connected|tick|send)$%%),
group_by_regex("WebSocket server lifecycle", mod, %regex~(make_web_socket_server|destroy_web_socket_server|start|stop)$%%),
group_by_regex("WebSocket server lifecycle", mod, %regex~(make_web_socket_server|destroy_web_socket_server|set_bind_host|start|stop)$%%),
group_by_regex("WebSocket channel send", mod, %regex~(send)$%%),
group_by_regex("Handle operations", mod, %regex~(is_alive|==|!=)$%%),
group_by_regex("HTTP route registration", mod, %regex~(GET|POST|PUT|DELETE|PATCH|HEAD|ANY|SSE|STREAM)$%%),
Expand Down
9 changes: 9 additions & 0 deletions doc/source/reference/tutorials/dasHV_06_websockets.rst
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@ Subclass ``HvWebSocketClient`` and override:
- ``onOpen()`` — connection established
- ``onClose()`` — connection lost
- ``onMessage(msg)`` — text message received
- ``onMessageFrame(msg, byte_count, opcode)`` — lossless text or binary frame;
``byte_count`` is authoritative when the payload contains embedded zero bytes

.. code-block:: das

Expand All @@ -93,6 +95,11 @@ Subclass ``HvWebSocketClient`` and override:
def override onMessage(msg : string#) {
received |> push_clone(string(msg))
}

def override onMessageFrame(msg : string#; byte_count : int; opcode : ws_opcode) {
if (opcode == ws_opcode.WS_OPCODE_TEXT) onMessage(msg)
// Binary consumers use msg together with byte_count here.
}
}

Connecting and Receiving
Expand Down Expand Up @@ -257,6 +264,8 @@ Quick Reference
- Client: connection lost
* - ``onMessage(msg)``
- Client: text message received
* - ``onMessageFrame(msg, byte_count, opcode)``
- Client: lossless text/binary frame (defaults to forwarding text to ``onMessage``)
* - ``init(url) : int``
- Client: connect to WebSocket server
* - ``send(text)``
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Closes a connected WebSocket channel. Returns 0 on success and -1 for an invalid channel handle.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Selects the WebSocket server interface to bind before start. Returns false for an invalid server handle, an empty host, or a host that is too long.
7 changes: 5 additions & 2 deletions examples/hv/ws_chat_client.das
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ class TerminalChatClient : HvWebSocketClient {
def override onMessage(msg : string#) {
print("{msg}\n")
}
def override onMessageFrame(msg : string#; byte_count : int; opcode : ws_opcode) {
if (opcode == ws_opcode.WS_OPCODE_TEXT) onMessage(msg)
}
}

[export]
Expand All @@ -48,13 +51,13 @@ def main() {
client->process_event_que()
// Non-blocking read from stdin
let line = fgets(fstdin())
if (length(line) > 0) {
if (!empty(line)) {
let text = strip(line)
if (text == "/quit") {
client->close()
break
}
if (length(text) > 0) {
if (!empty(text)) {
client->send(text)
}
}
Expand Down
13 changes: 12 additions & 1 deletion modules/dasHV/dashv/dashv_boost.das
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ options no_unused_function_arguments = false
//! High-level WebSocket and HTTP wrappers for the ``dashv`` C++ module.
//!
//! Provides ``HvWebSocketClient`` for WebSocket client connections with
//! ``onOpen``, ``onClose``, and ``onMessage`` callbacks, and ``HvWebServer``
//! ``onOpen``, ``onClose``, ``onMessage``, and lossless ``onMessageFrame``
//! callbacks, and ``HvWebServer``
//! for HTTP/WebSocket servers with route registration and static file serving.
//! Also includes ``with_http_request`` and ``get_body_bytes`` helpers.

Expand Down Expand Up @@ -45,6 +46,12 @@ class HvWebSocketClient {
//! Called when the WebSocket connection is closed.
def abstract onMessage(msg : string#) : void
//! Called when a text message is received from the server.
def onMessageFrame(msg : string#; byte_count : int; opcode : ws_opcode) {
//! Called for every complete text or binary frame. ``byte_count`` is
//! authoritative and preserves embedded zero bytes. The default keeps
//! the original text-only contract by forwarding text frames.
if (opcode == ws_opcode.WS_OPCODE_TEXT) onMessage(msg)
}
def is_connected : bool {
//! Returns ``true`` if the WebSocket connection is currently open.
return is_connected(client)
Expand Down Expand Up @@ -100,6 +107,10 @@ class HvWebServer {
//! Starts the server. Returns 0 on success.
return start(server)
}
def set_bind_host(host : string) : bool {
//! Selects the interface to bind before ``start`` (for example, ``127.0.0.1``).
return set_bind_host(server, host)
}
def stop : int {
//! Stops the server. Returns 0 on success.
return stop(server)
Expand Down
2 changes: 2 additions & 0 deletions modules/dasHV/src/aot_hv.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ namespace das {
int das_wss_send ( Handle<hv::WebSocketChannel> h, const char * msg, ws_opcode opcode, bool fin );
int das_wss_send_buf ( Handle<hv::WebSocketChannel> h, const char * buf, int32_t len, ws_opcode opcode, bool fin );
int das_wss_send_fragment ( Handle<hv::WebSocketChannel> h, const char * buf, int32_t len, int32_t fragment, ws_opcode opcode );
int das_wss_close_channel ( Handle<hv::WebSocketChannel> h );
bool das_wss_set_bind_host ( Handle<hv::WebSocketServer> h, const char * host );
int das_wss_start ( Handle<hv::WebSocketServer> h );
void das_wss_tick ( Handle<hv::WebSocketServer> h );
int das_wss_stop ( Handle<hv::WebSocketServer> h );
Expand Down
33 changes: 28 additions & 5 deletions modules/dasHV/src/dasHV.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -177,9 +177,10 @@ class WebSocketClient_Adapter : public hv::WebSocketClient, public HvWebSocketCl
});
};
onmessage = [=]( const string & msg ) {
const auto message_opcode = opcode();
lock_guard<mutex> guard(lock);
que.emplace_back([=](){
onMessage(msg);
que.emplace_back([=]() {
onMessageFrame(msg, message_opcode);
});
};
}
Expand All @@ -193,9 +194,10 @@ class WebSocketClient_Adapter : public hv::WebSocketClient, public HvWebSocketCl
invoke_onClose(context,fnOnClose,classPtr);
}
}
void onMessage ( const string & msg ) {
if ( auto fnOnMessage = get_onMessage(classPtr) ) {
invoke_onMessage(context,fnOnMessage,classPtr,(char *)msg.c_str());
void onMessageFrame ( const string & msg, ws_opcode opcode ) {
if ( auto fnOnMessageFrame = get_onMessageFrame(classPtr) ) {
invoke_onMessageFrame(context, fnOnMessageFrame, classPtr,
(char *)msg.data(), static_cast<int32_t>(msg.size()), opcode);
}
}
Comment on lines +197 to 202
void tick() {
Expand Down Expand Up @@ -591,6 +593,21 @@ int das_wss_send_fragment ( Handle<hv::WebSocketChannel> h, const char * buf, in
return p->send(buf, len, fragment, opcode);
}

int das_wss_close_channel ( Handle<hv::WebSocketChannel> h ) {
auto p = HandleRegistry<hv::WebSocketChannel>::instance().lookup(h);
if ( !p ) return -1;
return p->close();
}

bool das_wss_set_bind_host ( Handle<hv::WebSocketServer> h, const char * host ) {
auto adapter = lookup_server(h);
if ( !adapter || !host ) return false;
const size_t len = strlen(host);
if ( len == 0 || len >= sizeof(adapter->host) ) return false;
memcpy(adapter->host, host, len + 1);
return true;
}

int das_wss_start ( Handle<hv::WebSocketServer> h ) {
auto adapter = lookup_server(h);
if ( !adapter ) return -1;
Expand Down Expand Up @@ -1299,6 +1316,12 @@ class Module_HV : public Module {
addExtern<DAS_BIND_FUN(das_wss_send_fragment)> (*this, lib, "send",
SideEffects::worstDefault, "das_wss_send_fragment")
->args({"channel","msg","len","fragment","opcode"});
addExtern<DAS_BIND_FUN(das_wss_close_channel)> (*this, lib, "close",
SideEffects::worstDefault, "das_wss_close_channel")
->args({"channel"});
addExtern<DAS_BIND_FUN(das_wss_set_bind_host)> (*this, lib, "set_bind_host",
SideEffects::worstDefault, "das_wss_set_bind_host")
->args({"server","host"});
addExtern<DAS_BIND_FUN(das_wss_start)> (*this, lib, "start",
SideEffects::worstDefault, "das_wss_start")
->args({"server"});
Expand Down
2 changes: 2 additions & 0 deletions modules/dasHV/src/dasHV.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ Handle<hv::WebSocketServer> makeWebSocketServer ( int port, int httpsPort, const
int das_wss_send ( Handle<hv::WebSocketChannel> h, const char * msg, ws_opcode opcode, bool fin );
int das_wss_send_buf ( Handle<hv::WebSocketChannel> h, const char * buf, int32_t len, ws_opcode opcode, bool fin );
int das_wss_send_fragment ( Handle<hv::WebSocketChannel> h, const char * buf, int32_t len, int32_t fragment, ws_opcode opcode );
int das_wss_close_channel ( Handle<hv::WebSocketChannel> h );
bool das_wss_set_bind_host ( Handle<hv::WebSocketServer> h, const char * host );
int das_wss_start ( Handle<hv::WebSocketServer> h );
void das_wss_tick ( Handle<hv::WebSocketServer> h );
int das_wss_stop ( Handle<hv::WebSocketServer> h );
Expand Down
21 changes: 16 additions & 5 deletions modules/dasHV/src/dashv_gen.inc
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,16 @@ protected:
__fn_onOpen = 0,
__fn_onClose = 1,
__fn_onMessage = 2,
__fn_onMessageFrame = 3,
};
protected:
int _das_class_method_offset[3];
int _das_class_method_offset[4];
public:
HvWebSocketClient_Adapter ( const StructInfo * info ) {
_das_class_method_offset[__fn_onOpen] = info->fields[5]->offset;
_das_class_method_offset[__fn_onClose] = info->fields[6]->offset;
_das_class_method_offset[__fn_onMessage] = info->fields[7]->offset;
_das_class_method_offset[__fn_onMessageFrame] = info->fields[8]->offset;
}
__forceinline Func get_onOpen ( void * self ) const {
return getDasClassMethod(self,_das_class_method_offset[__fn_onOpen]);
Expand Down Expand Up @@ -40,6 +42,15 @@ public:
(__context__,nullptr,__funcCall__,
self,msg);
}
__forceinline Func get_onMessageFrame ( void * self ) const {
return getDasClassMethod(self,_das_class_method_offset[__fn_onMessageFrame]);
}
__forceinline void invoke_onMessageFrame ( Context * __context__, Func __funcCall__, void * self, char * const msg, int32_t byte_count, DAS_COMMENT(bound_enum) ws_opcode opcode ) const {
das_invoke_function<void>::invoke
<void *,char * const ,int32_t,DAS_COMMENT(bound_enum) ws_opcode>
(__context__,nullptr,__funcCall__,
self,msg,byte_count,opcode);
}
};

class HvWebServer_Adapter {
Expand All @@ -54,10 +65,10 @@ protected:
int _das_class_method_offset[4];
public:
HvWebServer_Adapter ( const StructInfo * info ) {
_das_class_method_offset[__fn_onWsOpen] = info->fields[11]->offset;
_das_class_method_offset[__fn_onWsClose] = info->fields[12]->offset;
_das_class_method_offset[__fn_onWsMessage] = info->fields[13]->offset;
_das_class_method_offset[__fn_onTick] = info->fields[14]->offset;
_das_class_method_offset[__fn_onWsOpen] = info->fields[12]->offset;
_das_class_method_offset[__fn_onWsClose] = info->fields[13]->offset;
_das_class_method_offset[__fn_onWsMessage] = info->fields[14]->offset;
_das_class_method_offset[__fn_onTick] = info->fields[15]->offset;
}
__forceinline Func get_onWsOpen ( void * self ) const {
return getDasClassMethod(self,_das_class_method_offset[__fn_onWsOpen]);
Expand Down
35 changes: 25 additions & 10 deletions modules/dasTerminal/daslib/terminal.das
Original file line number Diff line number Diff line change
Expand Up @@ -391,6 +391,21 @@ def private copy_cell(cell : TerminalCell const; row, column : int) : TerminalCe
hyperlink = cell.hyperlink)
}

def private erased_cell(buffer : TerminalBuffer const; row, column : int) : TerminalCell {
return TerminalCell(row = row, column = column, grapheme = " ", width = 1,
attributes = buffer.pen.attributes, foreground = buffer.pen.foreground,
background = buffer.pen.background)
}

def private erased_row(buffer : TerminalBuffer const; columns, row : int) : TerminalRow {
var result = TerminalRow()
result.cells |> reserve(columns)
for (column in range(columns)) {
result.cells |> push(erased_cell(buffer, row, column))
}
return <- result
}

def private clear_cell(terminal : Terminal const; var buffer : TerminalBuffer;
row, column : int) {
if (row < 0 || row >= terminal.rows || column < 0 || column >= terminal.columns) return
Expand All @@ -400,12 +415,12 @@ def private clear_cell(terminal : Terminal const; var buffer : TerminalBuffer;
first--
}
if (buffer.screen[physical_row].cells[first].width == 2 && first + 1 >= column) {
buffer.screen[physical_row].cells[first] = blank_cell(row, first)
buffer.screen[physical_row].cells[first] = erased_cell(buffer, row, first)
if (first + 1 < terminal.columns) {
buffer.screen[physical_row].cells[first + 1] = blank_cell(row, first + 1)
buffer.screen[physical_row].cells[first + 1] = erased_cell(buffer, row, first + 1)
}
} else {
buffer.screen[physical_row].cells[column] = blank_cell(row, column)
buffer.screen[physical_row].cells[column] = erased_cell(buffer, row, column)
}
}

Expand All @@ -423,7 +438,7 @@ def private scroll_up(terminal : Terminal const; var buffer : TerminalBuffer; no
if (normal) append_history_row(buffer, buffer.screen[first])
buffer.screen_start = (buffer.screen_start + 1) % terminal.rows
let last = screen_index(buffer, terminal.rows - 1)
buffer.screen[last] <- blank_row(terminal.columns, terminal.rows - 1)
buffer.screen[last] <- erased_row(buffer, terminal.columns, terminal.rows - 1)
}

def private scroll_rows_up(terminal : Terminal const; var buffer : TerminalBuffer;
Expand All @@ -445,7 +460,7 @@ def private scroll_rows_up(terminal : Terminal const; var buffer : TerminalBuffe
}
for (row in range(last - count + 1, last + 1)) {
let destination = screen_index(buffer, row)
buffer.screen[destination] <- blank_row(terminal.columns, row)
buffer.screen[destination] <- erased_row(buffer, terminal.columns, row)
}
}

Expand All @@ -459,7 +474,7 @@ def private scroll_rows_down(terminal : Terminal const; var buffer : TerminalBuf
for (_ in range(count)) {
buffer.screen_start = (buffer.screen_start + terminal.rows - 1) % terminal.rows
let top = screen_index(buffer, 0)
buffer.screen[top] <- blank_row(terminal.columns, 0)
buffer.screen[top] <- erased_row(buffer, terminal.columns, 0)
}
return
}
Expand All @@ -472,7 +487,7 @@ def private scroll_rows_down(terminal : Terminal const; var buffer : TerminalBuf
}
for (blank in range(first, first + count)) {
let destination = screen_index(buffer, blank)
buffer.screen[destination] <- blank_row(terminal.columns, blank)
buffer.screen[destination] <- erased_row(buffer, terminal.columns, blank)
}
}

Expand Down Expand Up @@ -624,7 +639,7 @@ def private erase_display(terminal : Terminal const; var buffer : TerminalBuffer
if (mode == 2 || mode == 3) {
for (row in range(terminal.rows)) {
let physical = screen_index(buffer, row)
buffer.screen[physical] <- blank_row(terminal.columns, row)
buffer.screen[physical] <- erased_row(buffer, terminal.columns, row)
}
if (mode == 3) {
buffer.history |> clear()
Expand All @@ -634,13 +649,13 @@ def private erase_display(terminal : Terminal const; var buffer : TerminalBuffer
erase_line(terminal, buffer, 0)
for (row in range(buffer.cursor.row + 1, terminal.rows)) {
let physical = screen_index(buffer, row)
buffer.screen[physical] <- blank_row(terminal.columns, row)
buffer.screen[physical] <- erased_row(buffer, terminal.columns, row)
}
} elif (mode == 1) {
erase_line(terminal, buffer, 1)
for (row in range(0, buffer.cursor.row)) {
let physical = screen_index(buffer, row)
buffer.screen[physical] <- blank_row(terminal.columns, row)
buffer.screen[physical] <- erased_row(buffer, terminal.columns, row)
}
}
}
Expand Down
Loading
Loading