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
preceded
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:
103http_wrapper(Goal, In, Out, Close, Options) :- 104 status(Id, State0), 105 catch(http_read_request(In, Request0), ReqError, true), 106 ( Request0 == end_of_file 107 -> Close = close, 108 extend_request(Options, [], _) % return request 109 ; var(ReqError) 110 -> byte_count(In, ByteCount), 111 ignore(memberchk(byte_count(ByteCount), Options)), 112 extend_request(Options, Request0, Request1), 113 cgi_open(Out, CGI, cgi_hook, [request(Request1)]), 114 cgi_property(CGI, id(Id)), 115 ( debugging(http(request)) 116 -> memberchk(method(Method), Request1), 117 memberchk(path(Location), Request1), 118 debug(http(request), "[~D] ~w ~w ...", [Id,Method,Location]) 119 ; true 120 ), 121 handler_with_output_to(Goal, Id, Request1, CGI, Error), 122 cgi_close(CGI, Request1, State0, Error, Close) 123 ; Id = 0, 124 add_header_context(ReqError), 125 ( debugging(http(request)) 126 -> print_message(warning, ReqError) 127 ; true 128 ), 129 send_error(Out, [], State0, ReqError, Close), 130 extend_request(Options, [], _) 131 ). 132 133add_header_context(error(_,context(_,in_http_request))) :- !. 134add_header_context(_). 135 136status(Id, state0(Thread, CPU, Id)) :- 137 thread_self(Thread), 138 thread_cputime(CPU).
148http_wrap_spawned(Goal, Request, Close) :- 149 current_output(CGI), 150 cgi_property(CGI, id(Id)), 151 handler_with_output_to(Goal, Id, -, current_output, Error), 152 ( retract(spawned(ThreadId)) 153 -> Close = spawned(ThreadId) 154 ; status(Id, State0), 155 catch(cgi_close(CGI, Request, State0, Error, Close), 156 _, 157 Close = close) 158 ). 159 160 161:- thread_local 162 spawned/1.
169http_spawned(ThreadId) :-
170 assert(spawned(ThreadId)).
not_modified
, moved
) or a request to reply with
the content of a file.186cgi_close(_, _, _, _, Close) :- 187 retract(spawned(ThreadId)), 188 !, 189 Close = spawned(ThreadId). 190cgi_close(CGI, _, State0, ok, Close) :- 191 !, 192 catch(cgi_finish(CGI, Status, Close, Bytes), E, true), 193 ( var(E) 194 -> http_done(Status, ok, Bytes, State0) 195 ; http_done(500, E, 0, State0), % TBD: amount written? 196 throw(E) 197 ). 198cgi_close(CGI, Request, Id, http_reply(Status), Close) :- 199 !, 200 cgi_close(CGI, Request, Id, http_reply(Status, []), Close). 201cgi_close(CGI, _Request, _Id, http_reply(hangup, _), close) :- 202 cgi_discard(CGI), 203 close(CGI). 204cgi_close(CGI, Request, Id, http_reply(Status, ExtraHdrOpts), Close) :- 205 cgi_property(CGI, header_codes(Text)), 206 Text \== [], 207 !, 208 http_parse_header(Text, ExtraHdrCGI), 209 cgi_property(CGI, client(Out)), 210 cgi_discard(CGI), 211 close(CGI), 212 append(ExtraHdrCGI, ExtraHdrOpts, ExtraHdr), 213 send_error(Out, Request, Id, http_reply(Status, ExtraHdr), Close). 214cgi_close(CGI, Request, Id, Error, Close) :- 215 cgi_property(CGI, client(Out)), 216 cgi_discard(CGI), 217 close(CGI), 218 send_error(Out, Request, Id, Error, Close). 219 220cgi_finish(CGI, Status, Close, Bytes) :- 221 flush_output(CGI), % update the content-length 222 cgi_property(CGI, connection(Close)), 223 cgi_property(CGI, content_length(Bytes)), 224 ( cgi_property(CGI, header(Header)), 225 memberchk(status(Status), Header) 226 -> true 227 ; Status = 200 228 ), 229 close(CGI).
current_output
no
longer points to the CGI stream, but simply to the socket that
connects us to the client.
240send_error(Out, Request, State0, Error, Close) :- 241 map_exception_to_http_status(Error, Reply, HdrExtra0, Context), 242 update_keep_alive(HdrExtra0, HdrExtra, Request), 243 catch(http_reply(Reply, 244 Out, 245 [ content_length(CLen) 246 | HdrExtra 247 ], 248 Context, 249 Request, 250 Code), 251 E, true), 252 ( var(E) 253 -> http_done(Code, Error, CLen, State0) 254 ; http_done(500, E, 0, State0), 255 throw(E) % is that wise? 256 ), 257 ( Error = http_reply(switching_protocols(Goal, SwitchOptions), _) 258 -> Close = switch_protocol(Goal, SwitchOptions) 259 ; memberchk(connection(Close), HdrExtra) 260 -> true 261 ; Close = close 262 ). 263 264update_keep_alive(Header0, Header, Request) :- 265 memberchk(connection(C), Header0), 266 !, 267 ( C == close 268 -> Header = Header0 269 ; client_wants_close(Request) 270 -> selectchk(connection(C), Header0, 271 connection(close), Header) 272 ; Header = Header0 273 ). 274update_keep_alive(Header, Header, _). 275 276client_wants_close(Request) :- 277 memberchk(connection(C), Request), 278 !, 279 C == close. 280client_wants_close(Request) :- 281 \+ ( memberchk(http_version(Major-_Minor), Request), 282 Major >= 1 283 ).
291http_done(Code, Status, Bytes, state0(_Thread, CPU0, Id)) :-
292 thread_cputime(CPU1),
293 CPU is CPU1 - CPU0,
294 ( debugging(http(request))
295 -> debug_request(Code, Status, Id, CPU, Bytes)
296 ; true
297 ),
298 broadcast(http(request_finished(Id, Code, Status, CPU, Bytes))).
ok
, the error from catch/3 or a term error(goal_failed(Goal),
_)
.
310handler_with_output_to(Goal, Id, Request, current_output, Status) :- 311 !, 312 ( catch(call_handler(Goal, Id, Request), Status, true) 313 -> ( var(Status) 314 -> Status = ok 315 ; true 316 ) 317 ; Status = error(goal_failed(Goal),_) 318 ). 319handler_with_output_to(Goal, Id, Request, Output, Error) :- 320 stream_property(OldOut, alias(current_output)), 321 set_output(Output), 322 handler_with_output_to(Goal, Id, Request, current_output, Error), 323 set_output(OldOut). 324 325call_handler(Goal, _, -) :- % continuation through http_spawn/2 326 !, 327 call(Goal). 328call_handler(Goal, Id, Request0) :- 329 expand_request(Request0, Request), 330 current_output(CGI), 331 cgi_set(CGI, request(Request)), 332 broadcast(http(request_start(Id, Request))), 333 call(Goal, Request).
339thread_cputime(CPU) :-
340 statistics(cputime, CPU).
http_stream.pl
for details.347:- public cgi_hook/2. 348 349cgi_hook(What, _CGI) :- 350 debug(http(hook), 'Running hook: ~q', [What]), 351 fail. 352cgi_hook(header, CGI) :- 353 cgi_property(CGI, header_codes(HeadText)), 354 cgi_property(CGI, header(Header0)), % see http_send_header/1 355 http_parse_header(HeadText, CgiHeader0), 356 append(Header0, CgiHeader0, CgiHeader), 357 cgi_property(CGI, request(Request)), 358 http_update_connection(CgiHeader, Request, Connection, Header1), 359 http_update_transfer(Request, Header1, Transfer, Header2), 360 http_update_encoding(Header2, Encoding, Header), 361 set_stream(CGI, encoding(Encoding)), 362 cgi_set(CGI, connection(Connection)), 363 cgi_set(CGI, header(Header)), 364 debug(http(transfer_encoding), 'Transfer-encoding: ~w', [Transfer]), 365 cgi_set(CGI, transfer_encoding(Transfer)). % must be LAST 366cgi_hook(send_header, CGI) :- 367 cgi_property(CGI, header(Header)), 368 debug(http(cgi), 'Header: ~q', [Header]), 369 cgi_property(CGI, client(Out)), 370 ( redirect(Header, Action, RedirectHeader) 371 -> http_status_reply(Action, Out, RedirectHeader, _), 372 cgi_discard(CGI) 373 ; cgi_property(CGI, transfer_encoding(chunked)) 374 -> http_reply_header(Out, chunked_data, Header) 375 ; cgi_property(CGI, transfer_encoding(event_stream)), 376 http_reply_header(Out, event_stream, Header), 377 flush_output(Out) 378 ; cgi_property(CGI, content_length(Len)) 379 -> http_reply_header(Out, cgi_data(Len), Header) 380 ). 381cgi_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.389redirect(Header, Action, RestHeader) :- 390 selectchk(location(To), Header, Header1), 391 ( selectchk(status(Status), Header1, RestHeader) 392 -> between(300, 399, Status) 393 ; RestHeader = Header1, 394 Status = 302 395 ), 396 redirect_action(Status, To, Action). 397 398redirect_action(301, To, moved(To)). 399redirect_action(302, To, moved_temporary(To)). 400redirect_action(303, To, see_other(To)).
411http_send_header(Header) :-
412 current_output(CGI),
413 cgi_property(CGI, header(Header0)),
414 cgi_set(CGI, header([Header|Header0])).
422expand_request(R0, R) :- 423 http:request_expansion(R0, R1), % Hook 424 R1 \== R0, 425 !, 426 expand_request(R1, R). 427expand_request(R, R).
434extend_request([], R, R). 435extend_request([request(R)|T], R0, R) :- 436 !, 437 extend_request(T, R0, R). 438extend_request([H|T], R0, R) :- 439 request_option(H), 440 !, 441 extend_request(T, [H|R0], R). 442extend_request([_|T], R0, R) :- 443 extend_request(T, R0, R). 444 445request_option(peer(_)). 446request_option(protocol(_)). 447request_option(pool(_)).
456http_current_request(Request) :-
457 current_output(CGI),
458 is_cgi_stream(CGI),
459 cgi_property(CGI, request(Request)).
Fastly-client-ip
X-real-ip
X-forwarded-for
479http_peer(Request, Peer) :- 480 memberchk(fastly_client_ip(Peer), Request), !. 481http_peer(Request, Peer) :- 482 memberchk(x_real_ip(Peer), Request), !. 483http_peer(Request, IP) :- 484 memberchk(x_forwarded_for(IP0), Request), 485 !, 486 atomic_list_concat(Parts, ', ', IP0), 487 last(Parts, IP). 488http_peer(Request, IP) :- 489 memberchk(peer(Peer), Request), 490 !, 491 peer_to_ip(Peer, IP). 492 493peer_to_ip(ip(A,B,C,D), IP) :- 494 atomic_list_concat([A,B,C,D], '.', IP).
504http_relative_path(Path, RelPath) :- 505 http_current_request(Request), 506 memberchk(path(RelTo), Request), 507 http_relative_path(Path, RelTo, RelPath), 508 !. 509http_relative_path(Path, Path). 510 511http_relative_path(Path, RelTo, RelPath) :- 512 atomic_list_concat(PL, /, Path), 513 atomic_list_concat(RL, /, RelTo), 514 delete_common_prefix(PL, RL, PL1, PL2), 515 to_dot_dot(PL2, DotDot, PL1), 516 atomic_list_concat(DotDot, /, RelPath). 517 518delete_common_prefix([H|T01], [H|T02], T1, T2) :- 519 !, 520 delete_common_prefix(T01, T02, T1, T2). 521delete_common_prefix(T1, T2, T1, T2). 522 523to_dot_dot([], Tail, Tail). 524to_dot_dot([_], Tail, Tail) :- !. 525to_dot_dot([_|T0], ['..'|T], Tail) :- 526 to_dot_dot(T0, T, Tail). 527 528 529 /******************************* 530 * DEBUG SUPPORT * 531 *******************************/
537debug_request(Code, ok, Id, CPU, Bytes) :- 538 !, 539 debug(http(request), '[~D] ~w OK (~3f seconds; ~D bytes)', 540 [Id, Code, CPU, Bytes]). 541debug_request(Code, Status, Id, _, Bytes) :- 542 map_exception(Status, Reply), 543 !, 544 debug(http(request), '[~D] ~w ~w; ~D bytes', 545 [Id, Code, Reply, Bytes]). 546debug_request(Code, Except, Id, _, _) :- 547 Except = error(_,_), 548 !, 549 message_to_string(Except, Message), 550 debug(http(request), '[~D] ~w ERROR: ~w', 551 [Id, Code, Message]). 552debug_request(Code, Status, Id, _, Bytes) :- 553 debug(http(request), '[~D] ~w ~w; ~D bytes', 554 [Id, Code, Status, Bytes]). 555 556map_exception(http_reply(Reply), Reply). 557map_exception(http_reply(Reply, _), Reply). 558map_exception(error(existence_error(http_location, Location), _Stack), 559 error(404, Location))
Server processing of an HTTP request
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 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 occasionally useful for applications:
X-Forwarded-For
)