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-2014, 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:- use_module(library(lists)).   42:- use_module(library(error)).   43:- use_module(library(apply)).   44:- use_module(library(debug)).   45:- use_module(library(option)).   46:- use_module(library(settings)).   47:- use_module(library(broadcast)).   48:- use_module(library(uri)).   49:- use_module(library(http/http_host)).   50:- use_module(library(http/http_wrapper)).   51
   52
   53:- 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) :-
        ...

*/

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

  119:- multifile
  120    http:location/3.                % Alias, Expansion, Options
  121:- dynamic
  122    http:location/3.                % Alias, Expansion, Options
  123
  124http:location(root, Root, [priority(-100)]) :-
  125    (   setting(http:prefix, Prefix),
  126        Prefix \== ''
  127    ->  Root = Prefix
  128    ;   Root = (/)
  129    ).
 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
  140http_absolute_uri(Spec, URI) :-
  141    http_current_host(_Request, Host, Port,
  142                      [ global(true)
  143                      ]),
  144    http_absolute_location(Spec, Path, []),
  145    uri_authority_data(host, AuthC, Host),
  146    (   Port == 80                  % HTTP scheme
  147    ->  true
  148    ;   uri_authority_data(port, AuthC, Port)
  149    ),
  150    uri_authority_components(Authority, AuthC),
  151    uri_data(path, Components, Path),
  152    uri_data(scheme, Components, http),
  153    uri_data(authority, Components, Authority),
  154    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.
  169:- dynamic
  170    location_cache/3.  171
  172http_absolute_location(Spec, Path, Options) :-
  173    must_be(ground, Spec),
  174    option(relative_to(Base), Options, /),
  175    absolute_location(Spec, Base, Path, Options),
  176    debug(http_path, '~q (~q) --> ~q', [Spec, Base, Path]).
  177
  178absolute_location(Spec, Base, Path, _Options) :-
  179    location_cache(Spec, Base, Cache),
  180    !,
  181    Path = Cache.
  182absolute_location(Spec, Base, Path, Options) :-
  183    expand_location(Spec, Base, L, Options),
  184    assert(location_cache(Spec, Base, L)),
  185    Path = L.
  186
  187expand_location(Spec, Base, Path, _Options) :-
  188    atomic(Spec),
  189    !,
  190    (   uri_components(Spec, Components),
  191        uri_data(scheme, Components, Scheme),
  192        atom(Scheme)
  193    ->  Path = Spec
  194    ;   relative_to(Base, Spec, Path)
  195    ).
  196expand_location(Spec, _Base, Path, Options) :-
  197    Spec =.. [Alias, Sub],
  198    http_location_path(Alias, Parent),
  199    absolute_location(Parent, /, ParentLocation, Options),
  200    phrase(path_list(Sub), List),
  201    atomic_list_concat(List, /, SubAtom),
  202    (   ParentLocation == ''
  203    ->  Path = SubAtom
  204    ;   sub_atom(ParentLocation, _, _, 0, /)
  205    ->  atom_concat(ParentLocation, SubAtom, Path)
  206    ;   atomic_list_concat([ParentLocation, SubAtom], /, Path)
  207    ).
 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)
  219http_location_path(Alias, Path) :-
  220    findall(P-L, http_location_path(Alias, L, P), Pairs),
  221    sort(Pairs, Sorted0),
  222    reverse(Sorted0, Result),
  223    (   Result = [_-One]
  224    ->  Path = One
  225    ;   Result == []
  226    ->  existence_error(http_alias, Alias)
  227    ;   Result = [P-Best,P2-_|_],
  228        P \== P2
  229    ->  Path = Best
  230    ;   Result = [_-First|_],
  231        pairs_values(Result, Paths),
  232        print_message(warning, http(ambiguous_location(Alias, Paths))),
  233        Path = First
  234    ).
 http_location_path(+Alias, -Path, -Priority) is nondet
To be done
- prefix(Path) is discouraged; use root(Path)
  241http_location_path(Alias, Path, Priority) :-
  242    http:location(Alias, Path, Options),
  243    option(priority(Priority), Options, 0).
  244http_location_path(prefix, Path, 0) :-
  245    (   catch(setting(http:prefix, Prefix), _, fail),
  246        Prefix \== ''
  247    ->  (   sub_atom(Prefix, 0, _, _, /)
  248        ->  Path = Prefix
  249        ;   atom_concat(/, Prefix, Path)
  250        )
  251    ;   Path = /
  252    ).
 relative_to(+Base, +Path, -AbsPath) is det
AbsPath is an absolute URL location created from Base and Path. The result is cleaned
  260relative_to(/, Path, Path) :- !.
  261relative_to(_Base, Path, Path) :-
  262    sub_atom(Path, 0, _, _, /),
  263    !.
  264relative_to(Base, Local, Path) :-
  265    sub_atom(Base, 0, _, _, /),    % file version
  266    !,
  267    path_segments(Base, BaseSegments),
  268    append(BaseDir, [_], BaseSegments) ->
  269    path_segments(Local, LocalSegments),
  270    append(BaseDir, LocalSegments, Segments0),
  271    clean_segments(Segments0, Segments),
  272    path_segments(Path, Segments).
  273relative_to(Base, Local, Global) :-
  274    uri_normalized(Local, Base, Global).
  275
  276path_segments(Path, Segments) :-
  277    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.
  284clean_segments([''|T0], [''|T]) :-
  285    !,
  286    exclude(empty_segment, T0, T1),
  287    clean_parent_segments(T1, T).
  288clean_segments(T0, T) :-
  289    exclude(empty_segment, T0, T1),
  290    clean_parent_segments(T1, T).
  291
  292clean_parent_segments([], []).
  293clean_parent_segments([..|T0], T) :-
  294    !,
  295    clean_parent_segments(T0, T).
  296clean_parent_segments([_,..|T0], T) :-
  297    !,
  298    clean_parent_segments(T0, T).
  299clean_parent_segments([H|T0], [H|T]) :-
  300    clean_parent_segments(T0, T).
  301
  302empty_segment('').
  303empty_segment('.').
 path_list(+Spec, -List) is det
Translate seg1/seg2/... into [seg1,seg2,...].
Errors
- instantiation_error
- type_error(atomic, X)
  313path_list(Var) -->
  314    { var(Var),
  315      !,
  316      instantiation_error(Var)
  317    }.
  318path_list(A/B) -->
  319    !,
  320    path_list(A),
  321    path_list(B).
  322path_list(.) -->
  323    !,
  324    [].
  325path_list(A) -->
  326    { must_be(atomic, A) },
  327    [A].
  328
  329
  330                 /*******************************
  331                 *            MESSAGES          *
  332                 *******************************/
  333
  334:- multifile
  335    prolog:message/3.  336
  337prolog:message(http(ambiguous_location(Spec, Paths))) -->
  338    [ 'http_absolute_location/2: ambiguous specification: ~q: ~p'-
  339      [Spec, Paths]
  340    ].
  341
  342
  343                 /*******************************
  344                 *        CACHE CLEANUP         *
  345                 *******************************/
 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.
  354http_clean_location_cache :-
  355    retractall(location_cache(_,_,_)).
  356
  357:- listen(settings(changed(http:prefix, _, _)),
  358          http_clean_location_cache).  359
  360:- multifile
  361    user:message_hook/3.  362:- dynamic
  363    user:message_hook/3.  364
  365user:message_hook(make(done(Reload)), _Level, _Lines) :-
  366    Reload \== [],
  367    http_clean_location_cache,
  368    fail