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:
102http_wrapper(Goal, In, Out, Close, Options) :- 103 status(Id, State0), 104 catch(http_read_request(In, Request0), ReqError, true), 105 ( Request0 == end_of_file 106 -> Close = close, 107 extend_request(Options, [], _) % return request 108 ; var(ReqError) 109 -> extend_request(Options, Request0, Request1), 110 cgi_open(Out, CGI, cgi_hook, [request(Request1)]), 111 cgi_property(CGI, id(Id)), 112 ( debugging(http(request)) 113 -> memberchk(method(Method), Request1), 114 memberchk(path(Location), Request1), 115 debug(http(request), "[~D] ~w ~w ...", [Id,Method,Location]) 116 ; true 117 ), 118 handler_with_output_to(Goal, Id, Request1, CGI, Error), 119 cgi_close(CGI, Request1, State0, Error, Close) 120 ; Id = 0, 121 add_header_context(ReqError), 122 ( debugging(http(request)) 123 -> print_message(warning, ReqError) 124 ; true 125 ), 126 send_error(Out, [], State0, ReqError, Close), 127 extend_request(Options, [], _) 128 ). 129 130add_header_context(error(_,context(_,in_http_request))) :- !. 131add_header_context(_). 132 133status(Id, state0(Thread, CPU, Id)) :- 134 thread_self(Thread), 135 thread_cputime(CPU).
145http_wrap_spawned(Goal, Request, Close) :- 146 current_output(CGI), 147 cgi_property(CGI, id(Id)), 148 handler_with_output_to(Goal, Id, -, current_output, Error), 149 ( retract(spawned(ThreadId)) 150 -> Close = spawned(ThreadId), 151 Request = [] 152 ; cgi_property(CGI, request(Request)), 153 status(Id, State0), 154 catch(cgi_close(CGI, Request, State0, Error, Close), 155 _, 156 Close = close) 157 ). 158 159 160:- thread_local 161 spawned/1.
168http_spawned(ThreadId) :-
169 assert(spawned(ThreadId)).
not_modified
, moved
) or a request to reply with
the content of a file.185cgi_close(_, _, _, _, Close) :- 186 retract(spawned(ThreadId)), 187 !, 188 Close = spawned(ThreadId). 189cgi_close(CGI, _, State0, ok, Close) :- 190 !, 191 catch(cgi_finish(CGI, Status, Close, Bytes), E, true), 192 ( var(E) 193 -> http_done(Status, ok, Bytes, State0) 194 ; http_done(500, E, 0, State0), % TBD: amount written? 195 throw(E) 196 ). 197cgi_close(CGI, Request, Id, http_reply(Status), Close) :- 198 !, 199 cgi_close(CGI, Request, Id, http_reply(Status, []), Close). 200cgi_close(CGI, Request, Id, http_reply(Status, ExtraHdrOpts), Close) :- 201 cgi_property(CGI, header_codes(Text)), 202 Text \== [], 203 !, 204 http_parse_header(Text, ExtraHdrCGI), 205 cgi_property(CGI, client(Out)), 206 cgi_discard(CGI), 207 close(CGI), 208 append(ExtraHdrCGI, ExtraHdrOpts, ExtraHdr), 209 send_error(Out, Request, Id, http_reply(Status, ExtraHdr), Close). 210cgi_close(CGI, Request, Id, Error, Close) :- 211 cgi_property(CGI, client(Out)), 212 cgi_discard(CGI), 213 close(CGI), 214 send_error(Out, Request, Id, Error, Close). 215 216cgi_finish(CGI, Status, Close, Bytes) :- 217 flush_output(CGI), % update the content-length 218 cgi_property(CGI, connection(Close)), 219 cgi_property(CGI, content_length(Bytes)), 220 ( cgi_property(CGI, header(Header)), 221 memberchk(status(Status), Header) 222 -> true 223 ; Status = 200 224 ), 225 close(CGI).
current_output
no
longer points to the CGI stream, but simply to the socket that
connects us to the client.
236send_error(Out, Request, State0, Error, Close) :- 237 map_exception_to_http_status(Error, Reply, HdrExtra0, Context), 238 update_keep_alive(HdrExtra0, HdrExtra, Request), 239 catch(http_reply(Reply, 240 Out, 241 [ content_length(CLen) 242 | HdrExtra 243 ], 244 Context, 245 Request, 246 Code), 247 E, true), 248 ( var(E) 249 -> http_done(Code, Error, CLen, State0) 250 ; http_done(500, E, 0, State0), 251 throw(E) % is that wise? 252 ), 253 ( Error = http_reply(switching_protocols(Goal, SwitchOptions), _) 254 -> Close = switch_protocol(Goal, SwitchOptions) 255 ; memberchk(connection(Close), HdrExtra) 256 -> true 257 ; Close = close 258 ). 259 260update_keep_alive(Header0, Header, Request) :- 261 memberchk(connection(C), Header0), 262 !, 263 ( C == close 264 -> Header = Header0 265 ; client_wants_close(Request) 266 -> selectchk(connection(C), Header0, 267 connection(close), Header) 268 ; Header = Header0 269 ). 270update_keep_alive(Header, Header, _). 271 272client_wants_close(Request) :- 273 memberchk(connection(C), Request), 274 !, 275 C == close. 276client_wants_close(Request) :- 277 \+ ( memberchk(http_version(Major-_Minor), Request), 278 Major >= 1 279 ).
287http_done(Code, Status, Bytes, state0(_Thread, CPU0, Id)) :-
288 thread_cputime(CPU1),
289 CPU is CPU1 - CPU0,
290 ( debugging(http(request))
291 -> debug_request(Code, Status, Id, CPU, Bytes)
292 ; true
293 ),
294 broadcast(http(request_finished(Id, Code, Status, CPU, Bytes))).
ok
, the error from catch/3 or a term error(goal_failed(Goal),
_)
.
306handler_with_output_to(Goal, Id, Request, current_output, Status) :- 307 !, 308 ( catch(call_handler(Goal, Id, Request), Status, true) 309 -> ( var(Status) 310 -> Status = ok 311 ; true 312 ) 313 ; Status = error(goal_failed(Goal),_) 314 ). 315handler_with_output_to(Goal, Id, Request, Output, Error) :- 316 stream_property(OldOut, alias(current_output)), 317 set_output(Output), 318 handler_with_output_to(Goal, Id, Request, current_output, Error), 319 set_output(OldOut). 320 321call_handler(Goal, _, -) :- % continuation through http_spawn/2 322 !, 323 call(Goal). 324call_handler(Goal, Id, Request0) :- 325 expand_request(Request0, Request), 326 current_output(CGI), 327 cgi_set(CGI, request(Request)), 328 broadcast(http(request_start(Id, Request))), 329 call(Goal, Request).
335thread_cputime(CPU) :-
336 statistics(cputime, CPU).
http_stream.pl
for details.343:- public cgi_hook/2. 344 345cgi_hook(What, _CGI) :- 346 debug(http(hook), 'Running hook: ~q', [What]), 347 fail. 348cgi_hook(header, CGI) :- 349 cgi_property(CGI, header_codes(HeadText)), 350 cgi_property(CGI, header(Header0)), % see http_send_header/1 351 http_parse_header(HeadText, CgiHeader0), 352 append(Header0, CgiHeader0, CgiHeader), 353 cgi_property(CGI, request(Request)), 354 http_update_connection(CgiHeader, Request, Connection, Header1), 355 http_update_transfer(Request, Header1, Transfer, Header2), 356 http_update_encoding(Header2, Encoding, Header), 357 set_stream(CGI, encoding(Encoding)), 358 cgi_set(CGI, connection(Connection)), 359 cgi_set(CGI, header(Header)), 360 debug(http(transfer_encoding), 'Transfer-encoding: ~w', [Transfer]), 361 cgi_set(CGI, transfer_encoding(Transfer)). % must be LAST 362cgi_hook(send_header, CGI) :- 363 cgi_property(CGI, header(Header)), 364 debug(http(cgi), 'Header: ~q', [Header]), 365 cgi_property(CGI, client(Out)), 366 ( redirect(Header, Action, RedirectHeader) 367 -> http_status_reply(Action, Out, RedirectHeader, _), 368 cgi_discard(CGI) 369 ; cgi_property(CGI, transfer_encoding(chunked)) 370 -> http_reply_header(Out, chunked_data, Header) 371 ; cgi_property(CGI, content_length(Len)) 372 -> http_reply_header(Out, cgi_data(Len), Header) 373 ). 374cgi_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.382redirect(Header, Action, RestHeader) :- 383 selectchk(location(To), Header, Header1), 384 ( selectchk(status(Status), Header1, RestHeader) 385 -> between(300, 399, Status) 386 ; RestHeader = Header1, 387 Status = 302 388 ), 389 redirect_action(Status, To, Action). 390 391redirect_action(301, To, moved(To)). 392redirect_action(302, To, moved_temporary(To)). 393redirect_action(303, To, see_other(To)).
404http_send_header(Header) :-
405 current_output(CGI),
406 cgi_property(CGI, header(Header0)),
407 cgi_set(CGI, header([Header|Header0])).
415expand_request(R0, R) :- 416 http:request_expansion(R0, R1), % Hook 417 R1 \== R0, 418 !, 419 expand_request(R1, R). 420expand_request(R, R).
427extend_request([], R, R). 428extend_request([request(R)|T], R0, R) :- 429 !, 430 extend_request(T, R0, R). 431extend_request([H|T], R0, R) :- 432 request_option(H), 433 !, 434 extend_request(T, [H|R0], R). 435extend_request([_|T], R0, R) :- 436 extend_request(T, R0, R). 437 438request_option(peer(_)). 439request_option(protocol(_)). 440request_option(pool(_)).
449http_current_request(Request) :-
450 current_output(CGI),
451 is_cgi_stream(CGI),
452 cgi_property(CGI, request(Request)).
Fastly-client-ip
X-real-ip
X-forwarded-for
472http_peer(Request, Peer) :- 473 memberchk(fastly_client_ip(Peer), Request), !. 474http_peer(Request, Peer) :- 475 memberchk(x_real_ip(Peer), Request), !. 476http_peer(Request, IP) :- 477 memberchk(x_forwarded_for(IP0), Request), 478 !, 479 atomic_list_concat(Parts, ', ', IP0), 480 last(Parts, IP). 481http_peer(Request, IP) :- 482 memberchk(peer(Peer), Request), 483 !, 484 peer_to_ip(Peer, IP). 485 486peer_to_ip(ip(A,B,C,D), IP) :- 487 atomic_list_concat([A,B,C,D], '.', IP).
497http_relative_path(Path, RelPath) :- 498 http_current_request(Request), 499 memberchk(path(RelTo), Request), 500 http_relative_path(Path, RelTo, RelPath), 501 !. 502http_relative_path(Path, Path). 503 504http_relative_path(Path, RelTo, RelPath) :- 505 atomic_list_concat(PL, /, Path), 506 atomic_list_concat(RL, /, RelTo), 507 delete_common_prefix(PL, RL, PL1, PL2), 508 to_dot_dot(PL2, DotDot, PL1), 509 atomic_list_concat(DotDot, /, RelPath). 510 511delete_common_prefix([H|T01], [H|T02], T1, T2) :- 512 !, 513 delete_common_prefix(T01, T02, T1, T2). 514delete_common_prefix(T1, T2, T1, T2). 515 516to_dot_dot([], Tail, Tail). 517to_dot_dot([_], Tail, Tail) :- !. 518to_dot_dot([_|T0], ['..'|T], Tail) :- 519 to_dot_dot(T0, T, Tail). 520 521 522 /******************************* 523 * DEBUG SUPPORT * 524 *******************************/
530debug_request(Code, ok, Id, CPU, Bytes) :- 531 !, 532 debug(http(request), '[~D] ~w OK (~3f seconds; ~D bytes)', 533 [Id, Code, CPU, Bytes]). 534debug_request(Code, Status, Id, _, Bytes) :- 535 map_exception(Status, Reply), 536 !, 537 debug(http(request), '[~D] ~w ~w; ~D bytes', 538 [Id, Code, Reply, Bytes]). 539debug_request(Code, Except, Id, _, _) :- 540 Except = error(_,_), 541 !, 542 message_to_string(Except, Message), 543 debug(http(request), '[~D] ~w ERROR: ~w', 544 [Id, Code, Message]). 545debug_request(Code, Status, Id, _, Bytes) :- 546 debug(http(request), '[~D] ~w ~w; ~D bytes', 547 [Id, Code, Status, Bytes]). 548 549map_exception(http_reply(Reply), Reply). 550map_exception(http_reply(Reply, _), Reply). 551map_exception(error(existence_error(http_location, Location), _Stack), 552 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 occasinally useful for applications:
X-Forwarded-For
)