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.

1 Like

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

1 Like

Hi,

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

Cheers

Hans

1 Like

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.

2 Likes

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.

1 Like

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

1 Like

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