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
326thread_cputime(CPU) :-
327 statistics(cputime, CPU).
328
333
334:- public cgi_hook/2. 335
336cgi_hook(What, _CGI) :-
337 debug(http(hook), 'Running hook: ~q', [What]),
338 fail.
339cgi_hook(header, CGI) :-
340 cgi_property(CGI, header_codes(HeadText)),
341 cgi_property(CGI, header(Header0)), 342 http_parse_header(HeadText, CgiHeader0),
343 append(Header0, CgiHeader0, CgiHeader),
344 cgi_property(CGI, request(Request)),
345 http_update_connection(CgiHeader, Request, Connection, Header1),
346 http_update_transfer(Request, Header1, Transfer, Header2),
347 http_update_encoding(Header2, Encoding, Header),
348 set_stream(CGI, encoding(Encoding)),
349 cgi_set(CGI, connection(Connection)),
350 cgi_set(CGI, header(Header)),
351 debug(http(transfer_encoding), 'Transfer-encoding: ~w', [Transfer]),
352 cgi_set(CGI, transfer_encoding(Transfer)). 353cgi_hook(send_header, CGI) :-
354 cgi_property(CGI, header(Header)),
355 debug(http(cgi), 'Header: ~q', [Header]),
356 cgi_property(CGI, client(Out)),
357 ( redirect(Header, Action, RedirectHeader)
358 -> http_status_reply(Action, Out, RedirectHeader, _),
359 cgi_discard(CGI)
360 ; cgi_property(CGI, transfer_encoding(chunked))
361 -> http_reply_header(Out, chunked_data, Header)
362 ; cgi_property(CGI, content_length(Len))
363 -> http_reply_header(Out, cgi_data(Len), Header)
364 ).
365cgi_hook(close, _).
366
372
373redirect(Header, Action, RestHeader) :-
374 selectchk(location(To), Header, Header1),
375 ( selectchk(status(Status), Header1, RestHeader)
376 -> between(300, 399, Status)
377 ; RestHeader = Header1,
378 Status = 302
379 ),
380 redirect_action(Status, To, Action).
381
382redirect_action(301, To, moved(To)).
383redirect_action(302, To, moved_temporary(To)).
384redirect_action(303, To, see_other(To)).
385
386
394
(Header) :-
396 current_output(CGI),
397 cgi_property(CGI, header(Header0)),
398 cgi_set(CGI, header([Header|Header0])).
399
400
405
406expand_request(R0, R) :-
407 http:request_expansion(R0, R1), 408 R1 \== R0,
409 !,
410 expand_request(R1, R).
411expand_request(R, R).
412
413
417
418extend_request([], R, R).
419extend_request([request(R)|T], R0, R) :-
420 !,
421 extend_request(T, R0, R).
422extend_request([H|T], R0, R) :-
423 request_option(H),
424 !,
425 extend_request(T, [H|R0], R).
426extend_request([_|T], R0, R) :-
427 extend_request(T, R0, R).
428
429request_option(peer(_)).
430request_option(protocol(_)).
431request_option(pool(_)).
432
433
439
440http_current_request(Request) :-
441 current_output(CGI),
442 is_cgi_stream(CGI),
443 cgi_property(CGI, request(Request)).
444
445
462
463http_peer(Request, Peer) :-
464 memberchk(fastly_client_ip(Peer), Request), !.
465http_peer(Request, Peer) :-
466 memberchk(x_real_ip(Peer), Request), !.
467http_peer(Request, IP) :-
468 memberchk(x_forwarded_for(IP0), Request),
469 !,
470 atomic_list_concat(Parts, ', ', IP0),
471 last(Parts, IP).
472http_peer(Request, IP) :-
473 memberchk(peer(Peer), Request),
474 !,
475 peer_to_ip(Peer, IP).
476
477peer_to_ip(ip(A,B,C,D), IP) :-
478 atomic_list_concat([A,B,C,D], '.', IP).
479
480
487
488http_relative_path(Path, RelPath) :-
489 http_current_request(Request),
490 memberchk(path(RelTo), Request),
491 http_relative_path(Path, RelTo, RelPath),
492 !.
493http_relative_path(Path, Path).
494
495http_relative_path(Path, RelTo, RelPath) :-
496 atomic_list_concat(PL, /, Path),
497 atomic_list_concat(RL, /, RelTo),
498 delete_common_prefix(PL, RL, PL1, PL2),
499 to_dot_dot(PL2, DotDot, PL1),
500 atomic_list_concat(DotDot, /, RelPath).
501
502delete_common_prefix([H|T01], [H|T02], T1, T2) :-
503 !,
504 delete_common_prefix(T01, T02, T1, T2).
505delete_common_prefix(T1, T2, T1, T2).
506
507to_dot_dot([], Tail, Tail).
508to_dot_dot([_], Tail, Tail) :- !.
509to_dot_dot([_|T0], ['..'|T], Tail) :-
510 to_dot_dot(T0, T, Tail).
511
512
513 516
520
521debug_request(Code, ok, Id, CPU, Bytes) :-
522 !,
523 debug(http(request), '[~D] ~w OK (~3f seconds; ~D bytes)',
524 [Id, Code, CPU, Bytes]).
525debug_request(Code, Status, Id, _, Bytes) :-
526 map_exception(Status, Reply),
527 !,
528 debug(http(request), '[~D] ~w ~w; ~D bytes',
529 [Id, Code, Reply, Bytes]).
530debug_request(Code, Except, Id, _, _) :-
531 Except = error(_,_),
532 !,
533 message_to_string(Except, Message),
534 debug(http(request), '[~D] ~w ERROR: ~w',
535 [Id, Code, Message]).
536debug_request(Code, Status, Id, _, Bytes) :-
537 debug(http(request), '[~D] ~w ~w; ~D bytes',
538 [Id, Code, Status, Bytes]).
539
540map_exception(http_reply(Reply), Reply).
541map_exception(http_reply(Reply, _), Reply).
542map_exception(error(existence_error(http_location, Location), _Stack),
543 error(404, Location))