Websocket buffering

Hi,

regard the standard ws handler

handle(WS) :-
     ws_recieve(WS, Message), 
     ( Message.opcode == close
        -> true
        ;
        decode(Message, Message2), % do something with the message
        ws_send(WS, json(Message2)), 
        handle(WS)
      ).

when another message is received during processing decode/2, will the message be ignored or dropped or will it be buffered. In my application it seems that the next message can only be received if ws_send is done. But I’m not sure, therefore I ask here :slight_smile:

cheers

Hans

You should consider ws_receive/2 and ws_send/2 simply as websocket replacements of read/write. So, yes you can process messages asynchronously if you have one thread reading them and dispatching the received messages over a pool of threads that handle them and respond to them. The docs say that ws_send/2 can be used safely from multiple threads. Of course you cannot sent two messages at the same time over a single TCP/IP socket, so they are serialized.

In a single thread (as you use above) handle/1 isn’t called before ws_send/2 completes, so you get what you describe.

Hi Jan,

thanks, that’s what I thought. Do you please have a pointer how to use multiple threads, I’m new to this topic :slight_smile: ?

Cheers

Hans

This is a case where I would search other repositories for such code.

The first one I always try is SWI-Prolog itself.

I have not tried this code but it looks like it has what you seek.

https://github.com/SWI-Prolog/packages-http/blob/0b90c2055b748cc12a816a5a1fde554163105efe/hub.pl

The documentation page

library(http/hub): Manage a hub for websockets

Hi,

yeah this looks good. Thank you :slight_smile: (sometimes you just have to know the right search key word :slight_smile: )

Cheers

Hans

If the hub library does what you need, it is great. It can be a bit of an overkill though. Its primary intend is to deal with servers that maintain websocket communication with many clients.

Hi,

I’ve based on the chat server example. The code is

:- module(logicserver, [server/1]).

:- use_module(library(http/websocket)).
:- use_module(library(http/thread_httpd)).
:- use_module(library(http/http_dispatch)).
:- use_module(library(http/http_files)).
:- use_module(library(http/http_server_files)).
:- use_module(library(http/http_json)).
:- use_module(library(http/hub)).

:- use_module(library(sandbox)).

:- http_handler(web(.), serve_files, [prefix]).
:- http_handler(data(.), serve_files, [prefix]).
:- http_handler(css(.), serve_files, [prefix]).
:- http_handler(lib(.), serve_files, [prefix]).
:- http_handler(root(.), main, [prefix]).
:- http_handler(ws(logic), 
				http_upgrade_to_websocket(acceptWS, []), 
				[id(logic_websocket)]).

:- multifile http_json/1.

%:- initialization(server(3045)).

http_json:json_type('application/x-javascript').
http_json:json_type('application/javascript').
http_json:json_type('text/javascript').
http_json:json_type('text/x-javascript').
http_json:json_type('text/x-json').
http_json:json_type('text/x-prolog').
http_json:json_type('text/prolog').


http:location(css, '/css', []).
http:location(lib, '/lib', []).
http:location(data, '/data', []).
http:location(web, '/web', []).
http:location(ws, '/cmd', []).

main(Request) :-
	http_reply_from_files('.', [], Request).

acceptWS(WS) :-
	hub_add(main, WS, ID),
	format("WS added ~w", [ID]).
	 
server(Port) :-
	hub_create(main, WS , _{}),
	thread_create(handle(WS), _, [alias(handle)]),
	http_server(http_dispatch, [port(Port)]).

serve_files(Request) :-
	http_reply_from_files(web, [], Request).

serve_files(Request) :-
	http_reply_from_files(lib, [], Request).

serve_files(Request) :-
	http_reply_from_files(css, [], Request).

serve_files(Request) :-
	http_reply_from_files(data, [], Request).
		
serve_files(Request) :-
	  http_404([\p('ouch')], Request).

handle(WS) :-
	format("Handle called", []),
	thread_get_message(WS.queues.event, json(Message)),
	
	msgHandler::decode(Message, Message2),
	hub_broadcast(WS.name, Message),
	handle(WS).	

The message “WS added” is coming, but not the message “Handle called”, which means handle(WS) will never be called. And I don’t see what is wrong. BTW the Messages are JSONs

[UPDATE] Sorry, a typo in the original code has lead to a permanen false of handle/1. So it is called, but the problem is now, that the JSONs seems to induce trouble. The message is:

ERROR: [Thread httpd@3045_2] Unknown error term: websocket_error(unexpected_message,websocket{data:"{“from”:“D”,“to”:“L”,“cmd”:“result”,“data”:" Ego -1.0 0.0014213432822582917 0.0014213432822582917 direction L D pack “,“seq”:1}”,format:string,opcode:text})

Cheers

Hans

For me this code writes “Handle called” after fixing the syntax error in this clause. I doubt the serve_files/1 likes this will work as (if I recall correctly), http_reply_from_files/3 will raise an (404) exception if the file does not exist. The argument order of this predicate is designed such that you do the call from the http_handler/3 registration immediately.

See docs for ws_close/3. That should give a clue.

Hi Jan,

now my puzzle pieces in brain fits to a picture :slight_smile: My error was to think that (in the code above) WS.queues.event always provides a websocket message … I was surprised to see that it can be a hub dict too. :wink: (and yes, this indicates also the chat server code, but NOW I understand it :D)

So, when initial the hub dict came into handle(WS), of msgHandler::decode/2 gives false and Websocket closed after that, but the client sends the next JSON object → resulting in Websocket_error(unexpected_message)

In sum, this was another nice trip into SWI things, so thanks for help!

Cheers

Hans