View source with raw comments or as raw
    1/*  Part of SWI-Prolog
    2
    3    Author:        Jan Wielemaker
    4    E-mail:        J.Wielemaker@vu.nl
    5    WWW:           http://www.swi-prolog.org
    6    Copyright (c)  2007-2018, University of Amsterdam
    7                              VU University Amsterdam
    8                              CWI, Amsterdam
    9    All rights reserved.
   10
   11    Redistribution and use in source and binary forms, with or without
   12    modification, are permitted provided that the following conditions
   13    are met:
   14
   15    1. Redistributions of source code must retain the above copyright
   16       notice, this list of conditions and the following disclaimer.
   17
   18    2. Redistributions in binary form must reproduce the above copyright
   19       notice, this list of conditions and the following disclaimer in
   20       the documentation and/or other materials provided with the
   21       distribution.
   22
   23    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
   24    "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
   25    LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
   26    FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
   27    COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
   28    INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
   29    BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
   30    LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
   31    CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
   32    LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
   33    ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
   34    POSSIBILITY OF SUCH DAMAGE.
   35*/
   36
   37:- module(http_dispatch,
   38          [ http_dispatch/1,            % +Request
   39            http_handler/3,             % +Path, +Predicate, +Options
   40            http_delete_handler/1,      % +Path
   41            http_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(option)).   54:- use_module(library(lists)).   55:- use_module(library(pairs)).   56:- use_module(library(time)).   57:- use_module(library(error)).   58:- use_module(library(settings)).   59:- use_module(library(uri)).   60:- use_module(library(apply)).   61:- use_module(library(http/mimetype)).   62:- use_module(library(http/http_path)).   63:- use_module(library(http/http_header)).   64:- use_module(library(http/thread_httpd)).   65
   66:- predicate_options(http_404/2, 1, [index(any)]).   67:- predicate_options(http_reply_file/3, 2,
   68                     [ cache(boolean),
   69                       mime_type(any),
   70                       static_gzip(boolean),
   71                       pass_to(http_safe_file/2, 2),
   72                       headers(list)
   73                     ]).   74:- predicate_options(http_safe_file/2, 2, [unsafe(boolean)]).   75:- predicate_options(http_switch_protocol/2, 2, []).

Dispatch requests in the HTTP server

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

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

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

write_index(Request) :-
        ...

*/

   98:- setting(http:time_limit, nonneg, 300,
   99           '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(+Term)
Identifier of the handler. The default identifier is the predicate name. Used by http_location_by_id/2.
hide_children(+Bool)
If true on a prefix-handler (see prefix), possible children are masked. This can be used to (temporary) overrule part of the tree.
method(+Method)
Declare that the handler processes Method. This is equivalent to methods([Method]). Using method(*) allows for all methods.
methods(+ListOfMethods)
Declare that the handler processes all of the given methods. If this option appears multiple times, the methods are combined.
prefix
Call Pred on any location that is a specialisation of Path. If multiple handlers match, the one with the longest path is used. Options defined with a prefix handler are the default options for paths that start with this prefix. Note that the handler acts as a fallback handler for the tree below it:
:- http_handler(/, http_404([index('index.html')]),
                [spawn(my_pool),prefix]).
priority(+Integer)
If two handlers handle the same path, the one with the highest priority is used. If equal, the last registered is used. Please be aware that the order of clauses in multifile predicates can change due to reloading files. The default priority is 0 (zero).
spawn(+SpawnOptions)
Run the handler in a seperate thread. If SpawnOptions is an atom, it is interpreted as a thread pool name (see create_thread_pool/3). Otherwise the options are passed to http_spawn/2 and from there to thread_create/3. These options are typically used to set the stack limits.
time_limit(+Spec)
One of infinite, default or a positive number (seconds). If default, the value from the setting http:time_limit is taken. The default of this setting is 300 (5 minutes). See setting/2.

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

Errors
- existence_error(http_location, Location)
- 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.
  207:- dynamic handler/4.                   % Path, Action, IsPrefix, Options
  208:- multifile handler/4.  209:- dynamic generation/1.  210
  211:- meta_predicate
  212    http_handler(+, :, +),
  213    http_current_handler(?, :),
  214    http_current_handler(?, :, ?),
  215    http_request_expansion(3, +),
  216    http_switch_protocol(2, +).  217
  218http_handler(Path, Pred, Options) :-
  219    compile_handler(Path, Pred, Options, Clause),
  220    next_generation,
  221    assert(Clause).
  222
  223:- multifile
  224    system:term_expansion/2.  225
  226system:term_expansion((:- http_handler(Path, Pred, Options)), Clause) :-
  227    \+ current_prolog_flag(xref, true),
  228    prolog_load_context(module, M),
  229    compile_handler(Path, M:Pred, Options, Clause),
  230    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.
  245http_delete_handler(id(Id)) :-
  246    !,
  247    clause(handler(_Path, _:Pred, _, Options), true, Ref),
  248    functor(Pred, DefID, _),
  249    option(id(Id0), Options, DefID),
  250    Id == Id0,
  251    erase(Ref),
  252    next_generation.
  253http_delete_handler(path(Path)) :-
  254    !,
  255    retractall(handler(Path, _Pred, _, _Options)),
  256    next_generation.
  257http_delete_handler(Path) :-
  258    http_delete_handler(path(Path)).
 next_generation is det
 current_generation(-G) is det
Increment the generation count.
  266next_generation :-
  267    retractall(id_location_cache(_,_)),
  268    with_mutex(http_dispatch, next_generation_unlocked).
  269
  270next_generation_unlocked :-
  271    retract(generation(G0)),
  272    !,
  273    G is G0 + 1,
  274    assert(generation(G)).
  275next_generation_unlocked :-
  276    assert(generation(1)).
  277
  278current_generation(G) :-
  279    with_mutex(http_dispatch, generation(G)),
  280    !.
  281current_generation(0).
 compile_handler(+Path, :Pred, +Options, -Clause) is det
Compile a handler specification.
  288compile_handler(Path, Pred, Options0,
  289                http_dispatch:handler(Path1, Pred, IsPrefix, Options)) :-
  290    check_path(Path, Path1, PathOptions),
  291    (   memberchk(segment_pattern(_), PathOptions)
  292    ->  IsPrefix = true,
  293        Options1 = Options0
  294    ;   select(prefix, Options0, Options1)
  295    ->  IsPrefix = true
  296    ;   IsPrefix = false,
  297        Options1 = Options0
  298    ),
  299    partition(ground, Options1, Options2, QueryOptions),
  300    Pred = M:_,
  301    maplist(qualify_option(M), Options2, Options3),
  302    combine_methods(Options3, Options4),
  303    (   QueryOptions == []
  304    ->  append(PathOptions, Options4, Options)
  305    ;   append(PathOptions, ['$extract'(QueryOptions)|Options4], Options)
  306    ).
  307
  308qualify_option(M, condition(Pred), condition(M:Pred)) :-
  309    Pred \= _:_, !.
  310qualify_option(_, Option, Option).
 combine_methods(+OptionsIn, -Options) is det
Combine method(M) and methods(MList) options into a single methods(MList) option.
  317combine_methods(Options0, Options) :-
  318    collect_methods(Options0, Options1, Methods),
  319    (   Methods == []
  320    ->  Options = Options0
  321    ;   append(Methods, Flat),
  322        sort(Flat, Unique),
  323        (   memberchk('*', Unique)
  324        ->  Final = '*'
  325        ;   Final = Unique
  326        ),
  327        Options = [methods(Final)|Options1]
  328    ).
  329
  330collect_methods([], [], []).
  331collect_methods([method(M)|T0], T, [[M]|TM]) :-
  332    !,
  333    (   M == '*'
  334    ->  true
  335    ;   must_be_method(M)
  336    ),
  337    collect_methods(T0, T, TM).
  338collect_methods([methods(M)|T0], T, [M|TM]) :-
  339    !,
  340    must_be(list, M),
  341    maplist(must_be_method, M),
  342    collect_methods(T0, T, TM).
  343collect_methods([H|T0], [H|T], TM) :-
  344    !,
  345    collect_methods(T0, T, TM).
  346
  347must_be_method(M) :-
  348    must_be(atom, M),
  349    (   method(M)
  350    ->  true
  351    ;   domain_error(http_method, M)
  352    ).
  353
  354method(get).
  355method(put).
  356method(head).
  357method(post).
  358method(delete).
  359method(patch).
  360method(options).
  361method(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
  381check_path(Path, Path, []) :-
  382    atom(Path),
  383    !,
  384    (   sub_atom(Path, 0, _, _, /)
  385    ->  true
  386    ;   domain_error(absolute_http_location, Path)
  387    ).
  388check_path(Alias, AliasOut, Options) :-
  389    compound(Alias),
  390    Alias =.. [Name, Relative],
  391    !,
  392    local_path(Relative, Local, Options),
  393    (   sub_atom(Local, 0, _, _, /)
  394    ->  domain_error(relative_location, Relative)
  395    ;   AliasOut =.. [Name, Local]
  396    ).
  397check_path(PathSpec, _, _) :-
  398    type_error(path_or_alias, PathSpec).
  399
  400local_path(Atom, Atom, []) :-
  401    atom(Atom),
  402    !.
  403local_path(Path, Atom, Options) :-
  404    phrase(path_to_list(Path), Components),
  405    !,
  406    (   maplist(atom, Components)
  407    ->  atomic_list_concat(Components, '/', Atom),
  408        Options = []
  409    ;   append(Pre, [Var|Rest], Components),
  410        var(Var)
  411    ->  append(Pre, [''], PreSep),
  412        atomic_list_concat(PreSep, '/', Atom),
  413        Options = [segment_pattern([Var|Rest])]
  414    ).
  415local_path(Path, _, _) :-
  416    ground(Path),
  417    !,
  418    type_error(relative_location, Path).
  419local_path(Path, _, _) :-
  420    instantiation_error(Path).
  421
  422path_to_list(Var) -->
  423    { var(Var) },
  424    !,
  425    [Var].
  426path_to_list(A/B) -->
  427    !,
  428    path_to_list(A),
  429    path_to_list(B).
  430path_to_list(Atom) -->
  431    { atom(Atom) },
  432    !,
  433    [Atom].
  434path_to_list(Value) -->
  435    { must_be(atom, Value) }.
 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.
  461http_dispatch(Request) :-
  462    memberchk(path(Path), Request),
  463    find_handler(Path, Closure, Options),
  464    supports_method(Request, Options),
  465    expand_request(Request, Request1, Options),
  466    extract_from_request(Request1, Options),
  467    action(Closure, Request1, Options).
  468
  469extract_from_request(Request, Options) :-
  470    memberchk('$extract'(Fields), Options),
  471    !,
  472    extract_fields(Fields, Request).
  473extract_from_request(_, _).
  474
  475extract_fields([], _).
  476extract_fields([H|T], Request) :-
  477    memberchk(H, Request),
  478    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.

  500http_request_expansion(Goal, Rank) :-
  501    throw(error(context_error(nodirective, http_request_expansion(Goal, Rank)), _)).
  502
  503:- multifile
  504    request_expansion/2.  505
  506system:term_expansion((:- http_request_expansion(Goal, Rank)),
  507                      http_dispatch:request_expansion(M:Callable, Rank)) :-
  508    must_be(number, Rank),
  509    prolog_load_context(module, M0),
  510    strip_module(M0:Goal, M, Callable),
  511    must_be(callable, Callable).
  512
  513request_expanders(Closures) :-
  514    findall(Rank-Closure, request_expansion(Closure, Rank), Pairs),
  515    keysort(Pairs, Sorted),
  516    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).
  523expand_request(Request0, Request, Options) :-
  524    request_expanders(Closures),
  525    expand_request(Closures, Request0, Request, Options).
  526
  527expand_request([], Request, Request, _).
  528expand_request([H|T], Request0, Request, Options) :-
  529    expand_request1(H, Request0, Request1, Options),
  530    expand_request(T, Request1, Request, Options).
  531
  532expand_request1(Closure, Request0, Request, Options) :-
  533    call(Closure, Request0, Request, Options),
  534    !.
  535expand_request1(_, Request, Request, _).
 http_current_handler(+Location, :Closure) is semidet
http_current_handler(-Location, :Closure) is nondet
True if Location is handled by Closure.
  543http_current_handler(Path, Closure) :-
  544    atom(Path),
  545    !,
  546    path_tree(Tree),
  547    find_handler(Tree, Path, Closure, _).
  548http_current_handler(Path, M:C) :-
  549    handler(Spec, M:C, _, _),
  550    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.
  557http_current_handler(Path, Closure, Options) :-
  558    atom(Path),
  559    !,
  560    path_tree(Tree),
  561    find_handler(Tree, Path, Closure, Options).
  562http_current_handler(Path, M:C, Options) :-
  563    handler(Spec, M:C, _, _),
  564    http_absolute_location(Spec, Path, []),
  565    path_tree(Tree),
  566    find_handler(Tree, Path, _, Options).
 http_location_by_id(+ID, -Location) is det
Find the HTTP Location of handler with ID. If the setting (see setting/2) http:prefix is active, Location is the handler location prefixed with the prefix setting. Handler IDs can be specified in two ways:
id(ID)
If this appears in the option list of the handler, this it is used and takes preference over using the predicate.
M:PredName
The module-qualified name of the predicate.
PredName
The unqualified name of the predicate.
Errors
- existence_error(http_handler_id, Id).
deprecated
- The predicate http_link_to_id/3 provides the same functionality with the option to add query parameters or a path parameter.
  589:- dynamic
  590    id_location_cache/2.  591
  592http_location_by_id(ID, Location) :-
  593    must_be(ground, ID),
  594    id_location_cache(ID, L0),
  595    !,
  596    Location = L0.
  597http_location_by_id(ID, Location) :-
  598    findall(P-L, location_by_id(ID, L, P), List),
  599    keysort(List, RevSorted),
  600    reverse(RevSorted, Sorted),
  601    (   Sorted = [_-One]
  602    ->  assert(id_location_cache(ID, One)),
  603        Location = One
  604    ;   List == []
  605    ->  existence_error(http_handler_id, ID)
  606    ;   List = [P0-Best,P1-_|_]
  607    ->  (   P0 == P1
  608        ->  print_message(warning,
  609                          http_dispatch(ambiguous_id(ID, Sorted, Best)))
  610        ;   true
  611        ),
  612        assert(id_location_cache(ID, Best)),
  613        Location = Best
  614    ).
  615
  616location_by_id(ID, Location, Priority) :-
  617    location_by_id_raw(ID, L0, Priority),
  618    to_path(L0, Location).
  619
  620to_path(prefix(Path0), Path) :-        % old style prefix notation
  621    !,
  622    add_prefix(Path0, Path).
  623to_path(Path0, Path) :-
  624    atomic(Path0),                 % old style notation
  625    !,
  626    add_prefix(Path0, Path).
  627to_path(Spec, Path) :-                  % new style notation
  628    http_absolute_location(Spec, Path, []).
  629
  630add_prefix(P0, P) :-
  631    (   catch(setting(http:prefix, Prefix), _, fail),
  632        Prefix \== ''
  633    ->  atom_concat(Prefix, P0, P)
  634    ;   P = P0
  635    ).
  636
  637location_by_id_raw(ID, Location, Priority) :-
  638    handler(Location, _, _, Options),
  639    option(id(ID), Options),
  640    option(priority(P0), Options, 0),
  641    Priority is P0+1000.            % id(ID) takes preference over predicate
  642location_by_id_raw(ID, Location, Priority) :-
  643    handler(Location, M:C, _, Options),
  644    option(priority(Priority), Options, 0),
  645    functor(C, PN, _),
  646    (   ID = M:PN
  647    ;   ID = PN
  648    ),
  649    !.
 http_link_to_id(+HandleID, +Parameters, -HREF)
HREF is a link on the local server to a handler with given ID, passing the given Parameters. This predicate is typically used to formulate a HREF that resolves to a handler implementing a particular predicate. The code below provides a typical example. The predicate user_details/1 returns a page with details about a user from a given id. This predicate is registered as a handler. The DCG user_link//1 renders a link to a user, displaying the name and calling user_details/1 when clicked. Note that the location (root(user_details)) is irrelevant in this equation and HTTP locations can thus be moved freely without breaking this code fragment.
:- http_handler(root(user_details), user_details, []).

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

user_link(ID) -->
    { user_name(ID, Name),
      http_link_to_id(user_details, [id(ID)], HREF)
    },
    html(a([class(user), href(HREF)], Name)).
Arguments:
Parameters- is one of
  • path_postfix(File) to pass a single value as the last segment of the HTTP location (path). This way of passing a parameter is commonly used in REST APIs.
  • A list of search parameters for a GET request.
See also
- http_location_by_id/2 and http_handler/3 for defining and specifying handler IDs.
  692http_link_to_id(HandleID, path_postfix(File), HREF) :-
  693    !,
  694    http_location_by_id(HandleID, HandlerLocation),
  695    uri_encoded(path, File, EncFile),
  696    directory_file_path(HandlerLocation, EncFile, Location),
  697    uri_data(path, Components, Location),
  698    uri_components(HREF, Components).
  699http_link_to_id(HandleID, Parameters, HREF) :-
  700    must_be(list, Parameters),
  701    http_location_by_id(HandleID, Location),
  702    uri_data(path, Components, Location),
  703    uri_query_components(String, Parameters),
  704    uri_data(search, Components, String),
  705    uri_components(HREF, Components).
 http_reload_with_parameters(+Request, +Parameters, -HREF) is det
Create a request on the current handler with replaced search parameters.
  712http_reload_with_parameters(Request, NewParams, HREF) :-
  713    memberchk(path(Path), Request),
  714    (   memberchk(search(Params), Request)
  715    ->  true
  716    ;   Params = []
  717    ),
  718    merge_options(NewParams, Params, AllParams),
  719    uri_query_components(Search, AllParams),
  720    uri_data(path, Data, Path),
  721    uri_data(search, Data, Search),
  722    uri_components(HREF, Data).
  723
  724
  725%       hook into html_write:attribute_value//1.
  726
  727:- multifile
  728    html_write:expand_attribute_value//1.  729
  730html_write:expand_attribute_value(location_by_id(ID)) -->
  731    { http_location_by_id(ID, Location) },
  732    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.
  747:- multifile
  748    http:authenticate/3.  749
  750authentication([], _, []).
  751authentication([authentication(Type)|Options], Request, Fields) :-
  752    !,
  753    (   http:authenticate(Type, Request, XFields)
  754    ->  append(XFields, More, Fields),
  755        authentication(Options, Request, More)
  756    ;   memberchk(path(Path), Request),
  757        permission_error(access, http_location, Path)
  758    ).
  759authentication([_|Options], Request, Fields) :-
  760    authentication(Options, Request, Fields).
  761
  762:- 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
  771auth_expansion(Request0, Request, Options) :-
  772    authentication(Options, Request0, Extra),
  773    append(Extra, Request, Request0).
 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?
  791find_handler(Path, Action, Options) :-
  792    path_tree(Tree),
  793    (   find_handler(Tree, Path, Action, Options),
  794        eval_condition(Options)
  795    ->  true
  796    ;   \+ sub_atom(Path, _, _, 0, /),
  797        atom_concat(Path, /, Dir),
  798        find_handler(Tree, Dir, Action, Options)
  799    ->  throw(http_reply(moved(Dir)))
  800    ;   throw(error(existence_error(http_location, Path), _))
  801    ).
  802
  803
  804find_handler([node(prefix(Prefix), PAction, POptions, Children)|_],
  805             Path, Action, Options) :-
  806    sub_atom(Path, 0, _, After, Prefix),
  807    !,
  808    (   option(hide_children(false), POptions, false),
  809        find_handler(Children, Path, Action, Options)
  810    ->  true
  811    ;   member(segment_pattern(Pattern, PatAction, PatOptions), POptions),
  812        copy_term(t(Pattern,PatAction,PatOptions), t(Pattern2,Action,Options)),
  813        match_segments(After, Path, Pattern2)
  814    ->  true
  815    ;   PAction \== nop
  816    ->  Action = PAction,
  817        path_info(After, Path, POptions, Options)
  818    ).
  819find_handler([node(Path, Action, Options, _)|_], Path, Action, Options) :- !.
  820find_handler([_|Tree], Path, Action, Options) :-
  821    find_handler(Tree, Path, Action, Options).
  822
  823path_info(0, _, Options,
  824          [prefix(true)|Options]) :- !.
  825path_info(After, Path, Options,
  826          [path_info(PathInfo),prefix(true)|Options]) :-
  827    sub_atom(Path, _, After, 0, PathInfo).
  828
  829match_segments(After, Path, [Var]) :-
  830    !,
  831    sub_atom(Path, _, After, 0, Var).
  832match_segments(After, Path, Pattern) :-
  833    sub_atom(Path, _, After, 0, PathInfo),
  834    split_string(PathInfo, "/", "", Segments),
  835    match_segment_pattern(Pattern, Segments).
  836
  837match_segment_pattern([], []).
  838match_segment_pattern([Var], Segments) :-
  839    !,
  840    atomic_list_concat(Segments, '/', Var).
  841match_segment_pattern([H0|T0], [H|T]) :-
  842    atom_string(H0, H),
  843    match_segment_pattern(T0, T).
  844
  845
  846eval_condition(Options) :-
  847    (   memberchk(condition(Cond), Options)
  848    ->  catch(Cond, E, (print_message(warning, E), fail))
  849    ;   true
  850    ).
 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).
  861supports_method(Request, Options) :-
  862    (   option(methods(Methods), Options)
  863    ->  (   Methods == '*'
  864        ->  true
  865        ;   memberchk(method(Method), Request),
  866            memberchk(Method, Methods)
  867        )
  868    ;   true
  869    ),
  870    !.
  871supports_method(Request, _Options) :-
  872    memberchk(path(Location), Request),
  873    memberchk(method(Method), Request),
  874    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)
  884action(Action, Request, Options) :-
  885    memberchk(chunked, Options),
  886    !,
  887    format('Transfer-encoding: chunked~n'),
  888    spawn_action(Action, Request, Options).
  889action(Action, Request, Options) :-
  890    spawn_action(Action, Request, Options).
  891
  892spawn_action(Action, Request, Options) :-
  893    option(spawn(Spawn), Options),
  894    !,
  895    spawn_options(Spawn, SpawnOption),
  896    http_spawn(time_limit_action(Action, Request, Options), SpawnOption).
  897spawn_action(Action, Request, Options) :-
  898    time_limit_action(Action, Request, Options).
  899
  900spawn_options([], []) :- !.
  901spawn_options(Pool, Options) :-
  902    atom(Pool),
  903    !,
  904    Options = [pool(Pool)].
  905spawn_options(List, List).
  906
  907time_limit_action(Action, Request, Options) :-
  908    (   option(time_limit(TimeLimit), Options),
  909        TimeLimit \== default
  910    ->  true
  911    ;   setting(http:time_limit, TimeLimit)
  912    ),
  913    number(TimeLimit),
  914    TimeLimit > 0,
  915    !,
  916    call_with_time_limit(TimeLimit, call_action(Action, Request, Options)).
  917time_limit_action(Action, Request, Options) :-
  918    call_action(Action, Request, Options).
 call_action(+Action, +Request, +Options)
To be done
- reply_file is normal call?
  925call_action(reply_file(File, FileOptions), Request, _Options) :-
  926    !,
  927    http_reply_file(File, FileOptions, Request).
  928call_action(Pred, Request, Options) :-
  929    memberchk(path_info(PathInfo), Options),
  930    !,
  931    call_action(Pred, [path_info(PathInfo)|Request]).
  932call_action(Pred, Request, _Options) :-
  933    call_action(Pred, Request).
  934
  935call_action(Pred, Request) :-
  936    (   call(Pred, Request)
  937    ->  true
  938    ;   extend(Pred, [Request], Goal),
  939        throw(error(goal_failed(Goal), _))
  940    ).
  941
  942extend(Var, _, Var) :-
  943    var(Var),
  944    !.
  945extend(M:G0, Extra, M:G) :-
  946    extend(G0, Extra, G).
  947extend(G0, Extra, G) :-
  948    G0 =.. List,
  949    append(List, Extra, List2),
  950    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))
  986http_reply_file(File, Options, Request) :-
  987    http_safe_file(File, Options),
  988    absolute_file_name(File, Path,
  989                       [ access(read)
  990                       ]),
  991    (   option(cache(true), Options, true)
  992    ->  (   memberchk(if_modified_since(Since), Request),
  993            time_file(Path, Time),
  994            catch(http_timestamp(Time, Since), _, fail)
  995        ->  throw(http_reply(not_modified))
  996        ;   true
  997        ),
  998        (   memberchk(range(Range), Request)
  999        ->  Reply = file(Type, Path, Range)
 1000        ;   option(static_gzip(true), Options),
 1001            accepts_encoding(Request, gzip),
 1002            file_name_extension(Path, gz, PathGZ),
 1003            access_file(PathGZ, read),
 1004            time_file(PathGZ, TimeGZ),
 1005            time_file(Path, Time),
 1006            TimeGZ >= Time
 1007        ->  Reply = gzip_file(Type, PathGZ)
 1008        ;   Reply = file(Type, Path)
 1009        )
 1010    ;   Reply = tmp_file(Type, Path)
 1011    ),
 1012    (   option(mime_type(MediaType), Options)
 1013    ->  file_content_type(Path, MediaType, Type)
 1014    ;   file_content_type(Path, Type)
 1015    ->  true
 1016    ;   Type = text/plain           % fallback type
 1017    ),
 1018    option(headers(Headers), Options, []),
 1019    throw(http_reply(Reply, Headers)).
 1020
 1021accepts_encoding(Request, Enc) :-
 1022    memberchk(accept_encoding(Accept), Request),
 1023    split_string(Accept, ",", " ", Parts),
 1024    member(Part, Parts),
 1025    split_string(Part, ";", " ", [EncS|_]),
 1026    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)
 1039http_safe_file(File, _) :-
 1040    var(File),
 1041    !,
 1042    instantiation_error(File).
 1043http_safe_file(_, Options) :-
 1044    option(unsafe(true), Options, false),
 1045    !.
 1046http_safe_file(File, _) :-
 1047    http_safe_file(File).
 1048
 1049http_safe_file(File) :-
 1050    compound(File),
 1051    functor(File, _, 1),
 1052    !,
 1053    arg(1, File, Name),
 1054    safe_name(Name, File).
 1055http_safe_file(Name) :-
 1056    (   is_absolute_file_name(Name)
 1057    ->  permission_error(read, file, Name)
 1058    ;   true
 1059    ),
 1060    safe_name(Name, Name).
 1061
 1062safe_name(Name, _) :-
 1063    must_be(atom, Name),
 1064    prolog_to_os_filename(FileName, Name),
 1065    \+ unsafe_name(FileName),
 1066    !.
 1067safe_name(_, Spec) :-
 1068    permission_error(read, file, Spec).
 1069
 1070unsafe_name(Name) :- Name == '..'.
 1071unsafe_name(Name) :- sub_atom(Name, 0, _, _, '../').
 1072unsafe_name(Name) :- sub_atom(Name, _, _, _, '/../').
 1073unsafe_name(Name) :- sub_atom(Name, _, _, 0, '/..').
 http_redirect(+How, +To, +Request) is det
Redirect to a new location. The argument order, using the Request as last argument, allows for calling this directly from the handler declaration:
:- http_handler(root(.),
                http_redirect(moved, myapp('index.html')),
                []).
Arguments:
How- is one of moved, moved_temporary or see_other
To- is an atom, a aliased path as defined by http_absolute_location/3. or a term location_by_id(Id). If To is not absolute, it is resolved relative to the current location.
 1093http_redirect(How, To, Request) :-
 1094    (   To = location_by_id(Id)
 1095    ->  http_location_by_id(Id, URL)
 1096    ;   memberchk(path(Base), Request),
 1097        http_absolute_location(To, URL, [relative_to(Base)])
 1098    ),
 1099    must_be(oneof([moved, moved_temporary, see_other]), How),
 1100    Term =.. [How,URL],
 1101    throw(http_reply(Term)).
 http_404(+Options, +Request) is det
Reply using an "HTTP 404 not found" page. This handler is intended as fallback handler for prefix handlers. Options processed are:
index(Location)
If there is no path-info, redirect the request to Location using http_redirect/3.
Errors
- http_reply(not_found(Path))
 1116http_404(Options, Request) :-
 1117    option(index(Index), Options),
 1118    \+ ( option(path_info(PathInfo), Request),
 1119         PathInfo \== ''
 1120       ),
 1121    !,
 1122    http_redirect(moved, Index, Request).
 1123http_404(_Options, Request) :-
 1124    option(path(Path), Request),
 1125    !,
 1126    throw(http_reply(not_found(Path))).
 1127http_404(_Options, Request) :-
 1128    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).
 1162%       @throws http_reply(switch_protocol(Goal, Options))
 1163
 1164http_switch_protocol(Goal, Options) :-
 1165    throw(http_reply(switching_protocols(Goal, Options))).
 1166
 1167
 1168                 /*******************************
 1169                 *        PATH COMPILATION      *
 1170                 *******************************/
 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.

 1186path_tree(Tree) :-
 1187    current_generation(G),
 1188    nb_current(http_dispatch_tree, G-Tree),
 1189    !. % Avoid existence error
 1190path_tree(Tree) :-
 1191    path_tree_nocache(Tree),
 1192    current_generation(G),
 1193    nb_setval(http_dispatch_tree, G-Tree).
 1194
 1195path_tree_nocache(Tree) :-
 1196    findall(Prefix, prefix_handler(Prefix, _, _, _), Prefixes0),
 1197    sort(Prefixes0, Prefixes),
 1198    prefix_tree(Prefixes, [], PTree),
 1199    prefix_options(PTree, [], OPTree),
 1200    add_paths_tree(OPTree, Tree).
 1201
 1202prefix_handler(Prefix, Action, Options, Priority-PLen) :-
 1203    handler(Spec, Action, true, Options),
 1204    (   memberchk(priority(Priority), Options)
 1205    ->  true
 1206    ;   Priority = 0
 1207    ),
 1208    (   memberchk(segment_pattern(Pattern), Options)
 1209    ->  length(Pattern, PLen)
 1210    ;   PLen = 0
 1211    ),
 1212    Error = error(existence_error(http_alias,_),_),
 1213    catch(http_absolute_location(Spec, Prefix, []), Error,
 1214          (   print_message(warning, Error),
 1215              fail
 1216          )).
 prefix_tree(PrefixList, +Tree0, -Tree)
Arguments:
Tree- list(Prefix-list(Children))
 1222prefix_tree([], Tree, Tree).
 1223prefix_tree([H|T], Tree0, Tree) :-
 1224    insert_prefix(H, Tree0, Tree1),
 1225    prefix_tree(T, Tree1, Tree).
 1226
 1227insert_prefix(Prefix, Tree0, Tree) :-
 1228    select(P-T, Tree0, Tree1),
 1229    sub_atom(Prefix, 0, _, _, P),
 1230    !,
 1231    insert_prefix(Prefix, T, T1),
 1232    Tree = [P-T1|Tree1].
 1233insert_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?
 1242prefix_options([], _, []).
 1243prefix_options([Prefix-C|T0], DefOptions,
 1244               [node(prefix(Prefix), Action, PrefixOptions, Children)|T]) :-
 1245    findall(h(A,O,P), prefix_handler(Prefix,A,O,P), Handlers),
 1246    sort(3, >=, Handlers, Handlers1),
 1247    Handlers1 = [h(_,_,P0)|_],
 1248    same_priority_handlers(Handlers1, P0, Same),
 1249    option_patterns(Same, SegmentPatterns, Action),
 1250    last(Same, h(_, Options0, _-_)),
 1251    merge_options(Options0, DefOptions, Options),
 1252    append(SegmentPatterns, Options, PrefixOptions),
 1253    exclude(no_inherit, Options, InheritOpts),
 1254    prefix_options(C, InheritOpts, Children),
 1255    prefix_options(T0, DefOptions, T).
 1256
 1257no_inherit(id(_)).
 1258no_inherit('$extract'(_)).
 1259
 1260same_priority_handlers([H|T0], P, [H|T]) :-
 1261    H = h(_,_,P0-_),
 1262    P = P0-_,
 1263    !,
 1264    same_priority_handlers(T0, P, T).
 1265same_priority_handlers(_, _, []).
 1266
 1267option_patterns([], [], nop).
 1268option_patterns([h(A,_,_-0)|_], [], A) :-
 1269    !.
 1270option_patterns([h(A,O,_)|T0], [segment_pattern(P,A,O)|T], AF) :-
 1271    memberchk(segment_pattern(P), O),
 1272    option_patterns(T0, T, AF).
 add_paths_tree(+OPTree, -Tree) is det
Add the plain paths.
 1279add_paths_tree(OPTree, Tree) :-
 1280    findall(path(Path, Action, Options),
 1281            plain_path(Path, Action, Options),
 1282            Triples),
 1283    add_paths_tree(Triples, OPTree, Tree).
 1284
 1285add_paths_tree([], Tree, Tree).
 1286add_paths_tree([path(Path, Action, Options)|T], Tree0, Tree) :-
 1287    add_path_tree(Path, Action, Options, [], Tree0, Tree1),
 1288    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.
 1296plain_path(Path, Action, Options) :-
 1297    handler(Spec, Action, false, Options),
 1298    catch(http_absolute_location(Spec, Path, []), E,
 1299          (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.
 1308add_path_tree(Path, Action, Options0, DefOptions, [],
 1309              [node(Path, Action, Options, [])]) :-
 1310    !,
 1311    merge_options(Options0, DefOptions, Options).
 1312add_path_tree(Path, Action, Options, _,
 1313              [node(prefix(Prefix), PA, DefOptions, Children0)|RestTree],
 1314              [node(prefix(Prefix), PA, DefOptions, Children)|RestTree]) :-
 1315    sub_atom(Path, 0, _, _, Prefix),
 1316    !,
 1317    delete(DefOptions, id(_), InheritOpts),
 1318    add_path_tree(Path, Action, Options, InheritOpts, Children0, Children).
 1319add_path_tree(Path, Action, Options1, DefOptions, [H0|T], [H|T]) :-
 1320    H0 = node(Path, _, Options2, _),
 1321    option(priority(P1), Options1, 0),
 1322    option(priority(P2), Options2, 0),
 1323    P1 >= P2,
 1324    !,
 1325    merge_options(Options1, DefOptions, Options),
 1326    H = node(Path, Action, Options, []).
 1327add_path_tree(Path, Action, Options, DefOptions, [H|T0], [H|T]) :-
 1328    add_path_tree(Path, Action, Options, DefOptions, T0, T).
 1329
 1330
 1331                 /*******************************
 1332                 *            MESSAGES          *
 1333                 *******************************/
 1334
 1335:- multifile
 1336    prolog:message/3. 1337
 1338prolog:message(http_dispatch(ambiguous_id(ID, _List, Selected))) -->
 1339    [ 'HTTP dispatch: ambiguous handler ID ~q (selected ~q)'-[ID, Selected]
 1340    ].
 1341
 1342
 1343                 /*******************************
 1344                 *            XREF              *
 1345                 *******************************/
 1346
 1347:- multifile
 1348    prolog:meta_goal/2. 1349:- dynamic
 1350    prolog:meta_goal/2. 1351
 1352prolog:meta_goal(http_handler(_, G, _), [G+1]).
 1353prolog:meta_goal(http_current_handler(_, G), [G+1]).
 1354
 1355
 1356                 /*******************************
 1357                 *             EDIT             *
 1358                 *******************************/
 1359
 1360% Allow edit(Location) to edit the implementation for an HTTP location.
 1361
 1362:- multifile
 1363    prolog_edit:locate/3. 1364
 1365prolog_edit:locate(Path, Spec, Location) :-
 1366    atom(Path),
 1367    sub_atom(Path, 0, _, _, /),
 1368    Pred = _M:_H,
 1369    catch(http_current_handler(Path, Pred), _, fail),
 1370    closure_name_arity(Pred, 1, PI),
 1371    prolog_edit:locate(PI, Spec, Location).
 1372
 1373closure_name_arity(M:Term, Extra, M:Name/Arity) :-
 1374    !,
 1375    callable(Term),
 1376    functor(Term, Name, Arity0),
 1377    Arity is Arity0 + Extra.
 1378closure_name_arity(Term, Extra, Name/Arity) :-
 1379    callable(Term),
 1380    functor(Term, Name, Arity0),
 1381    Arity is Arity0 + Extra.
 1382
 1383
 1384                 /*******************************
 1385                 *        CACHE CLEANUP         *
 1386                 *******************************/
 1387
 1388:- listen(settings(changed(http:prefix, _, _)),
 1389          next_generation). 1390
 1391:- multifile
 1392    user:message_hook/3. 1393:- dynamic
 1394    user:message_hook/3. 1395
 1396user:message_hook(make(done(Reload)), _Level, _Lines) :-
 1397    Reload \== [],
 1398    next_generation,
 1399    fail