Running multiple HTTP Servers in SWI-prolog

Concrete server objects seem to be available in JavaScript, Python and Java.
How would I do the following in SWI-Prolog JavaScript file demo8.mjs:

// Create a local server(s) to receive data from
const server = http.createServer();
const server2 = http.createServer();

// Listen to the request event(s)
server.on('request', (req, res) => {
    res.writeHead(200, { 'content-type': 'text/html; charset=utf-8' });
    res.write('<p>Hello World!</p>');

server2.on('request', (req, res) => {
    res.writeHead(200, { 'content-type': 'text/html; charset=utf-8' });
    res.write('<p>Hello World 2!</p>');


Then run it on command line with:

node demo8.mjs

Seems to work, but didn’t test Java and Python yet, this is only JavaScript:


Presumably start it in 2 separate threads the usual way?

Hello world:

I’m not sure I understand what the problem is exactly. Just make a predicate to wrap whatever it needs to do, and pass in whatever you want to be variable, and then call that in a separate thread.

I’m not sure that object oriented vernacular transports particularly well to Prolog. In pretty much all OO languages, you’d have a class container and public properties for the purpose of configuration and dependency injection, but Prolog doesn’t have similar mechanism. Not to say that you couldnt make some elaborate functor to pass around, but that would be hideous.

Maybe what you’re looking for is dynamic dispatch so that you dont have to use the global http_handler:

:- use_module(library(http/thread_httpd)).
:- use_module(library(http/http_dispatch)).

server(Port) :-                                  
    % Dynamically add handlers
    http_handler(root(hello_world), say_hi, []),
    % Start the server
    http_server(http_dispatch, [port(Port)]).

say_hi(_Request) :-                              
    format('Content-type: text/plain~n~n'),
    format('Hello World!~n').

start_server(Port, Handler) :-
        thread_create(server(Port, Handler), _, [detached(true)]).

Now you’re free to have the same resource uri and different host/port combinations. Is that what you were after?

Probably one for @jan :slight_smile:

As show by this, you can have multiple servers that listen to different ports. The “object” in this case is a thread. Each HTTP server has its own pool of worker threads.

But, http_dispatch/1 dispatches over a set of globally registered HTTP handlers. That makes it a bit awkward to host two servers on different ports on the same Prolog process. You can do so by wrapping http_dispatch/1 with something that sets e.g., a global variable or thread-local predicate, but it remains ugly. If you really want to support that http_handler/3 needs to be expanded to take care of the different server instances somehow, for example by designating a module to a specific server instance.

That is all doable of course, but it is not very clear what the use case is to me. The SWI-Prolog HTTP infrastructure is first of all meant to make HTTP requests that are implemented by Prolog code,
not to serve e.g. static files. Two independent servers make sense if you want to implement two different applications in the same Prolog process, but why would you like to do that? Running two Prolog processes seems a lot simpler.

Two servers listening on different ports have a use case in dealing both with http and https traffic. Both using http_dispatch/1, they can simply serve the same stuff. Otherwise, one may redefine the dispatch handler of the http one to redirect to the https server.

Presumably one could also use http_handler to just handle / and then do the routing manually in the response handler? I.e. “say_hi” in my instance: presumably the Request object contains the full URL.

couldn’t you just run the debugger with a different URL prefix ?

Yeah but you still have to set somewhere what the server:port combinations are, so you may as well just set what the prefixes are too, why stop at host:port if all you need is host:port/debug/

Besides, if you need them to be entirely separate, why not actually run two separate swipl instances?

As said, the identity is clear. A server is a thread. You can start multiple of them and http_stop_server/2 can stop one, identified by the port. You cannot have two servers on the same port (not entirely sure about that as, while you normally create a server from a port number, you can also create one from a socket. This allows creating reserved sockets (POSIX ports below 1000) as root, and then drop admin rights before loading and initializing the remainder of the server. Never tried to create two from the same socket, but it might work).

It is just the dispatch framework that is not designed to deal with multiple servers doing different things. It is modular though, so you can invent another dispatch library that can do that or extend the current. Without a good use case I don’t see the point though.

For debugging and maintenance I normally use an administrative location prefix and add authentication to that. Different locations can use different authentications. But, in most cases I add the pack libssh that allow for ssh login to the server process. That gives you a normal interactive Prolog shell that allows enabling/disabling debug/3 messages, use trace/1 and reload files using make/0. All while the server happily keeps doing what it is supposed to do :slight_smile: Not sure how well libssh works on Windows, but on POSIX systems it gives pretty much the normal CLI interaction, including history, completion and color. How many other HTTP frameworks can do that?

Eventually I’d like to be able to run the CLI and GUI tracer remotely. Especially breaking into another thread to trace it in CLI mode shouldn’t be terribly hard :slight_smile:

I don’t think you understood what I’m saying. The libssh library creates an SSH server inside Prolog. That allows you to get a CLI on the Prolog server process that is running as a daemon, so using

ssh -p <port> localhost

the server creates a thread that provides a completely normal Prolog CLI interaction to the still happily running server. If you connect the ssh server to a publicly accessible port you even don’t have to login to the server. Authentication uses the usual ssh stuff and the connection is as usual encrypted.

The full experience (completion, command line editing, Control-C interrupts) relies on POSIX pseudo terminals (pty) and BSD libedit though. I don’t know how much of of that works without.

"libssh" pack for SWI-Prolog This is pretty much unrelated to HTTP. It allows connecting to any Prolog process you can’t easily access normally. For example, you can also use it to get a Prolog CLI for a Prolog process embedded in a C++ program that itself does not expose the Prolog CLI.

Again, a server is identified by a thread. Period. Yes, that is not an object in the OO sense. It does have state as well as behavior though. And, still, the dispatch framework is not designed to distinguish the port. It would be really easy to add the port to the Request argument of the handler. Probably it would be better to give a name to each server and pass that along. It is not rocket science to add all that, but as long as nobody passes by with a great story what nice things they could do if that was available I see little reason to work on that. If someone wishes to create a PR adding all this, I’ll surely seriously consider it.

And yes, we know Prolog is not OO. For that we have Logtalk. If you stay within Prolog, you use modules or extra arguments to distinguish as much as you like. For most scenarios I like Prolog better, although there are cases were OO has clear advantages.

You guys are lucky that I am extremly patient, immune to trolling attempts,
and goal oriented abbreviated as GO, not to be confused with OO.
Actually with enough coffee I am GO++.

A SWI-Prolog solution is rather straight forward a file demo8.p:

:- use_module(library(http/http_server)).

handler(_) :-
   write('content-type: text/plain;charset=utf-8'), nl,
   write('Hello World!').

handler2(_) :-
   write('content-type: text/plain;charset=utf-8'), nl,
   write('Hello World 2!').

:- http_server(handler, [port(8080)]).
:- http_server(handler2, [port(8082)]).

In terms of ADT was using http_server/2 instead of http_server/1 the
unary form is the same as http_server/2 with http_dispatch as first argument.
In the above I use two custom closures different from http_dispatch. In

terms of ADT the first initialization parameter is later part of the state of
the launched server object. Now run swipl and consult the file:

?- ['demo8.p'].
% Started server at http://localhost:8080/
% Started server at http://localhost:8082/

Works fine so far:


1 Like