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-2020, 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(lists), 54 [ select/3, append/3, append/2, same_length/2, member/2, 55 last/2, delete/3 56 ]). 57:- autoload(library(apply), 58 [partition/4,maplist/3,maplist/2,include/3,exclude/3]). 59:- autoload(library(broadcast),[listen/2]). 60:- autoload(library(error), 61 [ must_be/2, 62 domain_error/2, 63 type_error/2, 64 instantiation_error/1, 65 existence_error/2, 66 permission_error/3 67 ]). 68:- autoload(library(filesex),[directory_file_path/3]). 69:- autoload(library(option),[option/3,option/2,merge_options/3]). 70:- autoload(library(pairs),[pairs_values/2]). 71:- autoload(library(time),[call_with_time_limit/2]). 72:- autoload(library(uri), 73 [ uri_encoded/3, 74 uri_data/3, 75 uri_components/2, 76 uri_query_components/2 77 ]). 78:- autoload(library(http/http_header),[http_timestamp/2]). 79:- autoload(library(http/http_path),[http_absolute_location/3]). 80:- autoload(library(http/mimetype), 81 [file_content_type/2,file_content_type/3]). 82:- autoload(library(http/thread_httpd),[http_spawn/2]). 83:- use_module(library(settings),[setting/4,setting/2]). 84 85:- predicate_options(http_404/2, 1, [index(any)]). 86:- predicate_options(http_reply_file/3, 2, 87 [ cache(boolean), 88 mime_type(any), 89 static_gzip(boolean), 90 pass_to(http_safe_file/2, 2), 91 headers(list) 92 ]). 93:- predicate_options(http_safe_file/2, 2, [unsafe(boolean)]). 94:- predicate_options(http_switch_protocol/2, 2, []).
121:- setting(http:time_limit, nonneg, 300,
122 '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,post,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.
229:- dynamic handler/4. % Path, Action, IsPrefix, Options 230:- multifile handler/4. 231:- dynamic generation/1. 232 233:- meta_predicate 234 http_handler( , , ), 235 http_current_handler( , ), 236 http_current_handler( , , ), 237 http_request_expansion( , ), 238 http_switch_protocol( , ). 239 240http_handler(Path, Pred, Options) :- 241 compile_handler(Path, Pred, Options, Clause), 242 next_generation, 243 assert(Clause). 244 245:- multifile 246 system:term_expansion/2. 247 248systemterm_expansion((:- http_handler(Path, Pred, Options)), Clause) :- 249 \+ current_prolog_flag(xref, true), 250 prolog_load_context(module, M), 251 compile_handler(Path, M:Pred, Options, Clause), 252 next_generation.
267http_delete_handler(id(Id)) :- 268 !, 269 clause(handler(_Path, _:Pred, _, Options), true, Ref), 270 functor(Pred, DefID, _), 271 option(id(Id0), Options, DefID), 272 Id == Id0, 273 erase(Ref), 274 next_generation. 275http_delete_handler(path(Path)) :- 276 !, 277 retractall(handler(Path, _Pred, _, _Options)), 278 next_generation. 279http_delete_handler(Path) :- 280 http_delete_handler(path(Path)).
288next_generation :- 289 retractall(id_location_cache(_,_,_,_)), 290 with_mutex(http_dispatch, next_generation_unlocked). 291 292next_generation_unlocked :- 293 retract(generation(G0)), 294 !, 295 G is G0 + 1, 296 assert(generation(G)). 297next_generation_unlocked :- 298 assert(generation(1)). 299 300current_generation(G) :- 301 with_mutex(http_dispatch, generation(G)), 302 !. 303current_generation(0).
310compile_handler(Path, Pred, Options0, 311 http_dispatch:handler(Path1, Pred, IsPrefix, Options)) :- 312 check_path(Path, Path1, PathOptions), 313 check_id(Options0), 314 ( memberchk(segment_pattern(_), PathOptions) 315 -> IsPrefix = true, 316 Options1 = Options0 317 ; select(prefix, Options0, Options1) 318 -> IsPrefix = true 319 ; IsPrefix = false, 320 Options1 = Options0 321 ), 322 partition(ground, Options1, Options2, QueryOptions), 323 Pred = M:_, 324 maplist(qualify_option(M), Options2, Options3), 325 combine_methods(Options3, Options4), 326 ( QueryOptions == [] 327 -> append(PathOptions, Options4, Options) 328 ; append(PathOptions, ['$extract'(QueryOptions)|Options4], Options) 329 ). 330 331qualify_option(M, condition(Pred), condition(M:Pred)) :- 332 Pred \= _:_, !. 333qualify_option(_, Option, Option).
method(M)
and methods(MList)
options into a single
methods(MList)
option.340combine_methods(Options0, Options) :- 341 collect_methods(Options0, Options1, Methods), 342 ( Methods == [] 343 -> Options = Options0 344 ; append(Methods, Flat), 345 sort(Flat, Unique), 346 ( memberchk('*', Unique) 347 -> Final = '*' 348 ; Final = Unique 349 ), 350 Options = [methods(Final)|Options1] 351 ). 352 353collect_methods([], [], []). 354collect_methods([method(M)|T0], T, [[M]|TM]) :- 355 !, 356 ( M == '*' 357 -> true 358 ; must_be_method(M) 359 ), 360 collect_methods(T0, T, TM). 361collect_methods([methods(M)|T0], T, [M|TM]) :- 362 !, 363 must_be(list, M), 364 maplist(must_be_method, M), 365 collect_methods(T0, T, TM). 366collect_methods([H|T0], [H|T], TM) :- 367 !, 368 collect_methods(T0, T, TM). 369 370must_be_method(M) :- 371 must_be(atom, M), 372 ( method(M) 373 -> true 374 ; domain_error(http_method, M) 375 ). 376 377method(get). 378method(put). 379method(head). 380method(post). 381method(delete). 382method(patch). 383method(options). 384method(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)]
.
404check_path(Path, Path, []) :- 405 atom(Path), 406 !, 407 ( sub_atom(Path, 0, _, _, /) 408 -> true 409 ; domain_error(absolute_http_location, Path) 410 ). 411check_path(Alias, AliasOut, Options) :- 412 compound(Alias), 413 Alias =.. [Name, Relative], 414 !, 415 local_path(Relative, Local, Options), 416 ( sub_atom(Local, 0, _, _, /) 417 -> domain_error(relative_location, Relative) 418 ; AliasOut =.. [Name, Local] 419 ). 420check_path(PathSpec, _, _) :- 421 type_error(path_or_alias, PathSpec). 422 423local_path(Atom, Atom, []) :- 424 atom(Atom), 425 !. 426local_path(Path, Atom, Options) :- 427 phrase(path_to_list(Path), Components), 428 !, 429 ( maplist(atom, Components) 430 -> atomic_list_concat(Components, '/', Atom), 431 Options = [] 432 ; append(Pre, [Var|Rest], Components), 433 var(Var) 434 -> append(Pre, [''], PreSep), 435 atomic_list_concat(PreSep, '/', Atom), 436 Options = [segment_pattern([Var|Rest])] 437 ). 438local_path(Path, _, _) :- 439 ground(Path), 440 !, 441 type_error(relative_location, Path). 442local_path(Path, _, _) :- 443 instantiation_error(Path). 444 445path_to_list(Var) --> 446 { var(Var) }, 447 !, 448 [Var]. 449path_to_list(A/B) --> 450 !, 451 path_to_list(A), 452 path_to_list(B). 453path_to_list(Atom) --> 454 { atom(Atom) }, 455 !, 456 [Atom]. 457path_to_list(Value) --> 458 { must_be(atom, Value) }. 459 460check_id(Options) :- 461 memberchk(id(Id), Options), 462 !, 463 must_be(atom, Id). 464check_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.490http_dispatch(Request) :- 491 memberchk(path(Path), Request), 492 find_handler(Path, Closure, Options), 493 supports_method(Request, Options), 494 expand_request(Request, Request1, Options), 495 extract_from_request(Request1, Options), 496 action(Closure, Request1, Options). 497 498extract_from_request(Request, Options) :- 499 memberchk('$extract'(Fields), Options), 500 !, 501 extract_fields(Fields, Request). 502extract_from_request(_, _). 503 504extract_fields([], _). 505extract_fields([H|T], Request) :- 506 memberchk(H, Request), 507 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.
529http_request_expansion(Goal, Rank) :- 530 throw(error(context_error(nodirective, http_request_expansion(Goal, Rank)), _)). 531 532:- multifile 533 request_expansion/2. 534 535systemterm_expansion((:- http_request_expansion(Goal, Rank)), 536 http_dispatch:request_expansion(M:Callable, Rank)) :- 537 must_be(number, Rank), 538 prolog_load_context(module, M0), 539 strip_module(M0:Goal, M, Callable), 540 must_be(callable, Callable). 541 542request_expanders(Closures) :- 543 findall(Rank-Closure, request_expansion(Closure, Rank), Pairs), 544 keysort(Pairs, Sorted), 545 pairs_values(Sorted, Closures).
552expand_request(Request0, Request, Options) :- 553 request_expanders(Closures), 554 expand_request(Closures, Request0, Request, Options). 555 556expand_request([], Request, Request, _). 557expand_request([H|T], Request0, Request, Options) :- 558 expand_request1(H, Request0, Request1, Options), 559 expand_request(T, Request1, Request, Options). 560 561expand_request1(Closure, Request0, Request, Options) :- 562 call(Closure, Request0, Request, Options), 563 !. 564expand_request1(_, Request, Request, _).
572http_current_handler(Path, Closure) :- 573 atom(Path), 574 !, 575 path_tree(Tree), 576 find_handler(Tree, Path, Closure, _). 577http_current_handler(Path, M:C) :- 578 handler(Spec, M:C, _, _), 579 http_absolute_location(Spec, Path, []).
586http_current_handler(Path, Closure, Options) :- 587 atom(Path), 588 !, 589 path_tree(Tree), 590 find_handler(Tree, Path, Closure, Options). 591http_current_handler(Path, M:C, Options) :- 592 handler(Spec, M:C, _, _), 593 http_absolute_location(Spec, Path, []), 594 path_tree(Tree), 595 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.
628:- dynamic 629 id_location_cache/4. % Id, Argv, Location, Segments 630 631http_location_by_id(ID, _) :- 632 \+ ground(ID), 633 !, 634 instantiation_error(ID). 635http_location_by_id(M:ID, Location) :- 636 compound(ID), 637 !, 638 compound_name_arguments(ID, Name, Argv), 639 http_location_by_id(M:Name, Argv, Location). 640http_location_by_id(M:ID, Location) :- 641 atom(ID), 642 must_be(atom, M), 643 !, 644 http_location_by_id(M:ID, -, Location). 645http_location_by_id(ID, Location) :- 646 compound(ID), 647 !, 648 compound_name_arguments(ID, Name, Argv), 649 http_location_by_id(Name, Argv, Location). 650http_location_by_id(ID, Location) :- 651 atom(ID), 652 !, 653 http_location_by_id(ID, -, Location). 654http_location_by_id(ID, _) :- 655 type_error(location_id, ID). 656 657http_location_by_id(ID, Argv, Location) :- 658 id_location_cache(ID, Argv, Segments, Path), 659 !, 660 add_segments(Path, Segments, Location). 661http_location_by_id(ID, Argv, Location) :- 662 findall(t(Priority, ArgvP, Segments, Prefix), 663 location_by_id(ID, Argv, ArgvP, Segments, Prefix, Priority), 664 List), 665 sort(1, >=, List, Sorted), 666 ( Sorted = [t(_,ArgvP,Segments,Path)] 667 -> assert(id_location_cache(ID,ArgvP,Segments,Path)), 668 Argv = ArgvP 669 ; List == [] 670 -> existence_error(http_handler_id, ID) 671 ; List = [t(P0,ArgvP,Segments,Path),t(P1,_,_,_)|_] 672 -> ( P0 =:= P1 673 -> print_message(warning, 674 http_dispatch(ambiguous_id(ID, Sorted, Path))) 675 ; true 676 ), 677 assert(id_location_cache(ID,Argv,Segments,Path)), 678 Argv = ArgvP 679 ), 680 add_segments(Path, Segments, Location). 681 682add_segments(Path0, [], Path) :- 683 !, 684 Path = Path0. 685add_segments(Path0, Segments, Path) :- 686 maplist(uri_encoded(path), Segments, Encoded), 687 atomic_list_concat(Encoded, '/', Rest), 688 atom_concat(Path0, Rest, Path). 689 690location_by_id(ID, -, _, [], Location, Priority) :- 691 !, 692 location_by_id_raw(ID, L0, _Segments, Priority), 693 to_path(L0, Location). 694location_by_id(ID, Argv, ArgvP, Segments, Location, Priority) :- 695 location_by_id_raw(ID, L0, Segments, Priority), 696 include(var, Segments, ArgvP), 697 same_length(Argv, ArgvP), 698 to_path(L0, Location). 699 700to_path(prefix(Path0), Path) :- % old style prefix notation 701 !, 702 add_prefix(Path0, Path). 703to_path(Path0, Path) :- 704 atomic(Path0), % old style notation 705 !, 706 add_prefix(Path0, Path). 707to_path(Spec, Path) :- % new style notation 708 http_absolute_location(Spec, Path, []). 709 710add_prefix(P0, P) :- 711 ( catch(setting(http:prefix, Prefix), _, fail), 712 Prefix \== '' 713 -> atom_concat(Prefix, P0, P) 714 ; P = P0 715 ). 716 717location_by_id_raw(ID, Location, Pattern, Priority) :- 718 handler(Location, _, _, Options), 719 option(id(ID), Options), 720 option(priority(P0), Options, 0), 721 option(segment_pattern(Pattern), Options, []), 722 Priority is P0+1000. % id(ID) takes preference over predicate 723location_by_id_raw(ID, Location, Pattern, Priority) :- 724 handler(Location, M:C, _, Options), 725 option(priority(Priority), Options, 0), 726 functor(C, PN, _), 727 ( ID = M:PN 728 -> true 729 ; ID = PN 730 ), 731 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)).
781http_link_to_id(HandleID, path_postfix(File), HREF) :- 782 !, 783 http_location_by_id(HandleID, HandlerLocation), 784 uri_encoded(path, File, EncFile), 785 directory_file_path(HandlerLocation, EncFile, Location), 786 uri_data(path, Components, Location), 787 uri_components(HREF, Components). 788http_link_to_id(HandleID, Parameters, HREF) :- 789 must_be(list, Parameters), 790 http_location_by_id(HandleID, Location), 791 ( Parameters == [] 792 -> HREF = Location 793 ; uri_data(path, Components, Location), 794 uri_query_components(String, Parameters), 795 uri_data(search, Components, String), 796 uri_components(HREF, Components) 797 ).
804http_reload_with_parameters(Request, NewParams, HREF) :- 805 memberchk(path(Path), Request), 806 ( memberchk(search(Params), Request) 807 -> true 808 ; Params = [] 809 ), 810 merge_options(NewParams, Params, AllParams), 811 uri_query_components(Search, AllParams), 812 uri_data(path, Data, Path), 813 uri_data(search, Data, Search), 814 uri_components(HREF, Data). 815 816 817% hook into html_write:attribute_value//1. 818 819:- multifile 820 html_write:expand_attribute_value//1. 821 822html_writeexpand_attribute_value(location_by_id(ID)) --> 823 { http_location_by_id(ID, Location) }, 824 html_write:html_quoted_attribute(Location). 825html_writeexpand_attribute_value(#(ID)) --> 826 { http_location_by_id(ID, Location) }, 827 html_write:html_quoted_attribute(Location).
http_authenticate.pl
provides an implementation thereof.
842:- multifile 843 http:authenticate/3. 844 845authentication([], _, []). 846authentication([authentication(Type)|Options], Request, Fields) :- 847 !, 848 ( http:authenticate(Type, Request, XFields) 849 -> append(XFields, More, Fields), 850 authentication(Options, Request, More) 851 ; memberchk(path(Path), Request), 852 permission_error(access, http_location, Path) 853 ). 854authentication([_|Options], Request, Fields) :- 855 authentication(Options, Request, Fields). 856 857:- http_request_expansion(auth_expansion, 100).
866auth_expansion(Request0, Request, Options) :-
867 authentication(Options, Request0, Extra),
868 append(Extra, Request0, Request).
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.
886find_handler(Path, Action, Options) :- 887 path_tree(Tree), 888 ( find_handler(Tree, Path, Action, Options), 889 eval_condition(Options) 890 -> true 891 ; \+ sub_atom(Path, _, _, 0, /), 892 atom_concat(Path, /, Dir), 893 find_handler(Tree, Dir, Action, Options) 894 -> throw(http_reply(moved(Dir))) 895 ; throw(error(existence_error(http_location, Path), _)) 896 ). 897 898 899find_handler([node(prefix(Prefix), PAction, POptions, Children)|_], 900 Path, Action, Options) :- 901 sub_atom(Path, 0, _, After, Prefix), 902 !, 903 ( option(hide_children(false), POptions, false), 904 find_handler(Children, Path, Action, Options) 905 -> true 906 ; member(segment_pattern(Pattern, PatAction, PatOptions), POptions), 907 copy_term(t(Pattern,PatAction,PatOptions), t(Pattern2,Action,Options)), 908 match_segments(After, Path, Pattern2) 909 -> true 910 ; PAction \== nop 911 -> Action = PAction, 912 path_info(After, Path, POptions, Options) 913 ). 914find_handler([node(Path, Action, Options, _)|_], Path, Action, Options) :- !. 915find_handler([_|Tree], Path, Action, Options) :- 916 find_handler(Tree, Path, Action, Options). 917 918path_info(0, _, Options, 919 [prefix(true)|Options]) :- !. 920path_info(After, Path, Options, 921 [path_info(PathInfo),prefix(true)|Options]) :- 922 sub_atom(Path, _, After, 0, PathInfo). 923 924match_segments(After, Path, [Var]) :- 925 !, 926 sub_atom(Path, _, After, 0, Var). 927match_segments(After, Path, Pattern) :- 928 sub_atom(Path, _, After, 0, PathInfo), 929 split_string(PathInfo, "/", "", Segments), 930 match_segment_pattern(Pattern, Segments). 931 932match_segment_pattern([], []). 933match_segment_pattern([Var], Segments) :- 934 !, 935 atomic_list_concat(Segments, '/', Var). 936match_segment_pattern([H0|T0], [H|T]) :- 937 atom_string(H0, H), 938 match_segment_pattern(T0, T). 939 940 941eval_condition(Options) :- 942 ( memberchk(condition(Cond), Options) 943 -> catch(Cond, E, (print_message(warning, E), fail)) 944 ; true 945 ).
956supports_method(Request, Options) :- 957 ( option(methods(Methods), Options) 958 -> ( Methods == '*' 959 -> true 960 ; memberchk(method(Method), Request), 961 memberchk(Method, Methods) 962 ) 963 ; true 964 ), 965 !. 966supports_method(Request, _Options) :- 967 memberchk(path(Location), Request), 968 memberchk(method(Method), Request), 969 permission_error(http_method, Method, Location).
time_limit
, chunked
and spawn
.
979action(Action, Request, Options) :- 980 memberchk(chunked, Options), 981 !, 982 format('Transfer-encoding: chunked~n'), 983 spawn_action(Action, Request, Options). 984action(Action, Request, Options) :- 985 spawn_action(Action, Request, Options). 986 987spawn_action(Action, Request, Options) :- 988 option(spawn(Spawn), Options), 989 !, 990 spawn_options(Spawn, SpawnOption), 991 http_spawn(time_limit_action(Action, Request, Options), SpawnOption). 992spawn_action(Action, Request, Options) :- 993 time_limit_action(Action, Request, Options). 994 995spawn_options([], []) :- !. 996spawn_options(Pool, Options) :- 997 atom(Pool), 998 !, 999 Options = [pool(Pool)]. 1000spawn_options(List, List). 1001 1002time_limit_action(Action, Request, Options) :- 1003 ( option(time_limit(TimeLimit), Options), 1004 TimeLimit \== default 1005 -> true 1006 ; setting(http:time_limit, TimeLimit) 1007 ), 1008 number(TimeLimit), 1009 TimeLimit > 0, 1010 !, 1011 call_with_time_limit(TimeLimit, call_action(Action, Request, Options)). 1012time_limit_action(Action, Request, Options) :- 1013 call_action(Action, Request, Options).
1020call_action(reply_file(File, FileOptions), Request, _Options) :- 1021 !, 1022 http_reply_file(File, FileOptions, Request). 1023call_action(Pred, Request, Options) :- 1024 memberchk(path_info(PathInfo), Options), 1025 !, 1026 call_action(Pred, [path_info(PathInfo)|Request]). 1027call_action(Pred, Request, _Options) :- 1028 call_action(Pred, Request). 1029 1030call_action(Pred, Request) :- 1031 ( call(Pred, Request) 1032 -> true 1033 ; extend(Pred, [Request], Goal), 1034 throw(error(goal_failed(Goal), _)) 1035 ). 1036 1037extend(Var, _, Var) :- 1038 var(Var), 1039 !. 1040extend(M:G0, Extra, M:G) :- 1041 extend(G0, Extra, G). 1042extend(G0, Extra, G) :- 1043 G0 =.. List, 1044 append(List, Extra, List2), 1045 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
.
1081http_reply_file(File, Options, Request) :- 1082 http_safe_file(File, Options), 1083 absolute_file_name(File, Path, 1084 [ access(read) 1085 ]), 1086 ( option(cache(true), Options, true) 1087 -> ( memberchk(if_modified_since(Since), Request), 1088 time_file(Path, Time), 1089 catch(http_timestamp(Time, Since), _, fail) 1090 -> throw(http_reply(not_modified)) 1091 ; true 1092 ), 1093 ( memberchk(range(Range), Request) 1094 -> Reply = file(Type, Path, Range) 1095 ; option(static_gzip(true), Options), 1096 accepts_encoding(Request, gzip), 1097 file_name_extension(Path, gz, PathGZ), 1098 access_file(PathGZ, read), 1099 time_file(PathGZ, TimeGZ), 1100 time_file(Path, Time), 1101 TimeGZ >= Time 1102 -> Reply = gzip_file(Type, PathGZ) 1103 ; Reply = file(Type, Path) 1104 ) 1105 ; Reply = tmp_file(Type, Path) 1106 ), 1107 ( option(mime_type(MediaType), Options) 1108 -> file_content_type(Path, MediaType, Type) 1109 ; file_content_type(Path, Type) 1110 -> true 1111 ; Type = text/plain % fallback type 1112 ), 1113 option(headers(Headers), Options, []), 1114 throw(http_reply(Reply, Headers)). 1115 1116accepts_encoding(Request, Enc) :- 1117 memberchk(accept_encoding(Accept), Request), 1118 split_string(Accept, ",", " ", Parts), 1119 member(Part, Parts), 1120 split_string(Part, ";", " ", [EncS|_]), 1121 atom_string(Enc, EncS).
alias(Sub)
, than Sub cannot
have references to parent directories.
1134http_safe_file(File, _) :- 1135 var(File), 1136 !, 1137 instantiation_error(File). 1138http_safe_file(_, Options) :- 1139 option(unsafe(true), Options, false), 1140 !. 1141http_safe_file(File, _) :- 1142 http_safe_file(File). 1143 1144http_safe_file(File) :- 1145 compound(File), 1146 functor(File, _, 1), 1147 !, 1148 arg(1, File, Name), 1149 safe_name(Name, File). 1150http_safe_file(Name) :- 1151 ( is_absolute_file_name(Name) 1152 -> permission_error(read, file, Name) 1153 ; true 1154 ), 1155 safe_name(Name, Name). 1156 1157safe_name(Name, _) :- 1158 must_be(atom, Name), 1159 prolog_to_os_filename(FileName, Name), 1160 \+ unsafe_name(FileName), 1161 !. 1162safe_name(_, Spec) :- 1163 permission_error(read, file, Spec). 1164 1165unsafe_name(Name) :- Name == '..'. 1166unsafe_name(Name) :- sub_atom(Name, 0, _, _, '../'). 1167unsafe_name(Name) :- sub_atom(Name, _, _, _, '/../'). 1168unsafe_name(Name) :- sub_atom(Name, _, _, 0, '/..').
:- http_handler(root(.), http_redirect(moved, myapp('index.html')), []).
1189http_redirect(How, To, Request) :- 1190 must_be(oneof([moved, moved_temporary, see_other]), How), 1191 must_be(ground, To), 1192 ( id_location(To, URL) 1193 -> true 1194 ; memberchk(path(Base), Request), 1195 http_absolute_location(To, URL, [relative_to(Base)]) 1196 ), 1197 Term =.. [How,URL], 1198 throw(http_reply(Term)). 1199 1200id_location(location_by_id(Id), URL) :- 1201 http_location_by_id(Id, URL). 1202id_location(#(Id), URL) :- 1203 http_location_by_id(Id, URL). 1204id_location(#(Id)+Parameters, URL) :- 1205 http_link_to_id(Id, Parameters, URL).
1220http_404(Options, Request) :- 1221 option(index(Index), Options), 1222 \+ ( option(path_info(PathInfo), Request), 1223 PathInfo \== '' 1224 ), 1225 !, 1226 http_redirect(moved, Index, Request). 1227http_404(_Options, Request) :- 1228 option(path(Path), Request), 1229 !, 1230 throw(http_reply(not_found(Path))). 1231http_404(_Options, Request) :- 1232 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)
.1266% @throws http_reply(switch_protocol(Goal, Options)) 1267 1268http_switch_protocol(Goal, Options) :- 1269 throw(http_reply(switching_protocols(Goal, Options))). 1270 1271 1272 /******************************* 1273 * PATH COMPILATION * 1274 *******************************/
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.
1290path_tree(Tree) :- 1291 current_generation(G), 1292 nb_current(http_dispatch_tree, G-Tree), 1293 !. % Avoid existence error 1294path_tree(Tree) :- 1295 path_tree_nocache(Tree), 1296 current_generation(G), 1297 nb_setval(http_dispatch_tree, G-Tree). 1298 1299path_tree_nocache(Tree) :- 1300 findall(Prefix, prefix_handler(Prefix, _, _, _), Prefixes0), 1301 sort(Prefixes0, Prefixes), 1302 prefix_tree(Prefixes, [], PTree), 1303 prefix_options(PTree, [], OPTree), 1304 add_paths_tree(OPTree, Tree). 1305 1306prefix_handler(Prefix, Action, Options, Priority-PLen) :- 1307 handler(Spec, Action, true, Options), 1308 ( memberchk(priority(Priority), Options) 1309 -> true 1310 ; Priority = 0 1311 ), 1312 ( memberchk(segment_pattern(Pattern), Options) 1313 -> length(Pattern, PLen) 1314 ; PLen = 0 1315 ), 1316 Error = error(existence_error(http_alias,_),_), 1317 catch(http_absolute_location(Spec, Prefix, []), Error, 1318 ( print_message(warning, Error), 1319 fail 1320 )).
1326prefix_tree([], Tree, Tree). 1327prefix_tree([H|T], Tree0, Tree) :- 1328 insert_prefix(H, Tree0, Tree1), 1329 prefix_tree(T, Tree1, Tree). 1330 1331insert_prefix(Prefix, Tree0, Tree) :- 1332 select(P-T, Tree0, Tree1), 1333 sub_atom(Prefix, 0, _, _, P), 1334 !, 1335 insert_prefix(Prefix, T, T1), 1336 Tree = [P-T1|Tree1]. 1337insert_prefix(Prefix, Tree, [Prefix-[]|Tree]).
1346prefix_options([], _, []). 1347prefix_options([Prefix-C|T0], DefOptions, 1348 [node(prefix(Prefix), Action, PrefixOptions, Children)|T]) :- 1349 findall(h(A,O,P), prefix_handler(Prefix,A,O,P), Handlers), 1350 sort(3, >=, Handlers, Handlers1), 1351 Handlers1 = [h(_,_,P0)|_], 1352 same_priority_handlers(Handlers1, P0, Same), 1353 option_patterns(Same, SegmentPatterns, Action), 1354 last(Same, h(_, Options0, _-_)), 1355 merge_options(Options0, DefOptions, Options), 1356 append(SegmentPatterns, Options, PrefixOptions), 1357 exclude(no_inherit, Options, InheritOpts), 1358 prefix_options(C, InheritOpts, Children), 1359 prefix_options(T0, DefOptions, T). 1360 1361no_inherit(id(_)). 1362no_inherit('$extract'(_)). 1363 1364same_priority_handlers([H|T0], P, [H|T]) :- 1365 H = h(_,_,P0-_), 1366 P = P0-_, 1367 !, 1368 same_priority_handlers(T0, P, T). 1369same_priority_handlers(_, _, []). 1370 1371option_patterns([], [], nop). 1372option_patterns([h(A,_,_-0)|_], [], A) :- 1373 !. 1374option_patterns([h(A,O,_)|T0], [segment_pattern(P,A,O)|T], AF) :- 1375 memberchk(segment_pattern(P), O), 1376 option_patterns(T0, T, AF).
1383add_paths_tree(OPTree, Tree) :- 1384 findall(path(Path, Action, Options), 1385 plain_path(Path, Action, Options), 1386 Triples), 1387 add_paths_tree(Triples, OPTree, Tree). 1388 1389add_paths_tree([], Tree, Tree). 1390add_paths_tree([path(Path, Action, Options)|T], Tree0, Tree) :- 1391 add_path_tree(Path, Action, Options, [], Tree0, Tree1), 1392 add_paths_tree(T, Tree1, Tree).
1400plain_path(Path, Action, Options) :-
1401 handler(Spec, Action, false, Options),
1402 catch(http_absolute_location(Spec, Path, []), E,
1403 (print_message(error, E), fail)).
1412add_path_tree(Path, Action, Options0, DefOptions, [], 1413 [node(Path, Action, Options, [])]) :- 1414 !, 1415 merge_options(Options0, DefOptions, Options). 1416add_path_tree(Path, Action, Options, _, 1417 [node(prefix(Prefix), PA, DefOptions, Children0)|RestTree], 1418 [node(prefix(Prefix), PA, DefOptions, Children)|RestTree]) :- 1419 sub_atom(Path, 0, _, _, Prefix), 1420 !, 1421 delete(DefOptions, id(_), InheritOpts), 1422 add_path_tree(Path, Action, Options, InheritOpts, Children0, Children). 1423add_path_tree(Path, Action, Options1, DefOptions, [H0|T], [H|T]) :- 1424 H0 = node(Path, _, Options2, _), 1425 option(priority(P1), Options1, 0), 1426 option(priority(P2), Options2, 0), 1427 P1 >= P2, 1428 !, 1429 merge_options(Options1, DefOptions, Options), 1430 H = node(Path, Action, Options, []). 1431add_path_tree(Path, Action, Options, DefOptions, [H|T0], [H|T]) :- 1432 add_path_tree(Path, Action, Options, DefOptions, T0, T). 1433 1434 1435 /******************************* 1436 * MESSAGES * 1437 *******************************/ 1438 1439:- multifile 1440 prolog:message/3. 1441 1442prologmessage(http_dispatch(ambiguous_id(ID, _List, Selected))) --> 1443 [ 'HTTP dispatch: ambiguous handler ID ~q (selected ~q)'-[ID, Selected] 1444 ]. 1445 1446 1447 /******************************* 1448 * XREF * 1449 *******************************/ 1450 1451:- multifile 1452 prolog:meta_goal/2. 1453:- dynamic 1454 prolog:meta_goal/2. 1455 1456prologmeta_goal(http_handler(_, G, _), [G+1]). 1457prologmeta_goal(http_current_handler(_, G), [G+1]). 1458 1459 1460 /******************************* 1461 * EDIT * 1462 *******************************/ 1463 1464% Allow edit(Location) to edit the implementation for an HTTP location. 1465 1466:- multifile 1467 prolog_edit:locate/3. 1468 1469prolog_edit:locate(Path, Spec, Location) :- 1470 atom(Path), 1471 sub_atom(Path, 0, _, _, /), 1472 Pred = _M:_H, 1473 catch(http_current_handler(Path, Pred), _, fail), 1474 closure_name_arity(Pred, 1, PI), 1475 prolog_edit:locate(PI, Spec, Location). 1476 1477closure_name_arity(M:Term, Extra, M:Name/Arity) :- 1478 !, 1479 callable(Term), 1480 functor(Term, Name, Arity0), 1481 Arity is Arity0 + Extra. 1482closure_name_arity(Term, Extra, Name/Arity) :- 1483 callable(Term), 1484 functor(Term, Name, Arity0), 1485 Arity is Arity0 + Extra. 1486 1487 1488 /******************************* 1489 * CACHE CLEANUP * 1490 *******************************/ 1491 1492:- listen(settings(changed(http:prefix, _, _)), 1493 next_generation). 1494 1495:- multifile 1496 user:message_hook/3. 1497:- dynamic 1498 user:message_hook/3. 1499 1500user:message_hook(make(done(Reload)), _Level, _Lines) :- 1501 Reload \== [], 1502 next_generation, 1503 fail
Dispatch requests in the HTTP server
Most code doesn't need to use this directly; instead use library(http/http_server), which combines this library with the typical HTTP libraries that most servers need.
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:*/