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 memberchk(method(Method), Request1),
107 memberchk(path(Location), Request1),
108 cgi_open(Out, CGI, cgi_hook, [request(Request1)]),
109 cgi_property(CGI, id(Id)),
110 debug(http(request), '[~D] ~w ~w ...', [Id, Method, Location]),
111 handler_with_output_to(Goal, Id, Request1, CGI, Error),
112 cgi_close(CGI, Request1, State0, Error, Close)
113 ; Id = 0,
114 add_header_context(ReqError),
115 ( debugging(http(request))
116 -> print_message(warning, ReqError)
117 ; true
118 ),
119 send_error(Out, [], State0, ReqError, Close),
120 extend_request(Options, [], _)
121 ).
122
(error(_,context(_,in_http_request))) :- !.
124add_header_context(_).
125
126status(Id, state0(Thread, CPU, Id)) :-
127 thread_self(Thread),
128 thread_cputime(CPU).
129
130
137
138http_wrap_spawned(Goal, Request, Close) :-
139 current_output(CGI),
140 cgi_property(CGI, id(Id)),
141 handler_with_output_to(Goal, Id, -, current_output, Error),
142 ( retract(spawned(ThreadId))
143 -> Close = spawned(ThreadId),
144 Request = []
145 ; cgi_property(CGI, request(Request)),
146 status(Id, State0),
147 catch(cgi_close(CGI, Request, State0, Error, Close),
148 _,
149 Close = close)
150 ).
151
152
153:- thread_local
154 spawned/1.
155
160
161http_spawned(ThreadId) :-
162 assert(spawned(ThreadId)).
163
164
177
178cgi_close(_, _, _, _, Close) :-
179 retract(spawned(ThreadId)),
180 !,
181 Close = spawned(ThreadId).
182cgi_close(CGI, _, State0, ok, Close) :-
183 !,
184 catch(cgi_finish(CGI, Close, Bytes), E, true),
185 ( var(E)
186 -> http_done(200, ok, Bytes, State0)
187 ; http_done(500, E, 0, State0), 188 throw(E)
189 ).
190cgi_close(CGI, Request, Id, http_reply(Status), Close) :-
191 !,
192 cgi_close(CGI, Request, Id, http_reply(Status, []), Close).
193cgi_close(CGI, Request, Id, http_reply(Status, ExtraHdrOpts), Close) :-
194 cgi_property(CGI, header_codes(Text)),
195 Text \== [],
196 !,
197 http_parse_header(Text, ExtraHdrCGI),
198 cgi_property(CGI, client(Out)),
199 cgi_discard(CGI),
200 close(CGI),
201 append(ExtraHdrCGI, ExtraHdrOpts, ExtraHdr),
202 send_error(Out, Request, Id, http_reply(Status, ExtraHdr), Close).
203cgi_close(CGI, Request, Id, Error, Close) :-
204 cgi_property(CGI, client(Out)),
205 cgi_discard(CGI),
206 close(CGI),
207 send_error(Out, Request, Id, Error, Close).
208
209cgi_finish(CGI, Close, Bytes) :-
210 flush_output(CGI), 211 cgi_property(CGI, connection(Close)),
212 cgi_property(CGI, content_length(Bytes)),
213 close(CGI).
214
223
224send_error(Out, Request, State0, Error, Close) :-
225 map_exception_to_http_status(Error, Reply, HdrExtra0, Context),
226 update_keep_alive(HdrExtra0, HdrExtra, Request),
227 catch(http_reply(Reply,
228 Out,
229 [ content_length(CLen)
230 | HdrExtra
231 ],
232 Context,
233 Request,
234 Code),
235 E, true),
236 ( var(E)
237 -> http_done(Code, Error, CLen, State0)
238 ; http_done(500, E, 0, State0),
239 throw(E) 240 ),
241 ( Error = http_reply(switching_protocols(Goal, SwitchOptions), _)
242 -> Close = switch_protocol(Goal, SwitchOptions)
243 ; memberchk(connection(Close), HdrExtra)
244 -> true
245 ; Close = close
246 ).
247
248update_keep_alive(Header0, Header, Request) :-
249 memberchk(connection(C), Header0),
250 !,
251 ( C == close
252 -> Header = Header0
253 ; client_wants_close(Request)
254 -> selectchk(connection(C), Header0,
255 connection(close), Header)
256 ; Header = Header0
257 ).
258update_keep_alive(Header, Header, _).
259
260client_wants_close(Request) :-
261 memberchk(connection(C), Request),
262 !,
263 C == close.
264client_wants_close(Request) :-
265 \+ ( memberchk(http_version(Major-_Minor), Request),
266 Major >= 1
267 ).
268
269
274
275http_done(Code, Status, Bytes, state0(_Thread, CPU0, Id)) :-
276 thread_cputime(CPU1),
277 CPU is CPU1 - CPU0,
278 ( debugging(http(request))
279 -> debug_request(Code, Status, Id, CPU, Bytes)
280 ; true
281 ),
282 broadcast(http(request_finished(Id, Code, Status, CPU, Bytes))).
283
284
293
294handler_with_output_to(Goal, Id, Request, current_output, Status) :-
295 !,
296 ( catch(call_handler(Goal, Id, Request), Status, true)
297 -> ( var(Status)
298 -> Status = ok
299 ; true
300 )
301 ; Status = error(goal_failed(Goal),_)
302 ).
303handler_with_output_to(Goal, Id, Request, Output, Error) :-
304 current_output(OldOut),
305 set_output(Output),
306 handler_with_output_to(Goal, Id, Request, current_output, Error),
307 set_output(OldOut).
308
309call_handler(Goal, _, -) :- 310 !,
311 call(Goal).
312call_handler(Goal, Id, Request0) :-
313 expand_request(Request0, Request),
314 current_output(CGI),
315 cgi_set(CGI, request(Request)),
316 broadcast(http(request_start(Id, Request))),
317 call(Goal, Request).
318
322
323:- if(current_prolog_flag(threads, true)).
324thread_cputime(CPU) :-
325 thread_self(Me),
326 thread_statistics(Me, cputime, CPU).
327:- else.
328thread_cputime(CPU) :-
329 statistics(cputime, CPU).
330:- endif.
331
332
337
338:- public cgi_hook/2.
339
340cgi_hook(What, _CGI) :-
341 debug(http(hook), 'Running hook: ~q', [What]),
342 fail.
343cgi_hook(header, CGI) :-
344 cgi_property(CGI, header_codes(HeadText)),
345 cgi_property(CGI, header(Header0)), 346 http_parse_header(HeadText, CgiHeader0),
347 append(Header0, CgiHeader0, CgiHeader),
348 cgi_property(CGI, request(Request)),
349 http_update_connection(CgiHeader, Request, Connection, Header1),
350 http_update_transfer(Request, Header1, Transfer, Header2),
351 http_update_encoding(Header2, Encoding, Header),
352 set_stream(CGI, encoding(Encoding)),
353 cgi_set(CGI, connection(Connection)),
354 cgi_set(CGI, header(Header)),
355 debug(http(transfer_encoding), 'Transfer-encoding: ~w', [Transfer]),
356 cgi_set(CGI, transfer_encoding(Transfer)). 357cgi_hook(send_header, CGI) :-
358 cgi_property(CGI, header(Header)),
359 debug(http(cgi), 'Header: ~q', [Header]),
360 cgi_property(CGI, client(Out)),
361 ( redirect(Header, Action, RedirectHeader)
362 -> http_status_reply(Action, Out, RedirectHeader, _),
363 cgi_discard(CGI)
364 ; cgi_property(CGI, transfer_encoding(chunked))
365 -> http_reply_header(Out, chunked_data, Header)
366 ; cgi_property(CGI, content_length(Len))
367 -> http_reply_header(Out, cgi_data(Len), Header)
368 ).
369cgi_hook(close, _).
370
376
377redirect(Header, Action, RestHeader) :-
378 selectchk(location(To), Header, Header1),
379 ( selectchk(status(Status), Header1, RestHeader)
380 -> between(300, 399, Status)
381 ; RestHeader = Header1,
382 Status = 302
383 ),
384 redirect_action(Status, To, Action).
385
386redirect_action(301, To, moved(To)).
387redirect_action(302, To, moved_temporary(To)).
388redirect_action(303, To, see_other(To)).
389
390
398
(Header) :-
400 current_output(CGI),
401 cgi_property(CGI, header(Header0)),
402 cgi_set(CGI, header([Header|Header0])).
403
404
409
410expand_request(R0, R) :-
411 http:request_expansion(R0, R1), 412 R1 \== R0,
413 !,
414 expand_request(R1, R).
415expand_request(R, R).
416
417
421
422extend_request([], R, R).
423extend_request([request(R)|T], R0, R) :-
424 !,
425 extend_request(T, R0, R).
426extend_request([H|T], R0, R) :-
427 request_option(H),
428 !,
429 extend_request(T, [H|R0], R).
430extend_request([_|T], R0, R) :-
431 extend_request(T, R0, R).
432
433request_option(peer(_)).
434request_option(protocol(_)).
435request_option(pool(_)).
436
437
443
444http_current_request(Request) :-
445 current_output(CGI),
446 is_cgi_stream(CGI),
447 cgi_property(CGI, request(Request)).
448
449
456
457http_peer(Request, IP) :-
458 memberchk(x_forwarded_for(IP0), Request),
459 !,
460 atomic_list_concat(Parts, ', ', IP0),
461 last(Parts, IP).
462http_peer(Request, IP) :-
463 memberchk(peer(Peer), Request),
464 !,
465 peer_to_ip(Peer, IP).
466
467peer_to_ip(ip(A,B,C,D), IP) :-
468 atomic_list_concat([A,B,C,D], '.', IP).
469
470
477
478http_relative_path(Path, RelPath) :-
479 http_current_request(Request),
480 memberchk(path(RelTo), Request),
481 http_relative_path(Path, RelTo, RelPath),
482 !.
483http_relative_path(Path, Path).
484
485http_relative_path(Path, RelTo, RelPath) :-
486 atomic_list_concat(PL, /, Path),
487 atomic_list_concat(RL, /, RelTo),
488 delete_common_prefix(PL, RL, PL1, PL2),
489 to_dot_dot(PL2, DotDot, PL1),
490 atomic_list_concat(DotDot, /, RelPath).
491
492delete_common_prefix([H|T01], [H|T02], T1, T2) :-
493 !,
494 delete_common_prefix(T01, T02, T1, T2).
495delete_common_prefix(T1, T2, T1, T2).
496
497to_dot_dot([], Tail, Tail).
498to_dot_dot([_], Tail, Tail) :- !.
499to_dot_dot([_|T0], ['..'|T], Tail) :-
500 to_dot_dot(T0, T, Tail).
501
502
503 506
510
511debug_request(Code, ok, Id, CPU, Bytes) :-
512 !,
513 debug(http(request), '[~D] ~w OK (~3f seconds; ~D bytes)',
514 [Id, Code, CPU, Bytes]).
515debug_request(Code, Status, Id, _, Bytes) :-
516 map_exception(Status, Reply),
517 !,
518 debug(http(request), '[~D] ~w ~w; ~D bytes',
519 [Id, Code, Reply, Bytes]).
520debug_request(Code, Except, Id, _, _) :-
521 Except = error(_,_),
522 !,
523 message_to_string(Except, Message),
524 debug(http(request), '[~D] ~w ERROR: ~w',
525 [Id, Code, Message]).
526debug_request(Code, Status, Id, _, Bytes) :-
527 debug(http(request), '[~D] ~w ~w; ~D bytes',
528 [Id, Code, Status, Bytes]).
529
530map_exception(http_reply(Reply), Reply).
531map_exception(http_reply(Reply, _), Reply).
532map_exception(error(existence_error(http_location, Location), _Stack),
533 error(404, Location)).