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-2017, University of Amsterdam
    7                              VU University Amsterdam
    8    All rights reserved.
    9
   10    Redistribution and use in source and binary forms, with or without
   11    modification, are permitted provided that the following conditions
   12    are met:
   13
   14    1. Redistributions of source code must retain the above copyright
   15       notice, this list of conditions and the following disclaimer.
   16
   17    2. Redistributions in binary form must reproduce the above copyright
   18       notice, this list of conditions and the following disclaimer in
   19       the documentation and/or other materials provided with the
   20       distribution.
   21
   22    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
   23    "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
   24    LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
   25    FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
   26    COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
   27    INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
   28    BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
   29    LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
   30    CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
   31    LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
   32    ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
   33    POSSIBILITY OF SUCH DAMAGE.
   34*/
   35
   36:- module(http_dispatch,
   37          [ http_dispatch/1,            % +Request
   38            http_handler/3,             % +Path, +Predicate, +Options
   39            http_delete_handler/1,      % +Path
   40            http_reply_file/3,          % +File, +Options, +Request
   41            http_redirect/3,            % +How, +Path, +Request
   42            http_404/2,                 % +Options, +Request
   43            http_switch_protocol/2,     % :Goal, +Options
   44            http_current_handler/2,     % ?Path, ?Pred
   45            http_current_handler/3,     % ?Path, ?Pred, -Options
   46            http_location_by_id/2,      % +ID, -Location
   47            http_link_to_id/3,          % +ID, +Parameters, -HREF
   48            http_reload_with_parameters/3, % +Request, +Parameters, -HREF
   49            http_safe_file/2            % +Spec, +Options
   50          ]).   51:- use_module(library(option)).   52:- use_module(library(lists)).   53:- use_module(library(time)).   54:- use_module(library(error)).   55:- use_module(library(settings)).   56:- use_module(library(uri)).   57:- use_module(library(apply)).   58:- use_module(library(http/mimetype)).   59:- use_module(library(http/http_path)).   60:- use_module(library(http/http_header)).   61:- use_module(library(http/thread_httpd)).   62
   63:- predicate_options(http_404/2, 1, [index(any)]).   64:- predicate_options(http_reply_file/3, 2,
   65                     [ cache(boolean),
   66                       mime_type(any),
   67                       static_gzip(boolean),
   68                       pass_to(http_safe_file/2, 2),
   69                       headers(list)
   70                     ]).   71:- predicate_options(http_safe_file/2, 2, [unsafe(boolean)]).   72:- 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) :-
        ...

*/

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

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

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

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

Errors
- domain_error, type_error
See also
- http_absolute_location/3
  352check_path(Path, Path) :-
  353    atom(Path),
  354    !,
  355    (   sub_atom(Path, 0, _, _, /)
  356    ->  true
  357    ;   domain_error(absolute_http_location, Path)
  358    ).
  359check_path(Alias, AliasOut) :-
  360    compound(Alias),
  361    Alias =.. [Name, Relative],
  362    !,
  363    to_atom(Relative, Local),
  364    (   sub_atom(Local, 0, _, _, /)
  365    ->  domain_error(relative_location, Relative)
  366    ;   AliasOut =.. [Name, Local]
  367    ).
  368check_path(PathSpec, _) :-
  369    type_error(path_or_alias, PathSpec).
  370
  371to_atom(Atom, Atom) :-
  372    atom(Atom),
  373    !.
  374to_atom(Path, Atom) :-
  375    phrase(path_to_list(Path), Components),
  376    !,
  377    atomic_list_concat(Components, '/', Atom).
  378to_atom(Path, _) :-
  379    ground(Path),
  380    !,
  381    type_error(relative_location, Path).
  382to_atom(Path, _) :-
  383    instantiation_error(Path).
  384
  385path_to_list(Var) -->
  386    { var(Var),
  387      !,
  388      fail
  389    }.
  390path_to_list(A/B) -->
  391    path_to_list(A),
  392    path_to_list(B).
  393path_to_list(Atom) -->
  394    { atom(Atom) },
  395    [Atom].
 http_dispatch(Request) is det
Dispatch a Request using http_handler/3 registrations.
  403http_dispatch(Request) :-
  404    memberchk(path(Path), Request),
  405    find_handler(Path, Pred, Options),
  406    supports_method(Request, Options),
  407    authentication(Options, Request, Fields),
  408    append(Fields, Request, AuthRequest),
  409    action(Pred, AuthRequest, Options).
 http_current_handler(+Location, :Closure) is semidet
http_current_handler(-Location, :Closure) is nondet
True if Location is handled by Closure.
  417http_current_handler(Path, Closure) :-
  418    atom(Path),
  419    !,
  420    path_tree(Tree),
  421    find_handler(Tree, Path, Closure, _).
  422http_current_handler(Path, M:C) :-
  423    handler(Spec, M:C, _, _),
  424    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.
  431http_current_handler(Path, Closure, Options) :-
  432    atom(Path),
  433    !,
  434    path_tree(Tree),
  435    find_handler(Tree, Path, Closure, Options).
  436http_current_handler(Path, M:C, Options) :-
  437    handler(Spec, M:C, _, _),
  438    http_absolute_location(Spec, Path, []),
  439    path_tree(Tree),
  440    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.
  463:- dynamic
  464    id_location_cache/2.  465
  466http_location_by_id(ID, Location) :-
  467    must_be(ground, ID),
  468    id_location_cache(ID, L0),
  469    !,
  470    Location = L0.
  471http_location_by_id(ID, Location) :-
  472    findall(P-L, location_by_id(ID, L, P), List),
  473    keysort(List, RevSorted),
  474    reverse(RevSorted, Sorted),
  475    (   Sorted = [_-One]
  476    ->  assert(id_location_cache(ID, One)),
  477        Location = One
  478    ;   List == []
  479    ->  existence_error(http_handler_id, ID)
  480    ;   List = [P0-Best,P1-_|_]
  481    ->  (   P0 == P1
  482        ->  print_message(warning,
  483                          http_dispatch(ambiguous_id(ID, Sorted, Best)))
  484        ;   true
  485        ),
  486        assert(id_location_cache(ID, Best)),
  487        Location = Best
  488    ).
  489
  490location_by_id(ID, Location, Priority) :-
  491    location_by_id_raw(ID, L0, Priority),
  492    to_path(L0, Location).
  493
  494to_path(prefix(Path0), Path) :-        % old style prefix notation
  495    !,
  496    add_prefix(Path0, Path).
  497to_path(Path0, Path) :-
  498    atomic(Path0),                 % old style notation
  499    !,
  500    add_prefix(Path0, Path).
  501to_path(Spec, Path) :-                  % new style notation
  502    http_absolute_location(Spec, Path, []).
  503
  504add_prefix(P0, P) :-
  505    (   catch(setting(http:prefix, Prefix), _, fail),
  506        Prefix \== ''
  507    ->  atom_concat(Prefix, P0, P)
  508    ;   P = P0
  509    ).
  510
  511location_by_id_raw(ID, Location, Priority) :-
  512    handler(Location, _, _, Options),
  513    option(id(ID), Options),
  514    option(priority(P0), Options, 0),
  515    Priority is P0+1000.            % id(ID) takes preference over predicate
  516location_by_id_raw(ID, Location, Priority) :-
  517    handler(Location, M:C, _, Options),
  518    option(priority(Priority), Options, 0),
  519    functor(C, PN, _),
  520    (   ID = M:PN
  521    ;   ID = PN
  522    ),
  523    !.
 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.
  566http_link_to_id(HandleID, path_postfix(File), HREF) :-
  567    !,
  568    http_location_by_id(HandleID, HandlerLocation),
  569    uri_encoded(path, File, EncFile),
  570    directory_file_path(HandlerLocation, EncFile, Location),
  571    uri_data(path, Components, Location),
  572    uri_components(HREF, Components).
  573http_link_to_id(HandleID, Parameters, HREF) :-
  574    must_be(list, Parameters),
  575    http_location_by_id(HandleID, Location),
  576    uri_data(path, Components, Location),
  577    uri_query_components(String, Parameters),
  578    uri_data(search, Components, String),
  579    uri_components(HREF, Components).
 http_reload_with_parameters(+Request, +Parameters, -HREF) is det
Create a request on the current handler with replaced search parameters.
  586http_reload_with_parameters(Request, NewParams, HREF) :-
  587    memberchk(path(Path), Request),
  588    (   memberchk(search(Params), Request)
  589    ->  true
  590    ;   Params = []
  591    ),
  592    merge_options(NewParams, Params, AllParams),
  593    uri_query_components(Search, AllParams),
  594    uri_data(path, Data, Path),
  595    uri_data(search, Data, Search),
  596    uri_components(HREF, Data).
  597
  598
  599%       hook into html_write:attribute_value//1.
  600
  601:- multifile
  602    html_write:expand_attribute_value//1.  603
  604html_write:expand_attribute_value(location_by_id(ID)) -->
  605    { http_location_by_id(ID, Location) },
  606    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)
  618:- multifile
  619    http:authenticate/3.  620
  621authentication([], _, []).
  622authentication([authentication(Type)|Options], Request, Fields) :-
  623    !,
  624    (   http:authenticate(Type, Request, XFields)
  625    ->  append(XFields, More, Fields),
  626        authentication(Options, Request, More)
  627    ;   memberchk(path(Path), Request),
  628        permission_error(access, http_location, Path)
  629    ).
  630authentication([_|Options], Request, Fields) :-
  631    authentication(Options, Request, Fields).
 find_handler(+Path, -Action, -Options) is det
Find the handler to call from Path. Rules:

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

Errors
- existence_error(http_location, Location) @throw http_reply(moved(Dir))
To be done
- Introduce automatic redirection to indexes here?
  650find_handler(Path, Action, Options) :-
  651    path_tree(Tree),
  652    (   find_handler(Tree, Path, Action, Options),
  653        eval_condition(Options)
  654    ->  true
  655    ;   \+ sub_atom(Path, _, _, 0, /),
  656        atom_concat(Path, /, Dir),
  657        find_handler(Tree, Dir, Action, Options)
  658    ->  throw(http_reply(moved(Dir)))
  659    ;   throw(error(existence_error(http_location, Path), _))
  660    ).
  661
  662
  663find_handler([node(prefix(Prefix), PAction, POptions, Children)|_],
  664             Path, Action, Options) :-
  665    sub_atom(Path, 0, _, After, Prefix),
  666    !,
  667    (   option(hide_children(false), POptions, false),
  668        find_handler(Children, Path, Action, Options)
  669    ->  true
  670    ;   Action = PAction,
  671        path_info(After, Path, POptions, Options)
  672    ).
  673find_handler([node(Path, Action, Options, _)|_], Path, Action, Options) :- !.
  674find_handler([_|Tree], Path, Action, Options) :-
  675    find_handler(Tree, Path, Action, Options).
  676
  677path_info(0, _, Options,
  678          [prefix(true)|Options]) :- !.
  679path_info(After, Path, Options,
  680          [path_info(PathInfo),prefix(true)|Options]) :-
  681    sub_atom(Path, _, After, 0, PathInfo).
  682
  683eval_condition(Options) :-
  684    (   memberchk(condition(Cond), Options)
  685    ->  catch(Cond, E, (print_message(warning, E), fail))
  686    ;   true
  687    ).
 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).
  698supports_method(Request, Options) :-
  699    (   option(methods(Methods), Options)
  700    ->  (   Methods == '*'
  701        ->  true
  702        ;   memberchk(method(Method), Request),
  703            memberchk(Method, Methods)
  704        )
  705    ;   true
  706    ),
  707    !.
  708supports_method(Request, _Options) :-
  709    memberchk(path(Location), Request),
  710    memberchk(method(Method), Request),
  711    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)
  721action(Action, Request, Options) :-
  722    memberchk(chunked, Options),
  723    !,
  724    format('Transfer-encoding: chunked~n'),
  725    spawn_action(Action, Request, Options).
  726action(Action, Request, Options) :-
  727    spawn_action(Action, Request, Options).
  728
  729spawn_action(Action, Request, Options) :-
  730    option(spawn(Spawn), Options),
  731    !,
  732    spawn_options(Spawn, SpawnOption),
  733    http_spawn(time_limit_action(Action, Request, Options), SpawnOption).
  734spawn_action(Action, Request, Options) :-
  735    time_limit_action(Action, Request, Options).
  736
  737spawn_options([], []) :- !.
  738spawn_options(Pool, Options) :-
  739    atom(Pool),
  740    !,
  741    Options = [pool(Pool)].
  742spawn_options(List, List).
  743
  744time_limit_action(Action, Request, Options) :-
  745    (   option(time_limit(TimeLimit), Options),
  746        TimeLimit \== default
  747    ->  true
  748    ;   setting(http:time_limit, TimeLimit)
  749    ),
  750    number(TimeLimit),
  751    TimeLimit > 0,
  752    !,
  753    call_with_time_limit(TimeLimit, call_action(Action, Request, Options)).
  754time_limit_action(Action, Request, Options) :-
  755    call_action(Action, Request, Options).
 call_action(+Action, +Request, +Options)
To be done
- reply_file is normal call?
  762call_action(reply_file(File, FileOptions), Request, _Options) :-
  763    !,
  764    http_reply_file(File, FileOptions, Request).
  765call_action(Pred, Request, Options) :-
  766    memberchk(path_info(PathInfo), Options),
  767    !,
  768    call_action(Pred, [path_info(PathInfo)|Request]).
  769call_action(Pred, Request, _Options) :-
  770    call_action(Pred, Request).
  771
  772call_action(Pred, Request) :-
  773    (   call(Pred, Request)
  774    ->  true
  775    ;   extend(Pred, [Request], Goal),
  776        throw(error(goal_failed(Goal), _))
  777    ).
  778
  779extend(Var, _, Var) :-
  780    var(Var),
  781    !.
  782extend(M:G0, Extra, M:G) :-
  783    extend(G0, Extra, G).
  784extend(G0, Extra, G) :-
  785    G0 =.. List,
  786    append(List, Extra, List2),
  787    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))
  823http_reply_file(File, Options, Request) :-
  824    http_safe_file(File, Options),
  825    absolute_file_name(File, Path,
  826                       [ access(read)
  827                       ]),
  828    (   option(cache(true), Options, true)
  829    ->  (   memberchk(if_modified_since(Since), Request),
  830            time_file(Path, Time),
  831            catch(http_timestamp(Time, Since), _, fail)
  832        ->  throw(http_reply(not_modified))
  833        ;   true
  834        ),
  835        (   memberchk(range(Range), Request)
  836        ->  Reply = file(Type, Path, Range)
  837        ;   option(static_gzip(true), Options),
  838            accepts_encoding(Request, gzip),
  839            file_name_extension(Path, gz, PathGZ),
  840            access_file(PathGZ, read),
  841            time_file(PathGZ, TimeGZ),
  842            time_file(Path, Time),
  843            TimeGZ >= Time
  844        ->  Reply = gzip_file(Type, PathGZ)
  845        ;   Reply = file(Type, Path)
  846        )
  847    ;   Reply = tmp_file(Type, Path)
  848    ),
  849    (   option(mime_type(Type), Options)
  850    ->  true
  851    ;   file_mime_type(Path, Type)
  852    ->  true
  853    ;   Type = text/plain           % fallback type
  854    ),
  855    option(headers(Headers), Options, []),
  856    throw(http_reply(Reply, Headers)).
  857
  858accepts_encoding(Request, Enc) :-
  859    memberchk(accept_encoding(Accept), Request),
  860    split_string(Accept, ",", " ", Parts),
  861    member(Part, Parts),
  862    split_string(Part, ";", " ", [EncS|_]),
  863    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)
  876http_safe_file(File, _) :-
  877    var(File),
  878    !,
  879    instantiation_error(File).
  880http_safe_file(_, Options) :-
  881    option(unsafe(true), Options, false),
  882    !.
  883http_safe_file(File, _) :-
  884    http_safe_file(File).
  885
  886http_safe_file(File) :-
  887    compound(File),
  888    functor(File, _, 1),
  889    !,
  890    arg(1, File, Name),
  891    safe_name(Name, File).
  892http_safe_file(Name) :-
  893    (   is_absolute_file_name(Name)
  894    ->  permission_error(read, file, Name)
  895    ;   true
  896    ),
  897    safe_name(Name, Name).
  898
  899safe_name(Name, _) :-
  900    must_be(atom, Name),
  901    prolog_to_os_filename(FileName, Name),
  902    \+ unsafe_name(FileName),
  903    !.
  904safe_name(_, Spec) :-
  905    permission_error(read, file, Spec).
  906
  907unsafe_name(Name) :- Name == '..'.
  908unsafe_name(Name) :- sub_atom(Name, 0, _, _, '../').
  909unsafe_name(Name) :- sub_atom(Name, _, _, _, '/../').
  910unsafe_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.
  930http_redirect(How, To, Request) :-
  931    (   To = location_by_id(Id)
  932    ->  http_location_by_id(Id, URL)
  933    ;   memberchk(path(Base), Request),
  934        http_absolute_location(To, URL, [relative_to(Base)])
  935    ),
  936    must_be(oneof([moved, moved_temporary, see_other]), How),
  937    Term =.. [How,URL],
  938    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))
  953http_404(Options, Request) :-
  954    option(index(Index), Options),
  955    \+ ( option(path_info(PathInfo), Request),
  956         PathInfo \== ''
  957       ),
  958    !,
  959    http_redirect(moved, Index, Request).
  960http_404(_Options, Request) :-
  961    option(path(Path), Request),
  962    !,
  963    throw(http_reply(not_found(Path))).
  964http_404(_Options, Request) :-
  965    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).
  999%       @throws http_reply(switch_protocol(Goal, Options))
 1000
 1001http_switch_protocol(Goal, Options) :-
 1002    throw(http_reply(switching_protocols(Goal, Options))).
 1003
 1004
 1005                 /*******************************
 1006                 *        PATH COMPILATION      *
 1007                 *******************************/
 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.

 1023path_tree(Tree) :-
 1024    current_generation(G),
 1025    nb_current(http_dispatch_tree, G-Tree),
 1026    !. % Avoid existence error
 1027path_tree(Tree) :-
 1028    path_tree_nocache(Tree),
 1029    current_generation(G),
 1030    nb_setval(http_dispatch_tree, G-Tree).
 1031
 1032path_tree_nocache(Tree) :-
 1033    findall(Prefix, prefix_handler(Prefix, _, _), Prefixes0),
 1034    sort(Prefixes0, Prefixes),
 1035    prefix_tree(Prefixes, [], PTree),
 1036    prefix_options(PTree, [], OPTree),
 1037    add_paths_tree(OPTree, Tree).
 1038
 1039prefix_handler(Prefix, Action, Options) :-
 1040    handler(Spec, Action, true, Options),
 1041    Error = error(existence_error(http_alias,_),_),
 1042    catch(http_absolute_location(Spec, Prefix, []), Error,
 1043          (   print_message(warning, Error),
 1044              fail
 1045          )).
 prefix_tree(PrefixList, +Tree0, -Tree)
Arguments:
Tree- list(Prefix-list(Children))
 1051prefix_tree([], Tree, Tree).
 1052prefix_tree([H|T], Tree0, Tree) :-
 1053    insert_prefix(H, Tree0, Tree1),
 1054    prefix_tree(T, Tree1, Tree).
 1055
 1056insert_prefix(Prefix, Tree0, Tree) :-
 1057    select(P-T, Tree0, Tree1),
 1058    sub_atom(Prefix, 0, _, _, P),
 1059    !,
 1060    insert_prefix(Prefix, T, T1),
 1061    Tree = [P-T1|Tree1].
 1062insert_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?
 1071prefix_options([], _, []).
 1072prefix_options([P-C|T0], DefOptions,
 1073               [node(prefix(P), Action, Options, Children)|T]) :-
 1074    once(prefix_handler(P, Action, Options0)),
 1075    merge_options(Options0, DefOptions, Options),
 1076    delete(Options, id(_), InheritOpts),
 1077    prefix_options(C, InheritOpts, Children),
 1078    prefix_options(T0, DefOptions, T).
 add_paths_tree(+OPTree, -Tree) is det
Add the plain paths.
 1085add_paths_tree(OPTree, Tree) :-
 1086    findall(path(Path, Action, Options),
 1087            plain_path(Path, Action, Options),
 1088            Triples),
 1089    add_paths_tree(Triples, OPTree, Tree).
 1090
 1091add_paths_tree([], Tree, Tree).
 1092add_paths_tree([path(Path, Action, Options)|T], Tree0, Tree) :-
 1093    add_path_tree(Path, Action, Options, [], Tree0, Tree1),
 1094    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.
 1102plain_path(Path, Action, Options) :-
 1103    handler(Spec, Action, false, Options),
 1104    catch(http_absolute_location(Spec, Path, []), E,
 1105          (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.
 1114add_path_tree(Path, Action, Options0, DefOptions, [],
 1115              [node(Path, Action, Options, [])]) :-
 1116    !,
 1117    merge_options(Options0, DefOptions, Options).
 1118add_path_tree(Path, Action, Options, _,
 1119              [node(prefix(Prefix), PA, DefOptions, Children0)|RestTree],
 1120              [node(prefix(Prefix), PA, DefOptions, Children)|RestTree]) :-
 1121    sub_atom(Path, 0, _, _, Prefix),
 1122    !,
 1123    delete(DefOptions, id(_), InheritOpts),
 1124    add_path_tree(Path, Action, Options, InheritOpts, Children0, Children).
 1125add_path_tree(Path, Action, Options1, DefOptions, [H0|T], [H|T]) :-
 1126    H0 = node(Path, _, Options2, _),
 1127    option(priority(P1), Options1, 0),
 1128    option(priority(P2), Options2, 0),
 1129    P1 >= P2,
 1130    !,
 1131    merge_options(Options1, DefOptions, Options),
 1132    H = node(Path, Action, Options, []).
 1133add_path_tree(Path, Action, Options, DefOptions, [H|T0], [H|T]) :-
 1134    add_path_tree(Path, Action, Options, DefOptions, T0, T).
 1135
 1136
 1137                 /*******************************
 1138                 *            MESSAGES          *
 1139                 *******************************/
 1140
 1141:- multifile
 1142    prolog:message/3. 1143
 1144prolog:message(http_dispatch(ambiguous_id(ID, _List, Selected))) -->
 1145    [ 'HTTP dispatch: ambiguous handler ID ~q (selected ~q)'-[ID, Selected]
 1146    ].
 1147
 1148
 1149                 /*******************************
 1150                 *            XREF              *
 1151                 *******************************/
 1152
 1153:- multifile
 1154    prolog:meta_goal/2. 1155:- dynamic
 1156    prolog:meta_goal/2. 1157
 1158prolog:meta_goal(http_handler(_, G, _), [G+1]).
 1159prolog:meta_goal(http_current_handler(_, G), [G+1]).
 1160
 1161
 1162                 /*******************************
 1163                 *             EDIT             *
 1164                 *******************************/
 1165
 1166% Allow edit(Location) to edit the implementation for an HTTP location.
 1167
 1168:- multifile
 1169    prolog_edit:locate/3. 1170
 1171prolog_edit:locate(Path, Spec, Location) :-
 1172    atom(Path),
 1173    sub_atom(Path, 0, _, _, /),
 1174    Pred = _M:_H,
 1175    catch(http_current_handler(Path, Pred), _, fail),
 1176    closure_name_arity(Pred, 1, PI),
 1177    prolog_edit:locate(PI, Spec, Location).
 1178
 1179closure_name_arity(M:Term, Extra, M:Name/Arity) :-
 1180    !,
 1181    callable(Term),
 1182    functor(Term, Name, Arity0),
 1183    Arity is Arity0 + Extra.
 1184closure_name_arity(Term, Extra, Name/Arity) :-
 1185    callable(Term),
 1186    functor(Term, Name, Arity0),
 1187    Arity is Arity0 + Extra.
 1188
 1189
 1190                 /*******************************
 1191                 *        CACHE CLEANUP         *
 1192                 *******************************/
 1193
 1194:- listen(settings(changed(http:prefix, _, _)),
 1195          next_generation). 1196
 1197:- multifile
 1198    user:message_hook/3. 1199:- dynamic
 1200    user:message_hook/3. 1201
 1202user:message_hook(make(done(Reload)), _Level, _Lines) :-
 1203    Reload \== [],
 1204    next_generation,
 1205    fail