I’ve been teaching myself WebSocket, and I’d say these days it’s the best way to glue just about any languages together.
For SWI Prolog, setting up a WebSocket server is nearly identical to an http server. The only additions are importing library(http/websocket) and modifying the second argument of http_handler (+Path, :Closure, +Options) to be inside http_upgrade_to_websocket (:Goal, +Options, +Request).
A simple ping-pong example (the “Hello World” of concurrent programming) looks like this on the SWI Prolog side:
:- use_module(library(http/websocket)).
:- use_module(library(http/http_server)).
:- use_module(library(http/http_unix_daemon)).
:- initialization http_daemon.
:- http_handler(root(ws), http_upgrade_to_websocket(loop, []), [spawn([])]).
loop(Request) :-
ws_receive(Request, Message, [format(json)]),
( Message.data.request == "close"
-> ws_send(Request, close(1000, "normal"))
; request(Message.data.request, Message.data, Reply),
ws_send(Request, json(Reply)),
loop(Request)
).
request("ping", _Dict, json{reply: pong}).
and would be started on a Linux machine like
swipl server.pl --port=6455 --pidfile=http.pid
where I picked the port number in honour of WebSocket’s RFC number.
Edit to the above code: Following a tip from @jan in the comments below, I added library(http/http_server) to the import list. It imports library(http/http_dyn_workers) which is a better way of setting the number of threads than the --workers=4 in the shell command I originally used.
I’ve never used Angular, but assume the plain vanilla JavaScript code below would work:
const prologServer = new WebSocket("ws://localhost:6455/ws");
prologServer.addEventListener("open", function(event) {
prologServer.send(JSON.stringify({"request": "ping"}));
});
prologServer.addEventListener("message", function(event) {
document.querySelector("#message").innerText = JSON.parse(event.data).reply;
});
prologServer.addEventListener("close", function(event) {
prologServer.close();
});
window.addEventListener("unload", (event) => {
prologServer.send(JSON.stringify({"request": "close"}));
});
I basically got into WebSocket by experimenting with getting Erlang and SWI Prolog to interface which I put examples of on github showing using SWI Prolog as a server and Erlang as a client, and vice versa.
This style of programming, sending messages in JSON which SWI Prolog handily converts into a dictionary for easy reference (though one gotcha is the value is a string which needs to be translated into something manipulable) and then back, is very Erlangish. Known as the actor model, it took me a bit of getting used to, but I’ve become a fan.
Though very easy when the URL is ws://localhost:portnumber, getting it to work on a public server where browsers insist on wss://samedomainname.com:portnumber is something I’m currently bashing my head against, learning far more about nginx configuration files than I ever wanted to.