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)  2007-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(http_dispatch,
   38          [ http_dispatch/1,            % +Request
   39            http_handler/3,             % +Path, +Predicate, +Options
   40            http_delete_handler/1,      % +Path
   41            http_reply_file/3,          % +File, +Options, +Request
   42            http_redirect/3,            % +How, +Path, +Request
   43            http_404/2,                 % +Options, +Request
   44            http_switch_protocol/2,     % :Goal, +Options
   45            http_current_handler/2,     % ?Path, ?Pred
   46            http_current_handler/3,     % ?Path, ?Pred, -Options
   47            http_location_by_id/2,      % +ID, -Location
   48            http_link_to_id/3,          % +ID, +Parameters, -HREF
   49            http_reload_with_parameters/3, % +Request, +Parameters, -HREF
   50            http_safe_file/2            % +Spec, +Options
   51          ]).   52:- use_module(library(option)).   53:- use_module(library(lists)).   54:- use_module(library(time)).   55:- use_module(library(error)).   56:- use_module(library(settings)).   57:- use_module(library(uri)).   58:- use_module(library(apply)).   59:- use_module(library(aggregate)).   60:- use_module(library(http/mimetype)).   61:- use_module(library(http/http_path)).   62:- use_module(library(http/http_header)).   63:- use_module(library(http/thread_httpd)).   64
   65:- predicate_options(http_404/2, 1, [index(any)]).   66:- predicate_options(http_reply_file/3, 2,
   67                     [ cache(boolean),
   68                       mime_type(any),
   69                       static_gzip(boolean),
   70                       pass_to(http_safe_file/2, 2),
   71                       headers(list)
   72                     ]).   73:- predicate_options(http_safe_file/2, 2, [unsafe(boolean)]).   74:- predicate_options(http_switch_protocol/2, 2, []).

Dispatch requests in the HTTP server

This module can be placed between http_wrapper.pl and the application code to associate HTTP locations to predicates that serve the pages. In addition, it associates parameters with locations that deal with timeout handling and user authentication. The typical setup is:

server(Port, Options) :-
        http_server(http_dispatch,
                    [ port(Port)
                    | Options
                    ]).

:- http_handler('/index.html', write_index, []).

write_index(Request) :-
        ...

*/

   97:- setting(http:time_limit, nonneg, 300,
   98           'Time limit handling a single query (0=infinite)').
 http_handler(+Path, :Closure, +Options) is det
Register Closure as a handler for HTTP requests. Path is a specification as provided by http_path.pl. If an HTTP request arrives at the server that matches Path, Closure is called with one extra argument: the parsed HTTP request. Options is a list containing the following options:
authentication(+Type)
Demand authentication. Authentication methods are pluggable. The library http_authenticate.pl provides a plugin for user/password based Basic HTTP authentication.
chunked
Use Transfer-encoding: chunked if the client allows for it.
condition(:Goal)
If present, the handler is ignored if Goal does not succeed.
content_type(+Term)
Specifies the content-type of the reply. This value is currently not used by this library. It enhances the reflexive capabilities of this library through http_current_handler/3.
id(+Term)
Identifier of the handler. The default identifier is the predicate name. Used by http_location_by_id/2.
hide_children(+Bool)
If true on a prefix-handler (see prefix), possible children are masked. This can be used to (temporary) overrule part of the tree.
method(+Method)
Declare that the handler processes Method. This is equivalent to methods([Method]). Using method(*) allows for all methods.
methods(+ListOfMethods)
Declare that the handler processes all of the given methods. If this option appears multiple times, the methods are combined.
prefix
Call Pred on any location that is a specialisation of Path. If multiple handlers match, the one with the longest path is used. Options defined with a prefix handler are the default options for paths that start with this prefix. Note that the handler acts as a fallback handler for the tree below it:
:- http_handler(/, http_404([index('index.html')]),
                [spawn(my_pool),prefix]).
priority(+Integer)
If two handlers handle the same path, the one with the highest priority is used. If equal, the last registered is used. Please be aware that the order of clauses in multifile predicates can change due to reloading files. The default priority is 0 (zero).
spawn(+SpawnOptions)
Run the handler in a seperate thread. If SpawnOptions is an atom, it is interpreted as a thread pool name (see create_thread_pool/3). Otherwise the options are passed to http_spawn/2 and from there to thread_create/3. These options are typically used to set the stack limits.
time_limit(+Spec)
One of infinite, default or a positive number (seconds). If default, the value from the setting http:time_limit is taken. The default of this setting is 300 (5 minutes). See setting/2.

Note that http_handler/3 is normally invoked as a directive and processed using term-expansion. Using term-expansion ensures proper update through make/0 when the specification is modified. We do not expand when the cross-referencer is running to ensure proper handling of the meta-call.

Errors
- existence_error(http_location, Location)
See also
- http_reply_file/3 and http_redirect/3 are generic handlers to serve files and achieve redirects.
  190:- dynamic handler/4.                   % Path, Action, IsPrefix, Options
  191:- multifile handler/4.  192:- dynamic generation/1.  193
  194:- meta_predicate
  195    http_handler(+, :, +),
  196    http_current_handler(?, :),
  197    http_current_handler(?, :, ?),
  198    http_switch_protocol(2, +).  199
  200http_handler(Path, Pred, Options) :-
  201    compile_handler(Path, Pred, Options, Clause),
  202    next_generation,
  203    assert(Clause).
  204
  205:- multifile
  206    system:term_expansion/2.  207
  208system:term_expansion((:- http_handler(Path, Pred, Options)), Clause) :-
  209    \+ current_prolog_flag(xref, true),
  210    prolog_load_context(module, M),
  211    compile_handler(Path, M:Pred, Options, Clause),
  212    next_generation.
 http_delete_handler(+Spec) is det
Delete handler for Spec. Typically, this should only be used for handlers that are registered dynamically. Spec is one of:
id(Id)
Delete a handler with the given id. The default id is the handler-predicate-name.
path(Path)
Delete handler that serves the given path.
  227http_delete_handler(id(Id)) :-
  228    !,
  229    clause(handler(_Path, _:Pred, _, Options), true, Ref),
  230    functor(Pred, DefID, _),
  231    option(id(Id0), Options, DefID),
  232    Id == Id0,
  233    erase(Ref),
  234    next_generation.
  235http_delete_handler(path(Path)) :-
  236    !,
  237    retractall(handler(Path, _Pred, _, _Options)),
  238    next_generation.
  239http_delete_handler(Path) :-
  240    http_delete_handler(path(Path)).
 next_generation is det
 current_generation(-G) is det
Increment the generation count.
  248next_generation :-
  249    retractall(id_location_cache(_,_)),
  250    with_mutex(http_dispatch, next_generation_unlocked).
  251
  252next_generation_unlocked :-
  253    retract(generation(G0)),
  254    !,
  255    G is G0 + 1,
  256    assert(generation(G)).
  257next_generation_unlocked :-
  258    assert(generation(1)).
  259
  260current_generation(G) :-
  261    with_mutex(http_dispatch, generation(G)),
  262    !.
  263current_generation(0).
 compile_handler(+Path, :Pred, +Options) is det
Compile a handler specification. For now we this is a no-op, but in the feature can make this more efficiently, especially in the presence of one or multiple prefix declarations. We can also use this to detect conflicts.
  273compile_handler(Path, Pred, Options0,
  274                http_dispatch:handler(Path1, Pred, IsPrefix, Options)) :-
  275    check_path(Path, Path1),
  276    (   select(prefix, Options0, Options1)
  277    ->  IsPrefix = true
  278    ;   IsPrefix = false,
  279        Options1 = Options0
  280    ),
  281    Pred = M:_,
  282    maplist(qualify_option(M), Options1, Options2),
  283    combine_methods(Options2, Options).
  284
  285qualify_option(M, condition(Pred), condition(M:Pred)) :-
  286    Pred \= _:_, !.
  287qualify_option(_, Option, Option).
 combine_methods(+OptionsIn, -Options) is det
Combine method(M) and methods(MList) options into a single methods(MList) option.
  294combine_methods(Options0, Options) :-
  295    collect_methods(Options0, Options1, Methods),
  296    (   Methods == []
  297    ->  Options = Options0
  298    ;   append(Methods, Flat),
  299        sort(Flat, Unique),
  300        (   memberchk('*', Unique)
  301        ->  Final = '*'
  302        ;   Final = Unique
  303        ),
  304        Options = [methods(Final)|Options1]
  305    ).
  306
  307collect_methods([], [], []).
  308collect_methods([method(M)|T0], T, [[M]|TM]) :-
  309    !,
  310    (   M == '*'
  311    ->  true
  312    ;   must_be_method(M)
  313    ),
  314    collect_methods(T0, T, TM).
  315collect_methods([methods(M)|T0], T, [M|TM]) :-
  316    !,
  317    must_be(list, M),
  318    maplist(must_be_method, M),
  319    collect_methods(T0, T, TM).
  320collect_methods([H|T0], [H|T], TM) :-
  321    !,
  322    collect_methods(T0, T, TM).
  323
  324must_be_method(M) :-
  325    must_be(atom, M),
  326    (   method(M)
  327    ->  true
  328    ;   domain_error(http_method, M)
  329    ).
  330
  331method(get).
  332method(put).
  333method(head).
  334method(post).
  335method(delete).
  336method(patch).
  337method(options).
  338method(trace).
 check_path(+PathSpecIn, -PathSpecOut) is det
Validate the given path specification. We want one of

Similar to absolute_file_name/3, Relative can be a term Component/Component/...

Errors
- domain_error, type_error
See also
- http_absolute_location/3
  354check_path(Path, Path) :-
  355    atom(Path),
  356    !,
  357    (   sub_atom(Path, 0, _, _, /)
  358    ->  true
  359    ;   domain_error(absolute_http_location, Path)
  360    ).
  361check_path(Alias, AliasOut) :-
  362    compound(Alias),
  363    Alias =.. [Name, Relative],
  364    !,
  365    to_atom(Relative, Local),
  366    (   sub_atom(Local, 0, _, _, /)
  367    ->  domain_error(relative_location, Relative)
  368    ;   AliasOut =.. [Name, Local]
  369    ).
  370check_path(PathSpec, _) :-
  371    type_error(path_or_alias, PathSpec).
  372
  373to_atom(Atom, Atom) :-
  374    atom(Atom),
  375    !.
  376to_atom(Path, Atom) :-
  377    phrase(path_to_list(Path), Components),
  378    !,
  379    atomic_list_concat(Components, '/', Atom).
  380to_atom(Path, _) :-
  381    ground(Path),
  382    !,
  383    type_error(relative_location, Path).
  384to_atom(Path, _) :-
  385    instantiation_error(Path).
  386
  387path_to_list(Var) -->
  388    { var(Var),
  389      !,
  390      fail
  391    }.
  392path_to_list(A/B) -->
  393    path_to_list(A),
  394    path_to_list(B).
  395path_to_list(Atom) -->
  396    { atom(Atom) },
  397    [Atom].
 http_dispatch(Request) is det
Dispatch a Request using http_handler/3 registrations.
  405http_dispatch(Request) :-
  406    memberchk(path(Path), Request),
  407    find_handler(Path, Pred, Options),
  408    supports_method(Request, Options),
  409    authentication(Options, Request, Fields),
  410    append(Fields, Request, AuthRequest),
  411    action(Pred, AuthRequest, Options).
 http_current_handler(+Location, :Closure) is semidet
http_current_handler(-Location, :Closure) is nondet
True if Location is handled by Closure.
  419http_current_handler(Path, Closure) :-
  420    atom(Path),
  421    !,
  422    path_tree(Tree),
  423    find_handler(Tree, Path, Closure, _).
  424http_current_handler(Path, M:C) :-
  425    handler(Spec, M:C, _, _),
  426    http_absolute_location(Spec, Path, []).
 http_current_handler(+Location, :Closure, -Options) is semidet
http_current_handler(?Location, :Closure, ?Options) is nondet
Resolve the current handler and options to execute it.
  433http_current_handler(Path, Closure, Options) :-
  434    atom(Path),
  435    !,
  436    path_tree(Tree),
  437    find_handler(Tree, Path, Closure, Options).
  438http_current_handler(Path, M:C, Options) :-
  439    handler(Spec, M:C, _, _),
  440    http_absolute_location(Spec, Path, []),
  441    path_tree(Tree),
  442    find_handler(Tree, Path, _, Options).
 http_location_by_id(+ID, -Location) is det
Find the HTTP Location of handler with ID. If the setting (see setting/2) http:prefix is active, Location is the handler location prefixed with the prefix setting. Handler IDs can be specified in two ways:
id(ID)
If this appears in the option list of the handler, this it is used and takes preference over using the predicate.
M:PredName
The module-qualified name of the predicate.
PredName
The unqualified name of the predicate.
Errors
- existence_error(http_handler_id, Id).
deprecated
- The predicate http_link_to_id/3 provides the same functionality with the option to add query parameters or a path parameter.
  465:- dynamic
  466    id_location_cache/2.  467
  468http_location_by_id(ID, Location) :-
  469    must_be(ground, ID),
  470    id_location_cache(ID, L0),
  471    !,
  472    Location = L0.
  473http_location_by_id(ID, Location) :-
  474    findall(P-L, location_by_id(ID, L, P), List),
  475    keysort(List, RevSorted),
  476    reverse(RevSorted, Sorted),
  477    (   Sorted = [_-One]
  478    ->  assert(id_location_cache(ID, One)),
  479        Location = One
  480    ;   List == []
  481    ->  existence_error(http_handler_id, ID)
  482    ;   List = [P0-Best,P1-_|_]
  483    ->  (   P0 == P1
  484        ->  print_message(warning,
  485                          http_dispatch(ambiguous_id(ID, Sorted, Best)))
  486        ;   true
  487        ),
  488        assert(id_location_cache(ID, Best)),
  489        Location = Best
  490    ).
  491
  492location_by_id(ID, Location, Priority) :-
  493    location_by_id_raw(ID, L0, Priority),
  494    to_path(L0, Location).
  495
  496to_path(prefix(Path0), Path) :-        % old style prefix notation
  497    !,
  498    add_prefix(Path0, Path).
  499to_path(Path0, Path) :-
  500    atomic(Path0),                 % old style notation
  501    !,
  502    add_prefix(Path0, Path).
  503to_path(Spec, Path) :-                  % new style notation
  504    http_absolute_location(Spec, Path, []).
  505
  506add_prefix(P0, P) :-
  507    (   catch(setting(http:prefix, Prefix), _, fail),
  508        Prefix \== ''
  509    ->  atom_concat(Prefix, P0, P)
  510    ;   P = P0
  511    ).
  512
  513location_by_id_raw(ID, Location, Priority) :-
  514    handler(Location, _, _, Options),
  515    option(id(ID), Options),
  516    option(priority(P0), Options, 0),
  517    Priority is P0+1000.            % id(ID) takes preference over predicate
  518location_by_id_raw(ID, Location, Priority) :-
  519    handler(Location, M:C, _, Options),
  520    option(priority(Priority), Options, 0),
  521    functor(C, PN, _),
  522    (   ID = M:PN
  523    ;   ID = PN
  524    ),
  525    !.
 http_link_to_id(+HandleID, +Parameters, -HREF)
HREF is a link on the local server to a handler with given ID, passing the given Parameters. This predicate is typically used to formulate a HREF that resolves to a handler implementing a particular predicate. The code below provides a typical example. The predicate user_details/1 returns a page with details about a user from a given id. This predicate is registered as a handler. The DCG user_link//1 renders a link to a user, displaying the name and calling user_details/1 when clicked. Note that the location (root(user_details)) is irrelevant in this equation and HTTP locations can thus be moved freely without breaking this code fragment.
:- http_handler(root(user_details), user_details, []).

user_details(Request) :-
    http_parameters(Request,
                    [ user_id(ID)
                    ]),
    ...

user_link(ID) -->
    { user_name(ID, Name),
      http_link_to_id(user_details, [id(ID)], HREF)
    },
    html(a([class(user), href(HREF)], Name)).
Arguments:
Parameters- is one of
  • path_postfix(File) to pass a single value as the last segment of the HTTP location (path). This way of passing a parameter is commonly used in REST APIs.
  • A list of search parameters for a GET request.
See also
- http_location_by_id/2 and http_handler/3 for defining and specifying handler IDs.
  568http_link_to_id(HandleID, path_postfix(File), HREF) :-
  569    !,
  570    http_location_by_id(HandleID, HandlerLocation),
  571    uri_encoded(path, File, EncFile),
  572    directory_file_path(HandlerLocation, EncFile, Location),
  573    uri_data(path, Components, Location),
  574    uri_components(HREF, Components).
  575http_link_to_id(HandleID, Parameters, HREF) :-
  576    must_be(list, Parameters),
  577    http_location_by_id(HandleID, Location),
  578    uri_data(path, Components, Location),
  579    uri_query_components(String, Parameters),
  580    uri_data(search, Components, String),
  581    uri_components(HREF, Components).
 http_reload_with_parameters(+Request, +Parameters, -HREF) is det
Create a request on the current handler with replaced search parameters.
  588http_reload_with_parameters(Request, NewParams, HREF) :-
  589    memberchk(path(Path), Request),
  590    (   memberchk(search(Params), Request)
  591    ->  true
  592    ;   Params = []
  593    ),
  594    merge_options(NewParams, Params, AllParams),
  595    uri_query_components(Search, AllParams),
  596    uri_data(path, Data, Path),
  597    uri_data(search, Data, Search),
  598    uri_components(HREF, Data).
  599
  600
  601%       hook into html_write:attribute_value//1.
  602
  603:- multifile
  604    html_write:expand_attribute_value//1.  605
  606html_write:expand_attribute_value(location_by_id(ID)) -->
  607    { http_location_by_id(ID, Location) },
  608    html_write:html_quoted_attribute(Location).
 authentication(+Options, +Request, -Fields) is det
Verify authentication information. If authentication is requested through Options, demand it. The actual verification is done by the multifile predicate http:authenticate/3. The library http_authenticate.pl provides an implementation thereof.
Errors
- permission_error(access, http_location, Location)
  620:- multifile
  621    http:authenticate/3.  622
  623authentication([], _, []).
  624authentication([authentication(Type)|Options], Request, Fields) :-
  625    !,
  626    (   http:authenticate(Type, Request, XFields)
  627    ->  append(XFields, More, Fields),
  628        authentication(Options, Request, More)
  629    ;   memberchk(path(Path), Request),
  630        permission_error(access, http_location, Path)
  631    ).
  632authentication([_|Options], Request, Fields) :-
  633    authentication(Options, Request, Fields).
 find_handler(+Path, -Action, -Options) is det
Find the handler to call from Path. Rules:

If there is a handler for /dir/ and the requested path is /dir, find_handler/3 throws a http_reply exception, causing the wrapper to generate a 301 (Moved Permanently) reply.

Errors
- existence_error(http_location, Location) @throw http_reply(moved(Dir))
To be done
- Introduce automatic redirection to indexes here?
  652find_handler(Path, Action, Options) :-
  653    path_tree(Tree),
  654    (   find_handler(Tree, Path, Action, Options),
  655        eval_condition(Options)
  656    ->  true
  657    ;   \+ sub_atom(Path, _, _, 0, /),
  658        atom_concat(Path, /, Dir),
  659        find_handler(Tree, Dir, Action, Options)
  660    ->  throw(http_reply(moved(Dir)))
  661    ;   throw(error(existence_error(http_location, Path), _))
  662    ).
  663
  664
  665find_handler([node(prefix(Prefix), PAction, POptions, Children)|_],
  666             Path, Action, Options) :-
  667    sub_atom(Path, 0, _, After, Prefix),
  668    !,
  669    (   option(hide_children(false), POptions, false),
  670        find_handler(Children, Path, Action, Options)
  671    ->  true
  672    ;   Action = PAction,
  673        path_info(After, Path, POptions, Options)
  674    ).
  675find_handler([node(Path, Action, Options, _)|_], Path, Action, Options) :- !.
  676find_handler([_|Tree], Path, Action, Options) :-
  677    find_handler(Tree, Path, Action, Options).
  678
  679path_info(0, _, Options,
  680          [prefix(true)|Options]) :- !.
  681path_info(After, Path, Options,
  682          [path_info(PathInfo),prefix(true)|Options]) :-
  683    sub_atom(Path, _, After, 0, PathInfo).
  684
  685eval_condition(Options) :-
  686    (   memberchk(condition(Cond), Options)
  687    ->  catch(Cond, E, (print_message(warning, E), fail))
  688    ;   true
  689    ).
 supports_method(+Request, +Options) is det
Verify that the asked http method is supported by the handler. If not, raise an error that will be mapped to a 405 page by the http wrapper.
Errors
- permission_error(http_method, Method, Location).
  700supports_method(Request, Options) :-
  701    (   option(methods(Methods), Options)
  702    ->  (   Methods == '*'
  703        ->  true
  704        ;   memberchk(method(Method), Request),
  705            memberchk(Method, Methods)
  706        )
  707    ;   true
  708    ),
  709    !.
  710supports_method(Request, _Options) :-
  711    memberchk(path(Location), Request),
  712    memberchk(method(Method), Request),
  713    permission_error(http_method, Method, Location).
 action(+Action, +Request, +Options) is det
Execute the action found. Here we take care of the options time_limit, chunked and spawn.
Errors
- goal_failed(Goal)
  723action(Action, Request, Options) :-
  724    memberchk(chunked, Options),
  725    !,
  726    format('Transfer-encoding: chunked~n'),
  727    spawn_action(Action, Request, Options).
  728action(Action, Request, Options) :-
  729    spawn_action(Action, Request, Options).
  730
  731spawn_action(Action, Request, Options) :-
  732    option(spawn(Spawn), Options),
  733    !,
  734    spawn_options(Spawn, SpawnOption),
  735    http_spawn(time_limit_action(Action, Request, Options), SpawnOption).
  736spawn_action(Action, Request, Options) :-
  737    time_limit_action(Action, Request, Options).
  738
  739spawn_options([], []) :- !.
  740spawn_options(Pool, Options) :-
  741    atom(Pool),
  742    !,
  743    Options = [pool(Pool)].
  744spawn_options(List, List).
  745
  746time_limit_action(Action, Request, Options) :-
  747    (   option(time_limit(TimeLimit), Options),
  748        TimeLimit \== default
  749    ->  true
  750    ;   setting(http:time_limit, TimeLimit)
  751    ),
  752    number(TimeLimit),
  753    TimeLimit > 0,
  754    !,
  755    call_with_time_limit(TimeLimit, call_action(Action, Request, Options)).
  756time_limit_action(Action, Request, Options) :-
  757    call_action(Action, Request, Options).
 call_action(+Action, +Request, +Options)
To be done
- reply_file is normal call?
  764call_action(reply_file(File, FileOptions), Request, _Options) :-
  765    !,
  766    http_reply_file(File, FileOptions, Request).
  767call_action(Pred, Request, Options) :-
  768    memberchk(path_info(PathInfo), Options),
  769    !,
  770    call_action(Pred, [path_info(PathInfo)|Request]).
  771call_action(Pred, Request, _Options) :-
  772    call_action(Pred, Request).
  773
  774call_action(Pred, Request) :-
  775    (   call(Pred, Request)
  776    ->  true
  777    ;   extend(Pred, [Request], Goal),
  778        throw(error(goal_failed(Goal), _))
  779    ).
  780
  781extend(Var, _, Var) :-
  782    var(Var),
  783    !.
  784extend(M:G0, Extra, M:G) :-
  785    extend(G0, Extra, G).
  786extend(G0, Extra, G) :-
  787    G0 =.. List,
  788    append(List, Extra, List2),
  789    G =.. List2.
 http_reply_file(+FileSpec, +Options, +Request) is det
Options is a list of
cache(+Boolean)
If true (default), handle If-modified-since and send modification time.
mime_type(+Type)
Overrule mime-type guessing from the filename as provided by file_mime_type/2.
static_gzip(+Boolean)
If true (default false) and, in addition to the plain file, there is a .gz file that is not older than the plain file and the client acceps gzip encoding, send the compressed file with Transfer-encoding: gzip.
unsafe(+Boolean)
If false (default), validate that FileSpec does not contain references to parent directories. E.g., specifications such as www('../../etc/passwd') are not allowed.
headers(+List)
Provides additional reply-header fields, encoded as a list of Field(Value).

If caching is not disabled, it processes the request headers If-modified-since and Range.

throws
- http_reply(not_modified)
- http_reply(file(MimeType, Path))
  825http_reply_file(File, Options, Request) :-
  826    http_safe_file(File, Options),
  827    absolute_file_name(File, Path,
  828                       [ access(read)
  829                       ]),
  830    (   option(cache(true), Options, true)
  831    ->  (   memberchk(if_modified_since(Since), Request),
  832            time_file(Path, Time),
  833            catch(http_timestamp(Time, Since), _, fail)
  834        ->  throw(http_reply(not_modified))
  835        ;   true
  836        ),
  837        (   memberchk(range(Range), Request)
  838        ->  Reply = file(Type, Path, Range)
  839        ;   option(static_gzip(true), Options),
  840            accepts_encoding(Request, gzip),
  841            file_name_extension(Path, gz, PathGZ),
  842            access_file(PathGZ, read),
  843            time_file(PathGZ, TimeGZ),
  844            time_file(Path, Time),
  845            TimeGZ >= Time
  846        ->  Reply = gzip_file(Type, PathGZ)
  847        ;   Reply = file(Type, Path)
  848        )
  849    ;   Reply = tmp_file(Type, Path)
  850    ),
  851    (   option(mime_type(MediaType), Options)
  852    ->  file_content_type(Path, MediaType, Type)
  853    ;   file_content_type(Path, Type)
  854    ->  true
  855    ;   Type = text/plain           % fallback type
  856    ),
  857    option(headers(Headers), Options, []),
  858    throw(http_reply(Reply, Headers)).
  859
  860accepts_encoding(Request, Enc) :-
  861    memberchk(accept_encoding(Accept), Request),
  862    split_string(Accept, ",", " ", Parts),
  863    member(Part, Parts),
  864    split_string(Part, ";", " ", [EncS|_]),
  865    atom_string(Enc, EncS).
 http_safe_file(+FileSpec, +Options) is det
True if FileSpec is considered safe. If it is an atom, it cannot be absolute and cannot have references to parent directories. If it is of the form alias(Sub), than Sub cannot have references to parent directories.
Errors
- instantiation_error
- permission_error(read, file, FileSpec)
  878http_safe_file(File, _) :-
  879    var(File),
  880    !,
  881    instantiation_error(File).
  882http_safe_file(_, Options) :-
  883    option(unsafe(true), Options, false),
  884    !.
  885http_safe_file(File, _) :-
  886    http_safe_file(File).
  887
  888http_safe_file(File) :-
  889    compound(File),
  890    functor(File, _, 1),
  891    !,
  892    arg(1, File, Name),
  893    safe_name(Name, File).
  894http_safe_file(Name) :-
  895    (   is_absolute_file_name(Name)
  896    ->  permission_error(read, file, Name)
  897    ;   true
  898    ),
  899    safe_name(Name, Name).
  900
  901safe_name(Name, _) :-
  902    must_be(atom, Name),
  903    prolog_to_os_filename(FileName, Name),
  904    \+ unsafe_name(FileName),
  905    !.
  906safe_name(_, Spec) :-
  907    permission_error(read, file, Spec).
  908
  909unsafe_name(Name) :- Name == '..'.
  910unsafe_name(Name) :- sub_atom(Name, 0, _, _, '../').
  911unsafe_name(Name) :- sub_atom(Name, _, _, _, '/../').
  912unsafe_name(Name) :- sub_atom(Name, _, _, 0, '/..').
 http_redirect(+How, +To, +Request) is det
Redirect to a new location. The argument order, using the Request as last argument, allows for calling this directly from the handler declaration:
:- http_handler(root(.),
                http_redirect(moved, myapp('index.html')),
                []).
Arguments:
How- is one of moved, moved_temporary or see_other
To- is an atom, a aliased path as defined by http_absolute_location/3. or a term location_by_id(Id). If To is not absolute, it is resolved relative to the current location.
  932http_redirect(How, To, Request) :-
  933    (   To = location_by_id(Id)
  934    ->  http_location_by_id(Id, URL)
  935    ;   memberchk(path(Base), Request),
  936        http_absolute_location(To, URL, [relative_to(Base)])
  937    ),
  938    must_be(oneof([moved, moved_temporary, see_other]), How),
  939    Term =.. [How,URL],
  940    throw(http_reply(Term)).
 http_404(+Options, +Request) is det
Reply using an "HTTP 404 not found" page. This handler is intended as fallback handler for prefix handlers. Options processed are:
index(Location)
If there is no path-info, redirect the request to Location using http_redirect/3.
Errors
- http_reply(not_found(Path))
  955http_404(Options, Request) :-
  956    option(index(Index), Options),
  957    \+ ( option(path_info(PathInfo), Request),
  958         PathInfo \== ''
  959       ),
  960    !,
  961    http_redirect(moved, Index, Request).
  962http_404(_Options, Request) :-
  963    option(path(Path), Request),
  964    !,
  965    throw(http_reply(not_found(Path))).
  966http_404(_Options, Request) :-
  967    domain_error(http_request, Request).
 http_switch_protocol(:Goal, +Options)
Send an "HTTP 101 Switching Protocols" reply. After sending the reply, the HTTP library calls call(Goal, InStream, OutStream), where InStream and OutStream are the raw streams to the HTTP client. This allows the communication to continue using an an alternative protocol.

If Goal fails or throws an exception, the streams are closed by the server. Otherwise Goal is responsible for closing the streams. Note that Goal runs in the HTTP handler thread. Typically, the handler should be registered using the spawn option if http_handler/3 or Goal must call thread_create/3 to allow the HTTP worker to return to the worker pool.

The streams use binary (octet) encoding and have their I/O timeout set to the server timeout (default 60 seconds). The predicate set_stream/2 can be used to change the encoding, change or cancel the timeout.

This predicate interacts with the server library by throwing an exception.

The following options are supported:

header(+Headers)
Backward compatible. Use headers(+Headers).
headers(+Headers)
Additional headers send with the reply. Each header takes the form Name(Value).
 1001%       @throws http_reply(switch_protocol(Goal, Options))
 1002
 1003http_switch_protocol(Goal, Options) :-
 1004    throw(http_reply(switching_protocols(Goal, Options))).
 1005
 1006
 1007                 /*******************************
 1008                 *        PATH COMPILATION      *
 1009                 *******************************/
 path_tree(-Tree) is det
Compile paths into a tree. The treee is multi-rooted and represented as a list of nodes, where each node has the form:
node(PathOrPrefix, Action, Options, Children)

The tree is a potentially complicated structure. It is cached in a global variable. Note that this cache is per-thread, so each worker thread holds a copy of the tree. If handler facts are changed the generation is incremented using next_generation/0 and each worker thread will re-compute the tree on the next ocasion.

 1025path_tree(Tree) :-
 1026    current_generation(G),
 1027    nb_current(http_dispatch_tree, G-Tree),
 1028    !. % Avoid existence error
 1029path_tree(Tree) :-
 1030    path_tree_nocache(Tree),
 1031    current_generation(G),
 1032    nb_setval(http_dispatch_tree, G-Tree).
 1033
 1034path_tree_nocache(Tree) :-
 1035    findall(Prefix, prefix_handler(Prefix, _, _, _), Prefixes0),
 1036    sort(Prefixes0, Prefixes),
 1037    prefix_tree(Prefixes, [], PTree),
 1038    prefix_options(PTree, [], OPTree),
 1039    add_paths_tree(OPTree, Tree).
 1040
 1041prefix_handler(Prefix, Action, Options, Priority) :-
 1042    handler(Spec, Action, true, Options),
 1043    (   memberchk(priority(Priority), Options)
 1044    ->  true
 1045    ;   Priority = 0
 1046    ),
 1047    Error = error(existence_error(http_alias,_),_),
 1048    catch(http_absolute_location(Spec, Prefix, []), Error,
 1049          (   print_message(warning, Error),
 1050              fail
 1051          )).
 prefix_tree(PrefixList, +Tree0, -Tree)
Arguments:
Tree- list(Prefix-list(Children))
 1057prefix_tree([], Tree, Tree).
 1058prefix_tree([H|T], Tree0, Tree) :-
 1059    insert_prefix(H, Tree0, Tree1),
 1060    prefix_tree(T, Tree1, Tree).
 1061
 1062insert_prefix(Prefix, Tree0, Tree) :-
 1063    select(P-T, Tree0, Tree1),
 1064    sub_atom(Prefix, 0, _, _, P),
 1065    !,
 1066    insert_prefix(Prefix, T, T1),
 1067    Tree = [P-T1|Tree1].
 1068insert_prefix(Prefix, Tree, [Prefix-[]|Tree]).
 prefix_options(+PrefixTree, +DefOptions, -OptionTree)
Generate the option-tree for all prefix declarations.
To be done
- What to do if there are more?
 1077prefix_options([], _, []).
 1078prefix_options([P-C|T0], DefOptions,
 1079               [node(prefix(P), Action, Options, Children)|T]) :-
 1080    aggregate_all(max(Priority, Action-Options0),
 1081                  prefix_handler(P, Action, Options0, Priority),
 1082                  max(_, Action-Options0)),
 1083    merge_options(Options0, DefOptions, Options),
 1084    delete(Options, id(_), InheritOpts),
 1085    prefix_options(C, InheritOpts, Children),
 1086    prefix_options(T0, DefOptions, T).
 add_paths_tree(+OPTree, -Tree) is det
Add the plain paths.
 1093add_paths_tree(OPTree, Tree) :-
 1094    findall(path(Path, Action, Options),
 1095            plain_path(Path, Action, Options),
 1096            Triples),
 1097    add_paths_tree(Triples, OPTree, Tree).
 1098
 1099add_paths_tree([], Tree, Tree).
 1100add_paths_tree([path(Path, Action, Options)|T], Tree0, Tree) :-
 1101    add_path_tree(Path, Action, Options, [], Tree0, Tree1),
 1102    add_paths_tree(T, Tree1, Tree).
 plain_path(-Path, -Action, -Options) is nondet
True if {Path,Action,Options} is registered and Path is a plain (i.e. not prefix) location.
 1110plain_path(Path, Action, Options) :-
 1111    handler(Spec, Action, false, Options),
 1112    catch(http_absolute_location(Spec, Path, []), E,
 1113          (print_message(error, E), fail)).
 add_path_tree(+Path, +Action, +Options, +Tree0, -Tree) is det
Add a path to a tree. If a handler for the same path is already defined, the one with the highest priority or the latest takes precedence.
 1122add_path_tree(Path, Action, Options0, DefOptions, [],
 1123              [node(Path, Action, Options, [])]) :-
 1124    !,
 1125    merge_options(Options0, DefOptions, Options).
 1126add_path_tree(Path, Action, Options, _,
 1127              [node(prefix(Prefix), PA, DefOptions, Children0)|RestTree],
 1128              [node(prefix(Prefix), PA, DefOptions, Children)|RestTree]) :-
 1129    sub_atom(Path, 0, _, _, Prefix),
 1130    !,
 1131    delete(DefOptions, id(_), InheritOpts),
 1132    add_path_tree(Path, Action, Options, InheritOpts, Children0, Children).
 1133add_path_tree(Path, Action, Options1, DefOptions, [H0|T], [H|T]) :-
 1134    H0 = node(Path, _, Options2, _),
 1135    option(priority(P1), Options1, 0),
 1136    option(priority(P2), Options2, 0),
 1137    P1 >= P2,
 1138    !,
 1139    merge_options(Options1, DefOptions, Options),
 1140    H = node(Path, Action, Options, []).
 1141add_path_tree(Path, Action, Options, DefOptions, [H|T0], [H|T]) :-
 1142    add_path_tree(Path, Action, Options, DefOptions, T0, T).
 1143
 1144
 1145                 /*******************************
 1146                 *            MESSAGES          *
 1147                 *******************************/
 1148
 1149:- multifile
 1150    prolog:message/3. 1151
 1152prolog:message(http_dispatch(ambiguous_id(ID, _List, Selected))) -->
 1153    [ 'HTTP dispatch: ambiguous handler ID ~q (selected ~q)'-[ID, Selected]
 1154    ].
 1155
 1156
 1157                 /*******************************
 1158                 *            XREF              *
 1159                 *******************************/
 1160
 1161:- multifile
 1162    prolog:meta_goal/2. 1163:- dynamic
 1164    prolog:meta_goal/2. 1165
 1166prolog:meta_goal(http_handler(_, G, _), [G+1]).
 1167prolog:meta_goal(http_current_handler(_, G), [G+1]).
 1168
 1169
 1170                 /*******************************
 1171                 *             EDIT             *
 1172                 *******************************/
 1173
 1174% Allow edit(Location) to edit the implementation for an HTTP location.
 1175
 1176:- multifile
 1177    prolog_edit:locate/3. 1178
 1179prolog_edit:locate(Path, Spec, Location) :-
 1180    atom(Path),
 1181    sub_atom(Path, 0, _, _, /),
 1182    Pred = _M:_H,
 1183    catch(http_current_handler(Path, Pred), _, fail),
 1184    closure_name_arity(Pred, 1, PI),
 1185    prolog_edit:locate(PI, Spec, Location).
 1186
 1187closure_name_arity(M:Term, Extra, M:Name/Arity) :-
 1188    !,
 1189    callable(Term),
 1190    functor(Term, Name, Arity0),
 1191    Arity is Arity0 + Extra.
 1192closure_name_arity(Term, Extra, Name/Arity) :-
 1193    callable(Term),
 1194    functor(Term, Name, Arity0),
 1195    Arity is Arity0 + Extra.
 1196
 1197
 1198                 /*******************************
 1199                 *        CACHE CLEANUP         *
 1200                 *******************************/
 1201
 1202:- listen(settings(changed(http:prefix, _, _)),
 1203          next_generation). 1204
 1205:- multifile
 1206    user:message_hook/3. 1207:- dynamic
 1208    user:message_hook/3. 1209
 1210user:message_hook(make(done(Reload)), _Level, _Lines) :-
 1211    Reload \== [],
 1212    next_generation,
 1213    fail