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)  2008-2020, 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_path,
   37          [ http_absolute_uri/2,        % +Spec, -URI
   38            http_absolute_location/3,   % +Spec, -Path, +Options
   39            http_clean_location_cache/0
   40          ]).   41:- autoload(library(apply),[exclude/3]).   42:- autoload(library(broadcast),[listen/2]).   43:- autoload(library(debug),[debug/3]).   44:- autoload(library(error),
   45	    [must_be/2,existence_error/2,instantiation_error/1]).   46:- autoload(library(lists),[reverse/2,append/3]).   47:- autoload(library(option),[option/3]).   48:- autoload(library(pairs),[pairs_values/2]).   49:- autoload(library(uri),
   50	    [ uri_authority_data/3, uri_authority_components/2,
   51	      uri_data/3, uri_components/2, uri_normalized/3
   52	    ]).   53:- autoload(library(http/http_host),[http_current_host/4]).   54:- use_module(library(settings),[setting/4,setting/2]).   55
   56:- predicate_options(http_absolute_location/3, 3, [relative_to(atom)]).

Abstract specification of HTTP server locations

This module provides an abstract specification of HTTP server locations that is inspired on absolute_file_name/3. The specification is done by adding rules to the dynamic multifile predicate http:location/3. The speficiation is very similar to file_search_path/2, but takes an additional argument with options. Currently only one option is defined:

priority(+Integer)
If two rules match, take the one with highest priority. Using priorities is needed because we want to be able to overrule paths, but we do not want to become dependent on clause ordering.

The default priority is 0. Note however that notably libraries may decide to provide a fall-back using a negative priority. We suggest -100 for such cases.

This library predefines a single location at priority -100:

root
The root of the server. Default is /, but this may be overruled using the setting (see setting/2) http:prefix

To serve additional resource files such as CSS, JavaScript and icons, see library(http/http_server_files).

Here is an example that binds /login to login/1. The user can reuse this application while moving all locations using a new rule for the admin location with the option [priority(10)].

:- multifile http:location/3.
:- dynamic   http:location/3.

http:location(admin, /, []).

:- http_handler(admin(login), login, []).

login(Request) :-
        ...

*/

  101:- setting(http:prefix, atom, '',
  102           'Prefix for all locations of this server').
 http:location(+Alias, -Expansion, -Options) is nondet
Multifile hook used to specify new HTTP locations. Alias is the name of the abstract path. Expansion is either a term Alias2(Relative), telling http_absolute_location/3 to translate Alias by first translating Alias2 and then applying the relative path Relative or, Expansion is an absolute location, i.e., one that starts with a /. Options currently only supports the priority of the path. If http:location/3 returns multiple solutions the one with the highest priority is selected. The default priority is 0.

This library provides a default for the abstract location root. This defaults to the setting http:prefix or, when not available to the path /. It is adviced to define all locations (ultimately) relative to root. For example, use root('home.html') rather than '/home.html'.

  122:- multifile
  123    http:location/3.                % Alias, Expansion, Options
  124:- dynamic
  125    http:location/3.                % Alias, Expansion, Options
  126
  127http:location(root, Root, [priority(-100)]) :-
  128    (   setting(http:prefix, Prefix),
  129        Prefix \== ''
  130    ->  Root = Prefix
  131    ;   Root = (/)
  132    ).
 http_absolute_uri(+Spec, -URI) is det
URI is the absolute (i.e., starting with http://) URI for the abstract specification Spec. Use http_absolute_location/3 to create references to locations on the same server.
To be done
- Distinguish http from https
  143http_absolute_uri(Spec, URI) :-
  144    http_current_host(_Request, Host, Port,
  145                      [ global(true)
  146                      ]),
  147    http_absolute_location(Spec, Path, []),
  148    uri_authority_data(host, AuthC, Host),
  149    (   Port == 80                  % HTTP scheme
  150    ->  true
  151    ;   uri_authority_data(port, AuthC, Port)
  152    ),
  153    uri_authority_components(Authority, AuthC),
  154    uri_data(path, Components, Path),
  155    uri_data(scheme, Components, http),
  156    uri_data(authority, Components, Authority),
  157    uri_components(URI, Components).
 http_absolute_location(+Spec, -Path, +Options) is det
Path is the HTTP location for the abstract specification Spec. Options:
relative_to(Base)
Path is made relative to Base. Default is to generate absolute URLs.
See also
- http_absolute_uri/2 to create a reference that can be used on another server.
  172:- dynamic
  173    location_cache/3.  174
  175http_absolute_location(Spec, Path, Options) :-
  176    must_be(ground, Spec),
  177    option(relative_to(Base), Options, /),
  178    absolute_location(Spec, Base, Path, Options),
  179    debug(http_path, '~q (~q) --> ~q', [Spec, Base, Path]).
  180
  181absolute_location(Spec, Base, Path, _Options) :-
  182    location_cache(Spec, Base, Cache),
  183    !,
  184    Path = Cache.
  185absolute_location(Spec, Base, Path, Options) :-
  186    expand_location(Spec, Base, L, Options),
  187    assert(location_cache(Spec, Base, L)),
  188    Path = L.
  189
  190expand_location(Spec, Base, Path, _Options) :-
  191    atomic(Spec),
  192    !,
  193    (   uri_components(Spec, Components),
  194        uri_data(scheme, Components, Scheme),
  195        atom(Scheme)
  196    ->  Path = Spec
  197    ;   relative_to(Base, Spec, Path)
  198    ).
  199expand_location(Spec, _Base, Path, Options) :-
  200    Spec =.. [Alias, Sub],
  201    http_location_path(Alias, Parent),
  202    absolute_location(Parent, /, ParentLocation, Options),
  203    phrase(path_list(Sub), List),
  204    atomic_list_concat(List, /, SubAtom),
  205    (   ParentLocation == ''
  206    ->  Path = SubAtom
  207    ;   sub_atom(ParentLocation, _, _, 0, /)
  208    ->  atom_concat(ParentLocation, SubAtom, Path)
  209    ;   atomic_list_concat([ParentLocation, SubAtom], /, Path)
  210    ).
 http_location_path(+Alias, -Expansion) is det
Expansion is the expanded HTTP location for Alias. As we have no condition search, we demand a single expansion for an alias. An ambiguous alias results in a printed warning. A lacking alias results in an exception.
Errors
- existence_error(http_alias, Alias)
  222http_location_path(Alias, Path) :-
  223    findall(P-L, http_location_path(Alias, L, P), Pairs),
  224    sort(Pairs, Sorted0),
  225    reverse(Sorted0, Result),
  226    (   Result = [_-One]
  227    ->  Path = One
  228    ;   Result == []
  229    ->  existence_error(http_alias, Alias)
  230    ;   Result = [P-Best,P2-_|_],
  231        P \== P2
  232    ->  Path = Best
  233    ;   Result = [_-First|_],
  234        pairs_values(Result, Paths),
  235        print_message(warning, http(ambiguous_location(Alias, Paths))),
  236        Path = First
  237    ).
 http_location_path(+Alias, -Path, -Priority) is nondet
To be done
- prefix(Path) is discouraged; use root(Path)
  244http_location_path(Alias, Path, Priority) :-
  245    http:location(Alias, Path, Options),
  246    option(priority(Priority), Options, 0).
  247http_location_path(prefix, Path, 0) :-
  248    (   catch(setting(http:prefix, Prefix), _, fail),
  249        Prefix \== ''
  250    ->  (   sub_atom(Prefix, 0, _, _, /)
  251        ->  Path = Prefix
  252        ;   atom_concat(/, Prefix, Path)
  253        )
  254    ;   Path = /
  255    ).
 relative_to(+Base, +Path, -AbsPath) is det
AbsPath is an absolute URL location created from Base and Path. The result is cleaned
  263relative_to(/, Path, Path) :- !.
  264relative_to(_Base, Path, Path) :-
  265    sub_atom(Path, 0, _, _, /),
  266    !.
  267relative_to(Base, Local, Path) :-
  268    sub_atom(Base, 0, _, _, /),    % file version
  269    !,
  270    path_segments(Base, BaseSegments),
  271    append(BaseDir, [_], BaseSegments) ->
  272    path_segments(Local, LocalSegments),
  273    append(BaseDir, LocalSegments, Segments0),
  274    clean_segments(Segments0, Segments),
  275    path_segments(Path, Segments).
  276relative_to(Base, Local, Global) :-
  277    uri_normalized(Local, Base, Global).
  278
  279path_segments(Path, Segments) :-
  280    atomic_list_concat(Segments, /, Path).
 clean_segments(+SegmentsIn, -SegmentsOut) is det
Clean a path represented as a segment list, removing empty segments and resolving .. based on syntax.
  287clean_segments([''|T0], [''|T]) :-
  288    !,
  289    exclude(empty_segment, T0, T1),
  290    clean_parent_segments(T1, T).
  291clean_segments(T0, T) :-
  292    exclude(empty_segment, T0, T1),
  293    clean_parent_segments(T1, T).
  294
  295clean_parent_segments([], []).
  296clean_parent_segments([..|T0], T) :-
  297    !,
  298    clean_parent_segments(T0, T).
  299clean_parent_segments([_,..|T0], T) :-
  300    !,
  301    clean_parent_segments(T0, T).
  302clean_parent_segments([H|T0], [H|T]) :-
  303    clean_parent_segments(T0, T).
  304
  305empty_segment('').
  306empty_segment('.').
 path_list(+Spec, -List) is det
Translate seg1/seg2/... into [seg1,seg2,...].
Errors
- instantiation_error
- type_error(atomic, X)
  316path_list(Var) -->
  317    { var(Var),
  318      !,
  319      instantiation_error(Var)
  320    }.
  321path_list(A/B) -->
  322    !,
  323    path_list(A),
  324    path_list(B).
  325path_list(.) -->
  326    !,
  327    [].
  328path_list(A) -->
  329    { must_be(atomic, A) },
  330    [A].
  331
  332
  333                 /*******************************
  334                 *            MESSAGES          *
  335                 *******************************/
  336
  337:- multifile
  338    prolog:message/3.  339
  340prolog:message(http(ambiguous_location(Spec, Paths))) -->
  341    [ 'http_absolute_location/2: ambiguous specification: ~q: ~p'-
  342      [Spec, Paths]
  343    ].
  344
  345
  346                 /*******************************
  347                 *        CACHE CLEANUP         *
  348                 *******************************/
 http_clean_location_cache
HTTP locations resolved through http_absolute_location/3 are cached. This predicate wipes the cache. The cache is automatically wiped by make/0 and if the setting http:prefix is changed.
  357http_clean_location_cache :-
  358    retractall(location_cache(_,_,_)).
  359
  360:- listen(settings(changed(http:prefix, _, _)),
  361          http_clean_location_cache).  362
  363:- multifile
  364    user:message_hook/3.  365:- dynamic
  366    user:message_hook/3.  367
  368user:message_hook(make(done(Reload)), _Level, _Lines) :-
  369    Reload \== [],
  370    http_clean_location_cache,
  371    fail