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
102
103http_wrapper(Goal, In, Out, Close, Options) :-
104 status(Id, State0),
105 catch(http_read_request(In, Request0), ReqError, true),
106 ( Request0 == end_of_file
107 -> Close = close,
108 extend_request(Options, [], _) 109 ; var(ReqError)
110 -> byte_count(In, ByteCount),
111 ignore(memberchk(byte_count(ByteCount), Options)),
112 extend_request(Options, Request0, Request1),
113 cgi_open(Out, CGI, cgi_hook, [request(Request1)]),
114 cgi_property(CGI, id(Id)),
115 ( debugging(http(request))
116 -> memberchk(method(Method), Request1),
117 memberchk(path(Location), Request1),
118 debug(http(request), "[~D] ~w ~w ...", [Id,Method,Location])
119 ; true
120 ),
121 handler_with_output_to(Goal, Id, Request1, CGI, Error),
122 cgi_close(CGI, Request1, State0, Error, Close)
123 ; Id = 0,
124 add_header_context(ReqError),
125 ( debugging(http(request))
126 -> print_message(warning, ReqError)
127 ; true
128 ),
129 send_error(Out, [], State0, ReqError, Close),
130 extend_request(Options, [], _)
131 ).
132
(error(_,context(_,in_http_request))) :- !.
134add_header_context(_).
135
136status(Id, state0(Thread, CPU, Id)) :-
137 thread_self(Thread),
138 thread_cputime(CPU).
139
140
147
148http_wrap_spawned(Goal, Request, Close) :-
149 current_output(CGI),
150 cgi_property(CGI, id(Id)),
151 handler_with_output_to(Goal, Id, -, current_output, Error),
152 ( retract(spawned(ThreadId))
153 -> Close = spawned(ThreadId)
154 ; status(Id, State0),
155 catch(cgi_close(CGI, Request, State0, Error, Close),
156 _,
157 Close = close)
158 ).
159
160
161:- thread_local
162 spawned/1. 163
168
169http_spawned(ThreadId) :-
170 assert(spawned(ThreadId)).
171
172
185
186cgi_close(_, _, _, _, Close) :-
187 retract(spawned(ThreadId)),
188 !,
189 Close = spawned(ThreadId).
190cgi_close(CGI, _, State0, ok, Close) :-
191 !,
192 catch(cgi_finish(CGI, Status, Close, Bytes), E, true),
193 ( var(E)
194 -> http_done(Status, ok, Bytes, State0)
195 ; http_done(500, E, 0, State0), 196 throw(E)
197 ).
198cgi_close(CGI, Request, Id, http_reply(Status), Close) :-
199 !,
200 cgi_close(CGI, Request, Id, http_reply(Status, []), Close).
201cgi_close(CGI, _Request, _Id, http_reply(hangup, _), close) :-
202 cgi_discard(CGI),
203 close(CGI).
204cgi_close(CGI, Request, Id, http_reply(Status, ExtraHdrOpts), Close) :-
205 cgi_property(CGI, header_codes(Text)),
206 Text \== [],
207 !,
208 http_parse_header(Text, ExtraHdrCGI),
209 cgi_property(CGI, client(Out)),
210 cgi_discard(CGI),
211 close(CGI),
212 append(ExtraHdrCGI, ExtraHdrOpts, ExtraHdr),
213 send_error(Out, Request, Id, http_reply(Status, ExtraHdr), Close).
214cgi_close(CGI, Request, Id, Error, Close) :-
215 cgi_property(CGI, client(Out)),
216 cgi_discard(CGI),
217 close(CGI),
218 send_error(Out, Request, Id, Error, Close).
219
220cgi_finish(CGI, Status, Close, Bytes) :-
221 flush_output(CGI), 222 cgi_property(CGI, connection(Close)),
223 cgi_property(CGI, content_length(Bytes)),
224 ( cgi_property(CGI, header(Header)),
225 memberchk(status(Status), Header)
226 -> true
227 ; Status = 200
228 ),
229 close(CGI).
230
239
240send_error(Out, Request, State0, Error, Close) :-
241 map_exception_to_http_status(Error, Reply, HdrExtra0, Context),
242 update_keep_alive(HdrExtra0, HdrExtra, Request),
243 catch(http_reply(Reply,
244 Out,
245 [ content_length(CLen)
246 | HdrExtra
247 ],
248 Context,
249 Request,
250 Code),
251 E, true),
252 ( var(E)
253 -> http_done(Code, Error, CLen, State0)
254 ; http_done(500, E, 0, State0),
255 throw(E) 256 ),
257 ( Error = http_reply(switching_protocols(Goal, SwitchOptions), _)
258 -> Close = switch_protocol(Goal, SwitchOptions)
259 ; memberchk(connection(Close), HdrExtra)
260 -> true
261 ; Close = close
262 ).
263
264update_keep_alive(Header0, Header, Request) :-
265 memberchk(connection(C), Header0),
266 !,
267 ( C == close
268 -> Header = Header0
269 ; client_wants_close(Request)
270 -> selectchk(connection(C), Header0,
271 connection(close), Header)
272 ; Header = Header0
273 ).
274update_keep_alive(Header, Header, _).
275
276client_wants_close(Request) :-
277 memberchk(connection(C), Request),
278 !,
279 C == close.
280client_wants_close(Request) :-
281 \+ ( memberchk(http_version(Major-_Minor), Request),
282 Major >= 1
283 ).
284
285
290
291http_done(Code, Status, Bytes, state0(_Thread, CPU0, Id)) :-
292 thread_cputime(CPU1),
293 CPU is CPU1 - CPU0,
294 ( debugging(http(request))
295 -> debug_request(Code, Status, Id, CPU, Bytes)
296 ; true
297 ),
298 broadcast(http(request_finished(Id, Code, Status, CPU, Bytes))).
299
300
309
310handler_with_output_to(Goal, Id, Request, current_output, Status) :-
311 !,
312 ( catch(call_handler(Goal, Id, Request), Status, true)
313 -> ( var(Status)
314 -> Status = ok
315 ; true
316 )
317 ; Status = error(goal_failed(Goal),_)
318 ).
319handler_with_output_to(Goal, Id, Request, Output, Error) :-
320 stream_property(OldOut, alias(current_output)),
321 set_output(Output),
322 handler_with_output_to(Goal, Id, Request, current_output, Error),
323 set_output(OldOut).
324
325call_handler(Goal, _, -) :- 326 !,
327 call(Goal).
328call_handler(Goal, Id, Request0) :-
329 expand_request(Request0, Request),
330 current_output(CGI),
331 cgi_set(CGI, request(Request)),
332 broadcast(http(request_start(Id, Request))),
333 call(Goal, Request).
334
338
339thread_cputime(CPU) :-
340 statistics(cputime, CPU).
341
346
347:- public cgi_hook/2. 348
349cgi_hook(What, _CGI) :-
350 debug(http(hook), 'Running hook: ~q', [What]),
351 fail.
352cgi_hook(header, CGI) :-
353 cgi_property(CGI, header_codes(HeadText)),
354 cgi_property(CGI, header(Header0)), 355 http_parse_header(HeadText, CgiHeader0),
356 append(Header0, CgiHeader0, CgiHeader),
357 cgi_property(CGI, request(Request)),
358 http_update_connection(CgiHeader, Request, Connection, Header1),
359 http_update_transfer(Request, Header1, Transfer, Header2),
360 http_update_encoding(Header2, Encoding, Header),
361 set_stream(CGI, encoding(Encoding)),
362 cgi_set(CGI, connection(Connection)),
363 cgi_set(CGI, header(Header)),
364 debug(http(transfer_encoding), 'Transfer-encoding: ~w', [Transfer]),
365 cgi_set(CGI, transfer_encoding(Transfer)). 366cgi_hook(send_header, CGI) :-
367 cgi_property(CGI, header(Header)),
368 debug(http(cgi), 'Header: ~q', [Header]),
369 cgi_property(CGI, client(Out)),
370 ( redirect(Header, Action, RedirectHeader)
371 -> http_status_reply(Action, Out, RedirectHeader, _),
372 cgi_discard(CGI)
373 ; cgi_property(CGI, transfer_encoding(chunked))
374 -> http_reply_header(Out, chunked_data, Header)
375 ; cgi_property(CGI, transfer_encoding(event_stream)),
376 http_reply_header(Out, event_stream, Header),
377 flush_output(Out)
378 ; cgi_property(CGI, content_length(Len))
379 -> http_reply_header(Out, cgi_data(Len), Header)
380 ).
381cgi_hook(close, _).
382
388
389redirect(Header, Action, RestHeader) :-
390 selectchk(location(To), Header, Header1),
391 ( selectchk(status(Status), Header1, RestHeader)
392 -> between(300, 399, Status)
393 ; RestHeader = Header1,
394 Status = 302
395 ),
396 redirect_action(Status, To, Action).
397
398redirect_action(301, To, moved(To)).
399redirect_action(302, To, moved_temporary(To)).
400redirect_action(303, To, see_other(To)).
401
402
410
(Header) :-
412 current_output(CGI),
413 cgi_property(CGI, header(Header0)),
414 cgi_set(CGI, header([Header|Header0])).
415
416
421
422expand_request(R0, R) :-
423 http:request_expansion(R0, R1), 424 R1 \== R0,
425 !,
426 expand_request(R1, R).
427expand_request(R, R).
428
429
433
434extend_request([], R, R).
435extend_request([request(R)|T], R0, R) :-
436 !,
437 extend_request(T, R0, R).
438extend_request([H|T], R0, R) :-
439 request_option(H),
440 !,
441 extend_request(T, [H|R0], R).
442extend_request([_|T], R0, R) :-
443 extend_request(T, R0, R).
444
445request_option(peer(_)).
446request_option(protocol(_)).
447request_option(pool(_)).
448
449
455
456http_current_request(Request) :-
457 current_output(CGI),
458 is_cgi_stream(CGI),
459 cgi_property(CGI, request(Request)).
460
461
478
479http_peer(Request, Peer) :-
480 memberchk(fastly_client_ip(Peer), Request), !.
481http_peer(Request, Peer) :-
482 memberchk(x_real_ip(Peer), Request), !.
483http_peer(Request, IP) :-
484 memberchk(x_forwarded_for(IP0), Request),
485 !,
486 atomic_list_concat(Parts, ', ', IP0),
487 last(Parts, IP).
488http_peer(Request, IP) :-
489 memberchk(peer(Peer), Request),
490 !,
491 peer_to_ip(Peer, IP).
492
493peer_to_ip(ip(A,B,C,D), IP) :-
494 atomic_list_concat([A,B,C,D], '.', IP).
495
496
503
504http_relative_path(Path, RelPath) :-
505 http_current_request(Request),
506 memberchk(path(RelTo), Request),
507 http_relative_path(Path, RelTo, RelPath),
508 !.
509http_relative_path(Path, Path).
510
511http_relative_path(Path, RelTo, RelPath) :-
512 atomic_list_concat(PL, /, Path),
513 atomic_list_concat(RL, /, RelTo),
514 delete_common_prefix(PL, RL, PL1, PL2),
515 to_dot_dot(PL2, DotDot, PL1),
516 atomic_list_concat(DotDot, /, RelPath).
517
518delete_common_prefix([H|T01], [H|T02], T1, T2) :-
519 !,
520 delete_common_prefix(T01, T02, T1, T2).
521delete_common_prefix(T1, T2, T1, T2).
522
523to_dot_dot([], Tail, Tail).
524to_dot_dot([_], Tail, Tail) :- !.
525to_dot_dot([_|T0], ['..'|T], Tail) :-
526 to_dot_dot(T0, T, Tail).
527
528
529 532
536
537debug_request(Code, ok, Id, CPU, Bytes) :-
538 !,
539 debug(http(request), '[~D] ~w OK (~3f seconds; ~D bytes)',
540 [Id, Code, CPU, Bytes]).
541debug_request(Code, Status, Id, _, Bytes) :-
542 map_exception(Status, Reply),
543 !,
544 debug(http(request), '[~D] ~w ~w; ~D bytes',
545 [Id, Code, Reply, Bytes]).
546debug_request(Code, Except, Id, _, _) :-
547 Except = error(_,_),
548 !,
549 message_to_string(Except, Message),
550 debug(http(request), '[~D] ~w ERROR: ~w',
551 [Id, Code, Message]).
552debug_request(Code, Status, Id, _, Bytes) :-
553 debug(http(request), '[~D] ~w ~w; ~D bytes',
554 [Id, Code, Status, Bytes]).
555
556map_exception(http_reply(Reply), Reply).
557map_exception(http_reply(Reply, _), Reply).
558map_exception(error(existence_error(http_location, Location), _Stack),
559 error(404, Location))