View source with raw comments or as raw
    1/*  Part of SWI-Prolog
    2
    3    Author:        Jan Wielemaker
    4    E-mail:        J.Wielemaker@vu.nl
    5    WWW:           http://www.swi-prolog.org
    6    Copyright (c)  2002-2018, University of Amsterdam
    7                              VU University Amsterdam
    8                              CWI, Amsterdam
    9    All rights reserved.
   10
   11    Redistribution and use in source and binary forms, with or without
   12    modification, are permitted provided that the following conditions
   13    are met:
   14
   15    1. Redistributions of source code must retain the above copyright
   16       notice, this list of conditions and the following disclaimer.
   17
   18    2. Redistributions in binary form must reproduce the above copyright
   19       notice, this list of conditions and the following disclaimer in
   20       the documentation and/or other materials provided with the
   21       distribution.
   22
   23    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
   24    "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
   25    LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
   26    FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
   27    COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
   28    INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
   29    BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
   30    LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
   31    CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
   32    LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
   33    ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
   34    POSSIBILITY OF SUCH DAMAGE.
   35*/
   36
   37:- module(thread_httpd,
   38          [ http_current_server/2,      % ?:Goal, ?Port
   39            http_server_property/2,     % ?Port, ?Property
   40            http_server/2,              % :Goal, +Options
   41            http_workers/2,             % +Port, ?WorkerCount
   42            http_add_worker/2,          % +Port, +Options
   43            http_current_worker/2,      % ?Port, ?ThreadID
   44            http_stop_server/2,         % +Port, +Options
   45            http_spawn/2,               % :Goal, +Options
   46
   47            http_requeue/1,             % +Request
   48            http_close_connection/1,    % +Request
   49            http_enough_workers/3       % +Queue, +Why, +Peer
   50          ]).   51:- use_module(library(debug)).   52:- use_module(library(error)).   53:- use_module(library(option)).   54:- use_module(library(socket)).   55:- use_module(library(thread_pool)).   56:- use_module(library(gensym)).   57:- use_module(http_wrapper).   58:- use_module(http_path).   59
   60
   61:- predicate_options(http_server/2, 2,
   62                     [ port(any),
   63                       entry_page(atom),
   64                       tcp_socket(any),
   65                       workers(positive_integer),
   66                       timeout(number),
   67                       keep_alive_timeout(number),
   68                       silent(boolean),
   69                       ssl(list(any)),  % if http/http_ssl_plugin is loaded
   70                       pass_to(system:thread_create/3, 3)
   71                     ]).   72:- predicate_options(http_spawn/2, 2,
   73                     [ pool(atom),
   74                       pass_to(system:thread_create/3, 3),
   75                       pass_to(thread_pool:thread_create_in_pool/4, 4)
   76                     ]).   77:- predicate_options(http_add_worker/2, 2,
   78                     [ timeout(number),
   79                       keep_alive_timeout(number),
   80                       max_idle_time(number),
   81                       pass_to(system:thread_create/3, 3)
   82                     ]).

Threaded HTTP server

This library defines the HTTP server frontend of choice for SWI-Prolog. It is based on the multi-threading capabilities of SWI-Prolog and thus exploits multiple cores to serve requests concurrently. The server scales well and can cooperate with library(thread_pool) to control the number of concurrent requests of a given type. For example, it can be configured to handle 200 file download requests concurrently, 2 requests that potentially uses a lot of memory and 8 requests that use a lot of CPU resources.

On Unix systems, this library can be combined with library(http/http_unix_daemon) to realise a proper Unix service process that creates a web server at port 80, runs under a specific account, optionally detaches from the controlling terminal, etc.

Combined with library(http/http_ssl_plugin) from the SSL package, this library can be used to create an HTTPS server. See <plbase>/doc/packages/examples/ssl/https for an example server using a self-signed SSL certificate. */

  106:- meta_predicate
  107    http_server(1, :),
  108    http_current_server(1, ?),
  109    http_spawn(0, +).  110
  111:- dynamic
  112    current_server/6,       % Port, Goal, Thread, Queue, Scheme, StartTime
  113    queue_worker/2,         % Queue, ThreadID
  114    queue_options/2.        % Queue, Options
  115
  116:- multifile
  117    make_socket_hook/3,
  118    accept_hook/2,
  119    close_hook/1,
  120    open_client_hook/6,
  121    http:create_pool/1,
  122    http:schedule_workers/1.
 http_server(:Goal, :Options) is det
Create a server at Port that calls Goal for each parsed request. Options provide a list of options. Defined options are
port(?Address)
Port to bind to. Address is either a port or a term Host:Port. The port may be a variable, causing the system to select a free port. See tcp_bind/2.
entry_page(+URI)
Affects the message printed while the server is started. Interpreted as a URI relative to the server root.
tcp_socket(+Socket)
If provided, use this socket instead of the creating one and binding it to an address. The socket must be bound to an address.
workers(+Count)
Determine the number of worker threads. Default is 5. This is fine for small scale usage. Public servers typically need a higher number.
timeout(+Seconds)
Maximum time of inactivity trying to read the request after a connection has been opened. Default is 60 seconds. See set_stream/1 using the timeout option.
keep_alive_timeout(+Seconds)
Time to keep `Keep alive' connections alive. Default is 2 seconds.
stack_limit(+Bytes)
Stack limit to use for the workers. The default is inherited from the main thread. If you need to control resource usage you may consider the spawn option of http_handler/3 and library(thread_pool).
silent(Bool)
If true (default false), do not print an informational message that the server was started.

A typical initialization for an HTTP server that uses http_dispatch/1 to relay requests to predicates is:

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

start_server(Port) :-
    http_server(http_dispatch, [port(Port)]).

Note that multiple servers can coexist in the same Prolog process. A notable application of this is to have both an HTTP and HTTPS server, where the HTTP server redirects to the HTTPS server for handling sensitive requests.

  183http_server(Goal, M:Options0) :-
  184    option(port(Port), Options0),
  185    !,
  186    make_socket(Port, M:Options0, Options),
  187    create_workers(Options),
  188    create_server(Goal, Port, Options),
  189    (   option(silent(true), Options0)
  190    ->  true
  191    ;   print_message(informational,
  192                      httpd_started_server(Port, Options0))
  193    ).
  194http_server(_Goal, _Options) :-
  195    existence_error(option, port).
 make_socket(?Port, :OptionsIn, -OptionsOut) is det
Create the HTTP server socket and worker pool queue. OptionsOut is quaranteed to hold the option queue(QueueId).
Arguments:
OptionsIn- is qualified to allow passing the module-sensitive ssl option argument.
  206make_socket(Port, Options0, Options) :-
  207    make_socket_hook(Port, Options0, Options),
  208    !.
  209make_socket(Port, _:Options0, Options) :-
  210    option(tcp_socket(_), Options0),
  211    !,
  212    make_addr_atom('httpd', Port, Queue),
  213    Options = [ queue(Queue)
  214              | Options0
  215              ].
  216make_socket(Port, _:Options0, Options) :-
  217    tcp_socket(Socket),
  218    tcp_setopt(Socket, reuseaddr),
  219    tcp_bind(Socket, Port),
  220    tcp_listen(Socket, 5),
  221    make_addr_atom('httpd', Port, Queue),
  222    Options = [ queue(Queue),
  223                tcp_socket(Socket)
  224              | Options0
  225              ].
 make_addr_atom(+Scheme, +Address, -Atom) is det
Create an atom that identifies the server's queue and thread resources.
  232make_addr_atom(Scheme, Address, Atom) :-
  233    phrase(address_parts(Address), Parts),
  234    atomic_list_concat([Scheme,@|Parts], Atom).
  235
  236address_parts(Atomic) -->
  237    { atomic(Atomic) },
  238    !,
  239    [Atomic].
  240address_parts(Host:Port) -->
  241    !,
  242    address_parts(Host), [:], address_parts(Port).
  243address_parts(ip(A,B,C,D)) -->
  244    !,
  245    [ A, '.', B, '.', C, '.', D ].
 create_server(:Goal, +Address, +Options) is det
Create the main server thread that runs accept_server/2 to listen to new requests.
  252create_server(Goal, Address, Options) :-
  253    get_time(StartTime),
  254    memberchk(queue(Queue), Options),
  255    scheme(Scheme, Options),
  256    autoload_https(Scheme),
  257    address_port(Address, Port),
  258    make_addr_atom(Scheme, Port, Alias),
  259    thread_self(Initiator),
  260    thread_create(accept_server(Goal, Initiator, Options), _,
  261                  [ alias(Alias)
  262                  ]),
  263    thread_get_message(server_started),
  264    assert(current_server(Port, Goal, Alias, Queue, Scheme, StartTime)).
  265
  266scheme(Scheme, Options) :-
  267    option(scheme(Scheme), Options),
  268    !.
  269scheme(Scheme, Options) :-
  270    (   option(ssl(_), Options)
  271    ;   option(ssl_instance(_), Options)
  272    ),
  273    !,
  274    Scheme = https.
  275scheme(http, _).
  276
  277address_port(_Host:Port, Port) :- !.
  278address_port(Port, Port).
  279
  280autoload_https(https) :-
  281    \+ clause(accept_hook(_Goal, _Options), _),
  282    exists_source(library(http/http_ssl_plugin)),
  283    !,
  284    use_module(library(http/http_ssl_plugin)).
  285autoload_https(_).
 http_current_server(:Goal, ?Port) is nondet
True if Goal is the goal of a server at Port.
deprecated
- Use http_server_property(Port, goal(Goal))
  293http_current_server(Goal, Port) :-
  294    current_server(Port, Goal, _, _, _, _).
 http_server_property(?Port, ?Property) is nondet
True if Property is a property of the HTTP server running at Port. Defined properties are:
goal(:Goal)
Goal used to start the server. This is often http_dispatch/1.
scheme(-Scheme)
Scheme is one of http or https.
start_time(?Time)
Time-stamp when the server was created.
  310http_server_property(_:Port, Property) :-
  311    integer(Port),
  312    !,
  313    server_property(Property, Port).
  314http_server_property(Port, Property) :-
  315    server_property(Property, Port).
  316
  317server_property(goal(Goal), Port) :-
  318    current_server(Port, Goal, _, _, _, _).
  319server_property(scheme(Scheme), Port) :-
  320    current_server(Port, _, _, _, Scheme, _).
  321server_property(start_time(Time), Port) :-
  322    current_server(Port, _, _, _, _, Time).
 http_workers(+Port, -Workers) is det
http_workers(+Port, +Workers:int) is det
Query or set the number of workers for the server at this port. The number of workers is dynamically modified. Setting it to 1 (one) can be used to profile the worker using tprofile/1.
  332http_workers(Port, Workers) :-
  333    must_be(ground, Port),
  334    current_server(Port, _, _, Queue, _, _),
  335    !,
  336    (   integer(Workers)
  337    ->  resize_pool(Queue, Workers)
  338    ;   findall(W, queue_worker(Queue, W), WorkerIDs),
  339        length(WorkerIDs, Workers)
  340    ).
  341http_workers(Port, _) :-
  342    existence_error(http_server, Port).
 http_add_worker(+Port, +Options) is det
Add a new worker to the HTTP server for port Port. Options overrule the default queue options. The following additional options are processed:
max_idle_time(+Seconds)
The created worker will automatically terminate if there is no new work within Seconds.
  355http_add_worker(Port, Options) :-
  356    must_be(ground, Port),
  357    current_server(Port, _, _, Queue, _, _),
  358    !,
  359    queue_options(Queue, QueueOptions),
  360    merge_options(Options, QueueOptions, WorkerOptions),
  361    atom_concat(Queue, '_', AliasBase),
  362    create_workers(1, 1, Queue, AliasBase, WorkerOptions).
  363http_add_worker(Port, _) :-
  364    existence_error(http_server, Port).
 http_current_worker(?Port, ?ThreadID) is nondet
True if ThreadID is the identifier of a Prolog thread serving Port. This predicate is motivated to allow for the use of arbitrary interaction with the worker thread for development and statistics.
  374http_current_worker(Port, ThreadID) :-
  375    current_server(Port, _, _, Queue, _, _),
  376    queue_worker(Queue, ThreadID).
 accept_server(:Goal, +Initiator, +Options)
The goal of a small server-thread accepting new requests and posting them to the queue of workers.
  384accept_server(Goal, Initiator, Options) :-
  385    catch(accept_server2(Goal, Initiator, Options), http_stop, true),
  386    thread_self(Thread),
  387    retract(current_server(_Port, _, Thread, _Queue, _Scheme, _StartTime)),
  388    close_server_socket(Options).
  389
  390accept_server2(Goal, Initiator, Options) :-
  391    thread_send_message(Initiator, server_started),
  392    repeat,
  393      (   catch(accept_server3(Goal, Options), E, true)
  394      ->  (   var(E)
  395          ->  fail
  396          ;   accept_rethrow_error(E)
  397          ->  throw(E)
  398          ;   print_message(error, E),
  399              fail
  400          )
  401      ;   print_message(error,      % internal error
  402                        goal_failed(accept_server3(Goal, Options))),
  403          fail
  404      ).
  405
  406accept_server3(Goal, Options) :-
  407    accept_hook(Goal, Options),
  408    !.
  409accept_server3(Goal, Options) :-
  410    memberchk(tcp_socket(Socket), Options),
  411    memberchk(queue(Queue), Options),
  412    tcp_accept(Socket, Client, Peer),
  413    debug(http(connection), 'New HTTP connection from ~p', [Peer]),
  414    thread_send_message(Queue, tcp_client(Client, Goal, Peer)),
  415    http_enough_workers(Queue, accept, Peer).
  416
  417accept_rethrow_error(http_stop).
  418accept_rethrow_error('$aborted').
 close_server_socket(+Options)
Close the server socket.
  425close_server_socket(Options) :-
  426    close_hook(Options),
  427    !.
  428close_server_socket(Options) :-
  429    memberchk(tcp_socket(Socket), Options),
  430    !,
  431    tcp_close_socket(Socket).
 http_stop_server(+Port, +Options)
Stop the indicated HTTP server gracefully. First stops all workers, then stops the server.
To be done
- Realise non-graceful stop
  441http_stop_server(Host:Port, Options) :-         % e.g., localhost:4000
  442    ground(Host),
  443    !,
  444    http_stop_server(Port, Options).
  445http_stop_server(Port, _Options) :-
  446    http_workers(Port, 0),                  % checks Port is ground
  447    current_server(Port, _, Thread, Queue, _Scheme, _Start),
  448    retractall(queue_options(Queue, _)),
  449    thread_signal(Thread, throw(http_stop)),
  450    catch(connect(localhost:Port), _, true),
  451    thread_join(Thread, _),
  452    message_queue_destroy(Queue).
  453
  454connect(Address) :-
  455    setup_call_cleanup(
  456        tcp_socket(Socket),
  457        tcp_connect(Socket, Address),
  458        tcp_close_socket(Socket)).
 http_enough_workers(+Queue, +Why, +Peer) is det
Check that we have enough workers in our queue. If not, call the hook http:schedule_workers/1 to extend the worker pool. This predicate can be used by accept_hook/2.
  466http_enough_workers(Queue, Why, Peer) :-
  467    message_queue_property(Queue, size(Size)),
  468    (   enough(Size, Why)
  469    ->  true
  470    ;   current_server(Port, _, _, Queue, _, _),
  471        catch(http:schedule_workers(_{port:Port,
  472                                      reason:Why,
  473                                      peer:Peer,
  474                                      waiting:Size,
  475                                      queue:Queue
  476                                     }),
  477              Error,
  478              print_message(error, Error))
  479    ->  true
  480    ;   true
  481    ).
  482
  483enough(0, _).
  484enough(1, keep_alive).                  % I will be ready myself
 http:schedule_workers(+Data:dict) is semidet
Hook called if a new connection or a keep-alive connection cannot be scheduled immediately to a worker. Dict contains the following keys:
port:Port
Port number that identifies the server.
reason:Reason
One of accept for a new connection or keep_alive if a worker tries to reschedule itself.
peer:Peer
Identify the other end of the connection
waiting:Size
Number of messages waiting in the queue.
queue:Queue
Message queue used to dispatch accepted messages.

Note that, when called with reason:accept, we are called in the time critical main accept loop. An implementation of this hook shall typically send the event to thread dedicated to dynamic worker-pool management.

See also
- http_add_worker/2 may be used to create (temporary) extra workers.
  514                 /*******************************
  515                 *    WORKER QUEUE OPERATIONS   *
  516                 *******************************/
 create_workers(+Options)
Create the pool of HTTP worker-threads. Each worker has the alias http_worker_N.
  523create_workers(Options) :-
  524    option(workers(N), Options, 5),
  525    option(queue(Queue), Options),
  526    catch(message_queue_create(Queue), _, true),
  527    atom_concat(Queue, '_', AliasBase),
  528    create_workers(1, N, Queue, AliasBase, Options),
  529    assert(queue_options(Queue, Options)).
  530
  531create_workers(I, N, _, _, _) :-
  532    I > N,
  533    !.
  534create_workers(I, N, Queue, AliasBase, Options) :-
  535    gensym(AliasBase, Alias),
  536    thread_create(http_worker(Options), Id,
  537                  [ alias(Alias)
  538                  | Options
  539                  ]),
  540    assertz(queue_worker(Queue, Id)),
  541    I2 is I + 1,
  542    create_workers(I2, N, Queue, AliasBase, Options).
 resize_pool(+Queue, +Workers) is det
Create or destroy workers. If workers are destroyed, the call waits until the desired number of waiters is reached.
  550resize_pool(Queue, Size) :-
  551    findall(W, queue_worker(Queue, W), Workers),
  552    length(Workers, Now),
  553    (   Now < Size
  554    ->  queue_options(Queue, Options),
  555        atom_concat(Queue, '_', AliasBase),
  556        I0 is Now+1,
  557        create_workers(I0, Size, Queue, AliasBase, Options)
  558    ;   Now == Size
  559    ->  true
  560    ;   Now > Size
  561    ->  Excess is Now - Size,
  562        thread_self(Me),
  563        forall(between(1, Excess, _), thread_send_message(Queue, quit(Me))),
  564        forall(between(1, Excess, _), thread_get_message(quitted(_)))
  565    ).
 http_worker(+Options)
Run HTTP worker main loop. Workers simply wait until they are passed an accepted socket to process a client.

If the message quit(Sender) is read from the queue, the worker stops.

  576http_worker(Options) :-
  577    thread_at_exit(done_worker),
  578    option(queue(Queue), Options),
  579    option(max_idle_time(MaxIdle), Options, infinite),
  580    repeat,
  581      garbage_collect,
  582      trim_stacks,
  583      debug(http(worker), 'Waiting for a job ...', []),
  584      (   MaxIdle == infinite
  585      ->  thread_get_message(Queue, Message)
  586      ;   thread_get_message(Queue, Message, [timeout(MaxIdle)])
  587      ->  true
  588      ;   Message = quit(idle)
  589      ),
  590      debug(http(worker), 'Got job ~p', [Message]),
  591      (   Message = quit(Sender)
  592      ->  !,
  593          thread_self(Self),
  594          thread_detach(Self),
  595          (   Sender == idle
  596          ->  true
  597          ;   retract(queue_worker(Queue, Self)),
  598              thread_send_message(Sender, quitted(Self))
  599          )
  600      ;   open_client(Message, Queue, Goal, In, Out,
  601                      Options, ClientOptions),
  602          (   catch(http_process(Goal, In, Out, ClientOptions),
  603                    Error, true)
  604          ->  true
  605          ;   Error = goal_failed(http_process/4)
  606          ),
  607          (   var(Error)
  608          ->  fail
  609          ;   current_message_level(Error, Level),
  610              print_message(Level, Error),
  611              memberchk(peer(Peer), ClientOptions),
  612              close_connection(Peer, In, Out),
  613              fail
  614          )
  615      ).
 open_client(+Message, +Queue, -Goal, -In, -Out, +Options, -ClientOptions) is semidet
Opens the connection to the client in a worker from the message sent to the queue by accept_server/2.
  624open_client(requeue(In, Out, Goal, ClOpts),
  625            _, Goal, In, Out, Opts, ClOpts) :-
  626    !,
  627    memberchk(peer(Peer), ClOpts),
  628    option(keep_alive_timeout(KeepAliveTMO), Opts, 2),
  629    check_keep_alive_connection(In, KeepAliveTMO, Peer, In, Out).
  630open_client(Message, Queue, Goal, In, Out, Opts,
  631            [ pool(client(Queue, Goal, In, Out)),
  632              timeout(Timeout)
  633            | Options
  634            ]) :-
  635    catch(open_client(Message, Goal, In, Out, Options, Opts),
  636          E, report_error(E)),
  637    option(timeout(Timeout), Opts, 60),
  638    (   debugging(http(connection))
  639    ->  memberchk(peer(Peer), Options),
  640        debug(http(connection), 'Opened connection from ~p', [Peer])
  641    ;   true
  642    ).
 open_client(+Message, +Goal, -In, -Out, -ClientOptions, +Options) is det
  648open_client(Message, Goal, In, Out, ClientOptions, Options) :-
  649    open_client_hook(Message, Goal, In, Out, ClientOptions, Options),
  650    !.
  651open_client(tcp_client(Socket, Goal, Peer), Goal, In, Out,
  652            [ peer(Peer),
  653              protocol(http)
  654            ], _) :-
  655    tcp_open_socket(Socket, In, Out).
  656
  657report_error(E) :-
  658    print_message(error, E),
  659    fail.
 check_keep_alive_connection(+In, +TimeOut, +Peer, +In, +Out) is semidet
Wait for the client for at most TimeOut seconds. Succeed if the client starts a new request within this time. Otherwise close the connection and fail.
  668check_keep_alive_connection(In, TMO, Peer, In, Out) :-
  669    stream_property(In, timeout(Old)),
  670    set_stream(In, timeout(TMO)),
  671    debug(http(keep_alive), 'Waiting for keep-alive ...', []),
  672    catch(peek_code(In, Code), E, true),
  673    (   var(E),                     % no exception
  674        Code \== -1                 % no end-of-file
  675    ->  set_stream(In, timeout(Old)),
  676        debug(http(keep_alive), '\tre-using keep-alive connection', [])
  677    ;   (   Code == -1
  678        ->  debug(http(keep_alive), '\tRemote closed keep-alive connection', [])
  679        ;   debug(http(keep_alive), '\tTimeout on keep-alive connection', [])
  680        ),
  681        close_connection(Peer, In, Out),
  682        fail
  683    ).
 done_worker
Called when worker is terminated due to http_workers/2 or a (debugging) exception. In the latter case, recreate_worker/2 creates a new worker.
  692done_worker :-
  693    thread_self(Self),
  694    thread_detach(Self),
  695    retract(queue_worker(Queue, Self)),
  696    thread_property(Self, status(Status)),
  697    !,
  698    (   catch(recreate_worker(Status, Queue), _, fail)
  699    ->  print_message(informational,
  700                      httpd_restarted_worker(Self))
  701    ;   done_status_message_level(Status, Level),
  702        print_message(Level,
  703                      httpd_stopped_worker(Self, Status))
  704    ).
  705done_worker :-                                  % received quit(Sender)
  706    thread_self(Self),
  707    thread_property(Self, status(Status)),
  708    done_status_message_level(Status, Level),
  709    print_message(Level,
  710                  httpd_stopped_worker(Self, Status)).
  711
  712done_status_message_level(true, silent) :- !.
  713done_status_message_level(exception('$aborted'), silent) :- !.
  714done_status_message_level(_, informational).
 recreate_worker(+Status, +Queue) is semidet
Deal with the possibility that threads are, during development, killed with abort/0. We recreate the worker to avoid that eventually we run out of workers. If we are aborted due to a halt/0 call, thread_create/3 will raise a permission error.

The first clause deals with the possibility that we cannot write to user_error. This is possible when Prolog is started as a service using some service managers. Would be nice if we could write an error, but where?

  729recreate_worker(exception(error(io_error(write,user_error),_)), _Queue) :-
  730    halt(2).
  731recreate_worker(exception(Error), Queue) :-
  732    recreate_on_error(Error),
  733    queue_options(Queue, Options),
  734    atom_concat(Queue, '_', AliasBase),
  735    create_workers(1, 1, Queue, AliasBase, Options).
  736
  737recreate_on_error('$aborted').
  738recreate_on_error(time_limit_exceeded).
 thread_httpd:message_level(+Exception, -Level)
Determine the message stream used for exceptions that may occur during server_loop/5. Being multifile, clauses can be added by the application to refine error handling. See also message_hook/3 for further programming error handling.
  747:- multifile
  748    message_level/2.  749
  750message_level(error(io_error(read, _), _),      silent).
  751message_level(error(socket_error(epipe,_), _),	silent).
  752message_level(error(timeout_error(read, _), _), informational).
  753message_level(keep_alive_timeout,               silent).
  754
  755current_message_level(Term, Level) :-
  756    (   message_level(Term, Level)
  757    ->  true
  758    ;   Level = error
  759    ).
 http_requeue(+Header)
Re-queue a connection to the worker pool. This deals with processing additional requests on keep-alive connections.
  767http_requeue(Header) :-
  768    requeue_header(Header, ClientOptions),
  769    memberchk(pool(client(Queue, Goal, In, Out)), ClientOptions),
  770    memberchk(peer(Peer), ClientOptions),
  771    http_enough_workers(Queue, keep_alive, Peer),
  772    thread_send_message(Queue, requeue(In, Out, Goal, ClientOptions)),
  773    !.
  774http_requeue(Header) :-
  775    debug(http(error), 'Re-queue failed: ~p', [Header]),
  776    fail.
  777
  778requeue_header([], []).
  779requeue_header([H|T0], [H|T]) :-
  780    requeue_keep(H),
  781    !,
  782    requeue_header(T0, T).
  783requeue_header([_|T0], T) :-
  784    requeue_header(T0, T).
  785
  786requeue_keep(pool(_)).
  787requeue_keep(peer(_)).
  788requeue_keep(protocol(_)).
 http_process(Message, Queue, +Options)
Handle a single client message on the given stream.
  795http_process(Goal, In, Out, Options) :-
  796    debug(http(server), 'Running server goal ~p on ~p -> ~p',
  797          [Goal, In, Out]),
  798    option(timeout(TMO), Options, 60),
  799    set_stream(In, timeout(TMO)),
  800    set_stream(Out, timeout(TMO)),
  801    http_wrapper(Goal, In, Out, Connection,
  802                 [ request(Request)
  803                 | Options
  804                 ]),
  805    next(Connection, Request).
  806
  807next(switch_protocol(SwitchGoal, _SwitchOptions), Request) :-
  808    !,
  809    memberchk(pool(client(_Queue, _Goal, In, Out)), Request),
  810    (   catch(call(SwitchGoal, In, Out), E,
  811              (   print_message(error, E),
  812                  fail))
  813    ->  true
  814    ;   http_close_connection(Request)
  815    ).
  816next(spawned(ThreadId), _) :-
  817    !,
  818    debug(http(spawn), 'Handler spawned to thread ~w', [ThreadId]).
  819next(Connection, Request) :-
  820    downcase_atom(Connection, 'keep-alive'),
  821    http_requeue(Request),
  822    !.
  823next(_, Request) :-
  824    http_close_connection(Request).
 http_close_connection(+Request)
Close connection associated to Request. See also http_requeue/1.
  831http_close_connection(Request) :-
  832    memberchk(pool(client(_Queue, _Goal, In, Out)), Request),
  833    memberchk(peer(Peer), Request),
  834    close_connection(Peer, In, Out).
 close_connection(+Peer, +In, +Out)
Closes the connection from the server to the client. Errors are currently silently ignored.
  841close_connection(Peer, In, Out) :-
  842    debug(http(connection), 'Closing connection from ~p', [Peer]),
  843    catch(close(In, [force(true)]), _, true),
  844    catch(close(Out, [force(true)]), _, true).
 http_spawn(:Goal, +Options) is det
Continue this connection on a new thread. A handler may call http_spawn/2 to start a new thread that continues processing the current request using Goal. The original thread returns to the worker pool for processing new requests. Options are passed to thread_create/3, except for:
pool(+Pool)
Interfaces to library(thread_pool), starting the thread on the given pool.

If a pool does not exist, this predicate calls the multifile hook create_pool/1 to create it. If this predicate succeeds the operation is retried.

  862http_spawn(Goal, Options) :-
  863    select_option(pool(Pool), Options, ThreadOptions),
  864    !,
  865    current_output(CGI),
  866    catch(thread_create_in_pool(Pool,
  867                                wrap_spawned(CGI, Goal), Id,
  868                                [ detached(true)
  869                                | ThreadOptions
  870                                ]),
  871          Error,
  872          true),
  873    (   var(Error)
  874    ->  http_spawned(Id)
  875    ;   Error = error(resource_error(threads_in_pool(_)), _)
  876    ->  throw(http_reply(busy))
  877    ;   Error = error(existence_error(thread_pool, Pool), _),
  878        create_pool(Pool)
  879    ->  http_spawn(Goal, Options)
  880    ;   throw(Error)
  881    ).
  882http_spawn(Goal, Options) :-
  883    current_output(CGI),
  884    thread_create(wrap_spawned(CGI, Goal), Id,
  885                  [ detached(true)
  886                  | Options
  887                  ]),
  888    http_spawned(Id).
  889
  890wrap_spawned(CGI, Goal) :-
  891    set_output(CGI),
  892    http_wrap_spawned(Goal, Request, Connection),
  893    next(Connection, Request).
 create_pool(+Pool)
Lazy creation of worker-pools for the HTTP server. This predicate calls the hook create_pool/1. If the hook fails it creates a default pool of size 10. This should suffice most typical usecases. Note that we get a permission error if the pool is already created. We can ignore this.
  903create_pool(Pool) :-
  904    E = error(permission_error(create, thread_pool, Pool), _),
  905    catch(http:create_pool(Pool), E, true).
  906create_pool(Pool) :-
  907    print_message(informational, httpd(created_pool(Pool))),
  908    thread_pool_create(Pool, 10, []).
  909
  910
  911
  912                 /*******************************
  913                 *            MESSAGES          *
  914                 *******************************/
  915
  916:- multifile
  917    prolog:message/3.  918
  919prolog:message(httpd_started_server(Port, Options)) -->
  920    [ 'Started server at '-[] ],
  921    http_root(Port, Options).
  922prolog:message(httpd_stopped_worker(Self, Status)) -->
  923    [ 'Stopped worker ~p: ~p'-[Self, Status] ].
  924prolog:message(httpd_restarted_worker(Self)) -->
  925    [ 'Replaced aborted worker ~p'-[Self] ].
  926prolog:message(httpd(created_pool(Pool))) -->
  927    [ 'Created thread-pool ~p of size 10'-[Pool], nl,
  928      'Create this pool at startup-time or define the hook ', nl,
  929      'http:create_pool/1 to avoid this message and create a ', nl,
  930      'pool that fits the usage-profile.'
  931    ].
  932
  933http_root(Address, Options) -->
  934    { landing_page(Address, URI, Options) },
  935    [ '~w'-[URI] ].
  936
  937landing_page(Host:Port, URI, Options) :-
  938    must_be(atom, Host),
  939    http_server_property(Port, scheme(Scheme)),
  940    (   default_port(Scheme, Port)
  941    ->  format(atom(Base), '~w://~w', [Scheme, Host])
  942    ;   format(atom(Base), '~w://~w:~w', [Scheme, Host, Port])
  943    ),
  944    entry_page(Base, URI, Options).
  945landing_page(Port, URI, Options) :-
  946    landing_page(localhost:Port, URI, Options).
  947
  948default_port(http, 80).
  949default_port(https, 443).
  950
  951entry_page(Base, URI, Options) :-
  952    option(entry_page(Entry), Options),
  953    !,
  954    uri_resolve(Entry, Base, URI).
  955entry_page(Base, URI, _) :-
  956    http_absolute_location(root(.), Entry, []),
  957    uri_resolve(Entry, Base, URI)