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-2020, 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_request_expansion/2,   % :Goal, +Rank
   42            http_reply_file/3,          % +File, +Options, +Request
   43            http_redirect/3,            % +How, +Path, +Request
   44            http_404/2,                 % +Options, +Request
   45            http_switch_protocol/2,     % :Goal, +Options
   46            http_current_handler/2,     % ?Path, ?Pred
   47            http_current_handler/3,     % ?Path, ?Pred, -Options
   48            http_location_by_id/2,      % +ID, -Location
   49            http_link_to_id/3,          % +ID, +Parameters, -HREF
   50            http_reload_with_parameters/3, % +Request, +Parameters, -HREF
   51            http_safe_file/2            % +Spec, +Options
   52          ]).   53:- use_module(library(lists),
   54              [ select/3, append/3, append/2, same_length/2, member/2,
   55                last/2, delete/3
   56              ]).   57:- autoload(library(apply),
   58	    [partition/4,maplist/3,maplist/2,include/3,exclude/3]).   59:- autoload(library(broadcast),[listen/2]).   60:- autoload(library(error),
   61	    [ must_be/2,
   62	      domain_error/2,
   63	      type_error/2,
   64	      instantiation_error/1,
   65	      existence_error/2,
   66	      permission_error/3
   67	    ]).   68:- autoload(library(filesex),[directory_file_path/3]).   69:- autoload(library(option),[option/3,option/2,merge_options/3]).   70:- autoload(library(pairs),[pairs_values/2]).   71:- autoload(library(time),[call_with_time_limit/2]).   72:- autoload(library(uri),
   73	    [ uri_encoded/3,
   74	      uri_data/3,
   75	      uri_components/2,
   76	      uri_query_components/2
   77	    ]).   78:- autoload(library(http/http_header),[http_timestamp/2]).   79:- autoload(library(http/http_path),[http_absolute_location/3]).   80:- autoload(library(http/mimetype),
   81	    [file_content_type/2,file_content_type/3]).   82:- autoload(library(http/thread_httpd),[http_spawn/2]).   83:- use_module(library(settings),[setting/4,setting/2]).   84
   85:- predicate_options(http_404/2, 1, [index(any)]).   86:- predicate_options(http_reply_file/3, 2,
   87                     [ cache(boolean),
   88                       mime_type(any),
   89                       static_gzip(boolean),
   90                       pass_to(http_safe_file/2, 2),
   91                       headers(list)
   92                     ]).   93:- predicate_options(http_safe_file/2, 2, [unsafe(boolean)]).   94:- predicate_options(http_switch_protocol/2, 2, []).

Dispatch requests in the HTTP server

Most code doesn't need to use this directly; instead use library(http/http_server), which combines this library with the typical HTTP libraries that most servers need.

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) :-
        ...

*/

  121:- setting(http:time_limit, nonneg, 300,
  122           '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 either an absolute path such as '/home.html' or a term Alias(Relative). Where Alias is associated with a concrete path using http:location/3 and resolved using http_absolute_location/3. Relative can be a single atom or a term `Segment1/Segment2/...`, where each element is either an atom or a variable. If a segment is a variable it matches any segment and the binding may be passed to the closure. If the last segment is a variable it may match multiple segments. This allows registering REST paths, for example:
:- http_handler(root(user/User), user(Method, User),
                [ method(Method),
                  methods([get,port,put])
                ]).

user(get, User, Request) :-
    ...
user(post, User, Request) :-
    ...

If an HTTP request arrives at the server that matches Path, Closure is called as below, where Request is the parsed HTTP request.

call(Closure, 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(+Atom)
Identifier of the handler. The default identifier is the predicate name. Used by http_location_by_id/2 and http_link_to_id/3.
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.

Errors
- existence_error(http_location, Location)
- permission_error(http_method, Method, Location)
See also
- http_reply_file/3 and http_redirect/3 are generic handlers to serve files and achieve redirects.
  229:- dynamic handler/4.                   % Path, Action, IsPrefix, Options
  230:- multifile handler/4.  231:- dynamic generation/1.  232
  233:- meta_predicate
  234    http_handler(+, :, +),
  235    http_current_handler(?, :),
  236    http_current_handler(?, :, ?),
  237    http_request_expansion(3, +),
  238    http_switch_protocol(2, +).  239
  240http_handler(Path, Pred, Options) :-
  241    compile_handler(Path, Pred, Options, Clause),
  242    next_generation,
  243    assert(Clause).
  244
  245:- multifile
  246    system:term_expansion/2.  247
  248system:term_expansion((:- http_handler(Path, Pred, Options)), Clause) :-
  249    \+ current_prolog_flag(xref, true),
  250    prolog_load_context(module, M),
  251    compile_handler(Path, M:Pred, Options, Clause),
  252    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.
  267http_delete_handler(id(Id)) :-
  268    !,
  269    clause(handler(_Path, _:Pred, _, Options), true, Ref),
  270    functor(Pred, DefID, _),
  271    option(id(Id0), Options, DefID),
  272    Id == Id0,
  273    erase(Ref),
  274    next_generation.
  275http_delete_handler(path(Path)) :-
  276    !,
  277    retractall(handler(Path, _Pred, _, _Options)),
  278    next_generation.
  279http_delete_handler(Path) :-
  280    http_delete_handler(path(Path)).
 next_generation is det
 current_generation(-G) is det
Increment the generation count.
  288next_generation :-
  289    retractall(id_location_cache(_,_,_,_)),
  290    with_mutex(http_dispatch, next_generation_unlocked).
  291
  292next_generation_unlocked :-
  293    retract(generation(G0)),
  294    !,
  295    G is G0 + 1,
  296    assert(generation(G)).
  297next_generation_unlocked :-
  298    assert(generation(1)).
  299
  300current_generation(G) :-
  301    with_mutex(http_dispatch, generation(G)),
  302    !.
  303current_generation(0).
 compile_handler(+Path, :Pred, +Options, -Clause) is det
Compile a handler specification.
  310compile_handler(Path, Pred, Options0,
  311                http_dispatch:handler(Path1, Pred, IsPrefix, Options)) :-
  312    check_path(Path, Path1, PathOptions),
  313    check_id(Options0),
  314    (   memberchk(segment_pattern(_), PathOptions)
  315    ->  IsPrefix = true,
  316        Options1 = Options0
  317    ;   select(prefix, Options0, Options1)
  318    ->  IsPrefix = true
  319    ;   IsPrefix = false,
  320        Options1 = Options0
  321    ),
  322    partition(ground, Options1, Options2, QueryOptions),
  323    Pred = M:_,
  324    maplist(qualify_option(M), Options2, Options3),
  325    combine_methods(Options3, Options4),
  326    (   QueryOptions == []
  327    ->  append(PathOptions, Options4, Options)
  328    ;   append(PathOptions, ['$extract'(QueryOptions)|Options4], Options)
  329    ).
  330
  331qualify_option(M, condition(Pred), condition(M:Pred)) :-
  332    Pred \= _:_, !.
  333qualify_option(_, Option, Option).
 combine_methods(+OptionsIn, -Options) is det
Combine method(M) and methods(MList) options into a single methods(MList) option.
  340combine_methods(Options0, Options) :-
  341    collect_methods(Options0, Options1, Methods),
  342    (   Methods == []
  343    ->  Options = Options0
  344    ;   append(Methods, Flat),
  345        sort(Flat, Unique),
  346        (   memberchk('*', Unique)
  347        ->  Final = '*'
  348        ;   Final = Unique
  349        ),
  350        Options = [methods(Final)|Options1]
  351    ).
  352
  353collect_methods([], [], []).
  354collect_methods([method(M)|T0], T, [[M]|TM]) :-
  355    !,
  356    (   M == '*'
  357    ->  true
  358    ;   must_be_method(M)
  359    ),
  360    collect_methods(T0, T, TM).
  361collect_methods([methods(M)|T0], T, [M|TM]) :-
  362    !,
  363    must_be(list, M),
  364    maplist(must_be_method, M),
  365    collect_methods(T0, T, TM).
  366collect_methods([H|T0], [H|T], TM) :-
  367    !,
  368    collect_methods(T0, T, TM).
  369
  370must_be_method(M) :-
  371    must_be(atom, M),
  372    (   method(M)
  373    ->  true
  374    ;   domain_error(http_method, M)
  375    ).
  376
  377method(get).
  378method(put).
  379method(head).
  380method(post).
  381method(delete).
  382method(patch).
  383method(options).
  384method(trace).
 check_path(+PathSpecIn, -PathSpecOut, -Options) is det
Validate the given path specification. We want one of

Similar to absolute_file_name/3, Relative can be a term Component/Component/.... Relative may be a / separated list of path segments, some of which may be variables. A variable patches any segment and its binding can be passed to the handler. If such a pattern is found Options is unified with [segment_pattern(SegmentList)].

Errors
- domain_error, type_error
See also
- http_absolute_location/3
  404check_path(Path, Path, []) :-
  405    atom(Path),
  406    !,
  407    (   sub_atom(Path, 0, _, _, /)
  408    ->  true
  409    ;   domain_error(absolute_http_location, Path)
  410    ).
  411check_path(Alias, AliasOut, Options) :-
  412    compound(Alias),
  413    Alias =.. [Name, Relative],
  414    !,
  415    local_path(Relative, Local, Options),
  416    (   sub_atom(Local, 0, _, _, /)
  417    ->  domain_error(relative_location, Relative)
  418    ;   AliasOut =.. [Name, Local]
  419    ).
  420check_path(PathSpec, _, _) :-
  421    type_error(path_or_alias, PathSpec).
  422
  423local_path(Atom, Atom, []) :-
  424    atom(Atom),
  425    !.
  426local_path(Path, Atom, Options) :-
  427    phrase(path_to_list(Path), Components),
  428    !,
  429    (   maplist(atom, Components)
  430    ->  atomic_list_concat(Components, '/', Atom),
  431        Options = []
  432    ;   append(Pre, [Var|Rest], Components),
  433        var(Var)
  434    ->  append(Pre, [''], PreSep),
  435        atomic_list_concat(PreSep, '/', Atom),
  436        Options = [segment_pattern([Var|Rest])]
  437    ).
  438local_path(Path, _, _) :-
  439    ground(Path),
  440    !,
  441    type_error(relative_location, Path).
  442local_path(Path, _, _) :-
  443    instantiation_error(Path).
  444
  445path_to_list(Var) -->
  446    { var(Var) },
  447    !,
  448    [Var].
  449path_to_list(A/B) -->
  450    !,
  451    path_to_list(A),
  452    path_to_list(B).
  453path_to_list(Atom) -->
  454    { atom(Atom) },
  455    !,
  456    [Atom].
  457path_to_list(Value) -->
  458    { must_be(atom, Value) }.
  459
  460check_id(Options) :-
  461    memberchk(id(Id), Options),
  462    !,
  463    must_be(atom, Id).
  464check_id(_).
 http_dispatch(Request) is det
Dispatch a Request using http_handler/3 registrations. It performs the following steps:
  1. Find a matching handler based on the path member of Request. If multiple handlers match due to the prefix option or variables in path segments (see http_handler/3), the longest specification is used. If multiple specifications of equal length match the one with the highest priority is used.
  2. Check that the handler matches the method member of the Request or throw permission_error(http_method, Method, Location)
  3. Expand the request using expansion hooks registered by http_request_expansion/3. This may add fields to the request, such the authenticated user, parsed parameters, etc. The hooks may also throw exceptions, notably using http_redirect/3 or by throwing http_reply(Term, ExtraHeader, Context) exceptions.
  4. Extract possible fields from the Request using e.g. method(Method) as one of the options.
  5. Call the registered closure, optionally spawning the request to a new thread or enforcing a time limit.
  490http_dispatch(Request) :-
  491    memberchk(path(Path), Request),
  492    find_handler(Path, Closure, Options),
  493    supports_method(Request, Options),
  494    expand_request(Request, Request1, Options),
  495    extract_from_request(Request1, Options),
  496    action(Closure, Request1, Options).
  497
  498extract_from_request(Request, Options) :-
  499    memberchk('$extract'(Fields), Options),
  500    !,
  501    extract_fields(Fields, Request).
  502extract_from_request(_, _).
  503
  504extract_fields([], _).
  505extract_fields([H|T], Request) :-
  506    memberchk(H, Request),
  507    extract_fields(T, Request).
 http_request_expansion(:Goal, +Rank:number)
Register Goal for expanding the HTTP request handler. Goal is called as below. If Goal fail the request is passed to the next expansion unmodified.
call(Goal, Request0, Request, Options)

If multiple goals are registered they expand the request in a pipeline starting with the expansion hook with the lowest rank.

Besides rewriting the request, for example by validating the user identity based on HTTP authentication or cookies and adding this to the request, the hook may raise HTTP exceptions to indicate a bad request, permission error, etc. See http_status_reply/4.

Initially, auth_expansion/3 is registered with rank 100 to deal with the older http:authenticate/3 hook.

  529http_request_expansion(Goal, Rank) :-
  530    throw(error(context_error(nodirective, http_request_expansion(Goal, Rank)), _)).
  531
  532:- multifile
  533    request_expansion/2.  534
  535system:term_expansion((:- http_request_expansion(Goal, Rank)),
  536                      http_dispatch:request_expansion(M:Callable, Rank)) :-
  537    must_be(number, Rank),
  538    prolog_load_context(module, M0),
  539    strip_module(M0:Goal, M, Callable),
  540    must_be(callable, Callable).
  541
  542request_expanders(Closures) :-
  543    findall(Rank-Closure, request_expansion(Closure, Rank), Pairs),
  544    keysort(Pairs, Sorted),
  545    pairs_values(Sorted, Closures).
 expand_request(+Request0, -Request, +Options)
Expand an HTTP request. Options is a list of combined options provided with the handler registration (see http_handler/3).
  552expand_request(Request0, Request, Options) :-
  553    request_expanders(Closures),
  554    expand_request(Closures, Request0, Request, Options).
  555
  556expand_request([], Request, Request, _).
  557expand_request([H|T], Request0, Request, Options) :-
  558    expand_request1(H, Request0, Request1, Options),
  559    expand_request(T, Request1, Request, Options).
  560
  561expand_request1(Closure, Request0, Request, Options) :-
  562    call(Closure, Request0, Request, Options),
  563    !.
  564expand_request1(_, Request, Request, _).
 http_current_handler(+Location, :Closure) is semidet
http_current_handler(-Location, :Closure) is nondet
True if Location is handled by Closure.
  572http_current_handler(Path, Closure) :-
  573    atom(Path),
  574    !,
  575    path_tree(Tree),
  576    find_handler(Tree, Path, Closure, _).
  577http_current_handler(Path, M:C) :-
  578    handler(Spec, M:C, _, _),
  579    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.
  586http_current_handler(Path, Closure, Options) :-
  587    atom(Path),
  588    !,
  589    path_tree(Tree),
  590    find_handler(Tree, Path, Closure, Options).
  591http_current_handler(Path, M:C, Options) :-
  592    handler(Spec, M:C, _, _),
  593    http_absolute_location(Spec, Path, []),
  594    path_tree(Tree),
  595    find_handler(Tree, Path, _, Options).
 http_location_by_id(+ID, -Location) is det
True when Location represents the HTTP path to which the handler with identifier ID is bound. Handler identifiers are deduced from the http_handler/3 declaration as follows:
Explicit id
If a term id(ID) appears in the option list of the handler, ID it is used and takes preference over using the predicate.
Using the handler predicate
ID matches a handler if the predicate name matches ID. The ID may have a module qualification, e.g., Module:Pred

If the handler is declared with a pattern, e.g., root(user/User), the location to access a particular user may be accessed using e.g., user('Bob'). The number of arguments to the compound term must match the number of variables in the path pattern.

A plain atom ID can be used to find a handler with a pattern. The returned location is the path up to the first variable, e.g., /user/ in the example above.

User code is adviced to use http_link_to_id/3 which can also add query parameters to the URL. This predicate is a helper for http_link_to_id/3.

Errors
- existence_error(http_handler_id, Id).
See also
- http_link_to_id/3 and the library(http/html_write) construct location_by_id(ID) or its abbreviation #(ID)
  628:- dynamic
  629    id_location_cache/4.                        % Id, Argv, Location, Segments
  630
  631http_location_by_id(ID, _) :-
  632    \+ ground(ID),
  633    !,
  634    instantiation_error(ID).
  635http_location_by_id(M:ID, Location) :-
  636    compound(ID),
  637    !,
  638    compound_name_arguments(ID, Name, Argv),
  639    http_location_by_id(M:Name, Argv, Location).
  640http_location_by_id(M:ID, Location) :-
  641    atom(ID),
  642    must_be(atom, M),
  643    !,
  644    http_location_by_id(M:ID, -, Location).
  645http_location_by_id(ID, Location) :-
  646    compound(ID),
  647    !,
  648    compound_name_arguments(ID, Name, Argv),
  649    http_location_by_id(Name, Argv, Location).
  650http_location_by_id(ID, Location) :-
  651    atom(ID),
  652    !,
  653    http_location_by_id(ID, -, Location).
  654http_location_by_id(ID, _) :-
  655    type_error(location_id, ID).
  656
  657http_location_by_id(ID, Argv, Location) :-
  658    id_location_cache(ID, Argv, Segments, Path),
  659    !,
  660    add_segments(Path, Segments, Location).
  661http_location_by_id(ID, Argv, Location) :-
  662    findall(t(Priority, ArgvP, Segments, Prefix),
  663            location_by_id(ID, Argv, ArgvP, Segments, Prefix, Priority),
  664            List),
  665    sort(1, >=, List, Sorted),
  666    (   Sorted = [t(_,ArgvP,Segments,Path)]
  667    ->  assert(id_location_cache(ID,ArgvP,Segments,Path)),
  668        Argv = ArgvP
  669    ;   List == []
  670    ->  existence_error(http_handler_id, ID)
  671    ;   List = [t(P0,ArgvP,Segments,Path),t(P1,_,_,_)|_]
  672    ->  (   P0 =:= P1
  673        ->  print_message(warning,
  674                          http_dispatch(ambiguous_id(ID, Sorted, Path)))
  675        ;   true
  676        ),
  677        assert(id_location_cache(ID,Argv,Segments,Path)),
  678        Argv = ArgvP
  679    ),
  680    add_segments(Path, Segments, Location).
  681
  682add_segments(Path0, [], Path) :-
  683    !,
  684    Path = Path0.
  685add_segments(Path0, Segments, Path) :-
  686    maplist(uri_encoded(path), Segments, Encoded),
  687    atomic_list_concat(Encoded, '/', Rest),
  688    atom_concat(Path0, Rest, Path).
  689
  690location_by_id(ID, -, _, [], Location, Priority) :-
  691    !,
  692    location_by_id_raw(ID, L0, _Segments, Priority),
  693    to_path(L0, Location).
  694location_by_id(ID, Argv, ArgvP, Segments, Location, Priority) :-
  695    location_by_id_raw(ID, L0, Segments, Priority),
  696    include(var, Segments, ArgvP),
  697    same_length(Argv, ArgvP),
  698    to_path(L0, Location).
  699
  700to_path(prefix(Path0), Path) :-         % old style prefix notation
  701    !,
  702    add_prefix(Path0, Path).
  703to_path(Path0, Path) :-
  704    atomic(Path0),                      % old style notation
  705    !,
  706    add_prefix(Path0, Path).
  707to_path(Spec, Path) :-                  % new style notation
  708    http_absolute_location(Spec, Path, []).
  709
  710add_prefix(P0, P) :-
  711    (   catch(setting(http:prefix, Prefix), _, fail),
  712        Prefix \== ''
  713    ->  atom_concat(Prefix, P0, P)
  714    ;   P = P0
  715    ).
  716
  717location_by_id_raw(ID, Location, Pattern, Priority) :-
  718    handler(Location, _, _, Options),
  719    option(id(ID), Options),
  720    option(priority(P0), Options, 0),
  721    option(segment_pattern(Pattern), Options, []),
  722    Priority is P0+1000.            % id(ID) takes preference over predicate
  723location_by_id_raw(ID, Location, Pattern, Priority) :-
  724    handler(Location, M:C, _, Options),
  725    option(priority(Priority), Options, 0),
  726    functor(C, PN, _),
  727    (   ID = M:PN
  728    ->  true
  729    ;   ID = PN
  730    ),
  731    option(segment_pattern(Pattern), Options, []).
 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:
HandleID- is either an atom, possibly module qualified predicate or a compound term if the hander is defined using a pattern. See http_handler/3 and http_location_by_id/2.
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.

    New code should use a path pattern in the handler declaration and a term `HandleID(Arg, ...)`

  • 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.
  781http_link_to_id(HandleID, path_postfix(File), HREF) :-
  782    !,
  783    http_location_by_id(HandleID, HandlerLocation),
  784    uri_encoded(path, File, EncFile),
  785    directory_file_path(HandlerLocation, EncFile, Location),
  786    uri_data(path, Components, Location),
  787    uri_components(HREF, Components).
  788http_link_to_id(HandleID, Parameters, HREF) :-
  789    must_be(list, Parameters),
  790    http_location_by_id(HandleID, Location),
  791    (   Parameters == []
  792    ->  HREF = Location
  793    ;   uri_data(path, Components, Location),
  794        uri_query_components(String, Parameters),
  795        uri_data(search, Components, String),
  796        uri_components(HREF, Components)
  797    ).
 http_reload_with_parameters(+Request, +Parameters, -HREF) is det
Create a request on the current handler with replaced search parameters.
  804http_reload_with_parameters(Request, NewParams, HREF) :-
  805    memberchk(path(Path), Request),
  806    (   memberchk(search(Params), Request)
  807    ->  true
  808    ;   Params = []
  809    ),
  810    merge_options(NewParams, Params, AllParams),
  811    uri_query_components(Search, AllParams),
  812    uri_data(path, Data, Path),
  813    uri_data(search, Data, Search),
  814    uri_components(HREF, Data).
  815
  816
  817%       hook into html_write:attribute_value//1.
  818
  819:- multifile
  820    html_write:expand_attribute_value//1.  821
  822html_write:expand_attribute_value(location_by_id(ID)) -->
  823    { http_location_by_id(ID, Location) },
  824    html_write:html_quoted_attribute(Location).
  825html_write:expand_attribute_value(#(ID)) -->
  826    { http_location_by_id(ID, Location) },
  827    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)
deprecated
- This hook predates the extensible request expansion provided by http_request_expansion/2. New hooks should use http_request_expansion/2 instead of http:authenticate/3.
  842:- multifile
  843    http:authenticate/3.  844
  845authentication([], _, []).
  846authentication([authentication(Type)|Options], Request, Fields) :-
  847    !,
  848    (   http:authenticate(Type, Request, XFields)
  849    ->  append(XFields, More, Fields),
  850        authentication(Options, Request, More)
  851    ;   memberchk(path(Path), Request),
  852        permission_error(access, http_location, Path)
  853    ).
  854authentication([_|Options], Request, Fields) :-
  855    authentication(Options, Request, Fields).
  856
  857:- http_request_expansion(auth_expansion, 100).
 auth_expansion(+Request0, -Request, +Options) is semidet
Connect the HTTP authentication infrastructure by means of http_request_expansion/2.
See also
- http:authenticate/3, http_digest.pl and http_authenticate.pl
  866auth_expansion(Request0, Request, Options) :-
  867    authentication(Options, Request0, Extra),
  868    append(Extra, Request0, Request).
 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?
  886find_handler(Path, Action, Options) :-
  887    path_tree(Tree),
  888    (   find_handler(Tree, Path, Action, Options),
  889        eval_condition(Options)
  890    ->  true
  891    ;   \+ sub_atom(Path, _, _, 0, /),
  892        atom_concat(Path, /, Dir),
  893        find_handler(Tree, Dir, Action, Options)
  894    ->  throw(http_reply(moved(Dir)))
  895    ;   throw(error(existence_error(http_location, Path), _))
  896    ).
  897
  898
  899find_handler([node(prefix(Prefix), PAction, POptions, Children)|_],
  900             Path, Action, Options) :-
  901    sub_atom(Path, 0, _, After, Prefix),
  902    !,
  903    (   option(hide_children(false), POptions, false),
  904        find_handler(Children, Path, Action, Options)
  905    ->  true
  906    ;   member(segment_pattern(Pattern, PatAction, PatOptions), POptions),
  907        copy_term(t(Pattern,PatAction,PatOptions), t(Pattern2,Action,Options)),
  908        match_segments(After, Path, Pattern2)
  909    ->  true
  910    ;   PAction \== nop
  911    ->  Action = PAction,
  912        path_info(After, Path, POptions, Options)
  913    ).
  914find_handler([node(Path, Action, Options, _)|_], Path, Action, Options) :- !.
  915find_handler([_|Tree], Path, Action, Options) :-
  916    find_handler(Tree, Path, Action, Options).
  917
  918path_info(0, _, Options,
  919          [prefix(true)|Options]) :- !.
  920path_info(After, Path, Options,
  921          [path_info(PathInfo),prefix(true)|Options]) :-
  922    sub_atom(Path, _, After, 0, PathInfo).
  923
  924match_segments(After, Path, [Var]) :-
  925    !,
  926    sub_atom(Path, _, After, 0, Var).
  927match_segments(After, Path, Pattern) :-
  928    sub_atom(Path, _, After, 0, PathInfo),
  929    split_string(PathInfo, "/", "", Segments),
  930    match_segment_pattern(Pattern, Segments).
  931
  932match_segment_pattern([], []).
  933match_segment_pattern([Var], Segments) :-
  934    !,
  935    atomic_list_concat(Segments, '/', Var).
  936match_segment_pattern([H0|T0], [H|T]) :-
  937    atom_string(H0, H),
  938    match_segment_pattern(T0, T).
  939
  940
  941eval_condition(Options) :-
  942    (   memberchk(condition(Cond), Options)
  943    ->  catch(Cond, E, (print_message(warning, E), fail))
  944    ;   true
  945    ).
 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).
  956supports_method(Request, Options) :-
  957    (   option(methods(Methods), Options)
  958    ->  (   Methods == '*'
  959        ->  true
  960        ;   memberchk(method(Method), Request),
  961            memberchk(Method, Methods)
  962        )
  963    ;   true
  964    ),
  965    !.
  966supports_method(Request, _Options) :-
  967    memberchk(path(Location), Request),
  968    memberchk(method(Method), Request),
  969    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)
  979action(Action, Request, Options) :-
  980    memberchk(chunked, Options),
  981    !,
  982    format('Transfer-encoding: chunked~n'),
  983    spawn_action(Action, Request, Options).
  984action(Action, Request, Options) :-
  985    spawn_action(Action, Request, Options).
  986
  987spawn_action(Action, Request, Options) :-
  988    option(spawn(Spawn), Options),
  989    !,
  990    spawn_options(Spawn, SpawnOption),
  991    http_spawn(time_limit_action(Action, Request, Options), SpawnOption).
  992spawn_action(Action, Request, Options) :-
  993    time_limit_action(Action, Request, Options).
  994
  995spawn_options([], []) :- !.
  996spawn_options(Pool, Options) :-
  997    atom(Pool),
  998    !,
  999    Options = [pool(Pool)].
 1000spawn_options(List, List).
 1001
 1002time_limit_action(Action, Request, Options) :-
 1003    (   option(time_limit(TimeLimit), Options),
 1004        TimeLimit \== default
 1005    ->  true
 1006    ;   setting(http:time_limit, TimeLimit)
 1007    ),
 1008    number(TimeLimit),
 1009    TimeLimit > 0,
 1010    !,
 1011    call_with_time_limit(TimeLimit, call_action(Action, Request, Options)).
 1012time_limit_action(Action, Request, Options) :-
 1013    call_action(Action, Request, Options).
 call_action(+Action, +Request, +Options)
To be done
- reply_file is normal call?
 1020call_action(reply_file(File, FileOptions), Request, _Options) :-
 1021    !,
 1022    http_reply_file(File, FileOptions, Request).
 1023call_action(Pred, Request, Options) :-
 1024    memberchk(path_info(PathInfo), Options),
 1025    !,
 1026    call_action(Pred, [path_info(PathInfo)|Request]).
 1027call_action(Pred, Request, _Options) :-
 1028    call_action(Pred, Request).
 1029
 1030call_action(Pred, Request) :-
 1031    (   call(Pred, Request)
 1032    ->  true
 1033    ;   extend(Pred, [Request], Goal),
 1034        throw(error(goal_failed(Goal), _))
 1035    ).
 1036
 1037extend(Var, _, Var) :-
 1038    var(Var),
 1039    !.
 1040extend(M:G0, Extra, M:G) :-
 1041    extend(G0, Extra, G).
 1042extend(G0, Extra, G) :-
 1043    G0 =.. List,
 1044    append(List, Extra, List2),
 1045    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))
 1081http_reply_file(File, Options, Request) :-
 1082    http_safe_file(File, Options),
 1083    absolute_file_name(File, Path,
 1084                       [ access(read)
 1085                       ]),
 1086    (   option(cache(true), Options, true)
 1087    ->  (   memberchk(if_modified_since(Since), Request),
 1088            time_file(Path, Time),
 1089            catch(http_timestamp(Time, Since), _, fail)
 1090        ->  throw(http_reply(not_modified))
 1091        ;   true
 1092        ),
 1093        (   memberchk(range(Range), Request)
 1094        ->  Reply = file(Type, Path, Range)
 1095        ;   option(static_gzip(true), Options),
 1096            accepts_encoding(Request, gzip),
 1097            file_name_extension(Path, gz, PathGZ),
 1098            access_file(PathGZ, read),
 1099            time_file(PathGZ, TimeGZ),
 1100            time_file(Path, Time),
 1101            TimeGZ >= Time
 1102        ->  Reply = gzip_file(Type, PathGZ)
 1103        ;   Reply = file(Type, Path)
 1104        )
 1105    ;   Reply = tmp_file(Type, Path)
 1106    ),
 1107    (   option(mime_type(MediaType), Options)
 1108    ->  file_content_type(Path, MediaType, Type)
 1109    ;   file_content_type(Path, Type)
 1110    ->  true
 1111    ;   Type = text/plain           % fallback type
 1112    ),
 1113    option(headers(Headers), Options, []),
 1114    throw(http_reply(Reply, Headers)).
 1115
 1116accepts_encoding(Request, Enc) :-
 1117    memberchk(accept_encoding(Accept), Request),
 1118    split_string(Accept, ",", " ", Parts),
 1119    member(Part, Parts),
 1120    split_string(Part, ";", " ", [EncS|_]),
 1121    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)
 1134http_safe_file(File, _) :-
 1135    var(File),
 1136    !,
 1137    instantiation_error(File).
 1138http_safe_file(_, Options) :-
 1139    option(unsafe(true), Options, false),
 1140    !.
 1141http_safe_file(File, _) :-
 1142    http_safe_file(File).
 1143
 1144http_safe_file(File) :-
 1145    compound(File),
 1146    functor(File, _, 1),
 1147    !,
 1148    arg(1, File, Name),
 1149    safe_name(Name, File).
 1150http_safe_file(Name) :-
 1151    (   is_absolute_file_name(Name)
 1152    ->  permission_error(read, file, Name)
 1153    ;   true
 1154    ),
 1155    safe_name(Name, Name).
 1156
 1157safe_name(Name, _) :-
 1158    must_be(atom, Name),
 1159    prolog_to_os_filename(FileName, Name),
 1160    \+ unsafe_name(FileName),
 1161    !.
 1162safe_name(_, Spec) :-
 1163    permission_error(read, file, Spec).
 1164
 1165unsafe_name(Name) :- Name == '..'.
 1166unsafe_name(Name) :- sub_atom(Name, 0, _, _, '../').
 1167unsafe_name(Name) :- sub_atom(Name, _, _, _, '/../').
 1168unsafe_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) or its abbreviations #(Id) or #(Id)+Parameters. If To is not absolute, it is resolved relative to the current location.
 1189http_redirect(How, To, Request) :-
 1190    must_be(oneof([moved, moved_temporary, see_other]), How),
 1191    must_be(ground, To),
 1192    (   id_location(To, URL)
 1193    ->  true
 1194    ;   memberchk(path(Base), Request),
 1195        http_absolute_location(To, URL, [relative_to(Base)])
 1196    ),
 1197    Term =.. [How,URL],
 1198    throw(http_reply(Term)).
 1199
 1200id_location(location_by_id(Id), URL) :-
 1201    http_location_by_id(Id, URL).
 1202id_location(#(Id), URL) :-
 1203    http_location_by_id(Id, URL).
 1204id_location(#(Id)+Parameters, URL) :-
 1205    http_link_to_id(Id, Parameters, URL).
 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))
 1220http_404(Options, Request) :-
 1221    option(index(Index), Options),
 1222    \+ ( option(path_info(PathInfo), Request),
 1223         PathInfo \== ''
 1224       ),
 1225    !,
 1226    http_redirect(moved, Index, Request).
 1227http_404(_Options, Request) :-
 1228    option(path(Path), Request),
 1229    !,
 1230    throw(http_reply(not_found(Path))).
 1231http_404(_Options, Request) :-
 1232    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).
 1266%       @throws http_reply(switch_protocol(Goal, Options))
 1267
 1268http_switch_protocol(Goal, Options) :-
 1269    throw(http_reply(switching_protocols(Goal, Options))).
 1270
 1271
 1272                 /*******************************
 1273                 *        PATH COMPILATION      *
 1274                 *******************************/
 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.

 1290path_tree(Tree) :-
 1291    current_generation(G),
 1292    nb_current(http_dispatch_tree, G-Tree),
 1293    !. % Avoid existence error
 1294path_tree(Tree) :-
 1295    path_tree_nocache(Tree),
 1296    current_generation(G),
 1297    nb_setval(http_dispatch_tree, G-Tree).
 1298
 1299path_tree_nocache(Tree) :-
 1300    findall(Prefix, prefix_handler(Prefix, _, _, _), Prefixes0),
 1301    sort(Prefixes0, Prefixes),
 1302    prefix_tree(Prefixes, [], PTree),
 1303    prefix_options(PTree, [], OPTree),
 1304    add_paths_tree(OPTree, Tree).
 1305
 1306prefix_handler(Prefix, Action, Options, Priority-PLen) :-
 1307    handler(Spec, Action, true, Options),
 1308    (   memberchk(priority(Priority), Options)
 1309    ->  true
 1310    ;   Priority = 0
 1311    ),
 1312    (   memberchk(segment_pattern(Pattern), Options)
 1313    ->  length(Pattern, PLen)
 1314    ;   PLen = 0
 1315    ),
 1316    Error = error(existence_error(http_alias,_),_),
 1317    catch(http_absolute_location(Spec, Prefix, []), Error,
 1318          (   print_message(warning, Error),
 1319              fail
 1320          )).
 prefix_tree(PrefixList, +Tree0, -Tree)
Arguments:
Tree- list(Prefix-list(Children))
 1326prefix_tree([], Tree, Tree).
 1327prefix_tree([H|T], Tree0, Tree) :-
 1328    insert_prefix(H, Tree0, Tree1),
 1329    prefix_tree(T, Tree1, Tree).
 1330
 1331insert_prefix(Prefix, Tree0, Tree) :-
 1332    select(P-T, Tree0, Tree1),
 1333    sub_atom(Prefix, 0, _, _, P),
 1334    !,
 1335    insert_prefix(Prefix, T, T1),
 1336    Tree = [P-T1|Tree1].
 1337insert_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?
 1346prefix_options([], _, []).
 1347prefix_options([Prefix-C|T0], DefOptions,
 1348               [node(prefix(Prefix), Action, PrefixOptions, Children)|T]) :-
 1349    findall(h(A,O,P), prefix_handler(Prefix,A,O,P), Handlers),
 1350    sort(3, >=, Handlers, Handlers1),
 1351    Handlers1 = [h(_,_,P0)|_],
 1352    same_priority_handlers(Handlers1, P0, Same),
 1353    option_patterns(Same, SegmentPatterns, Action),
 1354    last(Same, h(_, Options0, _-_)),
 1355    merge_options(Options0, DefOptions, Options),
 1356    append(SegmentPatterns, Options, PrefixOptions),
 1357    exclude(no_inherit, Options, InheritOpts),
 1358    prefix_options(C, InheritOpts, Children),
 1359    prefix_options(T0, DefOptions, T).
 1360
 1361no_inherit(id(_)).
 1362no_inherit('$extract'(_)).
 1363
 1364same_priority_handlers([H|T0], P, [H|T]) :-
 1365    H = h(_,_,P0-_),
 1366    P = P0-_,
 1367    !,
 1368    same_priority_handlers(T0, P, T).
 1369same_priority_handlers(_, _, []).
 1370
 1371option_patterns([], [], nop).
 1372option_patterns([h(A,_,_-0)|_], [], A) :-
 1373    !.
 1374option_patterns([h(A,O,_)|T0], [segment_pattern(P,A,O)|T], AF) :-
 1375    memberchk(segment_pattern(P), O),
 1376    option_patterns(T0, T, AF).
 add_paths_tree(+OPTree, -Tree) is det
Add the plain paths.
 1383add_paths_tree(OPTree, Tree) :-
 1384    findall(path(Path, Action, Options),
 1385            plain_path(Path, Action, Options),
 1386            Triples),
 1387    add_paths_tree(Triples, OPTree, Tree).
 1388
 1389add_paths_tree([], Tree, Tree).
 1390add_paths_tree([path(Path, Action, Options)|T], Tree0, Tree) :-
 1391    add_path_tree(Path, Action, Options, [], Tree0, Tree1),
 1392    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.
 1400plain_path(Path, Action, Options) :-
 1401    handler(Spec, Action, false, Options),
 1402    catch(http_absolute_location(Spec, Path, []), E,
 1403          (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.
 1412add_path_tree(Path, Action, Options0, DefOptions, [],
 1413              [node(Path, Action, Options, [])]) :-
 1414    !,
 1415    merge_options(Options0, DefOptions, Options).
 1416add_path_tree(Path, Action, Options, _,
 1417              [node(prefix(Prefix), PA, DefOptions, Children0)|RestTree],
 1418              [node(prefix(Prefix), PA, DefOptions, Children)|RestTree]) :-
 1419    sub_atom(Path, 0, _, _, Prefix),
 1420    !,
 1421    delete(DefOptions, id(_), InheritOpts),
 1422    add_path_tree(Path, Action, Options, InheritOpts, Children0, Children).
 1423add_path_tree(Path, Action, Options1, DefOptions, [H0|T], [H|T]) :-
 1424    H0 = node(Path, _, Options2, _),
 1425    option(priority(P1), Options1, 0),
 1426    option(priority(P2), Options2, 0),
 1427    P1 >= P2,
 1428    !,
 1429    merge_options(Options1, DefOptions, Options),
 1430    H = node(Path, Action, Options, []).
 1431add_path_tree(Path, Action, Options, DefOptions, [H|T0], [H|T]) :-
 1432    add_path_tree(Path, Action, Options, DefOptions, T0, T).
 1433
 1434
 1435                 /*******************************
 1436                 *            MESSAGES          *
 1437                 *******************************/
 1438
 1439:- multifile
 1440    prolog:message/3. 1441
 1442prolog:message(http_dispatch(ambiguous_id(ID, _List, Selected))) -->
 1443    [ 'HTTP dispatch: ambiguous handler ID ~q (selected ~q)'-[ID, Selected]
 1444    ].
 1445
 1446
 1447                 /*******************************
 1448                 *            XREF              *
 1449                 *******************************/
 1450
 1451:- multifile
 1452    prolog:meta_goal/2. 1453:- dynamic
 1454    prolog:meta_goal/2. 1455
 1456prolog:meta_goal(http_handler(_, G, _), [G+1]).
 1457prolog:meta_goal(http_current_handler(_, G), [G+1]).
 1458
 1459
 1460                 /*******************************
 1461                 *             EDIT             *
 1462                 *******************************/
 1463
 1464% Allow edit(Location) to edit the implementation for an HTTP location.
 1465
 1466:- multifile
 1467    prolog_edit:locate/3. 1468
 1469prolog_edit:locate(Path, Spec, Location) :-
 1470    atom(Path),
 1471    sub_atom(Path, 0, _, _, /),
 1472    Pred = _M:_H,
 1473    catch(http_current_handler(Path, Pred), _, fail),
 1474    closure_name_arity(Pred, 1, PI),
 1475    prolog_edit:locate(PI, Spec, Location).
 1476
 1477closure_name_arity(M:Term, Extra, M:Name/Arity) :-
 1478    !,
 1479    callable(Term),
 1480    functor(Term, Name, Arity0),
 1481    Arity is Arity0 + Extra.
 1482closure_name_arity(Term, Extra, Name/Arity) :-
 1483    callable(Term),
 1484    functor(Term, Name, Arity0),
 1485    Arity is Arity0 + Extra.
 1486
 1487
 1488                 /*******************************
 1489                 *        CACHE CLEANUP         *
 1490                 *******************************/
 1491
 1492:- listen(settings(changed(http:prefix, _, _)),
 1493          next_generation). 1494
 1495:- multifile
 1496    user:message_hook/3. 1497:- dynamic
 1498    user:message_hook/3. 1499
 1500user:message_hook(make(done(Reload)), _Level, _Lines) :-
 1501    Reload \== [],
 1502    next_generation,
 1503    fail