35
36:- module(http_open,
37 [ http_open/3, 38 http_set_authorization/2, 39 http_close_keep_alive/1 40 ]). 41:- use_module(library(uri)). 42:- use_module(library(readutil)). 43:- use_module(library(socket)). 44:- use_module(library(lists)). 45:- use_module(library(option)). 46:- use_module(library(error)). 47:- use_module(library(base64)). 48:- use_module(library(debug)). 49:- use_module(library(aggregate)). 50:- use_module(library(apply)). 51:- use_module(library(http/http_header), [http_parse_header/2]). 52:- use_module(library(http/http_stream)). 53
146
147:- multifile
148 http:encoding_filter/3, 149 http:current_transfer_encoding/1, 150 http:disable_encoding_filter/1, 151 http:http_protocol_hook/5, 152 153 http:open_options/2, 154 http:write_cookies/3, 155 http:update_cookies/3, 156 http:authenticate_client/2, 157 http:http_connection_over_proxy/6. 158
159:- meta_predicate
160 http_open(+,-,:). 161
162:- predicate_options(http_open/3, 3,
163 [ authorization(compound),
164 final_url(-atom),
165 header(+atom, -atom),
166 headers(-list),
167 connection(+atom),
168 method(oneof([delete,get,put,head,post,patch,options])),
169 size(-integer),
170 status_code(-integer),
171 output(-stream),
172 timeout(number),
173 proxy(atom, integer),
174 proxy_authorization(compound),
175 bypass_proxy(boolean),
176 request_header(any),
177 user_agent(atom),
178 version(-compound),
179 180 post(any),
181 182 pem_password_hook(callable),
183 cacert_file(atom),
184 cert_verify_hook(callable)
185 ]). 186
191
192user_agent('SWI-Prolog').
193
367
368:- multifile
369 socket:proxy_for_url/3. 370
371http_open(URL, Stream, QOptions) :-
372 meta_options(is_meta, QOptions, Options),
373 ( atomic(URL)
374 -> parse_url_ex(URL, Parts)
375 ; Parts = URL
376 ),
377 autoload_https(Parts),
378 add_authorization(Parts, Options, Options1),
379 findall(HostOptions,
380 http:open_options(Parts, HostOptions),
381 AllHostOptions),
382 foldl(merge_options_rev, AllHostOptions, Options1, Options2),
383 ( option(bypass_proxy(true), Options)
384 -> try_http_proxy(direct, Parts, Stream, Options2)
385 ; term_variables(Options2, Vars2),
386 findall(Result-Vars2,
387 try_a_proxy(Parts, Result, Options2),
388 ResultList),
389 last(ResultList, Status-Vars2)
390 -> ( Status = true(_Proxy, Stream)
391 -> true
392 ; throw(error(proxy_error(tried(ResultList)), _))
393 )
394 ; try_http_proxy(direct, Parts, Stream, Options2)
395 ).
396
397try_a_proxy(Parts, Result, Options) :-
398 parts_uri(Parts, AtomicURL),
399 option(host(Host), Parts),
400 ( ( option(proxy(ProxyHost:ProxyPort), Options)
401 ; is_list(Options),
402 memberchk(proxy(ProxyHost,ProxyPort), Options)
403 )
404 -> Proxy = proxy(ProxyHost, ProxyPort)
405 ; socket:proxy_for_url(AtomicURL, Host, Proxy)
406 ),
407 debug(http(proxy),
408 'http_open: Connecting via ~w to ~w', [Proxy, AtomicURL]),
409 ( catch(try_http_proxy(Proxy, Parts, Stream, Options), E, true)
410 -> ( var(E)
411 -> !, Result = true(Proxy, Stream)
412 ; Result = error(Proxy, E)
413 )
414 ; Result = false(Proxy)
415 ),
416 debug(http(proxy), 'http_open: ~w: ~p', [Proxy, Result]).
417
418try_http_proxy(Method, Parts, Stream, Options0) :-
419 option(host(Host), Parts),
420 ( Method == direct
421 -> parts_request_uri(Parts, RequestURI)
422 ; parts_uri(Parts, RequestURI)
423 ),
424 select_option(visited(Visited0), Options0, OptionsV, []),
425 Options = [visited([Parts|Visited0])|OptionsV],
426 parts_scheme(Parts, Scheme),
427 default_port(Scheme, DefPort),
428 url_part(port(Port), Parts, DefPort),
429 host_and_port(Host, DefPort, Port, HostPort),
430 ( option(connection(Connection), Options0),
431 keep_alive(Connection),
432 get_from_pool(Host:Port, StreamPair),
433 debug(http(connection), 'Trying Keep-alive to ~p using ~p',
434 [ Host:Port, StreamPair ]),
435 catch(send_rec_header(StreamPair, Stream, HostPort,
436 RequestURI, Parts, Options),
437 error(E,_),
438 keep_alive_error(E))
439 -> true
440 ; http:http_connection_over_proxy(Method, Parts, Host:Port,
441 SocketStreamPair, Options, Options1),
442 ( catch(http:http_protocol_hook(Scheme, Parts,
443 SocketStreamPair,
444 StreamPair, Options),
445 Error,
446 ( close(SocketStreamPair, [force(true)]),
447 throw(Error)))
448 -> true
449 ; StreamPair = SocketStreamPair
450 ),
451 send_rec_header(StreamPair, Stream, HostPort,
452 RequestURI, Parts, Options1)
453 ),
454 return_final_url(Options).
455
456http:http_connection_over_proxy(direct, _, Host:Port,
457 StreamPair, Options, Options) :-
458 !,
459 open_socket(Host:Port, StreamPair, Options).
460http:http_connection_over_proxy(proxy(ProxyHost, ProxyPort), Parts, _,
461 StreamPair, Options, Options) :-
462 \+ ( memberchk(scheme(Scheme), Parts),
463 secure_scheme(Scheme)
464 ),
465 !,
466 467 open_socket(ProxyHost:ProxyPort, StreamPair,
468 [bypass_proxy(true)|Options]).
469http:http_connection_over_proxy(socks(SocksHost, SocksPort), _Parts, Host:Port,
470 StreamPair, Options, Options) :-
471 !,
472 tcp_connect(SocksHost:SocksPort, StreamPair, [bypass_proxy(true)]),
473 catch(negotiate_socks_connection(Host:Port, StreamPair),
474 Error,
475 ( close(StreamPair, [force(true)]),
476 throw(Error)
477 )).
478
479
480merge_options_rev(Old, New, Merged) :-
481 merge_options(New, Old, Merged).
482
483is_meta(pem_password_hook). 484is_meta(cert_verify_hook).
485
486
487http:http_protocol_hook(http, _, StreamPair, StreamPair, _).
488
489default_port(https, 443) :- !.
490default_port(wss, 443) :- !.
491default_port(_, 80).
492
493host_and_port(Host, DefPort, DefPort, Host) :- !.
494host_and_port(Host, _, Port, Host:Port).
495
499
500autoload_https(Parts) :-
501 memberchk(scheme(S), Parts),
502 secure_scheme(S),
503 \+ clause(http:http_protocol_hook(S, _, StreamPair, StreamPair, _),_),
504 exists_source(library(http/http_ssl_plugin)),
505 !,
506 use_module(library(http/http_ssl_plugin)).
507autoload_https(_).
508
509secure_scheme(https).
510secure_scheme(wss).
511
517
(StreamPair, Stream, Host, RequestURI, Parts, Options) :-
519 ( catch(guarded_send_rec_header(StreamPair, Stream,
520 Host, RequestURI, Parts, Options),
521 E, true)
522 -> ( var(E)
523 -> ( option(output(StreamPair), Options)
524 -> true
525 ; true
526 )
527 ; close(StreamPair, [force(true)]),
528 throw(E)
529 )
530 ; close(StreamPair, [force(true)]),
531 fail
532 ).
533
(StreamPair, Stream, Host, RequestURI, Parts, Options) :-
535 user_agent(Agent, Options),
536 method(Options, MNAME),
537 http_version(Version),
538 option(connection(Connection), Options, close),
539 debug(http(send_request), "> ~w ~w HTTP/~w", [MNAME, RequestURI, Version]),
540 debug(http(send_request), "> Host: ~w", [Host]),
541 debug(http(send_request), "> User-Agent: ~w", [Agent]),
542 debug(http(send_request), "> Connection: ~w", [Connection]),
543 format(StreamPair,
544 '~w ~w HTTP/~w\r\n\c
545 Host: ~w\r\n\c
546 User-Agent: ~w\r\n\c
547 Connection: ~w\r\n',
548 [MNAME, RequestURI, Version, Host, Agent, Connection]),
549 parts_uri(Parts, URI),
550 x_headers(Options, URI, StreamPair),
551 write_cookies(StreamPair, Parts, Options),
552 ( option(post(PostData), Options)
553 -> http_header:http_post_data(PostData, StreamPair, [])
554 ; format(StreamPair, '\r\n', [])
555 ),
556 flush_output(StreamPair),
557 558 read_header(StreamPair, Parts, ReplyVersion, Code, Comment, Lines),
559 update_cookies(Lines, Parts, Options),
560 do_open(ReplyVersion, Code, Comment, Lines, Options, Parts, Host,
561 StreamPair, Stream).
562
563
568
569http_version('1.1') :-
570 http:current_transfer_encoding(chunked),
571 !.
572http_version('1.0').
573
574method(Options, MNAME) :-
575 option(post(_), Options),
576 !,
577 option(method(M), Options, post),
578 ( map_method(M, MNAME0)
579 -> MNAME = MNAME0
580 ; domain_error(method, M)
581 ).
582method(Options, MNAME) :-
583 option(method(M), Options, get),
584 ( map_method(M, MNAME0)
585 -> MNAME = MNAME0
586 ; map_method(_, M)
587 -> MNAME = M
588 ; domain_error(method, M)
589 ).
590
591map_method(delete, 'DELETE').
592map_method(get, 'GET').
593map_method(head, 'HEAD').
594map_method(post, 'POST').
595map_method(put, 'PUT').
596map_method(patch, 'PATCH').
597map_method(options, 'OPTIONS').
598
605
(Options, URI, Out) :-
607 x_headers_(Options, [url(URI)|Options], Out).
608
([], _, _).
610x_headers_([H|T], Options, Out) :-
611 x_header(H, Options, Out),
612 x_headers_(T, Options, Out).
613
(request_header(Name=Value), _, Out) :-
615 !,
616 debug(http(send_request), "> ~w: ~w", [Name, Value]),
617 format(Out, '~w: ~w\r\n', [Name, Value]).
618x_header(proxy_authorization(ProxyAuthorization), Options, Out) :-
619 !,
620 auth_header(ProxyAuthorization, Options, 'Proxy-Authorization', Out).
621x_header(authorization(Authorization), Options, Out) :-
622 !,
623 auth_header(Authorization, Options, 'Authorization', Out).
624x_header(range(Spec), _, Out) :-
625 !,
626 Spec =.. [Unit, From, To],
627 ( To == end
628 -> ToT = ''
629 ; must_be(integer, To),
630 ToT = To
631 ),
632 debug(http(send_request), "> Range: ~w=~d-~w", [Unit, From, ToT]),
633 format(Out, 'Range: ~w=~d-~w\r\n', [Unit, From, ToT]).
634x_header(_, _, _).
635
637
(basic(User, Password), _, Header, Out) :-
639 !,
640 format(codes(Codes), '~w:~w', [User, Password]),
641 phrase(base64(Codes), Base64Codes),
642 debug(http(send_request), "> ~w: Basic ~s", [Header, Base64Codes]),
643 format(Out, '~w: Basic ~s\r\n', [Header, Base64Codes]).
644auth_header(bearer(Token), _, Header, Out) :-
645 !,
646 debug(http(send_request), "> ~w: Bearer ~w", [Header,Token]),
647 format(Out, '~w: Bearer ~w\r\n', [Header, Token]).
648auth_header(Auth, Options, _, Out) :-
649 option(url(URL), Options),
650 add_method(Options, Options1),
651 http:authenticate_client(URL, send_auth_header(Auth, Out, Options1)),
652 !.
653auth_header(Auth, _, _, _) :-
654 domain_error(authorization, Auth).
655
656user_agent(Agent, Options) :-
657 ( option(user_agent(Agent), Options)
658 -> true
659 ; user_agent(Agent)
660 ).
661
662add_method(Options0, Options) :-
663 option(method(_), Options0),
664 !,
665 Options = Options0.
666add_method(Options0, Options) :-
667 option(post(_), Options0),
668 !,
669 Options = [method(post)|Options0].
670add_method(Options0, [method(get)|Options0]).
671
672
680
681 682do_open(_, Code, _, Lines, Options0, Parts, _, In, Stream) :-
683 redirect_code(Code),
684 option(redirect(true), Options0, true),
685 location(Lines, RequestURI),
686 !,
687 debug(http(redirect), 'http_open: redirecting to ~w', [RequestURI]),
688 close(In),
689 parts_uri(Parts, Base),
690 uri_resolve(RequestURI, Base, Redirected),
691 parse_url_ex(Redirected, RedirectedParts),
692 ( redirect_limit_exceeded(Options0, Max)
693 -> format(atom(Comment), 'max_redirect (~w) limit exceeded', [Max]),
694 throw(error(permission_error(redirect, http, Redirected),
695 context(_, Comment)))
696 ; redirect_loop(RedirectedParts, Options0)
697 -> throw(error(permission_error(redirect, http, Redirected),
698 context(_, 'Redirection loop')))
699 ; true
700 ),
701 redirect_options(Options0, Options),
702 http_open(RedirectedParts, Stream, Options).
703 704do_open(_Version, Code, _Comment, Lines, Options0, Parts, _Host, In0, Stream) :-
705 authenticate_code(Code),
706 option(authenticate(true), Options0, true),
707 parts_uri(Parts, URI),
708 parse_headers(Lines, Headers),
709 http:authenticate_client(
710 URI,
711 auth_reponse(Headers, Options0, Options)),
712 !,
713 close(In0),
714 http_open(Parts, Stream, Options).
715 716do_open(Version, Code, _, Lines, Options, Parts, Host, In0, In) :-
717 ( option(status_code(Code), Options),
718 Lines \== []
719 -> true
720 ; Code == 200
721 ),
722 !,
723 parts_uri(Parts, URI),
724 parse_headers(Lines, Headers),
725 return_version(Options, Version),
726 return_size(Options, Headers),
727 return_fields(Options, Headers),
728 return_headers(Options, Headers),
729 consider_keep_alive(Lines, Parts, Host, In0, In1, Options),
730 transfer_encoding_filter(Lines, In1, In),
731 732 set_stream(In, file_name(URI)),
733 set_stream(In, record_position(true)).
734do_open(_, _, _, [], Options, _, _, _, _) :-
735 option(connection(Connection), Options),
736 keep_alive(Connection),
737 !,
738 throw(error(keep_alive(closed),_)).
739 740do_open(_Version, Code, Comment, _, _, Parts, _, _, _) :-
741 parts_uri(Parts, URI),
742 ( map_error_code(Code, Error)
743 -> Formal =.. [Error, url, URI]
744 ; Formal = existence_error(url, URI)
745 ),
746 throw(error(Formal, context(_, status(Code, Comment)))).
747
748
752
753redirect_limit_exceeded(Options, Max) :-
754 option(visited(Visited), Options, []),
755 length(Visited, N),
756 option(max_redirect(Max), Options, 10),
757 (Max == infinite -> fail ; N > Max).
758
759
766
767redirect_loop(Parts, Options) :-
768 option(visited(Visited), Options, []),
769 include(==(Parts), Visited, Same),
770 length(Same, Count),
771 Count > 2.
772
773
779
780redirect_options(Options0, Options) :-
781 ( select_option(post(_), Options0, Options1)
782 -> true
783 ; Options1 = Options0
784 ),
785 ( select_option(method(Method), Options1, Options),
786 \+ redirect_method(Method)
787 -> true
788 ; Options = Options1
789 ).
790
791redirect_method(delete).
792redirect_method(get).
793redirect_method(head).
794
795
802
803map_error_code(401, permission_error).
804map_error_code(403, permission_error).
805map_error_code(404, existence_error).
806map_error_code(405, permission_error).
807map_error_code(407, permission_error).
808map_error_code(410, existence_error).
809
810redirect_code(301). 811redirect_code(302). 812redirect_code(303). 813
814authenticate_code(401).
815
826
827open_socket(Address, StreamPair, Options) :-
828 debug(http(open), 'http_open: Connecting to ~p ...', [Address]),
829 tcp_connect(Address, StreamPair, Options),
830 stream_pair(StreamPair, In, Out),
831 debug(http(open), '\tok ~p ---> ~p', [In, Out]),
832 set_stream(In, record_position(false)),
833 ( option(timeout(Timeout), Options)
834 -> set_stream(In, timeout(Timeout))
835 ; true
836 ).
837
838
839return_version(Options, Major-Minor) :-
840 option(version(Major-Minor), Options, _).
841
842return_size(Options, Headers) :-
843 ( memberchk(content_length(Size), Headers)
844 -> option(size(Size), Options, _)
845 ; true
846 ).
847
848return_fields([], _).
849return_fields([header(Name, Value)|T], Headers) :-
850 !,
851 ( Term =.. [Name,Value],
852 memberchk(Term, Headers)
853 -> true
854 ; Value = ''
855 ),
856 return_fields(T, Headers).
857return_fields([_|T], Lines) :-
858 return_fields(T, Lines).
859
(Options, Headers) :-
861 option(headers(Headers), Options, _).
862
868
([], []) :- !.
870parse_headers([Line|Lines], Headers) :-
871 catch(http_parse_header(Line, [Header]), Error, true),
872 ( var(Error)
873 -> Headers = [Header|More]
874 ; print_message(warning, Error),
875 Headers = More
876 ),
877 parse_headers(Lines, More).
878
879
884
885return_final_url(Options) :-
886 option(final_url(URL), Options),
887 var(URL),
888 !,
889 option(visited([Parts|_]), Options),
890 parts_uri(Parts, URL).
891return_final_url(_).
892
893
902
903transfer_encoding_filter(Lines, In0, In) :-
904 transfer_encoding(Lines, Encoding),
905 !,
906 transfer_encoding_filter_(Encoding, In0, In).
907transfer_encoding_filter(Lines, In0, In) :-
908 content_encoding(Lines, Encoding),
909 content_type(Lines, Type),
910 \+ http:disable_encoding_filter(Type),
911 !,
912 transfer_encoding_filter_(Encoding, In0, In).
913transfer_encoding_filter(_, In, In).
914
915transfer_encoding_filter_(Encoding, In0, In) :-
916 stream_pair(In0, In1, Out),
917 ( nonvar(Out)
918 -> close(Out)
919 ; true
920 ),
921 ( http:encoding_filter(Encoding, In1, In)
922 -> true
923 ; domain_error(http_encoding, Encoding)
924 ).
925
926content_type(Lines, Type) :-
927 member(Line, Lines),
928 phrase(field('content-type'), Line, Rest),
929 !,
930 atom_codes(Type, Rest).
931
937
938http:disable_encoding_filter('application/x-gzip').
939http:disable_encoding_filter('application/x-tar').
940http:disable_encoding_filter('x-world/x-vrml').
941http:disable_encoding_filter('application/zip').
942http:disable_encoding_filter('application/x-gzip').
943http:disable_encoding_filter('application/x-zip-compressed').
944http:disable_encoding_filter('application/x-compress').
945http:disable_encoding_filter('application/x-compressed').
946http:disable_encoding_filter('application/x-spoon').
947
952
953transfer_encoding(Lines, Encoding) :-
954 what_encoding(transfer_encoding, Lines, Encoding).
955
956what_encoding(What, Lines, Encoding) :-
957 member(Line, Lines),
958 phrase(encoding_(What, Debug), Line, Rest),
959 !,
960 atom_codes(Encoding, Rest),
961 debug(http(What), '~w: ~p', [Debug, Rest]).
962
963encoding_(content_encoding, 'Content-encoding') -->
964 field('content-encoding').
965encoding_(transfer_encoding, 'Transfer-encoding') -->
966 field('transfer-encoding').
967
972
973content_encoding(Lines, Encoding) :-
974 what_encoding(content_encoding, Lines, Encoding).
975
992
(In, Parts, Major-Minor, Code, Comment, Lines) :-
994 read_line_to_codes(In, Line),
995 ( Line == end_of_file
996 -> parts_uri(Parts, Uri),
997 existence_error(http_reply,Uri)
998 ; true
999 ),
1000 Line \== end_of_file,
1001 phrase(first_line(Major-Minor, Code, Comment), Line),
1002 debug(http(open), 'HTTP/~d.~d ~w ~w', [Major, Minor, Code, Comment]),
1003 read_line_to_codes(In, Line2),
1004 rest_header(Line2, In, Lines),
1005 !,
1006 ( debugging(http(open))
1007 -> forall(member(HL, Lines),
1008 debug(http(open), '~s', [HL]))
1009 ; true
1010 ).
1011read_header(_, _, 1-1, 500, 'Invalid reply header', []).
1012
([], _, []) :- !. 1014rest_header(L0, In, [L0|L]) :-
1015 read_line_to_codes(In, L1),
1016 rest_header(L1, In, L).
1017
1021
1022content_length(Lines, Length) :-
1023 member(Line, Lines),
1024 phrase(content_length(Length0), Line),
1025 !,
1026 Length = Length0.
1027
1028location(Lines, RequestURI) :-
1029 member(Line, Lines),
1030 phrase(atom_field(location, RequestURI), Line),
1031 !.
1032
1033connection(Lines, Connection) :-
1034 member(Line, Lines),
1035 phrase(atom_field(connection, Connection0), Line),
1036 !,
1037 Connection = Connection0.
1038
1039first_line(Major-Minor, Code, Comment) -->
1040 "HTTP/", integer(Major), ".", integer(Minor),
1041 skip_blanks,
1042 integer(Code),
1043 skip_blanks,
1044 rest(Comment).
1045
1046atom_field(Name, Value) -->
1047 field(Name),
1048 rest(Value).
1049
1050content_length(Len) -->
1051 field('content-length'),
1052 integer(Len).
1053
1054field(Name) -->
1055 { atom_codes(Name, Codes) },
1056 field_codes(Codes).
1057
1058field_codes([]) -->
1059 ":",
1060 skip_blanks.
1061field_codes([H|T]) -->
1062 [C],
1063 { match_header_char(H, C)
1064 },
1065 field_codes(T).
1066
(C, C) :- !.
1068match_header_char(C, U) :-
1069 code_type(C, to_lower(U)),
1070 !.
1071match_header_char(0'_, 0'-).
1072
1073
1074skip_blanks -->
1075 [C],
1076 { code_type(C, white)
1077 },
1078 !,
1079 skip_blanks.
1080skip_blanks -->
1081 [].
1082
1086
1087integer(Code) -->
1088 digit(D0),
1089 digits(D),
1090 { number_codes(Code, [D0|D])
1091 }.
1092
1093digit(C) -->
1094 [C],
1095 { code_type(C, digit)
1096 }.
1097
1098digits([D0|D]) -->
1099 digit(D0),
1100 !,
1101 digits(D).
1102digits([]) -->
1103 [].
1104
1108
1109rest(Atom) --> call(rest_(Atom)).
1110
1111rest_(Atom, L, []) :-
1112 atom_codes(Atom, L).
1113
1114
1115 1118
1132
1133:- dynamic
1134 stored_authorization/2,
1135 cached_authorization/2. 1136
1137http_set_authorization(URL, Authorization) :-
1138 must_be(atom, URL),
1139 retractall(stored_authorization(URL, _)),
1140 ( Authorization = (-)
1141 -> true
1142 ; check_authorization(Authorization),
1143 assert(stored_authorization(URL, Authorization))
1144 ),
1145 retractall(cached_authorization(_,_)).
1146
1147check_authorization(Var) :-
1148 var(Var),
1149 !,
1150 instantiation_error(Var).
1151check_authorization(basic(User, Password)) :-
1152 must_be(atom, User),
1153 must_be(text, Password).
1154check_authorization(digest(User, Password)) :-
1155 must_be(atom, User),
1156 must_be(text, Password).
1157
1163
1164authorization(_, _) :-
1165 \+ stored_authorization(_, _),
1166 !,
1167 fail.
1168authorization(URL, Authorization) :-
1169 cached_authorization(URL, Authorization),
1170 !,
1171 Authorization \== (-).
1172authorization(URL, Authorization) :-
1173 ( stored_authorization(Prefix, Authorization),
1174 sub_atom(URL, 0, _, _, Prefix)
1175 -> assert(cached_authorization(URL, Authorization))
1176 ; assert(cached_authorization(URL, -)),
1177 fail
1178 ).
1179
1180add_authorization(_, Options, Options) :-
1181 option(authorization(_), Options),
1182 !.
1183add_authorization(Parts, Options0, Options) :-
1184 url_part(user(User), Parts),
1185 url_part(password(Passwd), Parts),
1186 Options = [authorization(basic(User,Passwd))|Options0].
1187add_authorization(Parts, Options0, Options) :-
1188 stored_authorization(_, _) -> 1189 parts_uri(Parts, URL),
1190 authorization(URL, Auth),
1191 !,
1192 Options = [authorization(Auth)|Options0].
1193add_authorization(_, Options, Options).
1194
1195
1200
1201parse_url_ex(URL, [uri(URL)|Parts]) :-
1202 uri_components(URL, Components),
1203 phrase(components(Components), Parts),
1204 ( option(host(_), Parts)
1205 -> true
1206 ; domain_error(url, URL)
1207 ).
1208
1209components(Components) -->
1210 uri_scheme(Components),
1211 uri_authority(Components),
1212 uri_request_uri(Components).
1213
1214uri_scheme(Components) -->
1215 { uri_data(scheme, Components, Scheme), nonvar(Scheme) },
1216 !,
1217 [ scheme(Scheme)
1218 ].
1219uri_scheme(_) --> [].
1220
1221uri_authority(Components) -->
1222 { uri_data(authority, Components, Auth), nonvar(Auth),
1223 !,
1224 uri_authority_components(Auth, Data)
1225 },
1226 [ authority(Auth) ],
1227 auth_field(user, Data),
1228 auth_field(password, Data),
1229 auth_field(host, Data),
1230 auth_field(port, Data).
1231uri_authority(_) --> [].
1232
1233auth_field(Field, Data) -->
1234 { uri_authority_data(Field, Data, EncValue), nonvar(EncValue),
1235 !,
1236 ( atom(EncValue)
1237 -> uri_encoded(query_value, Value, EncValue)
1238 ; Value = EncValue
1239 ),
1240 Part =.. [Field,Value]
1241 },
1242 [ Part ].
1243auth_field(_, _) --> [].
1244
1245uri_request_uri(Components) -->
1246 { uri_data(path, Components, Path0),
1247 uri_data(search, Components, Search),
1248 ( Path0 == ''
1249 -> Path = (/)
1250 ; Path = Path0
1251 ),
1252 uri_data(path, Components2, Path),
1253 uri_data(search, Components2, Search),
1254 uri_components(RequestURI, Components2)
1255 },
1256 [ request_uri(RequestURI)
1257 ].
1258
1264
1265parts_scheme(Parts, Scheme) :-
1266 url_part(scheme(Scheme), Parts),
1267 !.
1268parts_scheme(Parts, Scheme) :- 1269 url_part(protocol(Scheme), Parts),
1270 !.
1271parts_scheme(_, http).
1272
1273parts_authority(Parts, Auth) :-
1274 url_part(authority(Auth), Parts),
1275 !.
1276parts_authority(Parts, Auth) :-
1277 url_part(host(Host), Parts, _),
1278 url_part(port(Port), Parts, _),
1279 url_part(user(User), Parts, _),
1280 url_part(password(Password), Parts, _),
1281 uri_authority_components(Auth,
1282 uri_authority(User, Password, Host, Port)).
1283
1284parts_request_uri(Parts, RequestURI) :-
1285 option(request_uri(RequestURI), Parts),
1286 !.
1287parts_request_uri(Parts, RequestURI) :-
1288 url_part(path(Path), Parts, /),
1289 ignore(parts_search(Parts, Search)),
1290 uri_data(path, Data, Path),
1291 uri_data(search, Data, Search),
1292 uri_components(RequestURI, Data).
1293
1294parts_search(Parts, Search) :-
1295 option(query_string(Search), Parts),
1296 !.
1297parts_search(Parts, Search) :-
1298 option(search(Fields), Parts),
1299 !,
1300 uri_query_components(Search, Fields).
1301
1302
1303parts_uri(Parts, URI) :-
1304 option(uri(URI), Parts),
1305 !.
1306parts_uri(Parts, URI) :-
1307 parts_scheme(Parts, Scheme),
1308 ignore(parts_authority(Parts, Auth)),
1309 parts_request_uri(Parts, RequestURI),
1310 uri_components(RequestURI, Data),
1311 uri_data(scheme, Data, Scheme),
1312 uri_data(authority, Data, Auth),
1313 uri_components(URI, Data).
1314
1315parts_port(Parts, Port) :-
1316 parts_scheme(Parts, Scheme),
1317 default_port(Scheme, DefPort),
1318 url_part(port(Port), Parts, DefPort).
1319
1320url_part(Part, Parts) :-
1321 Part =.. [Name,Value],
1322 Gen =.. [Name,RawValue],
1323 option(Gen, Parts),
1324 !,
1325 Value = RawValue.
1326
1327url_part(Part, Parts, Default) :-
1328 Part =.. [Name,Value],
1329 Gen =.. [Name,RawValue],
1330 ( option(Gen, Parts)
1331 -> Value = RawValue
1332 ; Value = Default
1333 ).
1334
1335
1336 1339
1340write_cookies(Out, Parts, Options) :-
1341 http:write_cookies(Out, Parts, Options),
1342 !.
1343write_cookies(_, _, _).
1344
1345update_cookies(_, _, _) :-
1346 predicate_property(http:update_cookies(_,_,_), number_of_clauses(0)),
1347 !.
1348update_cookies(Lines, Parts, Options) :-
1349 ( member(Line, Lines),
1350 phrase(atom_field('set_cookie', CookieData), Line),
1351 http:update_cookies(CookieData, Parts, Options),
1352 fail
1353 ; true
1354 ).
1355
1356
1357 1360
1361:- multifile iostream:open_hook/6. 1362
1368
1369iostream:open_hook(URL, read, Stream, Close, Options0, Options) :-
1370 (atom(URL) -> true ; string(URL)),
1371 uri_is_global(URL),
1372 uri_components(URL, Components),
1373 uri_data(scheme, Components, Scheme),
1374 http_scheme(Scheme),
1375 !,
1376 Options = Options0,
1377 Close = close(Stream),
1378 http_open(URL, Stream, Options0).
1379
1380http_scheme(http).
1381http_scheme(https).
1382
1383
1384 1387
1391
1392consider_keep_alive(Lines, Parts, Host, StreamPair, In, Options) :-
1393 option(connection(Asked), Options),
1394 keep_alive(Asked),
1395 connection(Lines, Given),
1396 keep_alive(Given),
1397 content_length(Lines, Bytes),
1398 !,
1399 stream_pair(StreamPair, In0, _),
1400 connection_address(Host, Parts, HostPort),
1401 debug(http(connection),
1402 'Keep-alive to ~w (~D bytes)', [HostPort, Bytes]),
1403 stream_range_open(In0, In,
1404 [ size(Bytes),
1405 onclose(keep_alive(StreamPair, HostPort))
1406 ]).
1407consider_keep_alive(_, _, _, Stream, Stream, _).
1408
1409connection_address(Host, _, Host) :-
1410 Host = _:_,
1411 !.
1412connection_address(Host, Parts, Host:Port) :-
1413 parts_port(Parts, Port).
1414
1415keep_alive(keep_alive) :- !.
1416keep_alive(Connection) :-
1417 downcase_atom(Connection, 'keep-alive').
1418
1419:- public keep_alive/4. 1420
1421keep_alive(StreamPair, Host, In, Left) :-
1422 read_incomplete(In, Left),
1423 add_to_pool(Host, StreamPair),
1424 !.
1425keep_alive(StreamPair, _, _, _) :-
1426 close(StreamPair, [force(true)]).
1427
1432
1433read_incomplete(_, 0) :- !.
1434read_incomplete(In, Left) :-
1435 Left < 100,
1436 !,
1437 catch(setup_call_cleanup(
1438 open_null_stream(Null),
1439 copy_stream_data(In, Null, Left),
1440 close(Null)),
1441 _,
1442 fail).
1443
1444:- dynamic
1445 connection_pool/4, 1446 connection_gc_time/1. 1447
1448add_to_pool(Address, StreamPair) :-
1449 keep_connection(Address),
1450 get_time(Now),
1451 term_hash(Address, Hash),
1452 assertz(connection_pool(Hash, Address, StreamPair, Now)).
1453
1454get_from_pool(Address, StreamPair) :-
1455 term_hash(Address, Hash),
1456 retract(connection_pool(Hash, Address, StreamPair, _)).
1457
1464
1465keep_connection(Address) :-
1466 close_old_connections(2),
1467 predicate_property(connection_pool(_,_,_,_), number_of_clauses(C)),
1468 C =< 10,
1469 term_hash(Address, Hash),
1470 aggregate_all(count, connection_pool(Hash, Address, _, _), Count),
1471 Count =< 2.
1472
1473close_old_connections(Timeout) :-
1474 get_time(Now),
1475 Before is Now - Timeout,
1476 ( connection_gc_time(GC),
1477 GC > Before
1478 -> true
1479 ; ( retractall(connection_gc_time(_)),
1480 asserta(connection_gc_time(Now)),
1481 connection_pool(Hash, Address, StreamPair, Added),
1482 Added < Before,
1483 retract(connection_pool(Hash, Address, StreamPair, Added)),
1484 debug(http(connection),
1485 'Closing inactive keep-alive to ~p', [Address]),
1486 close(StreamPair, [force(true)]),
1487 fail
1488 ; true
1489 )
1490 ).
1491
1492
1498
1499http_close_keep_alive(Address) :-
1500 forall(get_from_pool(Address, StreamPair),
1501 close(StreamPair, [force(true)])).
1502
1509
1510keep_alive_error(keep_alive(closed)) :-
1511 !,
1512 debug(http(connection), 'Keep-alive connection was closed', []),
1513 fail.
1514keep_alive_error(io_error(_,_)) :-
1515 !,
1516 debug(http(connection), 'IO error on Keep-alive connection', []),
1517 fail.
1518keep_alive_error(Error) :-
1519 throw(Error).
1520
1521
1522 1525
1545
1556