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
79
101
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, [], _) 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
(error(_,context(_,in_http_request))) :- !.
131add_header_context(_).
132
133status(Id, state0(Thread, CPU, Id)) :-
134 thread_self(Thread),
135 thread_cputime(CPU).
136
137
144
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. 162
167
168http_spawned(ThreadId) :-
169 assert(spawned(ThreadId)).
170
171
184
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), 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), 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).
226
235
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) 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 ).
280
281
286
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))).
295
296
305
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 current_output(OldOut),
317 set_output(Output),
318 handler_with_output_to(Goal, Id, Request, current_output, Error),
319 set_output(OldOut).
320
321call_handler(Goal, _, -) :- 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).
330
334
335thread_cputime(CPU) :-
336 statistics(cputime, CPU).
337
342
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)), 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)). 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, _).
375
381
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)).
394
395
403
(Header) :-
405 current_output(CGI),
406 cgi_property(CGI, header(Header0)),
407 cgi_set(CGI, header([Header|Header0])).
408
409
414
415expand_request(R0, R) :-
416 http:request_expansion(R0, R1), 417 R1 \== R0,
418 !,
419 expand_request(R1, R).
420expand_request(R, R).
421
422
426
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(_)).
441
442
448
449http_current_request(Request) :-
450 current_output(CGI),
451 is_cgi_stream(CGI),
452 cgi_property(CGI, request(Request)).
453
454
471
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).
488
489
496
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 525
529
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))