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-2022, University of Amsterdam
    7                              VU University Amsterdam
    8                              SWI-Prolog Solutions b.v.
    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_path,
   38          [ http_absolute_location/3,   % +Spec, -Path, +Options
   39            http_clean_location_cache/0
   40          ]).   41:- use_module(library(debug),[debug/3]).   42:- if(exists_source(library(http/http_host))).   43:- autoload(library(http/http_host),[http_public_host/4]).   44:- export(http_absolute_uri/2).         % +Spec, -URI
   45:- endif.   46:- autoload(library(apply),[exclude/3]).   47:- autoload(library(broadcast),[listen/2]).   48:- autoload(library(error),
   49	    [must_be/2,existence_error/2,instantiation_error/1]).   50:- autoload(library(lists),[reverse/2,append/3]).   51:- autoload(library(option),[option/3]).   52:- autoload(library(pairs),[pairs_values/2]).   53:- autoload(library(uri),
   54	    [ uri_authority_data/3, uri_authority_components/2,
   55	      uri_data/3, uri_components/2, uri_normalized/3
   56	    ]).   57:- use_module(library(settings),[setting/4,setting/2]).   58
   59:- 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 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) :-
        ...

*/

  104:- setting(http:prefix, atom, '',
  105           '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 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'.

  125:- multifile
  126    http:location/3.                % Alias, Expansion, Options
  127:- dynamic
  128    http:location/3.                % Alias, Expansion, Options
  129
  130http:location(root, Root, [priority(-100)]) :-
  131    (   setting(http:prefix, Prefix),
  132        Prefix \== ''
  133    ->  Root = Prefix
  134    ;   Root = (/)
  135    ).
  136
  137:- if(current_predicate(http_public_host/4)).
 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.
  144http_absolute_uri(Spec, URI) :-
  145    http_public_host(_Request, Host, Port,
  146                     [ global(true)
  147                     ]),
  148    (   setting(http:public_scheme, Scheme)
  149    ->  true
  150    ;   default_port(Scheme, Port)
  151    ->  true
  152    ;   Scheme = http
  153    ),
  154    http_absolute_location(Spec, Path, []),
  155    uri_authority_data(host, AuthC, Host),
  156    (   default_port(Scheme, Port)
  157    ->  true
  158    ;   uri_authority_data(port, AuthC, Port)
  159    ),
  160    uri_authority_components(Authority, AuthC),
  161    uri_data(path, Components, Path),
  162    uri_data(scheme, Components, Scheme),
  163    uri_data(authority, Components, Authority),
  164    uri_components(URI, Components).
  165
  166default_port(http,  80).
  167default_port(https, 443).
  168
  169:- endif.
 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.
  184:- dynamic
  185    location_cache/3.  186
  187http_absolute_location(Spec, Path, Options) :-
  188    must_be(ground, Spec),
  189    option(relative_to(Base), Options, /),
  190    absolute_location(Spec, Base, Path, Options),
  191    debug(http_path, '~q (~q) --> ~q', [Spec, Base, Path]).
  192
  193absolute_location(Spec, Base, Path, _Options) :-
  194    location_cache(Spec, Base, Cache),
  195    !,
  196    Path = Cache.
  197absolute_location(Spec, Base, Path, Options) :-
  198    expand_location(Spec, Base, L, Options),
  199    assert(location_cache(Spec, Base, L)),
  200    Path = L.
  201
  202expand_location(Spec, Base, Path, _Options) :-
  203    atomic(Spec),
  204    !,
  205    (   uri_components(Spec, Components),
  206        uri_data(scheme, Components, Scheme),
  207        atom(Scheme)
  208    ->  Path = Spec
  209    ;   relative_to(Base, Spec, Path)
  210    ).
  211expand_location(Spec, _Base, Path, Options) :-
  212    Spec =.. [Alias, Sub],
  213    http_location_path(Alias, Parent),
  214    absolute_location(Parent, /, ParentLocation, Options),
  215    phrase(path_list(Sub), List),
  216    atomic_list_concat(List, /, SubAtom),
  217    (   ParentLocation == ''
  218    ->  Path = SubAtom
  219    ;   sub_atom(ParentLocation, _, _, 0, /)
  220    ->  atom_concat(ParentLocation, SubAtom, Path)
  221    ;   atomic_list_concat([ParentLocation, SubAtom], /, Path)
  222    ).
 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)
  234http_location_path(Alias, Path) :-
  235    findall(P-L, http_location_path(Alias, L, P), Pairs),
  236    sort(Pairs, Sorted0),
  237    reverse(Sorted0, Result),
  238    (   Result = [_-One]
  239    ->  Path = One
  240    ;   Result == []
  241    ->  existence_error(http_alias, Alias)
  242    ;   Result = [P-Best,P2-_|_],
  243        P \== P2
  244    ->  Path = Best
  245    ;   Result = [_-First|_],
  246        pairs_values(Result, Paths),
  247        print_message(warning, http(ambiguous_location(Alias, Paths))),
  248        Path = First
  249    ).
 http_location_path(+Alias, -Path, -Priority) is nondet
To be done
- prefix(Path) is discouraged; use root(Path)
  256http_location_path(Alias, Path, Priority) :-
  257    http:location(Alias, Path, Options),
  258    option(priority(Priority), Options, 0).
  259http_location_path(prefix, Path, 0) :-
  260    (   catch(setting(http:prefix, Prefix), _, fail),
  261        Prefix \== ''
  262    ->  (   sub_atom(Prefix, 0, _, _, /)
  263        ->  Path = Prefix
  264        ;   atom_concat(/, Prefix, Path)
  265        )
  266    ;   Path = /
  267    ).
 relative_to(+Base, +Path, -AbsPath) is det
AbsPath is an absolute URL location created from Base and Path. The result is cleaned
  275relative_to(/, Path, Path) :- !.
  276relative_to(_Base, Path, Path) :-
  277    sub_atom(Path, 0, _, _, /),
  278    !.
  279relative_to(Base, Local, Path) :-
  280    sub_atom(Base, 0, _, _, /),    % file version
  281    !,
  282    path_segments(Base, BaseSegments),
  283    append(BaseDir, [_], BaseSegments) ->
  284    path_segments(Local, LocalSegments),
  285    append(BaseDir, LocalSegments, Segments0),
  286    clean_segments(Segments0, Segments),
  287    path_segments(Path, Segments).
  288relative_to(Base, Local, Global) :-
  289    uri_normalized(Local, Base, Global).
  290
  291path_segments(Path, Segments) :-
  292    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.
  299clean_segments([''|T0], [''|T]) :-
  300    !,
  301    exclude(empty_segment, T0, T1),
  302    clean_parent_segments(T1, T).
  303clean_segments(T0, T) :-
  304    exclude(empty_segment, T0, T1),
  305    clean_parent_segments(T1, T).
  306
  307clean_parent_segments([], []).
  308clean_parent_segments([..|T0], T) :-
  309    !,
  310    clean_parent_segments(T0, T).
  311clean_parent_segments([_,..|T0], T) :-
  312    !,
  313    clean_parent_segments(T0, T).
  314clean_parent_segments([H|T0], [H|T]) :-
  315    clean_parent_segments(T0, T).
  316
  317empty_segment('').
  318empty_segment('.').
 path_list(+Spec, -List) is det
Translate seg1/seg2/... into [seg1,seg2,...].
Errors
- instantiation_error
- type_error(atomic, X)
  328path_list(Var) -->
  329    { var(Var),
  330      !,
  331      instantiation_error(Var)
  332    }.
  333path_list(A/B) -->
  334    !,
  335    path_list(A),
  336    path_list(B).
  337path_list(.) -->
  338    !,
  339    [].
  340path_list(A) -->
  341    { must_be(atomic, A) },
  342    [A].
  343
  344
  345                 /*******************************
  346                 *            MESSAGES          *
  347                 *******************************/
  348
  349:- multifile
  350    prolog:message/3.  351
  352prolog:message(http(ambiguous_location(Spec, Paths))) -->
  353    [ 'http_absolute_location/2: ambiguous specification: ~q: ~p'-
  354      [Spec, Paths]
  355    ].
  356
  357
  358                 /*******************************
  359                 *        CACHE CLEANUP         *
  360                 *******************************/
 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.
  369http_clean_location_cache :-
  370    retractall(location_cache(_,_,_)).
  371
  372:- listen(settings(changed(http:prefix, _, _)),
  373          http_clean_location_cache).  374
  375:- multifile
  376    user:message_hook/3.  377:- dynamic
  378    user:message_hook/3.  379
  380user:message_hook(make(done(Reload)), _Level, _Lines) :-
  381    Reload \== [],
  382    http_clean_location_cache,
  383    fail