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-2019, 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, []).
98:- setting(http:time_limit, nonneg, 300,
99 'Time limit handling a single query (0=infinite)').
'/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:
http_authenticate.pl
provides a plugin for user/password
based Basic
HTTP authentication.Transfer-encoding: chunked
if the client allows for it.true
on a prefix-handler (see prefix), possible children
are masked. This can be used to (temporary) overrule part of the
tree.methods([Method])
. Using method(*)
allows for all methods.:- http_handler(/, http_404([index('index.html')]), [spawn(my_pool),prefix]).
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.
206:- dynamic handler/4. % Path, Action, IsPrefix, Options 207:- multifile handler/4. 208:- dynamic generation/1. 209 210:- meta_predicate 211 http_handler( , , ), 212 http_current_handler( , ), 213 http_current_handler( , , ), 214 http_request_expansion( , ), 215 http_switch_protocol( , ). 216 217http_handler(Path, Pred, Options) :- 218 compile_handler(Path, Pred, Options, Clause), 219 next_generation, 220 assert(Clause). 221 222:- multifile 223 system:term_expansion/2. 224 225systemterm_expansion((:- http_handler(Path, Pred, Options)), Clause) :- 226 \+ current_prolog_flag(xref, true), 227 prolog_load_context(module, M), 228 compile_handler(Path, M:Pred, Options, Clause), 229 next_generation.
244http_delete_handler(id(Id)) :- 245 !, 246 clause(handler(_Path, _:Pred, _, Options), true, Ref), 247 functor(Pred, DefID, _), 248 option(id(Id0), Options, DefID), 249 Id == Id0, 250 erase(Ref), 251 next_generation. 252http_delete_handler(path(Path)) :- 253 !, 254 retractall(handler(Path, _Pred, _, _Options)), 255 next_generation. 256http_delete_handler(Path) :- 257 http_delete_handler(path(Path)).
265next_generation :- 266 retractall(id_location_cache(_,_,_,_)), 267 with_mutex(http_dispatch, next_generation_unlocked). 268 269next_generation_unlocked :- 270 retract(generation(G0)), 271 !, 272 G is G0 + 1, 273 assert(generation(G)). 274next_generation_unlocked :- 275 assert(generation(1)). 276 277current_generation(G) :- 278 with_mutex(http_dispatch, generation(G)), 279 !. 280current_generation(0).
287compile_handler(Path, Pred, Options0, 288 http_dispatch:handler(Path1, Pred, IsPrefix, Options)) :- 289 check_path(Path, Path1, PathOptions), 290 check_id(Options0), 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).
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).
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)]
.
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) }. 436 437check_id(Options) :- 438 memberchk(id(Id), Options), 439 !, 440 must_be(atom, Id). 441check_id(_).
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.method
member of the
Request or throw permission_error(http_method, Method, Location)
http_reply(Term, ExtraHeader, Context)
exceptions.method(Method)
as one of the options.467http_dispatch(Request) :- 468 memberchk(path(Path), Request), 469 find_handler(Path, Closure, Options), 470 supports_method(Request, Options), 471 expand_request(Request, Request1, Options), 472 extract_from_request(Request1, Options), 473 action(Closure, Request1, Options). 474 475extract_from_request(Request, Options) :- 476 memberchk('$extract'(Fields), Options), 477 !, 478 extract_fields(Fields, Request). 479extract_from_request(_, _). 480 481extract_fields([], _). 482extract_fields([H|T], Request) :- 483 memberchk(H, Request), 484 extract_fields(T, Request).
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.
506http_request_expansion(Goal, Rank) :- 507 throw(error(context_error(nodirective, http_request_expansion(Goal, Rank)), _)). 508 509:- multifile 510 request_expansion/2. 511 512systemterm_expansion((:- http_request_expansion(Goal, Rank)), 513 http_dispatch:request_expansion(M:Callable, Rank)) :- 514 must_be(number, Rank), 515 prolog_load_context(module, M0), 516 strip_module(M0:Goal, M, Callable), 517 must_be(callable, Callable). 518 519request_expanders(Closures) :- 520 findall(Rank-Closure, request_expansion(Closure, Rank), Pairs), 521 keysort(Pairs, Sorted), 522 pairs_values(Sorted, Closures).
529expand_request(Request0, Request, Options) :- 530 request_expanders(Closures), 531 expand_request(Closures, Request0, Request, Options). 532 533expand_request([], Request, Request, _). 534expand_request([H|T], Request0, Request, Options) :- 535 expand_request1(H, Request0, Request1, Options), 536 expand_request(T, Request1, Request, Options). 537 538expand_request1(Closure, Request0, Request, Options) :- 539 call(Closure, Request0, Request, Options), 540 !. 541expand_request1(_, Request, Request, _).
549http_current_handler(Path, Closure) :- 550 atom(Path), 551 !, 552 path_tree(Tree), 553 find_handler(Tree, Path, Closure, _). 554http_current_handler(Path, M:C) :- 555 handler(Spec, M:C, _, _), 556 http_absolute_location(Spec, Path, []).
563http_current_handler(Path, Closure, Options) :- 564 atom(Path), 565 !, 566 path_tree(Tree), 567 find_handler(Tree, Path, Closure, Options). 568http_current_handler(Path, M:C, Options) :- 569 handler(Spec, M:C, _, _), 570 http_absolute_location(Spec, Path, []), 571 path_tree(Tree), 572 find_handler(Tree, Path, _, Options).
id(ID)
appears in the option list of the handler, ID
it is used and takes preference over using the predicate.Module:Pred
If the handler is declared with a pattern, e.g., root(user/User)
,
the location to access a particular user may be accessed using
e.g., user('Bob')
. The number of arguments to the compound term must
match the number of variables in the path pattern.
A plain atom ID can be used to find a handler with a pattern. The
returned location is the path up to the first variable, e.g.,
/user/
in the example above.
User code is adviced to use http_link_to_id/3 which can also add query parameters to the URL. This predicate is a helper for http_link_to_id/3.
605:- dynamic 606 id_location_cache/4. % Id, Argv, Location, Segments 607 608http_location_by_id(ID, _) :- 609 \+ ground(ID), 610 !, 611 instantiation_error(ID). 612http_location_by_id(M:ID, Location) :- 613 compound(ID), 614 !, 615 compound_name_arguments(ID, Name, Argv), 616 http_location_by_id(M:Name, Argv, Location). 617http_location_by_id(M:ID, Location) :- 618 atom(ID), 619 must_be(atom, M), 620 !, 621 http_location_by_id(M:ID, -, Location). 622http_location_by_id(ID, Location) :- 623 compound(ID), 624 !, 625 compound_name_arguments(ID, Name, Argv), 626 http_location_by_id(Name, Argv, Location). 627http_location_by_id(ID, Location) :- 628 atom(ID), 629 !, 630 http_location_by_id(ID, -, Location). 631http_location_by_id(ID, _) :- 632 type_error(location_id, ID). 633 634http_location_by_id(ID, Argv, Location) :- 635 id_location_cache(ID, Argv, Segments, Path), 636 !, 637 add_segments(Path, Segments, Location). 638http_location_by_id(ID, Argv, Location) :- 639 findall(t(Priority, ArgvP, Segments, Prefix), 640 location_by_id(ID, Argv, ArgvP, Segments, Prefix, Priority), 641 List), 642 sort(1, >=, List, Sorted), 643 ( Sorted = [t(_,ArgvP,Segments,Path)] 644 -> assert(id_location_cache(ID,ArgvP,Segments,Path)), 645 Argv = ArgvP 646 ; List == [] 647 -> existence_error(http_handler_id, ID) 648 ; List = [t(P0,ArgvP,Segments,Path),t(P1,_,_,_)|_] 649 -> ( P0 =:= P1 650 -> print_message(warning, 651 http_dispatch(ambiguous_id(ID, Sorted, Path))) 652 ; true 653 ), 654 assert(id_location_cache(ID,Argv,Segments,Path)), 655 Argv = ArgvP 656 ), 657 add_segments(Path, Segments, Location). 658 659add_segments(Path0, [], Path) :- 660 !, 661 Path = Path0. 662add_segments(Path0, Segments, Path) :- 663 maplist(uri_encoded(path), Segments, Encoded), 664 atomic_list_concat(Encoded, '/', Rest), 665 atom_concat(Path0, Rest, Path). 666 667location_by_id(ID, -, _, [], Location, Priority) :- 668 !, 669 location_by_id_raw(ID, L0, _Segments, Priority), 670 to_path(L0, Location). 671location_by_id(ID, Argv, ArgvP, Segments, Location, Priority) :- 672 location_by_id_raw(ID, L0, Segments, Priority), 673 include(var, Segments, ArgvP), 674 same_length(Argv, ArgvP), 675 to_path(L0, Location). 676 677to_path(prefix(Path0), Path) :- % old style prefix notation 678 !, 679 add_prefix(Path0, Path). 680to_path(Path0, Path) :- 681 atomic(Path0), % old style notation 682 !, 683 add_prefix(Path0, Path). 684to_path(Spec, Path) :- % new style notation 685 http_absolute_location(Spec, Path, []). 686 687add_prefix(P0, P) :- 688 ( catch(setting(http:prefix, Prefix), _, fail), 689 Prefix \== '' 690 -> atom_concat(Prefix, P0, P) 691 ; P = P0 692 ). 693 694location_by_id_raw(ID, Location, Pattern, Priority) :- 695 handler(Location, _, _, Options), 696 option(id(ID), Options), 697 option(priority(P0), Options, 0), 698 option(segment_pattern(Pattern), Options, []), 699 Priority is P0+1000. % id(ID) takes preference over predicate 700location_by_id_raw(ID, Location, Pattern, Priority) :- 701 handler(Location, M:C, _, Options), 702 option(priority(Priority), Options, 0), 703 functor(C, PN, _), 704 ( ID = M:PN 705 -> true 706 ; ID = PN 707 ), 708 option(segment_pattern(Pattern), Options, []).
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)).
758http_link_to_id(HandleID, path_postfix(File), HREF) :- 759 !, 760 http_location_by_id(HandleID, HandlerLocation), 761 uri_encoded(path, File, EncFile), 762 directory_file_path(HandlerLocation, EncFile, Location), 763 uri_data(path, Components, Location), 764 uri_components(HREF, Components). 765http_link_to_id(HandleID, Parameters, HREF) :- 766 must_be(list, Parameters), 767 http_location_by_id(HandleID, Location), 768 ( Parameters == [] 769 -> HREF = Location 770 ; uri_data(path, Components, Location), 771 uri_query_components(String, Parameters), 772 uri_data(search, Components, String), 773 uri_components(HREF, Components) 774 ).
781http_reload_with_parameters(Request, NewParams, HREF) :- 782 memberchk(path(Path), Request), 783 ( memberchk(search(Params), Request) 784 -> true 785 ; Params = [] 786 ), 787 merge_options(NewParams, Params, AllParams), 788 uri_query_components(Search, AllParams), 789 uri_data(path, Data, Path), 790 uri_data(search, Data, Search), 791 uri_components(HREF, Data). 792 793 794% hook into html_write:attribute_value//1. 795 796:- multifile 797 html_write:expand_attribute_value//1. 798 799html_writeexpand_attribute_value(location_by_id(ID)) --> 800 { http_location_by_id(ID, Location) }, 801 html_write:html_quoted_attribute(Location). 802html_writeexpand_attribute_value(#(ID)) --> 803 { http_location_by_id(ID, Location) }, 804 html_write:html_quoted_attribute(Location).
http_authenticate.pl
provides an implementation thereof.
819:- multifile 820 http:authenticate/3. 821 822authentication([], _, []). 823authentication([authentication(Type)|Options], Request, Fields) :- 824 !, 825 ( http:authenticate(Type, Request, XFields) 826 -> append(XFields, More, Fields), 827 authentication(Options, Request, More) 828 ; memberchk(path(Path), Request), 829 permission_error(access, http_location, Path) 830 ). 831authentication([_|Options], Request, Fields) :- 832 authentication(Options, Request, Fields). 833 834:- http_request_expansion(auth_expansion, 100).
843auth_expansion(Request0, Request, Options) :-
844 authentication(Options, Request0, Extra),
845 append(Extra, Request, Request0).
prefix(Path)
handlers, use the
longest.
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.
863find_handler(Path, Action, Options) :- 864 path_tree(Tree), 865 ( find_handler(Tree, Path, Action, Options), 866 eval_condition(Options) 867 -> true 868 ; \+ sub_atom(Path, _, _, 0, /), 869 atom_concat(Path, /, Dir), 870 find_handler(Tree, Dir, Action, Options) 871 -> throw(http_reply(moved(Dir))) 872 ; throw(error(existence_error(http_location, Path), _)) 873 ). 874 875 876find_handler([node(prefix(Prefix), PAction, POptions, Children)|_], 877 Path, Action, Options) :- 878 sub_atom(Path, 0, _, After, Prefix), 879 !, 880 ( option(hide_children(false), POptions, false), 881 find_handler(Children, Path, Action, Options) 882 -> true 883 ; member(segment_pattern(Pattern, PatAction, PatOptions), POptions), 884 copy_term(t(Pattern,PatAction,PatOptions), t(Pattern2,Action,Options)), 885 match_segments(After, Path, Pattern2) 886 -> true 887 ; PAction \== nop 888 -> Action = PAction, 889 path_info(After, Path, POptions, Options) 890 ). 891find_handler([node(Path, Action, Options, _)|_], Path, Action, Options) :- !. 892find_handler([_|Tree], Path, Action, Options) :- 893 find_handler(Tree, Path, Action, Options). 894 895path_info(0, _, Options, 896 [prefix(true)|Options]) :- !. 897path_info(After, Path, Options, 898 [path_info(PathInfo),prefix(true)|Options]) :- 899 sub_atom(Path, _, After, 0, PathInfo). 900 901match_segments(After, Path, [Var]) :- 902 !, 903 sub_atom(Path, _, After, 0, Var). 904match_segments(After, Path, Pattern) :- 905 sub_atom(Path, _, After, 0, PathInfo), 906 split_string(PathInfo, "/", "", Segments), 907 match_segment_pattern(Pattern, Segments). 908 909match_segment_pattern([], []). 910match_segment_pattern([Var], Segments) :- 911 !, 912 atomic_list_concat(Segments, '/', Var). 913match_segment_pattern([H0|T0], [H|T]) :- 914 atom_string(H0, H), 915 match_segment_pattern(T0, T). 916 917 918eval_condition(Options) :- 919 ( memberchk(condition(Cond), Options) 920 -> catch(Cond, E, (print_message(warning, E), fail)) 921 ; true 922 ).
933supports_method(Request, Options) :- 934 ( option(methods(Methods), Options) 935 -> ( Methods == '*' 936 -> true 937 ; memberchk(method(Method), Request), 938 memberchk(Method, Methods) 939 ) 940 ; true 941 ), 942 !. 943supports_method(Request, _Options) :- 944 memberchk(path(Location), Request), 945 memberchk(method(Method), Request), 946 permission_error(http_method, Method, Location).
time_limit
, chunked
and spawn
.
956action(Action, Request, Options) :- 957 memberchk(chunked, Options), 958 !, 959 format('Transfer-encoding: chunked~n'), 960 spawn_action(Action, Request, Options). 961action(Action, Request, Options) :- 962 spawn_action(Action, Request, Options). 963 964spawn_action(Action, Request, Options) :- 965 option(spawn(Spawn), Options), 966 !, 967 spawn_options(Spawn, SpawnOption), 968 http_spawn(time_limit_action(Action, Request, Options), SpawnOption). 969spawn_action(Action, Request, Options) :- 970 time_limit_action(Action, Request, Options). 971 972spawn_options([], []) :- !. 973spawn_options(Pool, Options) :- 974 atom(Pool), 975 !, 976 Options = [pool(Pool)]. 977spawn_options(List, List). 978 979time_limit_action(Action, Request, Options) :- 980 ( option(time_limit(TimeLimit), Options), 981 TimeLimit \== default 982 -> true 983 ; setting(http:time_limit, TimeLimit) 984 ), 985 number(TimeLimit), 986 TimeLimit > 0, 987 !, 988 call_with_time_limit(TimeLimit, call_action(Action, Request, Options)). 989time_limit_action(Action, Request, Options) :- 990 call_action(Action, Request, Options).
997call_action(reply_file(File, FileOptions), Request, _Options) :- 998 !, 999 http_reply_file(File, FileOptions, Request). 1000call_action(Pred, Request, Options) :- 1001 memberchk(path_info(PathInfo), Options), 1002 !, 1003 call_action(Pred, [path_info(PathInfo)|Request]). 1004call_action(Pred, Request, _Options) :- 1005 call_action(Pred, Request). 1006 1007call_action(Pred, Request) :- 1008 ( call(Pred, Request) 1009 -> true 1010 ; extend(Pred, [Request], Goal), 1011 throw(error(goal_failed(Goal), _)) 1012 ). 1013 1014extend(Var, _, Var) :- 1015 var(Var), 1016 !. 1017extend(M:G0, Extra, M:G) :- 1018 extend(G0, Extra, G). 1019extend(G0, Extra, G) :- 1020 G0 =.. List, 1021 append(List, Extra, List2), 1022 G =.. List2.
true
(default), handle If-modified-since and send
modification time.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
.false
(default), validate that FileSpec does not
contain references to parent directories. E.g.,
specifications such as www('../../etc/passwd')
are
not allowed.
If caching is not disabled, it processes the request headers
If-modified-since
and Range
.
1058http_reply_file(File, Options, Request) :- 1059 http_safe_file(File, Options), 1060 absolute_file_name(File, Path, 1061 [ access(read) 1062 ]), 1063 ( option(cache(true), Options, true) 1064 -> ( memberchk(if_modified_since(Since), Request), 1065 time_file(Path, Time), 1066 catch(http_timestamp(Time, Since), _, fail) 1067 -> throw(http_reply(not_modified)) 1068 ; true 1069 ), 1070 ( memberchk(range(Range), Request) 1071 -> Reply = file(Type, Path, Range) 1072 ; option(static_gzip(true), Options), 1073 accepts_encoding(Request, gzip), 1074 file_name_extension(Path, gz, PathGZ), 1075 access_file(PathGZ, read), 1076 time_file(PathGZ, TimeGZ), 1077 time_file(Path, Time), 1078 TimeGZ >= Time 1079 -> Reply = gzip_file(Type, PathGZ) 1080 ; Reply = file(Type, Path) 1081 ) 1082 ; Reply = tmp_file(Type, Path) 1083 ), 1084 ( option(mime_type(MediaType), Options) 1085 -> file_content_type(Path, MediaType, Type) 1086 ; file_content_type(Path, Type) 1087 -> true 1088 ; Type = text/plain % fallback type 1089 ), 1090 option(headers(Headers), Options, []), 1091 throw(http_reply(Reply, Headers)). 1092 1093accepts_encoding(Request, Enc) :- 1094 memberchk(accept_encoding(Accept), Request), 1095 split_string(Accept, ",", " ", Parts), 1096 member(Part, Parts), 1097 split_string(Part, ";", " ", [EncS|_]), 1098 atom_string(Enc, EncS).
alias(Sub)
, than Sub cannot
have references to parent directories.
1111http_safe_file(File, _) :- 1112 var(File), 1113 !, 1114 instantiation_error(File). 1115http_safe_file(_, Options) :- 1116 option(unsafe(true), Options, false), 1117 !. 1118http_safe_file(File, _) :- 1119 http_safe_file(File). 1120 1121http_safe_file(File) :- 1122 compound(File), 1123 functor(File, _, 1), 1124 !, 1125 arg(1, File, Name), 1126 safe_name(Name, File). 1127http_safe_file(Name) :- 1128 ( is_absolute_file_name(Name) 1129 -> permission_error(read, file, Name) 1130 ; true 1131 ), 1132 safe_name(Name, Name). 1133 1134safe_name(Name, _) :- 1135 must_be(atom, Name), 1136 prolog_to_os_filename(FileName, Name), 1137 \+ unsafe_name(FileName), 1138 !. 1139safe_name(_, Spec) :- 1140 permission_error(read, file, Spec). 1141 1142unsafe_name(Name) :- Name == '..'. 1143unsafe_name(Name) :- sub_atom(Name, 0, _, _, '../'). 1144unsafe_name(Name) :- sub_atom(Name, _, _, _, '/../'). 1145unsafe_name(Name) :- sub_atom(Name, _, _, 0, '/..').
:- http_handler(root(.), http_redirect(moved, myapp('index.html')), []).
1166http_redirect(How, To, Request) :- 1167 must_be(oneof([moved, moved_temporary, see_other]), How), 1168 must_be(ground, To), 1169 ( id_location(To, URL) 1170 -> true 1171 ; memberchk(path(Base), Request), 1172 http_absolute_location(To, URL, [relative_to(Base)]) 1173 ), 1174 Term =.. [How,URL], 1175 throw(http_reply(Term)). 1176 1177id_location(location_by_id(Id), URL) :- 1178 http_location_by_id(Id, URL). 1179id_location(#(Id), URL) :- 1180 http_location_by_id(Id, URL). 1181id_location(#(Id)+Parameters, URL) :- 1182 http_link_to_id(Id, Parameters, URL).
1197http_404(Options, Request) :- 1198 option(index(Index), Options), 1199 \+ ( option(path_info(PathInfo), Request), 1200 PathInfo \== '' 1201 ), 1202 !, 1203 http_redirect(moved, Index, Request). 1204http_404(_Options, Request) :- 1205 option(path(Path), Request), 1206 !, 1207 throw(http_reply(not_found(Path))). 1208http_404(_Options, Request) :- 1209 domain_error(http_request, Request).
"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:
headers(+Headers)
.1243% @throws http_reply(switch_protocol(Goal, Options)) 1244 1245http_switch_protocol(Goal, Options) :- 1246 throw(http_reply(switching_protocols(Goal, Options))). 1247 1248 1249 /******************************* 1250 * PATH COMPILATION * 1251 *******************************/
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.
1267path_tree(Tree) :- 1268 current_generation(G), 1269 nb_current(http_dispatch_tree, G-Tree), 1270 !. % Avoid existence error 1271path_tree(Tree) :- 1272 path_tree_nocache(Tree), 1273 current_generation(G), 1274 nb_setval(http_dispatch_tree, G-Tree). 1275 1276path_tree_nocache(Tree) :- 1277 findall(Prefix, prefix_handler(Prefix, _, _, _), Prefixes0), 1278 sort(Prefixes0, Prefixes), 1279 prefix_tree(Prefixes, [], PTree), 1280 prefix_options(PTree, [], OPTree), 1281 add_paths_tree(OPTree, Tree). 1282 1283prefix_handler(Prefix, Action, Options, Priority-PLen) :- 1284 handler(Spec, Action, true, Options), 1285 ( memberchk(priority(Priority), Options) 1286 -> true 1287 ; Priority = 0 1288 ), 1289 ( memberchk(segment_pattern(Pattern), Options) 1290 -> length(Pattern, PLen) 1291 ; PLen = 0 1292 ), 1293 Error = error(existence_error(http_alias,_),_), 1294 catch(http_absolute_location(Spec, Prefix, []), Error, 1295 ( print_message(warning, Error), 1296 fail 1297 )).
1303prefix_tree([], Tree, Tree). 1304prefix_tree([H|T], Tree0, Tree) :- 1305 insert_prefix(H, Tree0, Tree1), 1306 prefix_tree(T, Tree1, Tree). 1307 1308insert_prefix(Prefix, Tree0, Tree) :- 1309 select(P-T, Tree0, Tree1), 1310 sub_atom(Prefix, 0, _, _, P), 1311 !, 1312 insert_prefix(Prefix, T, T1), 1313 Tree = [P-T1|Tree1]. 1314insert_prefix(Prefix, Tree, [Prefix-[]|Tree]).
1323prefix_options([], _, []). 1324prefix_options([Prefix-C|T0], DefOptions, 1325 [node(prefix(Prefix), Action, PrefixOptions, Children)|T]) :- 1326 findall(h(A,O,P), prefix_handler(Prefix,A,O,P), Handlers), 1327 sort(3, >=, Handlers, Handlers1), 1328 Handlers1 = [h(_,_,P0)|_], 1329 same_priority_handlers(Handlers1, P0, Same), 1330 option_patterns(Same, SegmentPatterns, Action), 1331 last(Same, h(_, Options0, _-_)), 1332 merge_options(Options0, DefOptions, Options), 1333 append(SegmentPatterns, Options, PrefixOptions), 1334 exclude(no_inherit, Options, InheritOpts), 1335 prefix_options(C, InheritOpts, Children), 1336 prefix_options(T0, DefOptions, T). 1337 1338no_inherit(id(_)). 1339no_inherit('$extract'(_)). 1340 1341same_priority_handlers([H|T0], P, [H|T]) :- 1342 H = h(_,_,P0-_), 1343 P = P0-_, 1344 !, 1345 same_priority_handlers(T0, P, T). 1346same_priority_handlers(_, _, []). 1347 1348option_patterns([], [], nop). 1349option_patterns([h(A,_,_-0)|_], [], A) :- 1350 !. 1351option_patterns([h(A,O,_)|T0], [segment_pattern(P,A,O)|T], AF) :- 1352 memberchk(segment_pattern(P), O), 1353 option_patterns(T0, T, AF).
1360add_paths_tree(OPTree, Tree) :- 1361 findall(path(Path, Action, Options), 1362 plain_path(Path, Action, Options), 1363 Triples), 1364 add_paths_tree(Triples, OPTree, Tree). 1365 1366add_paths_tree([], Tree, Tree). 1367add_paths_tree([path(Path, Action, Options)|T], Tree0, Tree) :- 1368 add_path_tree(Path, Action, Options, [], Tree0, Tree1), 1369 add_paths_tree(T, Tree1, Tree).
1377plain_path(Path, Action, Options) :-
1378 handler(Spec, Action, false, Options),
1379 catch(http_absolute_location(Spec, Path, []), E,
1380 (print_message(error, E), fail)).
1389add_path_tree(Path, Action, Options0, DefOptions, [], 1390 [node(Path, Action, Options, [])]) :- 1391 !, 1392 merge_options(Options0, DefOptions, Options). 1393add_path_tree(Path, Action, Options, _, 1394 [node(prefix(Prefix), PA, DefOptions, Children0)|RestTree], 1395 [node(prefix(Prefix), PA, DefOptions, Children)|RestTree]) :- 1396 sub_atom(Path, 0, _, _, Prefix), 1397 !, 1398 delete(DefOptions, id(_), InheritOpts), 1399 add_path_tree(Path, Action, Options, InheritOpts, Children0, Children). 1400add_path_tree(Path, Action, Options1, DefOptions, [H0|T], [H|T]) :- 1401 H0 = node(Path, _, Options2, _), 1402 option(priority(P1), Options1, 0), 1403 option(priority(P2), Options2, 0), 1404 P1 >= P2, 1405 !, 1406 merge_options(Options1, DefOptions, Options), 1407 H = node(Path, Action, Options, []). 1408add_path_tree(Path, Action, Options, DefOptions, [H|T0], [H|T]) :- 1409 add_path_tree(Path, Action, Options, DefOptions, T0, T). 1410 1411 1412 /******************************* 1413 * MESSAGES * 1414 *******************************/ 1415 1416:- multifile 1417 prolog:message/3. 1418 1419prologmessage(http_dispatch(ambiguous_id(ID, _List, Selected))) --> 1420 [ 'HTTP dispatch: ambiguous handler ID ~q (selected ~q)'-[ID, Selected] 1421 ]. 1422 1423 1424 /******************************* 1425 * XREF * 1426 *******************************/ 1427 1428:- multifile 1429 prolog:meta_goal/2. 1430:- dynamic 1431 prolog:meta_goal/2. 1432 1433prologmeta_goal(http_handler(_, G, _), [G+1]). 1434prologmeta_goal(http_current_handler(_, G), [G+1]). 1435 1436 1437 /******************************* 1438 * EDIT * 1439 *******************************/ 1440 1441% Allow edit(Location) to edit the implementation for an HTTP location. 1442 1443:- multifile 1444 prolog_edit:locate/3. 1445 1446prolog_edit:locate(Path, Spec, Location) :- 1447 atom(Path), 1448 sub_atom(Path, 0, _, _, /), 1449 Pred = _M:_H, 1450 catch(http_current_handler(Path, Pred), _, fail), 1451 closure_name_arity(Pred, 1, PI), 1452 prolog_edit:locate(PI, Spec, Location). 1453 1454closure_name_arity(M:Term, Extra, M:Name/Arity) :- 1455 !, 1456 callable(Term), 1457 functor(Term, Name, Arity0), 1458 Arity is Arity0 + Extra. 1459closure_name_arity(Term, Extra, Name/Arity) :- 1460 callable(Term), 1461 functor(Term, Name, Arity0), 1462 Arity is Arity0 + Extra. 1463 1464 1465 /******************************* 1466 * CACHE CLEANUP * 1467 *******************************/ 1468 1469:- listen(settings(changed(http:prefix, _, _)), 1470 next_generation). 1471 1472:- multifile 1473 user:message_hook/3. 1474:- dynamic 1475 user:message_hook/3. 1476 1477user:message_hook(make(done(Reload)), _Level, _Lines) :- 1478 Reload \== [], 1479 next_generation, 1480 fail
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:*/