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(hangup, _), close) :- 201 cgi_discard(CGI), 202 close(CGI). 203cgi_close(CGI, Request, Id, http_reply(Status, ExtraHdrOpts), Close) :- 204 cgi_property(CGI, header_codes(Text)), 205 Text \== [], 206 !, 207 http_parse_header(Text, ExtraHdrCGI), 208 cgi_property(CGI, client(Out)), 209 cgi_discard(CGI), 210 close(CGI), 211 append(ExtraHdrCGI, ExtraHdrOpts, ExtraHdr), 212 send_error(Out, Request, Id, http_reply(Status, ExtraHdr), Close). 213cgi_close(CGI, Request, Id, Error, Close) :- 214 cgi_property(CGI, client(Out)), 215 cgi_discard(CGI), 216 close(CGI), 217 send_error(Out, Request, Id, Error, Close). 218 219cgi_finish(CGI, Status, Close, Bytes) :- 220 flush_output(CGI), % update the content-length 221 cgi_property(CGI, connection(Close)), 222 cgi_property(CGI, content_length(Bytes)), 223 ( cgi_property(CGI, header(Header)), 224 memberchk(status(Status), Header) 225 -> true 226 ; Status = 200 227 ), 228 close(CGI).
current_output
no
longer points to the CGI stream, but simply to the socket that
connects us to the client.
239send_error(Out, Request, State0, Error, Close) :- 240 map_exception_to_http_status(Error, Reply, HdrExtra0, Context), 241 update_keep_alive(HdrExtra0, HdrExtra, Request), 242 catch(http_reply(Reply, 243 Out, 244 [ content_length(CLen) 245 | HdrExtra 246 ], 247 Context, 248 Request, 249 Code), 250 E, true), 251 ( var(E) 252 -> http_done(Code, Error, CLen, State0) 253 ; http_done(500, E, 0, State0), 254 throw(E) % is that wise? 255 ), 256 ( Error = http_reply(switching_protocols(Goal, SwitchOptions), _) 257 -> Close = switch_protocol(Goal, SwitchOptions) 258 ; memberchk(connection(Close), HdrExtra) 259 -> true 260 ; Close = close 261 ). 262 263update_keep_alive(Header0, Header, Request) :- 264 memberchk(connection(C), Header0), 265 !, 266 ( C == close 267 -> Header = Header0 268 ; client_wants_close(Request) 269 -> selectchk(connection(C), Header0, 270 connection(close), Header) 271 ; Header = Header0 272 ). 273update_keep_alive(Header, Header, _). 274 275client_wants_close(Request) :- 276 memberchk(connection(C), Request), 277 !, 278 C == close. 279client_wants_close(Request) :- 280 \+ ( memberchk(http_version(Major-_Minor), Request), 281 Major >= 1 282 ).
290http_done(Code, Status, Bytes, state0(_Thread, CPU0, Id)) :-
291 thread_cputime(CPU1),
292 CPU is CPU1 - CPU0,
293 ( debugging(http(request))
294 -> debug_request(Code, Status, Id, CPU, Bytes)
295 ; true
296 ),
297 broadcast(http(request_finished(Id, Code, Status, CPU, Bytes))).
ok
, the error from catch/3 or a term error(goal_failed(Goal),
_)
.
309handler_with_output_to(Goal, Id, Request, current_output, Status) :- 310 !, 311 ( catch(call_handler(Goal, Id, Request), Status, true) 312 -> ( var(Status) 313 -> Status = ok 314 ; true 315 ) 316 ; Status = error(goal_failed(Goal),_) 317 ). 318handler_with_output_to(Goal, Id, Request, Output, Error) :- 319 stream_property(OldOut, alias(current_output)), 320 set_output(Output), 321 handler_with_output_to(Goal, Id, Request, current_output, Error), 322 set_output(OldOut). 323 324call_handler(Goal, _, -) :- % continuation through http_spawn/2 325 !, 326 call(Goal). 327call_handler(Goal, Id, Request0) :- 328 expand_request(Request0, Request), 329 current_output(CGI), 330 cgi_set(CGI, request(Request)), 331 broadcast(http(request_start(Id, Request))), 332 call(Goal, Request).
338thread_cputime(CPU) :-
339 statistics(cputime, CPU).
http_stream.pl
for details.346:- public cgi_hook/2. 347 348cgi_hook(What, _CGI) :- 349 debug(http(hook), 'Running hook: ~q', [What]), 350 fail. 351cgi_hook(header, CGI) :- 352 cgi_property(CGI, header_codes(HeadText)), 353 cgi_property(CGI, header(Header0)), % see http_send_header/1 354 http_parse_header(HeadText, CgiHeader0), 355 append(Header0, CgiHeader0, CgiHeader), 356 cgi_property(CGI, request(Request)), 357 http_update_connection(CgiHeader, Request, Connection, Header1), 358 http_update_transfer(Request, Header1, Transfer, Header2), 359 http_update_encoding(Header2, Encoding, Header), 360 set_stream(CGI, encoding(Encoding)), 361 cgi_set(CGI, connection(Connection)), 362 cgi_set(CGI, header(Header)), 363 debug(http(transfer_encoding), 'Transfer-encoding: ~w', [Transfer]), 364 cgi_set(CGI, transfer_encoding(Transfer)). % must be LAST 365cgi_hook(send_header, CGI) :- 366 cgi_property(CGI, header(Header)), 367 debug(http(cgi), 'Header: ~q', [Header]), 368 cgi_property(CGI, client(Out)), 369 ( redirect(Header, Action, RedirectHeader) 370 -> http_status_reply(Action, Out, RedirectHeader, _), 371 cgi_discard(CGI) 372 ; cgi_property(CGI, transfer_encoding(chunked)) 373 -> http_reply_header(Out, chunked_data, Header) 374 ; cgi_property(CGI, transfer_encoding(event_stream)), 375 http_reply_header(Out, event_stream, Header), 376 flush_output(Out) 377 ; cgi_property(CGI, content_length(Len)) 378 -> http_reply_header(Out, cgi_data(Len), Header) 379 ). 380cgi_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.388redirect(Header, Action, RestHeader) :- 389 selectchk(location(To), Header, Header1), 390 ( selectchk(status(Status), Header1, RestHeader) 391 -> between(300, 399, Status) 392 ; RestHeader = Header1, 393 Status = 302 394 ), 395 redirect_action(Status, To, Action). 396 397redirect_action(301, To, moved(To)). 398redirect_action(302, To, moved_temporary(To)). 399redirect_action(303, To, see_other(To)).
410http_send_header(Header) :-
411 current_output(CGI),
412 cgi_property(CGI, header(Header0)),
413 cgi_set(CGI, header([Header|Header0])).
421expand_request(R0, R) :- 422 http:request_expansion(R0, R1), % Hook 423 R1 \== R0, 424 !, 425 expand_request(R1, R). 426expand_request(R, R).
433extend_request([], R, R). 434extend_request([request(R)|T], R0, R) :- 435 !, 436 extend_request(T, R0, R). 437extend_request([H|T], R0, R) :- 438 request_option(H), 439 !, 440 extend_request(T, [H|R0], R). 441extend_request([_|T], R0, R) :- 442 extend_request(T, R0, R). 443 444request_option(peer(_)). 445request_option(protocol(_)). 446request_option(pool(_)).
455http_current_request(Request) :-
456 current_output(CGI),
457 is_cgi_stream(CGI),
458 cgi_property(CGI, request(Request)).
Fastly-client-ip
X-real-ip
X-forwarded-for
478http_peer(Request, Peer) :- 479 memberchk(fastly_client_ip(Peer), Request), !. 480http_peer(Request, Peer) :- 481 memberchk(x_real_ip(Peer), Request), !. 482http_peer(Request, IP) :- 483 memberchk(x_forwarded_for(IP0), Request), 484 !, 485 atomic_list_concat(Parts, ', ', IP0), 486 last(Parts, IP). 487http_peer(Request, IP) :- 488 memberchk(peer(Peer), Request), 489 !, 490 peer_to_ip(Peer, IP). 491 492peer_to_ip(ip(A,B,C,D), IP) :- 493 atomic_list_concat([A,B,C,D], '.', IP).
503http_relative_path(Path, RelPath) :- 504 http_current_request(Request), 505 memberchk(path(RelTo), Request), 506 http_relative_path(Path, RelTo, RelPath), 507 !. 508http_relative_path(Path, Path). 509 510http_relative_path(Path, RelTo, RelPath) :- 511 atomic_list_concat(PL, /, Path), 512 atomic_list_concat(RL, /, RelTo), 513 delete_common_prefix(PL, RL, PL1, PL2), 514 to_dot_dot(PL2, DotDot, PL1), 515 atomic_list_concat(DotDot, /, RelPath). 516 517delete_common_prefix([H|T01], [H|T02], T1, T2) :- 518 !, 519 delete_common_prefix(T01, T02, T1, T2). 520delete_common_prefix(T1, T2, T1, T2). 521 522to_dot_dot([], Tail, Tail). 523to_dot_dot([_], Tail, Tail) :- !. 524to_dot_dot([_|T0], ['..'|T], Tail) :- 525 to_dot_dot(T0, T, Tail). 526 527 528 /******************************* 529 * DEBUG SUPPORT * 530 *******************************/
536debug_request(Code, ok, Id, CPU, Bytes) :- 537 !, 538 debug(http(request), '[~D] ~w OK (~3f seconds; ~D bytes)', 539 [Id, Code, CPU, Bytes]). 540debug_request(Code, Status, Id, _, Bytes) :- 541 map_exception(Status, Reply), 542 !, 543 debug(http(request), '[~D] ~w ~w; ~D bytes', 544 [Id, Code, Reply, Bytes]). 545debug_request(Code, Except, Id, _, _) :- 546 Except = error(_,_), 547 !, 548 message_to_string(Except, Message), 549 debug(http(request), '[~D] ~w ERROR: ~w', 550 [Id, Code, Message]). 551debug_request(Code, Status, Id, _, Bytes) :- 552 debug(http(request), '[~D] ~w ~w; ~D bytes', 553 [Id, Code, Status, Bytes]). 554 555map_exception(http_reply(Reply), Reply). 556map_exception(http_reply(Reply, _), Reply). 557map_exception(error(existence_error(http_location, Location), _Stack), 558 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
)