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:- if(exists_source(library(time))).   72:- autoload(library(time),[call_with_time_limit/2]).   73:- endif.   74:- autoload(library(uri),
   75	    [ uri_encoded/3,
   76	      uri_data/3,
   77	      uri_components/2,
   78	      uri_query_components/2
   79	    ]).   80:- autoload(library(http/http_header),[http_timestamp/2]).   81:- autoload(library(http/http_path),[http_absolute_location/3]).   82:- autoload(library(http/mimetype),
   83	    [file_content_type/2,file_content_type/3]).   84:- if(exists_source(library(http/thread_httpd))).   85:- autoload(library(http/thread_httpd),[http_spawn/2]).   86:- endif.   87:- use_module(library(settings),[setting/4,setting/2]).   88
   89:- predicate_options(http_404/2, 1, [index(any)]).   90:- predicate_options(http_reply_file/3, 2,
   91                     [ cache(boolean),
   92                       mime_type(any),
   93                       static_gzip(boolean),
   94                       cached_gzip(boolean),
   95                       pass_to(http_safe_file/2, 2),
   96                       headers(list)
   97                     ]).   98:- predicate_options(http_safe_file/2, 2, [unsafe(boolean)]).   99:- 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) :-
        ...

*/

  126:- setting(http:time_limit, nonneg, 300,
  127           '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,post,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 separate 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.
  234:- dynamic handler/4.                   % Path, Action, IsPrefix, Options
  235:- multifile handler/4.  236:- dynamic generation/1.  237
  238:- meta_predicate
  239    http_handler(+, :, +),
  240    http_current_handler(?, :),
  241    http_current_handler(?, :, ?),
  242    http_request_expansion(3, +),
  243    http_switch_protocol(2, +).  244
  245http_handler(Path, Pred, Options) :-
  246    compile_handler(Path, Pred, Options, Clause),
  247    next_generation,
  248    assert(Clause).
  249
  250:- multifile
  251    system:term_expansion/2.  252
  253system:term_expansion((:- http_handler(Path, Pred, Options)), Clause) :-
  254    \+ current_prolog_flag(xref, true),
  255    prolog_load_context(module, M),
  256    compile_handler(Path, M:Pred, Options, Clause),
  257    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.
  272http_delete_handler(id(Id)) :-
  273    !,
  274    clause(handler(_Path, _:Pred, _, Options), true, Ref),
  275    functor(Pred, DefID, _),
  276    option(id(Id0), Options, DefID),
  277    Id == Id0,
  278    erase(Ref),
  279    next_generation.
  280http_delete_handler(path(Path)) :-
  281    !,
  282    retractall(handler(Path, _Pred, _, _Options)),
  283    next_generation.
  284http_delete_handler(Path) :-
  285    http_delete_handler(path(Path)).
 next_generation is det
 current_generation(-G) is det
Increment the generation count.
  293next_generation :-
  294    retractall(id_location_cache(_,_,_,_)),
  295    with_mutex(http_dispatch, next_generation_unlocked).
  296
  297next_generation_unlocked :-
  298    retract(generation(G0)),
  299    !,
  300    G is G0 + 1,
  301    assert(generation(G)).
  302next_generation_unlocked :-
  303    assert(generation(1)).
  304
  305current_generation(G) :-
  306    with_mutex(http_dispatch, generation(G)),
  307    !.
  308current_generation(0).
 compile_handler(+Path, :Pred, +Options, -Clause) is det
Compile a handler specification.
  315compile_handler(Path, Pred, Options0,
  316                http_dispatch:handler(Path1, Pred, IsPrefix, Options)) :-
  317    check_path(Path, Path1, PathOptions),
  318    check_id(Options0),
  319    (   memberchk(segment_pattern(_), PathOptions)
  320    ->  IsPrefix = true,
  321        Options1 = Options0
  322    ;   select(prefix, Options0, Options1)
  323    ->  IsPrefix = true
  324    ;   IsPrefix = false,
  325        Options1 = Options0
  326    ),
  327    partition(ground, Options1, Options2, QueryOptions),
  328    Pred = M:_,
  329    maplist(qualify_option(M), Options2, Options3),
  330    combine_methods(Options3, Options4),
  331    (   QueryOptions == []
  332    ->  append(PathOptions, Options4, Options)
  333    ;   append(PathOptions, ['$extract'(QueryOptions)|Options4], Options)
  334    ).
  335
  336qualify_option(M, condition(Pred), condition(M:Pred)) :-
  337    Pred \= _:_, !.
  338qualify_option(_, Option, Option).
 combine_methods(+OptionsIn, -Options) is det
Combine method(M) and methods(MList) options into a single methods(MList) option.
  345combine_methods(Options0, Options) :-
  346    collect_methods(Options0, Options1, Methods),
  347    (   Methods == []
  348    ->  Options = Options0
  349    ;   append(Methods, Flat),
  350        sort(Flat, Unique),
  351        (   memberchk('*', Unique)
  352        ->  Final = '*'
  353        ;   Final = Unique
  354        ),
  355        Options = [methods(Final)|Options1]
  356    ).
  357
  358collect_methods([], [], []).
  359collect_methods([method(M)|T0], T, [[M]|TM]) :-
  360    !,
  361    (   M == '*'
  362    ->  true
  363    ;   must_be_method(M)
  364    ),
  365    collect_methods(T0, T, TM).
  366collect_methods([methods(M)|T0], T, [M|TM]) :-
  367    !,
  368    must_be(list, M),
  369    maplist(must_be_method, M),
  370    collect_methods(T0, T, TM).
  371collect_methods([H|T0], [H|T], TM) :-
  372    !,
  373    collect_methods(T0, T, TM).
  374
  375must_be_method(M) :-
  376    must_be(atom, M),
  377    (   method(M)
  378    ->  true
  379    ;   domain_error(http_method, M)
  380    ).
  381
  382method(get).
  383method(put).
  384method(head).
  385method(post).
  386method(delete).
  387method(patch).
  388method(options).
  389method(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
  409check_path(Path, Path, []) :-
  410    atom(Path),
  411    !,
  412    (   sub_atom(Path, 0, _, _, /)
  413    ->  true
  414    ;   domain_error(absolute_http_location, Path)
  415    ).
  416check_path(Alias, AliasOut, Options) :-
  417    compound(Alias),
  418    Alias =.. [Name, Relative],
  419    !,
  420    local_path(Relative, Local, Options),
  421    (   sub_atom(Local, 0, _, _, /)
  422    ->  domain_error(relative_location, Relative)
  423    ;   AliasOut =.. [Name, Local]
  424    ).
  425check_path(PathSpec, _, _) :-
  426    type_error(path_or_alias, PathSpec).
  427
  428local_path(Atom, Atom, []) :-
  429    atom(Atom),
  430    !.
  431local_path(Path, Atom, Options) :-
  432    phrase(path_to_list(Path), Components),
  433    !,
  434    (   maplist(atom, Components)
  435    ->  atomic_list_concat(Components, '/', Atom),
  436        Options = []
  437    ;   append(Pre, [Var|Rest], Components),
  438        var(Var)
  439    ->  append(Pre, [''], PreSep),
  440        atomic_list_concat(PreSep, '/', Atom),
  441        Options = [segment_pattern([Var|Rest])]
  442    ).
  443local_path(Path, _, _) :-
  444    ground(Path),
  445    !,
  446    type_error(relative_location, Path).
  447local_path(Path, _, _) :-
  448    instantiation_error(Path).
  449
  450path_to_list(Var) -->
  451    { var(Var) },
  452    !,
  453    [Var].
  454path_to_list(A/B) -->
  455    !,
  456    path_to_list(A),
  457    path_to_list(B).
  458path_to_list(Atom) -->
  459    { atom(Atom) },
  460    !,
  461    [Atom].
  462path_to_list(Value) -->
  463    { must_be(atom, Value) }.
  464
  465check_id(Options) :-
  466    memberchk(id(Id), Options),
  467    !,
  468    must_be(atom, Id).
  469check_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.
  495http_dispatch(Request) :-
  496    memberchk(path(Path), Request),
  497    find_handler(Path, Closure, Options),
  498    supports_method(Request, Options),
  499    expand_request(Request, Request1, Options),
  500    extract_from_request(Request1, Options),
  501    action(Closure, Request1, Options).
  502
  503extract_from_request(Request, Options) :-
  504    memberchk('$extract'(Fields), Options),
  505    !,
  506    extract_fields(Fields, Request).
  507extract_from_request(_, _).
  508
  509extract_fields([], _).
  510extract_fields([H|T], Request) :-
  511    memberchk(H, Request),
  512    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.

  534http_request_expansion(Goal, Rank) :-
  535    throw(error(context_error(nodirective, http_request_expansion(Goal, Rank)), _)).
  536
  537:- multifile
  538    request_expansion/2.  539
  540system:term_expansion((:- http_request_expansion(Goal, Rank)),
  541                      http_dispatch:request_expansion(M:Callable, Rank)) :-
  542    must_be(number, Rank),
  543    prolog_load_context(module, M0),
  544    strip_module(M0:Goal, M, Callable),
  545    must_be(callable, Callable).
  546
  547request_expanders(Closures) :-
  548    findall(Rank-Closure, request_expansion(Closure, Rank), Pairs),
  549    keysort(Pairs, Sorted),
  550    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).
  557expand_request(Request0, Request, Options) :-
  558    request_expanders(Closures),
  559    expand_request(Closures, Request0, Request, Options).
  560
  561expand_request([], Request, Request, _).
  562expand_request([H|T], Request0, Request, Options) :-
  563    expand_request1(H, Request0, Request1, Options),
  564    expand_request(T, Request1, Request, Options).
  565
  566expand_request1(Closure, Request0, Request, Options) :-
  567    call(Closure, Request0, Request, Options),
  568    !.
  569expand_request1(_, Request, Request, _).
 http_current_handler(+Location, :Closure) is semidet
http_current_handler(-Location, :Closure) is nondet
True if Location is handled by Closure.
  577http_current_handler(Path, Closure) :-
  578    atom(Path),
  579    !,
  580    path_tree(Tree),
  581    find_handler(Tree, Path, Closure, _).
  582http_current_handler(Path, M:C) :-
  583    handler(Spec, M:C, _, _),
  584    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.
  591http_current_handler(Path, Closure, Options) :-
  592    atom(Path),
  593    !,
  594    path_tree(Tree),
  595    find_handler(Tree, Path, Closure, Options).
  596http_current_handler(Path, M:C, Options) :-
  597    handler(Spec, M:C, _, _),
  598    http_absolute_location(Spec, Path, []),
  599    path_tree(Tree),
  600    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)
  633:- dynamic
  634    id_location_cache/4.                        % Id, Argv, Location, Segments
  635
  636http_location_by_id(ID, _) :-
  637    \+ ground(ID),
  638    !,
  639    instantiation_error(ID).
  640http_location_by_id(M:ID, Location) :-
  641    compound(ID),
  642    !,
  643    compound_name_arguments(ID, Name, Argv),
  644    http_location_by_id(M:Name, Argv, Location).
  645http_location_by_id(M:ID, Location) :-
  646    atom(ID),
  647    must_be(atom, M),
  648    !,
  649    http_location_by_id(M:ID, -, Location).
  650http_location_by_id(ID, Location) :-
  651    compound(ID),
  652    !,
  653    compound_name_arguments(ID, Name, Argv),
  654    http_location_by_id(Name, Argv, Location).
  655http_location_by_id(ID, Location) :-
  656    atom(ID),
  657    !,
  658    http_location_by_id(ID, -, Location).
  659http_location_by_id(ID, _) :-
  660    type_error(location_id, ID).
  661
  662http_location_by_id(ID, Argv, Location) :-
  663    id_location_cache(ID, Argv, Segments, Path),
  664    !,
  665    add_segments(Path, Segments, Location).
  666http_location_by_id(ID, Argv, Location) :-
  667    findall(t(Priority, ArgvP, Segments, Prefix),
  668            location_by_id(ID, Argv, ArgvP, Segments, Prefix, Priority),
  669            List),
  670    sort(1, >=, List, Sorted),
  671    (   Sorted = [t(_,ArgvP,Segments,Path)]
  672    ->  assert(id_location_cache(ID,ArgvP,Segments,Path)),
  673        Argv = ArgvP
  674    ;   List == []
  675    ->  existence_error(http_handler_id, ID)
  676    ;   List = [t(P0,ArgvP,Segments,Path),t(P1,_,_,_)|_]
  677    ->  (   P0 =:= P1
  678        ->  print_message(warning,
  679                          http_dispatch(ambiguous_id(ID, Sorted, Path)))
  680        ;   true
  681        ),
  682        assert(id_location_cache(ID,Argv,Segments,Path)),
  683        Argv = ArgvP
  684    ),
  685    add_segments(Path, Segments, Location).
  686
  687add_segments(Path0, [], Path) :-
  688    !,
  689    Path = Path0.
  690add_segments(Path0, Segments, Path) :-
  691    maplist(uri_encoded(path), Segments, Encoded),
  692    atomic_list_concat(Encoded, '/', Rest),
  693    atom_concat(Path0, Rest, Path).
  694
  695location_by_id(ID, -, _, [], Location, Priority) :-
  696    !,
  697    location_by_id_raw(ID, L0, _Segments, Priority),
  698    to_path(L0, Location).
  699location_by_id(ID, Argv, ArgvP, Segments, Location, Priority) :-
  700    location_by_id_raw(ID, L0, Segments, Priority),
  701    include(var, Segments, ArgvP),
  702    same_length(Argv, ArgvP),
  703    to_path(L0, Location).
  704
  705to_path(prefix(Path0), Path) :-         % old style prefix notation
  706    !,
  707    add_prefix(Path0, Path).
  708to_path(Path0, Path) :-
  709    atomic(Path0),                      % old style notation
  710    !,
  711    add_prefix(Path0, Path).
  712to_path(Spec, Path) :-                  % new style notation
  713    http_absolute_location(Spec, Path, []).
  714
  715add_prefix(P0, P) :-
  716    (   catch(setting(http:prefix, Prefix), _, fail),
  717        Prefix \== ''
  718    ->  atom_concat(Prefix, P0, P)
  719    ;   P = P0
  720    ).
  721
  722location_by_id_raw(ID, Location, Pattern, Priority) :-
  723    handler(Location, _, _, Options),
  724    option(id(ID), Options),
  725    option(priority(P0), Options, 0),
  726    option(segment_pattern(Pattern), Options, []),
  727    Priority is P0+1000.            % id(ID) takes preference over predicate
  728location_by_id_raw(ID, Location, Pattern, Priority) :-
  729    handler(Location, M:C, _, Options),
  730    option(priority(Priority), Options, 0),
  731    functor(C, PN, _),
  732    (   ID = M:PN
  733    ->  true
  734    ;   ID = PN
  735    ),
  736    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.
  786http_link_to_id(HandleID, path_postfix(File), HREF) :-
  787    !,
  788    http_location_by_id(HandleID, HandlerLocation),
  789    uri_encoded(path, File, EncFile),
  790    directory_file_path(HandlerLocation, EncFile, Location),
  791    uri_data(path, Components, Location),
  792    uri_components(HREF, Components).
  793http_link_to_id(HandleID, Parameters, HREF) :-
  794    must_be(list, Parameters),
  795    http_location_by_id(HandleID, Location),
  796    (   Parameters == []
  797    ->  HREF = Location
  798    ;   uri_data(path, Components, Location),
  799        uri_query_components(String, Parameters),
  800        uri_data(search, Components, String),
  801        uri_components(HREF, Components)
  802    ).
 http_reload_with_parameters(+Request, +Parameters, -HREF) is det
Create a request on the current handler with replaced search parameters.
  809http_reload_with_parameters(Request, NewParams, HREF) :-
  810    memberchk(path(Path), Request),
  811    (   memberchk(search(Params), Request)
  812    ->  true
  813    ;   Params = []
  814    ),
  815    merge_options(NewParams, Params, AllParams),
  816    uri_query_components(Search, AllParams),
  817    uri_data(path, Data, Path),
  818    uri_data(search, Data, Search),
  819    uri_components(HREF, Data).
  820
  821
  822%       hook into html_write:attribute_value//1.
  823
  824:- multifile
  825    html_write:expand_attribute_value//1.  826
  827html_write:expand_attribute_value(location_by_id(ID)) -->
  828    { http_location_by_id(ID, Location) },
  829    html_write:html_quoted_attribute(Location).
  830html_write:expand_attribute_value(#(ID)) -->
  831    { http_location_by_id(ID, Location) },
  832    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.
  847:- multifile
  848    http:authenticate/3.  849
  850authentication([], _, []).
  851authentication([authentication(Type)|Options], Request, Fields) :-
  852    !,
  853    (   http:authenticate(Type, Request, XFields)
  854    ->  append(XFields, More, Fields),
  855        authentication(Options, Request, More)
  856    ;   memberchk(path(Path), Request),
  857        permission_error(access, http_location, Path)
  858    ).
  859authentication([_|Options], Request, Fields) :-
  860    authentication(Options, Request, Fields).
  861
  862:- 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
  871auth_expansion(Request0, Request, Options) :-
  872    authentication(Options, Request0, Extra),
  873    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?
  891find_handler(Path, Action, Options) :-
  892    path_tree(Tree),
  893    (   find_handler(Tree, Path, Action, Options),
  894        eval_condition(Options)
  895    ->  true
  896    ;   \+ sub_atom(Path, _, _, 0, /),
  897        atom_concat(Path, /, Dir),
  898        find_handler(Tree, Dir, Action, Options)
  899    ->  throw(http_reply(moved(Dir)))
  900    ;   throw(error(existence_error(http_location, Path), _))
  901    ).
  902
  903
  904find_handler([node(prefix(Prefix), PAction, POptions, Children)|_],
  905             Path, Action, Options) :-
  906    sub_atom(Path, 0, _, After, Prefix),
  907    !,
  908    (   option(hide_children(false), POptions, false),
  909        find_handler(Children, Path, Action, Options)
  910    ->  true
  911    ;   member(segment_pattern(Pattern, PatAction, PatOptions), POptions),
  912        copy_term(t(Pattern,PatAction,PatOptions), t(Pattern2,Action,Options)),
  913        match_segments(After, Path, Pattern2)
  914    ->  true
  915    ;   PAction \== nop
  916    ->  Action = PAction,
  917        path_info(After, Path, POptions, Options)
  918    ).
  919find_handler([node(Path, Action, Options, _)|_], Path, Action, Options) :- !.
  920find_handler([_|Tree], Path, Action, Options) :-
  921    find_handler(Tree, Path, Action, Options).
  922
  923path_info(0, _, Options,
  924          [prefix(true)|Options]) :- !.
  925path_info(After, Path, Options,
  926          [path_info(PathInfo),prefix(true)|Options]) :-
  927    sub_atom(Path, _, After, 0, PathInfo).
  928
  929match_segments(After, Path, [Var]) :-
  930    !,
  931    sub_atom(Path, _, After, 0, Var).
  932match_segments(After, Path, Pattern) :-
  933    sub_atom(Path, _, After, 0, PathInfo),
  934    split_string(PathInfo, "/", "", Segments),
  935    match_segment_pattern(Pattern, Segments).
  936
  937match_segment_pattern([], []).
  938match_segment_pattern([Var], Segments) :-
  939    !,
  940    atomic_list_concat(Segments, '/', Var).
  941match_segment_pattern([H0|T0], [H|T]) :-
  942    atom_string(H0, H),
  943    match_segment_pattern(T0, T).
  944
  945
  946eval_condition(Options) :-
  947    (   memberchk(condition(Cond), Options)
  948    ->  catch(Cond, E, (print_message(warning, E), fail))
  949    ;   true
  950    ).
 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).
  961supports_method(Request, Options) :-
  962    (   option(methods(Methods), Options)
  963    ->  (   Methods == '*'
  964        ->  true
  965        ;   memberchk(method(Method), Request),
  966            memberchk(Method, Methods)
  967        )
  968    ;   true
  969    ),
  970    !.
  971supports_method(Request, _Options) :-
  972    memberchk(path(Location), Request),
  973    memberchk(method(Method), Request),
  974    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)
  984action(Action, Request, Options) :-
  985    memberchk(chunked, Options),
  986    !,
  987    format('Transfer-encoding: chunked~n'),
  988    spawn_action(Action, Request, Options).
  989action(Action, Request, Options) :-
  990    spawn_action(Action, Request, Options).
  991
  992:- if(current_predicate(http_spawn/2)).  993spawn_action(Action, Request, Options) :-
  994    option(spawn(Spawn), Options),
  995    !,
  996    spawn_options(Spawn, SpawnOption),
  997    http_spawn(time_limit_action(Action, Request, Options), SpawnOption).
  998:- endif.  999spawn_action(Action, Request, Options) :-
 1000    time_limit_action(Action, Request, Options).
 1001
 1002spawn_options([], []) :- !.
 1003spawn_options(Pool, Options) :-
 1004    atom(Pool),
 1005    !,
 1006    Options = [pool(Pool)].
 1007spawn_options(List, List).
 1008
 1009:- if(current_predicate(call_with_time_limit/2)). 1010time_limit_action(Action, Request, Options) :-
 1011    (   option(time_limit(TimeLimit), Options),
 1012        TimeLimit \== default
 1013    ->  true
 1014    ;   setting(http:time_limit, TimeLimit)
 1015    ),
 1016    number(TimeLimit),
 1017    TimeLimit > 0,
 1018    !,
 1019    call_with_time_limit(TimeLimit, call_action(Action, Request, Options)).
 1020:- endif. 1021time_limit_action(Action, Request, Options) :-
 1022    call_action(Action, Request, Options).
 call_action(+Action, +Request, +Options)
To be done
- reply_file is normal call?
 1029call_action(reply_file(File, FileOptions), Request, _Options) :-
 1030    !,
 1031    http_reply_file(File, FileOptions, Request).
 1032call_action(Pred, Request, Options) :-
 1033    memberchk(path_info(PathInfo), Options),
 1034    !,
 1035    call_action(Pred, [path_info(PathInfo)|Request]).
 1036call_action(Pred, Request, _Options) :-
 1037    call_action(Pred, Request).
 1038
 1039call_action(Pred, Request) :-
 1040    (   call(Pred, Request)
 1041    ->  true
 1042    ;   extend(Pred, [Request], Goal),
 1043        throw(error(goal_failed(Goal), _))
 1044    ).
 1045
 1046extend(Var, _, Var) :-
 1047    var(Var),
 1048    !.
 1049extend(M:G0, Extra, M:G) :-
 1050    extend(G0, Extra, G).
 1051extend(G0, Extra, G) :-
 1052    G0 =.. List,
 1053    append(List, Extra, List2),
 1054    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.
cached_gzip(+Boolean)
If true (default false) the system maintains cached gzipped files in a directory accessible using the file search path http_gzip_cache and serves these similar to the static_gzip(true) option. If the gzip file does not exist or is older than the input the file is recreated.
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))
 1098http_reply_file(File, Options, Request) :-
 1099    http_safe_file(File, Options),
 1100    absolute_file_name(File, Path,
 1101                       [ access(read)
 1102                       ]),
 1103    (   option(cache(true), Options, true)
 1104    ->  (   memberchk(if_modified_since(Since), Request),
 1105            time_file(Path, Time),
 1106            catch(http_timestamp(Time, Since), _, fail)
 1107        ->  throw(http_reply(not_modified))
 1108        ;   true
 1109        ),
 1110        (   memberchk(range(Range), Request)
 1111        ->  Reply = file(Type, Path, Range)
 1112        ;   option(static_gzip(true), Options),
 1113            accepts_encoding(Request, gzip),
 1114            file_name_extension(Path, gz, PathGZ),
 1115            access_file(PathGZ, read),
 1116            time_file(PathGZ, TimeGZ),
 1117            time_file(Path, Time),
 1118            TimeGZ >= Time
 1119        ->  Reply = gzip_file(Type, PathGZ)
 1120        ;   option(cached_gzip(true), Options),
 1121            accepts_encoding(Request, gzip),
 1122            gzip_cached(Path, PathGZ)
 1123        ->  Reply = gzip_file(Type, PathGZ)
 1124        ;   Reply = file(Type, Path)
 1125        )
 1126    ;   Reply = tmp_file(Type, Path)
 1127    ),
 1128    (   option(mime_type(MediaType), Options)
 1129    ->  file_content_type(Path, MediaType, Type)
 1130    ;   file_content_type(Path, Type)
 1131    ->  true
 1132    ;   Type = text/plain           % fallback type
 1133    ),
 1134    option(headers(Headers), Options, []),
 1135    throw(http_reply(Reply, Headers)).
 1136
 1137accepts_encoding(Request, Enc) :-
 1138    memberchk(accept_encoding(Accept), Request),
 1139    split_string(Accept, ",", " ", Parts),
 1140    member(Part, Parts),
 1141    split_string(Part, ";", " ", [EncS|_]),
 1142    atom_string(Enc, EncS).
 1143
 1144gzip_cached(Path, PathGZ) :-
 1145    with_mutex(http_reply_file, gzip_cached_sync(Path, PathGZ)).
 1146
 1147gzip_cached_sync(Path, PathGZ) :-
 1148    time_file(Path, Time),
 1149    variant_sha1(Path, SHA1),
 1150    (   absolute_file_name(http_gzip_cache(SHA1),
 1151                           PathGZ,
 1152                           [ access(read),
 1153                             file_errors(fail)
 1154                           ]),
 1155        time_file(PathGZ, TimeGZ),
 1156        TimeGZ >= Time
 1157    ->  true
 1158    ;   absolute_file_name(http_gzip_cache(SHA1),
 1159                           PathGZ,
 1160                           [ access(write),
 1161                             file_errors(fail)
 1162                           ])
 1163    ->  setup_call_cleanup(
 1164            gzopen(PathGZ, write, Out, [type(binary)]),
 1165            setup_call_cleanup(
 1166                open(Path, read, In, [type(binary)]),
 1167                copy_stream_data(In, Out),
 1168                close(In)),
 1169            close(Out))
 1170    ).
 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)
 1182http_safe_file(File, _) :-
 1183    var(File),
 1184    !,
 1185    instantiation_error(File).
 1186http_safe_file(_, Options) :-
 1187    option(unsafe(true), Options, false),
 1188    !.
 1189http_safe_file(File, _) :-
 1190    http_safe_file(File).
 1191
 1192http_safe_file(File) :-
 1193    compound(File),
 1194    functor(File, _, 1),
 1195    !,
 1196    arg(1, File, Name),
 1197    safe_name(Name, File).
 1198http_safe_file(Name) :-
 1199    (   is_absolute_file_name(Name)
 1200    ->  permission_error(read, file, Name)
 1201    ;   true
 1202    ),
 1203    safe_name(Name, Name).
 1204
 1205safe_name(Name, _) :-
 1206    must_be(atom, Name),
 1207    prolog_to_os_filename(FileName, Name),
 1208    \+ unsafe_name(FileName),
 1209    !.
 1210safe_name(_, Spec) :-
 1211    permission_error(read, file, Spec).
 1212
 1213unsafe_name(Name) :- Name == '..'.
 1214unsafe_name(Name) :- sub_atom(Name, 0, _, _, '../').
 1215unsafe_name(Name) :- sub_atom(Name, _, _, _, '/../').
 1216unsafe_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.
 1237http_redirect(How, To, Request) :-
 1238    must_be(oneof([moved, moved_temporary, see_other]), How),
 1239    must_be(ground, To),
 1240    (   id_location(To, URL)
 1241    ->  true
 1242    ;   memberchk(path(Base), Request),
 1243        http_absolute_location(To, URL, [relative_to(Base)])
 1244    ),
 1245    Term =.. [How,URL],
 1246    throw(http_reply(Term)).
 1247
 1248id_location(location_by_id(Id), URL) :-
 1249    http_location_by_id(Id, URL).
 1250id_location(#(Id), URL) :-
 1251    http_location_by_id(Id, URL).
 1252id_location(#(Id)+Parameters, URL) :-
 1253    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))
 1268http_404(Options, Request) :-
 1269    option(index(Index), Options),
 1270    \+ ( option(path_info(PathInfo), Request),
 1271         PathInfo \== ''
 1272       ),
 1273    !,
 1274    http_redirect(moved, Index, Request).
 1275http_404(_Options, Request) :-
 1276    option(path(Path), Request),
 1277    !,
 1278    throw(http_reply(not_found(Path))).
 1279http_404(_Options, Request) :-
 1280    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).
 1314%       @throws http_reply(switch_protocol(Goal, Options))
 1315
 1316http_switch_protocol(Goal, Options) :-
 1317    throw(http_reply(switching_protocols(Goal, Options))).
 1318
 1319
 1320                 /*******************************
 1321                 *        PATH COMPILATION      *
 1322                 *******************************/
 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.

 1338path_tree(Tree) :-
 1339    current_generation(G),
 1340    nb_current(http_dispatch_tree, G-Tree),
 1341    !. % Avoid existence error
 1342path_tree(Tree) :-
 1343    path_tree_nocache(Tree),
 1344    current_generation(G),
 1345    nb_setval(http_dispatch_tree, G-Tree).
 1346
 1347path_tree_nocache(Tree) :-
 1348    findall(Prefix, prefix_handler(Prefix, _, _, _), Prefixes0),
 1349    sort(Prefixes0, Prefixes),
 1350    prefix_tree(Prefixes, [], PTree),
 1351    prefix_options(PTree, [], OPTree),
 1352    add_paths_tree(OPTree, Tree).
 1353
 1354prefix_handler(Prefix, Action, Options, Priority-PLen) :-
 1355    handler(Spec, Action, true, Options),
 1356    (   memberchk(priority(Priority), Options)
 1357    ->  true
 1358    ;   Priority = 0
 1359    ),
 1360    (   memberchk(segment_pattern(Pattern), Options)
 1361    ->  length(Pattern, PLen)
 1362    ;   PLen = 0
 1363    ),
 1364    Error = error(existence_error(http_alias,_),_),
 1365    catch(http_absolute_location(Spec, Prefix, []), Error,
 1366          (   print_message(warning, Error),
 1367              fail
 1368          )).
 prefix_tree(PrefixList, +Tree0, -Tree)
Arguments:
Tree- list(Prefix-list(Children))
 1374prefix_tree([], Tree, Tree).
 1375prefix_tree([H|T], Tree0, Tree) :-
 1376    insert_prefix(H, Tree0, Tree1),
 1377    prefix_tree(T, Tree1, Tree).
 1378
 1379insert_prefix(Prefix, Tree0, Tree) :-
 1380    select(P-T, Tree0, Tree1),
 1381    sub_atom(Prefix, 0, _, _, P),
 1382    !,
 1383    insert_prefix(Prefix, T, T1),
 1384    Tree = [P-T1|Tree1].
 1385insert_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?
 1394prefix_options([], _, []).
 1395prefix_options([Prefix-C|T0], DefOptions,
 1396               [node(prefix(Prefix), Action, PrefixOptions, Children)|T]) :-
 1397    findall(h(A,O,P), prefix_handler(Prefix,A,O,P), Handlers),
 1398    sort(3, >=, Handlers, Handlers1),
 1399    Handlers1 = [h(_,_,P0)|_],
 1400    same_priority_handlers(Handlers1, P0, Same),
 1401    option_patterns(Same, SegmentPatterns, Action),
 1402    last(Same, h(_, Options0, _-_)),
 1403    merge_options(Options0, DefOptions, Options),
 1404    append(SegmentPatterns, Options, PrefixOptions),
 1405    exclude(no_inherit, Options, InheritOpts),
 1406    prefix_options(C, InheritOpts, Children),
 1407    prefix_options(T0, DefOptions, T).
 1408
 1409no_inherit(id(_)).
 1410no_inherit('$extract'(_)).
 1411
 1412same_priority_handlers([H|T0], P, [H|T]) :-
 1413    H = h(_,_,P0-_),
 1414    P = P0-_,
 1415    !,
 1416    same_priority_handlers(T0, P, T).
 1417same_priority_handlers(_, _, []).
 1418
 1419option_patterns([], [], nop).
 1420option_patterns([h(A,_,_-0)|_], [], A) :-
 1421    !.
 1422option_patterns([h(A,O,_)|T0], [segment_pattern(P,A,O)|T], AF) :-
 1423    memberchk(segment_pattern(P), O),
 1424    option_patterns(T0, T, AF).
 add_paths_tree(+OPTree, -Tree) is det
Add the plain paths.
 1431add_paths_tree(OPTree, Tree) :-
 1432    findall(path(Path, Action, Options),
 1433            plain_path(Path, Action, Options),
 1434            Triples),
 1435    add_paths_tree(Triples, OPTree, Tree).
 1436
 1437add_paths_tree([], Tree, Tree).
 1438add_paths_tree([path(Path, Action, Options)|T], Tree0, Tree) :-
 1439    add_path_tree(Path, Action, Options, [], Tree0, Tree1),
 1440    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.
 1448plain_path(Path, Action, Options) :-
 1449    handler(Spec, Action, false, Options),
 1450    catch(http_absolute_location(Spec, Path, []), E,
 1451          (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.
 1460add_path_tree(Path, Action, Options0, DefOptions, [],
 1461              [node(Path, Action, Options, [])]) :-
 1462    !,
 1463    merge_options(Options0, DefOptions, Options).
 1464add_path_tree(Path, Action, Options, _,
 1465              [node(prefix(Prefix), PA, DefOptions, Children0)|RestTree],
 1466              [node(prefix(Prefix), PA, DefOptions, Children)|RestTree]) :-
 1467    sub_atom(Path, 0, _, _, Prefix),
 1468    !,
 1469    delete(DefOptions, id(_), InheritOpts),
 1470    add_path_tree(Path, Action, Options, InheritOpts, Children0, Children).
 1471add_path_tree(Path, Action, Options1, DefOptions, [H0|T], [H|T]) :-
 1472    H0 = node(Path, _, Options2, _),
 1473    option(priority(P1), Options1, 0),
 1474    option(priority(P2), Options2, 0),
 1475    P1 >= P2,
 1476    !,
 1477    merge_options(Options1, DefOptions, Options),
 1478    H = node(Path, Action, Options, []).
 1479add_path_tree(Path, Action, Options, DefOptions, [H|T0], [H|T]) :-
 1480    add_path_tree(Path, Action, Options, DefOptions, T0, T).
 1481
 1482
 1483                 /*******************************
 1484                 *            MESSAGES          *
 1485                 *******************************/
 1486
 1487:- multifile
 1488    prolog:message/3. 1489
 1490prolog:message(http_dispatch(ambiguous_id(ID, _List, Selected))) -->
 1491    [ 'HTTP dispatch: ambiguous handler ID ~q (selected ~q)'-[ID, Selected]
 1492    ].
 1493
 1494
 1495                 /*******************************
 1496                 *            XREF              *
 1497                 *******************************/
 1498
 1499:- multifile
 1500    prolog:meta_goal/2. 1501:- dynamic
 1502    prolog:meta_goal/2. 1503
 1504prolog:meta_goal(http_handler(_, G, _), [G+1]).
 1505prolog:meta_goal(http_current_handler(_, G), [G+1]).
 1506
 1507
 1508                 /*******************************
 1509                 *             EDIT             *
 1510                 *******************************/
 1511
 1512% Allow edit(Location) to edit the implementation for an HTTP location.
 1513
 1514:- multifile
 1515    prolog_edit:locate/3. 1516
 1517prolog_edit:locate(Path, Spec, Location) :-
 1518    atom(Path),
 1519    sub_atom(Path, 0, _, _, /),
 1520    Pred = _M:_H,
 1521    catch(http_current_handler(Path, Pred), _, fail),
 1522    closure_name_arity(Pred, 1, PI),
 1523    prolog_edit:locate(PI, Spec, Location).
 1524
 1525closure_name_arity(M:Term, Extra, M:Name/Arity) :-
 1526    !,
 1527    callable(Term),
 1528    functor(Term, Name, Arity0),
 1529    Arity is Arity0 + Extra.
 1530closure_name_arity(Term, Extra, Name/Arity) :-
 1531    callable(Term),
 1532    functor(Term, Name, Arity0),
 1533    Arity is Arity0 + Extra.
 1534
 1535
 1536                 /*******************************
 1537                 *        CACHE CLEANUP         *
 1538                 *******************************/
 1539
 1540:- listen(settings(changed(http:prefix, _, _)),
 1541          next_generation). 1542
 1543:- multifile
 1544    user:message_hook/3. 1545:- dynamic
 1546    user:message_hook/3. 1547
 1548user:message_hook(make(done(Reload)), _Level, _Lines) :-
 1549    Reload \== [],
 1550    next_generation,
 1551    fail