35
36:- module(httpd_wrapper,
37 [ http_wrapper/5, 38 http_current_request/1, 39 http_peer/2, 40 http_send_header/1, 41 http_relative_path/2, 42 43 http_wrap_spawned/3, 44 http_spawned/1 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(0, +, +, -, +). 55:- multifile
56 http:request_expansion/2. 57
75
97
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, [], _) 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
(error(_,context(_,in_http_request))) :- !.
127add_header_context(_).
128
129status(Id, state0(Thread, CPU, Id)) :-
130 thread_self(Thread),
131 thread_cputime(CPU).
132
133
140
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. 158
163
164http_spawned(ThreadId) :-
165 assert(spawned(ThreadId)).
166
167
180
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), 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), 214 cgi_property(CGI, connection(Close)),
215 cgi_property(CGI, content_length(Bytes)),
216 close(CGI).
217
226
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) 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 ).
271
272
277
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))).
286
287
296
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, _, -) :- 313 !,
314 call(Goal).
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).
321
325
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. 334
335
340
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)), 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)). 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, _).
373
379
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)).
392
393
401
(Header) :-
403 current_output(CGI),
404 cgi_property(CGI, header(Header0)),
405 cgi_set(CGI, header([Header|Header0])).
406
407
412
413expand_request(R0, R) :-
414 http:request_expansion(R0, R1), 415 R1 \== R0,
416 !,
417 expand_request(R1, R).
418expand_request(R, R).
419
420
424
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(_)).
439
440
446
447http_current_request(Request) :-
448 current_output(CGI),
449 is_cgi_stream(CGI),
450 cgi_property(CGI, request(Request)).
451
452
469
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).
486
487
494
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 523
527
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))