35
36:- module(http_dispatch,
37 [ http_dispatch/1, 38 http_handler/3, 39 http_delete_handler/1, 40 http_reply_file/3, 41 http_redirect/3, 42 http_404/2, 43 http_switch_protocol/2, 44 http_current_handler/2, 45 http_current_handler/3, 46 http_location_by_id/2, 47 http_link_to_id/3, 48 http_reload_with_parameters/3, 49 http_safe_file/2 50 ]). 51:- use_module(library(option)). 52:- use_module(library(lists)). 53:- use_module(library(time)). 54:- use_module(library(error)). 55:- use_module(library(settings)). 56:- use_module(library(uri)). 57:- use_module(library(apply)). 58:- use_module(library(http/mimetype)). 59:- use_module(library(http/http_path)). 60:- use_module(library(http/http_header)). 61:- use_module(library(http/thread_httpd)). 62
63:- predicate_options(http_404/2, 1, [index(any)]). 64:- predicate_options(http_reply_file/3, 2,
65 [ cache(boolean),
66 mime_type(any),
67 static_gzip(boolean),
68 pass_to(http_safe_file/2, 2),
69 headers(list)
70 ]). 71:- predicate_options(http_safe_file/2, 2, [unsafe(boolean)]). 72:- predicate_options(http_switch_protocol/2, 2, []). 73
94
95:- setting(http:time_limit, nonneg, 300,
96 'Time limit handling a single query (0=infinite)'). 97
187
188:- dynamic handler/4. 189:- multifile handler/4. 190:- dynamic generation/1. 191
192:- meta_predicate
193 http_handler(+, :, +),
194 http_current_handler(?, :),
195 http_current_handler(?, :, ?),
196 http_switch_protocol(2, +). 197
198http_handler(Path, Pred, Options) :-
199 compile_handler(Path, Pred, Options, Clause),
200 next_generation,
201 assert(Clause).
202
203:- multifile
204 system:term_expansion/2. 205
206system:term_expansion((:- http_handler(Path, Pred, Options)), Clause) :-
207 \+ current_prolog_flag(xref, true),
208 prolog_load_context(module, M),
209 compile_handler(Path, M:Pred, Options, Clause),
210 next_generation.
211
212
224
225http_delete_handler(id(Id)) :-
226 !,
227 clause(handler(_Path, _:Pred, _, Options), true, Ref),
228 functor(Pred, DefID, _),
229 option(id(Id0), Options, DefID),
230 Id == Id0,
231 erase(Ref),
232 next_generation.
233http_delete_handler(path(Path)) :-
234 !,
235 retractall(handler(Path, _Pred, _, _Options)),
236 next_generation.
237http_delete_handler(Path) :-
238 http_delete_handler(path(Path)).
239
240
245
246next_generation :-
247 retractall(id_location_cache(_,_)),
248 with_mutex(http_dispatch, next_generation_unlocked).
249
250next_generation_unlocked :-
251 retract(generation(G0)),
252 !,
253 G is G0 + 1,
254 assert(generation(G)).
255next_generation_unlocked :-
256 assert(generation(1)).
257
258current_generation(G) :-
259 with_mutex(http_dispatch, generation(G)),
260 !.
261current_generation(0).
262
263
270
271compile_handler(Path, Pred, Options0,
272 http_dispatch:handler(Path1, Pred, IsPrefix, Options)) :-
273 check_path(Path, Path1),
274 ( select(prefix, Options0, Options1)
275 -> IsPrefix = true
276 ; IsPrefix = false,
277 Options1 = Options0
278 ),
279 Pred = M:_,
280 maplist(qualify_option(M), Options1, Options2),
281 combine_methods(Options2, Options).
282
283qualify_option(M, condition(Pred), condition(M:Pred)) :-
284 Pred \= _:_, !.
285qualify_option(_, Option, Option).
286
291
292combine_methods(Options0, Options) :-
293 collect_methods(Options0, Options1, Methods),
294 ( Methods == []
295 -> Options = Options0
296 ; append(Methods, Flat),
297 sort(Flat, Unique),
298 ( memberchk('*', Unique)
299 -> Final = '*'
300 ; Final = Unique
301 ),
302 Options = [methods(Final)|Options1]
303 ).
304
305collect_methods([], [], []).
306collect_methods([method(M)|T0], T, [[M]|TM]) :-
307 !,
308 ( M == '*'
309 -> true
310 ; must_be_method(M)
311 ),
312 collect_methods(T0, T, TM).
313collect_methods([methods(M)|T0], T, [M|TM]) :-
314 !,
315 must_be(list, M),
316 maplist(must_be_method, M),
317 collect_methods(T0, T, TM).
318collect_methods([H|T0], [H|T], TM) :-
319 !,
320 collect_methods(T0, T, TM).
321
322must_be_method(M) :-
323 must_be(atom, M),
324 ( method(M)
325 -> true
326 ; domain_error(http_method, M)
327 ).
328
329method(get).
330method(put).
331method(head).
332method(post).
333method(delete).
334method(patch).
335method(options).
336method(trace).
337
338
351
352check_path(Path, Path) :-
353 atom(Path),
354 !,
355 ( sub_atom(Path, 0, _, _, /)
356 -> true
357 ; domain_error(absolute_http_location, Path)
358 ).
359check_path(Alias, AliasOut) :-
360 compound(Alias),
361 Alias =.. [Name, Relative],
362 !,
363 to_atom(Relative, Local),
364 ( sub_atom(Local, 0, _, _, /)
365 -> domain_error(relative_location, Relative)
366 ; AliasOut =.. [Name, Local]
367 ).
368check_path(PathSpec, _) :-
369 type_error(path_or_alias, PathSpec).
370
371to_atom(Atom, Atom) :-
372 atom(Atom),
373 !.
374to_atom(Path, Atom) :-
375 phrase(path_to_list(Path), Components),
376 !,
377 atomic_list_concat(Components, '/', Atom).
378to_atom(Path, _) :-
379 ground(Path),
380 !,
381 type_error(relative_location, Path).
382to_atom(Path, _) :-
383 instantiation_error(Path).
384
385path_to_list(Var) -->
386 { var(Var),
387 !,
388 fail
389 }.
390path_to_list(A/B) -->
391 path_to_list(A),
392 path_to_list(B).
393path_to_list(Atom) -->
394 { atom(Atom) },
395 [Atom].
396
397
398
402
403http_dispatch(Request) :-
404 memberchk(path(Path), Request),
405 find_handler(Path, Pred, Options),
406 supports_method(Request, Options),
407 authentication(Options, Request, Fields),
408 append(Fields, Request, AuthRequest),
409 action(Pred, AuthRequest, Options).
410
411
416
417http_current_handler(Path, Closure) :-
418 atom(Path),
419 !,
420 path_tree(Tree),
421 find_handler(Tree, Path, Closure, _).
422http_current_handler(Path, M:C) :-
423 handler(Spec, M:C, _, _),
424 http_absolute_location(Spec, Path, []).
425
430
431http_current_handler(Path, Closure, Options) :-
432 atom(Path),
433 !,
434 path_tree(Tree),
435 find_handler(Tree, Path, Closure, Options).
436http_current_handler(Path, M:C, Options) :-
437 handler(Spec, M:C, _, _),
438 http_absolute_location(Spec, Path, []),
439 path_tree(Tree),
440 find_handler(Tree, Path, _, Options).
441
442
462
463:- dynamic
464 id_location_cache/2. 465
466http_location_by_id(ID, Location) :-
467 must_be(ground, ID),
468 id_location_cache(ID, L0),
469 !,
470 Location = L0.
471http_location_by_id(ID, Location) :-
472 findall(P-L, location_by_id(ID, L, P), List),
473 keysort(List, RevSorted),
474 reverse(RevSorted, Sorted),
475 ( Sorted = [_-One]
476 -> assert(id_location_cache(ID, One)),
477 Location = One
478 ; List == []
479 -> existence_error(http_handler_id, ID)
480 ; List = [P0-Best,P1-_|_]
481 -> ( P0 == P1
482 -> print_message(warning,
483 http_dispatch(ambiguous_id(ID, Sorted, Best)))
484 ; true
485 ),
486 assert(id_location_cache(ID, Best)),
487 Location = Best
488 ).
489
490location_by_id(ID, Location, Priority) :-
491 location_by_id_raw(ID, L0, Priority),
492 to_path(L0, Location).
493
494to_path(prefix(Path0), Path) :- 495 !,
496 add_prefix(Path0, Path).
497to_path(Path0, Path) :-
498 atomic(Path0), 499 !,
500 add_prefix(Path0, Path).
501to_path(Spec, Path) :- 502 http_absolute_location(Spec, Path, []).
503
504add_prefix(P0, P) :-
505 ( catch(setting(http:prefix, Prefix), _, fail),
506 Prefix \== ''
507 -> atom_concat(Prefix, P0, P)
508 ; P = P0
509 ).
510
511location_by_id_raw(ID, Location, Priority) :-
512 handler(Location, _, _, Options),
513 option(id(ID), Options),
514 option(priority(P0), Options, 0),
515 Priority is P0+1000. 516location_by_id_raw(ID, Location, Priority) :-
517 handler(Location, M:C, _, Options),
518 option(priority(Priority), Options, 0),
519 functor(C, PN, _),
520 ( ID = M:PN
521 ; ID = PN
522 ),
523 !.
524
525
565
566http_link_to_id(HandleID, path_postfix(File), HREF) :-
567 !,
568 http_location_by_id(HandleID, HandlerLocation),
569 uri_encoded(path, File, EncFile),
570 directory_file_path(HandlerLocation, EncFile, Location),
571 uri_data(path, Components, Location),
572 uri_components(HREF, Components).
573http_link_to_id(HandleID, Parameters, HREF) :-
574 must_be(list, Parameters),
575 http_location_by_id(HandleID, Location),
576 uri_data(path, Components, Location),
577 uri_query_components(String, Parameters),
578 uri_data(search, Components, String),
579 uri_components(HREF, Components).
580
585
586http_reload_with_parameters(Request, NewParams, HREF) :-
587 memberchk(path(Path), Request),
588 ( memberchk(search(Params), Request)
589 -> true
590 ; Params = []
591 ),
592 merge_options(NewParams, Params, AllParams),
593 uri_query_components(Search, AllParams),
594 uri_data(path, Data, Path),
595 uri_data(search, Data, Search),
596 uri_components(HREF, Data).
597
598
600
601:- multifile
602 html_write:expand_attribute_value//1. 603
604html_write:expand_attribute_value(location_by_id(ID)) -->
605 { http_location_by_id(ID, Location) },
606 html_write:html_quoted_attribute(Location).
607
608
617
618:- multifile
619 http:authenticate/3. 620
621authentication([], _, []).
622authentication([authentication(Type)|Options], Request, Fields) :-
623 !,
624 ( http:authenticate(Type, Request, XFields)
625 -> append(XFields, More, Fields),
626 authentication(Options, Request, More)
627 ; memberchk(path(Path), Request),
628 permission_error(access, http_location, Path)
629 ).
630authentication([_|Options], Request, Fields) :-
631 authentication(Options, Request, Fields).
632
633
649
650find_handler(Path, Action, Options) :-
651 path_tree(Tree),
652 ( find_handler(Tree, Path, Action, Options),
653 eval_condition(Options)
654 -> true
655 ; \+ sub_atom(Path, _, _, 0, /),
656 atom_concat(Path, /, Dir),
657 find_handler(Tree, Dir, Action, Options)
658 -> throw(http_reply(moved(Dir)))
659 ; throw(error(existence_error(http_location, Path), _))
660 ).
661
662
663find_handler([node(prefix(Prefix), PAction, POptions, Children)|_],
664 Path, Action, Options) :-
665 sub_atom(Path, 0, _, After, Prefix),
666 !,
667 ( option(hide_children(false), POptions, false),
668 find_handler(Children, Path, Action, Options)
669 -> true
670 ; Action = PAction,
671 path_info(After, Path, POptions, Options)
672 ).
673find_handler([node(Path, Action, Options, _)|_], Path, Action, Options) :- !.
674find_handler([_|Tree], Path, Action, Options) :-
675 find_handler(Tree, Path, Action, Options).
676
677path_info(0, _, Options,
678 [prefix(true)|Options]) :- !.
679path_info(After, Path, Options,
680 [path_info(PathInfo),prefix(true)|Options]) :-
681 sub_atom(Path, _, After, 0, PathInfo).
682
683eval_condition(Options) :-
684 ( memberchk(condition(Cond), Options)
685 -> catch(Cond, E, (print_message(warning, E), fail))
686 ; true
687 ).
688
689
697
698supports_method(Request, Options) :-
699 ( option(methods(Methods), Options)
700 -> ( Methods == '*'
701 -> true
702 ; memberchk(method(Method), Request),
703 memberchk(Method, Methods)
704 )
705 ; true
706 ),
707 !.
708supports_method(Request, _Options) :-
709 memberchk(path(Location), Request),
710 memberchk(method(Method), Request),
711 permission_error(http_method, Method, Location).
712
713
720
721action(Action, Request, Options) :-
722 memberchk(chunked, Options),
723 !,
724 format('Transfer-encoding: chunked~n'),
725 spawn_action(Action, Request, Options).
726action(Action, Request, Options) :-
727 spawn_action(Action, Request, Options).
728
729spawn_action(Action, Request, Options) :-
730 option(spawn(Spawn), Options),
731 !,
732 spawn_options(Spawn, SpawnOption),
733 http_spawn(time_limit_action(Action, Request, Options), SpawnOption).
734spawn_action(Action, Request, Options) :-
735 time_limit_action(Action, Request, Options).
736
737spawn_options([], []) :- !.
738spawn_options(Pool, Options) :-
739 atom(Pool),
740 !,
741 Options = [pool(Pool)].
742spawn_options(List, List).
743
744time_limit_action(Action, Request, Options) :-
745 ( option(time_limit(TimeLimit), Options),
746 TimeLimit \== default
747 -> true
748 ; setting(http:time_limit, TimeLimit)
749 ),
750 number(TimeLimit),
751 TimeLimit > 0,
752 !,
753 call_with_time_limit(TimeLimit, call_action(Action, Request, Options)).
754time_limit_action(Action, Request, Options) :-
755 call_action(Action, Request, Options).
756
757
761
762call_action(reply_file(File, FileOptions), Request, _Options) :-
763 !,
764 http_reply_file(File, FileOptions, Request).
765call_action(Pred, Request, Options) :-
766 memberchk(path_info(PathInfo), Options),
767 !,
768 call_action(Pred, [path_info(PathInfo)|Request]).
769call_action(Pred, Request, _Options) :-
770 call_action(Pred, Request).
771
772call_action(Pred, Request) :-
773 ( call(Pred, Request)
774 -> true
775 ; extend(Pred, [Request], Goal),
776 throw(error(goal_failed(Goal), _))
777 ).
778
779extend(Var, _, Var) :-
780 var(Var),
781 !.
782extend(M:G0, Extra, M:G) :-
783 extend(G0, Extra, G).
784extend(G0, Extra, G) :-
785 G0 =.. List,
786 append(List, Extra, List2),
787 G =.. List2.
788
822
823http_reply_file(File, Options, Request) :-
824 http_safe_file(File, Options),
825 absolute_file_name(File, Path,
826 [ access(read)
827 ]),
828 ( option(cache(true), Options, true)
829 -> ( memberchk(if_modified_since(Since), Request),
830 time_file(Path, Time),
831 catch(http_timestamp(Time, Since), _, fail)
832 -> throw(http_reply(not_modified))
833 ; true
834 ),
835 ( memberchk(range(Range), Request)
836 -> Reply = file(Type, Path, Range)
837 ; option(static_gzip(true), Options),
838 accepts_encoding(Request, gzip),
839 file_name_extension(Path, gz, PathGZ),
840 access_file(PathGZ, read),
841 time_file(PathGZ, TimeGZ),
842 time_file(Path, Time),
843 TimeGZ >= Time
844 -> Reply = gzip_file(Type, PathGZ)
845 ; Reply = file(Type, Path)
846 )
847 ; Reply = tmp_file(Type, Path)
848 ),
849 ( option(mime_type(Type), Options)
850 -> true
851 ; file_mime_type(Path, Type)
852 -> true
853 ; Type = text/plain 854 ),
855 option(headers(Headers), Options, []),
856 throw(http_reply(Reply, Headers)).
857
858accepts_encoding(Request, Enc) :-
859 memberchk(accept_encoding(Accept), Request),
860 split_string(Accept, ",", " ", Parts),
861 member(Part, Parts),
862 split_string(Part, ";", " ", [EncS|_]),
863 atom_string(Enc, EncS).
864
865
875
876http_safe_file(File, _) :-
877 var(File),
878 !,
879 instantiation_error(File).
880http_safe_file(_, Options) :-
881 option(unsafe(true), Options, false),
882 !.
883http_safe_file(File, _) :-
884 http_safe_file(File).
885
886http_safe_file(File) :-
887 compound(File),
888 functor(File, _, 1),
889 !,
890 arg(1, File, Name),
891 safe_name(Name, File).
892http_safe_file(Name) :-
893 ( is_absolute_file_name(Name)
894 -> permission_error(read, file, Name)
895 ; true
896 ),
897 safe_name(Name, Name).
898
899safe_name(Name, _) :-
900 must_be(atom, Name),
901 prolog_to_os_filename(FileName, Name),
902 \+ unsafe_name(FileName),
903 !.
904safe_name(_, Spec) :-
905 permission_error(read, file, Spec).
906
907unsafe_name(Name) :- Name == '..'.
908unsafe_name(Name) :- sub_atom(Name, 0, _, _, '../').
909unsafe_name(Name) :- sub_atom(Name, _, _, _, '/../').
910unsafe_name(Name) :- sub_atom(Name, _, _, 0, '/..').
911
912
929
930http_redirect(How, To, Request) :-
931 ( To = location_by_id(Id)
932 -> http_location_by_id(Id, URL)
933 ; memberchk(path(Base), Request),
934 http_absolute_location(To, URL, [relative_to(Base)])
935 ),
936 must_be(oneof([moved, moved_temporary, see_other]), How),
937 Term =.. [How,URL],
938 throw(http_reply(Term)).
939
940
952
953http_404(Options, Request) :-
954 option(index(Index), Options),
955 \+ ( option(path_info(PathInfo), Request),
956 PathInfo \== ''
957 ),
958 !,
959 http_redirect(moved, Index, Request).
960http_404(_Options, Request) :-
961 option(path(Path), Request),
962 !,
963 throw(http_reply(not_found(Path))).
964http_404(_Options, Request) :-
965 domain_error(http_request, Request).
966
967
998
1000
1001http_switch_protocol(Goal, Options) :-
1002 throw(http_reply(switching_protocols(Goal, Options))).
1003
1004
1005 1008
1022
1023path_tree(Tree) :-
1024 current_generation(G),
1025 nb_current(http_dispatch_tree, G-Tree),
1026 !. 1027path_tree(Tree) :-
1028 path_tree_nocache(Tree),
1029 current_generation(G),
1030 nb_setval(http_dispatch_tree, G-Tree).
1031
1032path_tree_nocache(Tree) :-
1033 findall(Prefix, prefix_handler(Prefix, _, _), Prefixes0),
1034 sort(Prefixes0, Prefixes),
1035 prefix_tree(Prefixes, [], PTree),
1036 prefix_options(PTree, [], OPTree),
1037 add_paths_tree(OPTree, Tree).
1038
1039prefix_handler(Prefix, Action, Options) :-
1040 handler(Spec, Action, true, Options),
1041 Error = error(existence_error(http_alias,_),_),
1042 catch(http_absolute_location(Spec, Prefix, []), Error,
1043 ( print_message(warning, Error),
1044 fail
1045 )).
1046
1050
1051prefix_tree([], Tree, Tree).
1052prefix_tree([H|T], Tree0, Tree) :-
1053 insert_prefix(H, Tree0, Tree1),
1054 prefix_tree(T, Tree1, Tree).
1055
1056insert_prefix(Prefix, Tree0, Tree) :-
1057 select(P-T, Tree0, Tree1),
1058 sub_atom(Prefix, 0, _, _, P),
1059 !,
1060 insert_prefix(Prefix, T, T1),
1061 Tree = [P-T1|Tree1].
1062insert_prefix(Prefix, Tree, [Prefix-[]|Tree]).
1063
1064
1070
1071prefix_options([], _, []).
1072prefix_options([P-C|T0], DefOptions,
1073 [node(prefix(P), Action, Options, Children)|T]) :-
1074 once(prefix_handler(P, Action, Options0)),
1075 merge_options(Options0, DefOptions, Options),
1076 delete(Options, id(_), InheritOpts),
1077 prefix_options(C, InheritOpts, Children),
1078 prefix_options(T0, DefOptions, T).
1079
1080
1084
1085add_paths_tree(OPTree, Tree) :-
1086 findall(path(Path, Action, Options),
1087 plain_path(Path, Action, Options),
1088 Triples),
1089 add_paths_tree(Triples, OPTree, Tree).
1090
1091add_paths_tree([], Tree, Tree).
1092add_paths_tree([path(Path, Action, Options)|T], Tree0, Tree) :-
1093 add_path_tree(Path, Action, Options, [], Tree0, Tree1),
1094 add_paths_tree(T, Tree1, Tree).
1095
1096
1101
1102plain_path(Path, Action, Options) :-
1103 handler(Spec, Action, false, Options),
1104 catch(http_absolute_location(Spec, Path, []), E,
1105 (print_message(error, E), fail)).
1106
1107
1113
1114add_path_tree(Path, Action, Options0, DefOptions, [],
1115 [node(Path, Action, Options, [])]) :-
1116 !,
1117 merge_options(Options0, DefOptions, Options).
1118add_path_tree(Path, Action, Options, _,
1119 [node(prefix(Prefix), PA, DefOptions, Children0)|RestTree],
1120 [node(prefix(Prefix), PA, DefOptions, Children)|RestTree]) :-
1121 sub_atom(Path, 0, _, _, Prefix),
1122 !,
1123 delete(DefOptions, id(_), InheritOpts),
1124 add_path_tree(Path, Action, Options, InheritOpts, Children0, Children).
1125add_path_tree(Path, Action, Options1, DefOptions, [H0|T], [H|T]) :-
1126 H0 = node(Path, _, Options2, _),
1127 option(priority(P1), Options1, 0),
1128 option(priority(P2), Options2, 0),
1129 P1 >= P2,
1130 !,
1131 merge_options(Options1, DefOptions, Options),
1132 H = node(Path, Action, Options, []).
1133add_path_tree(Path, Action, Options, DefOptions, [H|T0], [H|T]) :-
1134 add_path_tree(Path, Action, Options, DefOptions, T0, T).
1135
1136
1137 1140
1141:- multifile
1142 prolog:message/3. 1143
1144prolog:message(http_dispatch(ambiguous_id(ID, _List, Selected))) -->
1145 [ 'HTTP dispatch: ambiguous handler ID ~q (selected ~q)'-[ID, Selected]
1146 ].
1147
1148
1149 1152
1153:- multifile
1154 prolog:meta_goal/2. 1155:- dynamic
1156 prolog:meta_goal/2. 1157
1158prolog:meta_goal(http_handler(_, G, _), [G+1]).
1159prolog:meta_goal(http_current_handler(_, G), [G+1]).
1160
1161
1162 1165
1167
1168:- multifile
1169 prolog_edit:locate/3. 1170
1171prolog_edit:locate(Path, Spec, Location) :-
1172 atom(Path),
1173 sub_atom(Path, 0, _, _, /),
1174 Pred = _M:_H,
1175 catch(http_current_handler(Path, Pred), _, fail),
1176 closure_name_arity(Pred, 1, PI),
1177 prolog_edit:locate(PI, Spec, Location).
1178
1179closure_name_arity(M:Term, Extra, M:Name/Arity) :-
1180 !,
1181 callable(Term),
1182 functor(Term, Name, Arity0),
1183 Arity is Arity0 + Extra.
1184closure_name_arity(Term, Extra, Name/Arity) :-
1185 callable(Term),
1186 functor(Term, Name, Arity0),
1187 Arity is Arity0 + Extra.
1188
1189
1190 1193
1194:- listen(settings(changed(http:prefix, _, _)),
1195 next_generation). 1196
1197:- multifile
1198 user:message_hook/3. 1199:- dynamic
1200 user:message_hook/3. 1201
1202user:message_hook(make(done(Reload)), _Level, _Lines) :-
1203 Reload \== [],
1204 next_generation,
1205 fail