Interfacing with Angular

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.

2 Likes