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) 2002-2017, University of Amsterdam 7 VU University Amsterdam 8 All rights reserved. 9 10 Redistribution and use in source and binary forms, with or without 11 modification, are permitted provided that the following conditions 12 are met: 13 14 1. Redistributions of source code must retain the above copyright 15 notice, this list of conditions and the following disclaimer. 16 17 2. Redistributions in binary form must reproduce the above copyright 18 notice, this list of conditions and the following disclaimer in 19 the documentation and/or other materials provided with the 20 distribution. 21 22 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 23 "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 24 LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 25 FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 26 COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 27 INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 28 BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 29 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 30 CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 31 LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 32 ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 33 POSSIBILITY OF SUCH DAMAGE. 34*/ 35 36:- module(httpd_wrapper, 37 [ http_wrapper/5, % :Goal, +In, +Out, -Conn, +Options 38 http_current_request/1, % -Request 39 http_peer/2, % +Request, -PeerIP 40 http_send_header/1, % +Term 41 http_relative_path/2, % +AbsPath, -RelPath 42 % Internal API 43 http_wrap_spawned/3, % :Goal, -Request, -Connection 44 http_spawned/1 % +ThreadId 45 ]). 46:- use_module(http_header). 47:- use_module(http_stream). 48:- use_module(http_exception). 49:- use_module(library(lists)). 50:- use_module(library(debug)). 51:- use_module(library(broadcast)). 52 53:- meta_predicate 54 http_wrapper( , , , , ). 55:- multifile 56 http:request_expansion/2.
The goal is assumed to write the reply to current_output
preceeded by an HTTP header, closed by a blank line. The header
must contain a Content-type: <type> line. It may optionally
contain a line Transfer-encoding: chunked
to request chunked
encoding.
Options:
98http_wrapper(Goal, In, Out, Close, Options) :- 99 status(Id, State0), 100 catch(http_read_request(In, Request0), ReqError, true), 101 ( Request0 == end_of_file 102 -> Close = close, 103 extend_request(Options, [], _) % return request 104 ; var(ReqError) 105 -> extend_request(Options, Request0, Request1), 106 cgi_open(Out, CGI, cgi_hook, [request(Request1)]), 107 cgi_property(CGI, id(Id)), 108 ( debugging(http(request)) 109 -> memberchk(method(Method), Request1), 110 memberchk(path(Location), Request1), 111 debug(http(request), "[~D] ~w ~w ...", [Id,Method,Location]) 112 ; true 113 ), 114 handler_with_output_to(Goal, Id, Request1, CGI, Error), 115 cgi_close(CGI, Request1, State0, Error, Close) 116 ; Id = 0, 117 add_header_context(ReqError), 118 ( debugging(http(request)) 119 -> print_message(warning, ReqError) 120 ; true 121 ), 122 send_error(Out, [], State0, ReqError, Close), 123 extend_request(Options, [], _) 124 ). 125 126add_header_context(error(_,context(_,in_http_request))) :- !. 127add_header_context(_). 128 129status(Id, state0(Thread, CPU, Id)) :- 130 thread_self(Thread), 131 thread_cputime(CPU).
141http_wrap_spawned(Goal, Request, Close) :- 142 current_output(CGI), 143 cgi_property(CGI, id(Id)), 144 handler_with_output_to(Goal, Id, -, current_output, Error), 145 ( retract(spawned(ThreadId)) 146 -> Close = spawned(ThreadId), 147 Request = [] 148 ; cgi_property(CGI, request(Request)), 149 status(Id, State0), 150 catch(cgi_close(CGI, Request, State0, Error, Close), 151 _, 152 Close = close) 153 ). 154 155 156:- thread_local 157 spawned/1.
164http_spawned(ThreadId) :-
165 assert(spawned(ThreadId)).
not_modified
, moved
) or a request to reply with
the content of a file.181cgi_close(_, _, _, _, Close) :- 182 retract(spawned(ThreadId)), 183 !, 184 Close = spawned(ThreadId). 185cgi_close(CGI, _, State0, ok, Close) :- 186 !, 187 catch(cgi_finish(CGI, Close, Bytes), E, true), 188 ( var(E) 189 -> http_done(200, ok, Bytes, State0) 190 ; http_done(500, E, 0, State0), % TBD: amount written? 191 throw(E) 192 ). 193cgi_close(CGI, Request, Id, http_reply(Status), Close) :- 194 !, 195 cgi_close(CGI, Request, Id, http_reply(Status, []), Close). 196cgi_close(CGI, Request, Id, http_reply(Status, ExtraHdrOpts), Close) :- 197 cgi_property(CGI, header_codes(Text)), 198 Text \== [], 199 !, 200 http_parse_header(Text, ExtraHdrCGI), 201 cgi_property(CGI, client(Out)), 202 cgi_discard(CGI), 203 close(CGI), 204 append(ExtraHdrCGI, ExtraHdrOpts, ExtraHdr), 205 send_error(Out, Request, Id, http_reply(Status, ExtraHdr), Close). 206cgi_close(CGI, Request, Id, Error, Close) :- 207 cgi_property(CGI, client(Out)), 208 cgi_discard(CGI), 209 close(CGI), 210 send_error(Out, Request, Id, Error, Close). 211 212cgi_finish(CGI, Close, Bytes) :- 213 flush_output(CGI), % update the content-length 214 cgi_property(CGI, connection(Close)), 215 cgi_property(CGI, content_length(Bytes)), 216 close(CGI).
current_output
no
longer points to the CGI stream, but simply to the socket that
connects us to the client.
227send_error(Out, Request, State0, Error, Close) :- 228 map_exception_to_http_status(Error, Reply, HdrExtra0, Context), 229 update_keep_alive(HdrExtra0, HdrExtra, Request), 230 catch(http_reply(Reply, 231 Out, 232 [ content_length(CLen) 233 | HdrExtra 234 ], 235 Context, 236 Request, 237 Code), 238 E, true), 239 ( var(E) 240 -> http_done(Code, Error, CLen, State0) 241 ; http_done(500, E, 0, State0), 242 throw(E) % is that wise? 243 ), 244 ( Error = http_reply(switching_protocols(Goal, SwitchOptions), _) 245 -> Close = switch_protocol(Goal, SwitchOptions) 246 ; memberchk(connection(Close), HdrExtra) 247 -> true 248 ; Close = close 249 ). 250 251update_keep_alive(Header0, Header, Request) :- 252 memberchk(connection(C), Header0), 253 !, 254 ( C == close 255 -> Header = Header0 256 ; client_wants_close(Request) 257 -> selectchk(connection(C), Header0, 258 connection(close), Header) 259 ; Header = Header0 260 ). 261update_keep_alive(Header, Header, _). 262 263client_wants_close(Request) :- 264 memberchk(connection(C), Request), 265 !, 266 C == close. 267client_wants_close(Request) :- 268 \+ ( memberchk(http_version(Major-_Minor), Request), 269 Major >= 1 270 ).
278http_done(Code, Status, Bytes, state0(_Thread, CPU0, Id)) :-
279 thread_cputime(CPU1),
280 CPU is CPU1 - CPU0,
281 ( debugging(http(request))
282 -> debug_request(Code, Status, Id, CPU, Bytes)
283 ; true
284 ),
285 broadcast(http(request_finished(Id, Code, Status, CPU, Bytes))).
ok
, the error from catch/3 or a term error(goal_failed(Goal),
_)
.
297handler_with_output_to(Goal, Id, Request, current_output, Status) :- 298 !, 299 ( catch(call_handler(Goal, Id, Request), Status, true) 300 -> ( var(Status) 301 -> Status = ok 302 ; true 303 ) 304 ; Status = error(goal_failed(Goal),_) 305 ). 306handler_with_output_to(Goal, Id, Request, Output, Error) :- 307 current_output(OldOut), 308 set_output(Output), 309 handler_with_output_to(Goal, Id, Request, current_output, Error), 310 set_output(OldOut). 311 312call_handler(Goal, _, -) :- % continuation through http_spawn/2 313 !, 314 call(). 315call_handler(Goal, Id, Request0) :- 316 expand_request(Request0, Request), 317 current_output(CGI), 318 cgi_set(CGI, request(Request)), 319 broadcast(http(request_start(Id, Request))), 320 call(Goal, Request).
326:- if(current_prolog_flag(threads, true)). 327thread_cputime(CPU) :- 328 thread_self(Me), 329 thread_statistics(Me, cputime, CPU). 330:- else. 331thread_cputime(CPU) :- 332 statistics(cputime, CPU). 333:- endif.
http_stream.pl
for details.341:- public cgi_hook/2. 342 343cgi_hook(What, _CGI) :- 344 debug(http(hook), 'Running hook: ~q', [What]), 345 fail. 346cgi_hook(header, CGI) :- 347 cgi_property(CGI, header_codes(HeadText)), 348 cgi_property(CGI, header(Header0)), % see http_send_header/1 349 http_parse_header(HeadText, CgiHeader0), 350 append(Header0, CgiHeader0, CgiHeader), 351 cgi_property(CGI, request(Request)), 352 http_update_connection(CgiHeader, Request, Connection, Header1), 353 http_update_transfer(Request, Header1, Transfer, Header2), 354 http_update_encoding(Header2, Encoding, Header), 355 set_stream(CGI, encoding(Encoding)), 356 cgi_set(CGI, connection(Connection)), 357 cgi_set(CGI, header(Header)), 358 debug(http(transfer_encoding), 'Transfer-encoding: ~w', [Transfer]), 359 cgi_set(CGI, transfer_encoding(Transfer)). % must be LAST 360cgi_hook(send_header, CGI) :- 361 cgi_property(CGI, header(Header)), 362 debug(http(cgi), 'Header: ~q', [Header]), 363 cgi_property(CGI, client(Out)), 364 ( redirect(Header, Action, RedirectHeader) 365 -> http_status_reply(Action, Out, RedirectHeader, _), 366 cgi_discard(CGI) 367 ; cgi_property(CGI, transfer_encoding(chunked)) 368 -> http_reply_header(Out, chunked_data, Header) 369 ; cgi_property(CGI, content_length(Len)) 370 -> http_reply_header(Out, cgi_data(Len), Header) 371 ). 372cgi_hook(close, _).
Location
and optional Status
headers for
formulating a HTTP redirect. Redirection is only established if
no Status
is provided, or Status
is 3XX.380redirect(Header, Action, RestHeader) :- 381 selectchk(location(To), Header, Header1), 382 ( selectchk(status(Status), Header1, RestHeader) 383 -> between(300, 399, Status) 384 ; RestHeader = Header1, 385 Status = 302 386 ), 387 redirect_action(Status, To, Action). 388 389redirect_action(301, To, moved(To)). 390redirect_action(302, To, moved_temporary(To)). 391redirect_action(303, To, see_other(To)).
402http_send_header(Header) :-
403 current_output(CGI),
404 cgi_property(CGI, header(Header0)),
405 cgi_set(CGI, header([Header|Header0])).
413expand_request(R0, R) :- 414 http:request_expansion(R0, R1), % Hook 415 R1 \== R0, 416 !, 417 expand_request(R1, R). 418expand_request(R, R).
425extend_request([], R, R). 426extend_request([request(R)|T], R0, R) :- 427 !, 428 extend_request(T, R0, R). 429extend_request([H|T], R0, R) :- 430 request_option(H), 431 !, 432 extend_request(T, [H|R0], R). 433extend_request([_|T], R0, R) :- 434 extend_request(T, R0, R). 435 436request_option(peer(_)). 437request_option(protocol(_)). 438request_option(pool(_)).
447http_current_request(Request) :-
448 current_output(CGI),
449 is_cgi_stream(CGI),
450 cgi_property(CGI, request(Request)).
Fastly-client-ip
X-real-ip
X-forwarded-for
470http_peer(Request, Peer) :- 471 memberchk(fastly_client_ip(Peer), Request), !. 472http_peer(Request, Peer) :- 473 memberchk(x_real_ip(Peer), Request), !. 474http_peer(Request, IP) :- 475 memberchk(x_forwarded_for(IP0), Request), 476 !, 477 atomic_list_concat(Parts, ', ', IP0), 478 last(Parts, IP). 479http_peer(Request, IP) :- 480 memberchk(peer(Peer), Request), 481 !, 482 peer_to_ip(Peer, IP). 483 484peer_to_ip(ip(A,B,C,D), IP) :- 485 atomic_list_concat([A,B,C,D], '.', IP).
495http_relative_path(Path, RelPath) :- 496 http_current_request(Request), 497 memberchk(path(RelTo), Request), 498 http_relative_path(Path, RelTo, RelPath), 499 !. 500http_relative_path(Path, Path). 501 502http_relative_path(Path, RelTo, RelPath) :- 503 atomic_list_concat(PL, /, Path), 504 atomic_list_concat(RL, /, RelTo), 505 delete_common_prefix(PL, RL, PL1, PL2), 506 to_dot_dot(PL2, DotDot, PL1), 507 atomic_list_concat(DotDot, /, RelPath). 508 509delete_common_prefix([H|T01], [H|T02], T1, T2) :- 510 !, 511 delete_common_prefix(T01, T02, T1, T2). 512delete_common_prefix(T1, T2, T1, T2). 513 514to_dot_dot([], Tail, Tail). 515to_dot_dot([_], Tail, Tail) :- !. 516to_dot_dot([_|T0], ['..'|T], Tail) :- 517 to_dot_dot(T0, T, Tail). 518 519 520 /******************************* 521 * DEBUG SUPPORT * 522 *******************************/
528debug_request(Code, ok, Id, CPU, Bytes) :- 529 !, 530 debug(http(request), '[~D] ~w OK (~3f seconds; ~D bytes)', 531 [Id, Code, CPU, Bytes]). 532debug_request(Code, Status, Id, _, Bytes) :- 533 map_exception(Status, Reply), 534 !, 535 debug(http(request), '[~D] ~w ~w; ~D bytes', 536 [Id, Code, Reply, Bytes]). 537debug_request(Code, Except, Id, _, _) :- 538 Except = error(_,_), 539 !, 540 message_to_string(Except, Message), 541 debug(http(request), '[~D] ~w ERROR: ~w', 542 [Id, Code, Message]). 543debug_request(Code, Status, Id, _, Bytes) :- 544 debug(http(request), '[~D] ~w ~w; ~D bytes', 545 [Id, Code, Status, Bytes]). 546 547map_exception(http_reply(Reply), Reply). 548map_exception(http_reply(Reply, _), Reply). 549map_exception(error(existence_error(http_location, Location), _Stack), 550 error(404, Location))
Server processing of an HTTP request
This library provides the core of the implementation of the HTTP protocol at the server side and is mainly intended for internal use. It is used by
library(thread_httpd)
andlibrary(inet_httpd)
(deprecated).Still, it provides a few predicates that are occasinally useful for applications:
X-Forwarded-For
)