1
%% ``The contents of this file are subject to the Erlang Public License,
2
%% Version 1.1, (the "License"); you may not use this file except in
3
%% compliance with the License. You should have received a copy of the
4
%% Erlang Public License along with this software. If not, it can be
5
%% retrieved via the world wide web at http://www.erlang.org/.
7
%% Software distributed under the License is distributed on an "AS IS"
8
%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
9
%% the License for the specific language governing rights and limitations
12
%% The Initial Developer of the Original Code is Ericsson Utvecklings AB.
13
%% Portions created by Ericsson are Copyright 1999, Ericsson Utvecklings
14
%% AB. All Rights Reserved.''
16
%% $Id: httpd_response.erl,v 1.1 2008/12/17 09:53:34 mikpe Exp $
18
-module(httpd_response).
19
-export([send/1, send_status/3, send_status/5]).
21
%%code is the key for the statuscode ex: 200 404 ...
22
-define(HTTP11HEADERFIELDS,[content_length, accept_ranges, cache_control, date,
23
pragma, trailer, transfer_encoding, etag, location,
24
retry_after, server, allow,
25
content_encoding, content_language,
26
content_location, content_MD5, content_range,
27
content_type, expires, last_modified]).
29
-define(HTTP10HEADERFIELDS,[content_length, date, pragma, transfer_encoding,
30
location, server, allow, content_encoding,
31
content_type, last_modified]).
33
-define(PROCEED_RESPONSE(StatusCode, Info),
35
[{response,{already_sent, StatusCode,
36
httpd_util:key1search(Info#mod.data,content_lenght)}}]}).
39
-include("httpd.hrl").
41
-define(VMODULE,"RESPONSE").
42
-include("httpd_verbosity.hrl").
46
send(#mod{config_db = ConfigDB} = Info) ->
47
?vtrace("send -> Request line: ~p", [Info#mod.request_line]),
48
Modules = httpd_util:lookup(ConfigDB,modules,[mod_get, mod_head, mod_log]),
49
case traverse_modules(Info, Modules) of
53
case httpd_util:key1search(Data, status) of
54
{StatusCode, PhraseArgs, Reason} ->
55
?vdebug("send -> proceed/status: ~n"
59
[StatusCode, PhraseArgs, Reason]),
60
send_status(Info, StatusCode, PhraseArgs),
64
case httpd_util:key1search(Data, response) of
65
{already_sent, StatusCode, Size} ->
66
?vtrace("send -> already sent: "
71
{response, Header, Body} -> %% New way
72
send_response(Info, Header, Body),
74
{StatusCode, Response} -> %% Old way
75
send_response_old(Info, StatusCode, Response),
78
?vtrace("send -> undefined response", []),
79
send_status(Info, 500, none),
88
traverse_modules(Info,[]) ->
89
{proceed,Info#mod.data};
90
traverse_modules(Info,[Module|Rest]) ->
91
case (catch apply(Module,do,[Info])) of
93
?vlog("traverse_modules -> exit reason: ~p",[Reason]),
96
io_lib:format("traverse exit from apply: ~p:do => ~n~p",
98
report_error(mod_log, Info#mod.config_db, String),
99
report_error(mod_disk_log, Info#mod.config_db, String),
106
traverse_modules(Info#mod{data=NewData},Rest)
112
send_status(#mod{socket_type = SocketType,
114
connection = Conn} = Info, 100, _PhraseArgs) ->
115
?DEBUG("send_status -> StatusCode: ~p~n",[100]),
116
Header = httpd_util:header(100, Conn),
117
httpd_socket:deliver(SocketType, Socket,
118
[Header, "Content-Length:0\r\n\r\n"]);
120
send_status(#mod{socket_type = SocketType,
122
config_db = ConfigDB} = Info, StatusCode, PhraseArgs) ->
123
send_status(SocketType, Socket, StatusCode, PhraseArgs, ConfigDB).
125
send_status(SocketType, Socket, StatusCode, PhraseArgs, ConfigDB) ->
126
?DEBUG("send_status -> ~n"
129
[StatusCode, PhraseArgs]),
130
Header = httpd_util:header(StatusCode, "text/html", false),
131
ReasonPhrase = httpd_util:reason_phrase(StatusCode),
132
Message = httpd_util:message(StatusCode, PhraseArgs, ConfigDB),
133
Body = get_body(ReasonPhrase, Message),
137
integer_to_list(length(Body)) ++
139
httpd_socket:deliver(SocketType, Socket, [Header1, Body]).
142
get_body(ReasonPhrase, Message)->
145
<TITLE>"++ReasonPhrase++"</TITLE>
148
<H1>"++ReasonPhrase++"</H1>\n"++Message++"\n</BODY>
152
%%% Create a response from the Key/Val tuples In the Head List
153
%%% Body is a tuple {body,Fun(),Args}
158
% HTTP-Version StatusCode Reason-Phrase
161
% entity-headers)CRLF)
165
% General Header fields
166
% ======================
167
% Cache-Control cache_control
168
% Connection %%Is set dependiong on the request
174
% Response Header field
175
% =====================
177
% (Age) Mostly for proxys
180
% (Proxy-Authenticate) Only for proxies
186
% Entity Header Fields
187
% ====================
200
send_response(Info, Header, Body) ->
201
?vtrace("send_response -> (new) entry with"
202
"~n Header: ~p", [Header]),
203
case httpd_util:key1search(Header, code) of
206
%% Ooops this must be very bad:
207
%% generate a 404 content not availible
208
send_status(Info, 404, "The file is not availible");
210
case send_header(Info, StatusCode, Header) of
212
send_body(Info, StatusCode, Body);
214
?vlog("head delivery failure: ~p", [Error]),
220
send_header(#mod{socket_type = Type, socket = Sock,
221
http_version = Ver, connection = Conn} = Info,
222
StatusCode, Head0) ->
223
?vtrace("send_haeder -> entry with"
225
"~n Conn: ~p", [Ver, Conn]),
226
Head1 = create_header(Ver, Head0),
227
StatusLine = [Ver, " ",
228
io_lib:write(StatusCode), " ",
229
httpd_util:reason_phrase(StatusCode), "\r\n"],
230
Connection = get_connection(Conn, Ver),
231
Head = list_to_binary([StatusLine, Head1, Connection,"\r\n"]),
232
?vtrace("deliver head", []),
233
httpd_socket:deliver(Type, Sock, Head).
236
send_body(_, _, nobody) ->
237
?vtrace("send_body -> no body", []),
240
send_body(#mod{socket_type = Type, socket = Sock},
241
StatusCode, Body) when list(Body) ->
242
?vtrace("deliver body of size ~p", [length(Body)]),
243
httpd_socket:deliver(Type, Sock, Body);
245
send_body(#mod{socket_type = Type, socket = Sock} = Info,
246
StatusCode, {Fun, Args}) ->
247
case (catch apply(Fun, Args)) of
249
httpd_socket:close(Type, Sock),
253
?PROCEED_RESPONSE(StatusCode, Info);
256
?vtrace("deliver body", []),
257
case httpd_socket:deliver(Type, Sock, Body) of
259
?PROCEED_RESPONSE(StatusCode, Info);
261
?vlog("body delivery failure: ~p", [Error]),
266
?vlog("failure of apply(~p,~p): ~p", [Fun, Args, Error]),
269
send_body(I, S, B) ->
273
"~n B: ~p", [I, S, B]),
274
exit({bad_args, {I, S, B}}).
277
%% Return a HTTP-header field that indicates that the
278
%% connection will be inpersistent
279
get_connection(true,"HTTP/1.0")->
280
"Connection:close\r\n";
281
get_connection(false,"HTTP/1.1") ->
282
"Connection:close\r\n";
283
get_connection(_,_) ->
287
create_header("HTTP/1.1", Data) ->
288
create_header1(?HTTP11HEADERFIELDS, Data);
289
create_header(_, Data) ->
290
create_header1(?HTTP10HEADERFIELDS, Data).
292
create_header1(Fields, Data) ->
293
?DEBUG("create_header() -> "
294
"~n Fields :~p~n Data: ~p ~n", [Fields, Data]),
295
mapfilter(fun(Field)->
296
transform({Field, httpd_util:key1search(Data, Field)})
297
end, Fields, undefined).
300
%% Do a map and removes the values that evaluates to RemoveVal
301
mapfilter(Fun,List,RemoveVal)->
302
mapfilter(Fun,List,[],RemoveVal).
304
mapfilter(Fun,[],[RemoveVal|Acc],RemoveVal)->
306
mapfilter(Fun,[],Acc,_RemoveVal)->
309
mapfilter(Fun,[Elem|Rest],[RemoveVal|Acc],RemoveVal)->
310
mapfilter(Fun,Rest,[Fun(Elem)|Acc],RemoveVal);
311
mapfilter(Fun,[Elem|Rest],Acc,RemoveVal)->
312
mapfilter(Fun,Rest,[Fun(Elem)|Acc],RemoveVal).
315
transform({content_type,undefined})->
316
["Content-Type:text/plain\r\n"];
318
transform({date,undefined})->
319
["Date:",httpd_util:rfc1123_date(),"\r\n"];
321
transform({date,RFCDate})->
322
["Date:",RFCDate,"\r\n"];
325
transform({_Key,undefined})->
327
transform({accept_ranges,Value})->
328
["Accept-Ranges:",Value,"\r\n"];
329
transform({cache_control,Value})->
330
["Cache-Control:",Value,"\r\n"];
331
transform({pragma,Value})->
332
["Pragma:",Value,"\r\n"];
333
transform({trailer,Value})->
334
["Trailer:",Value,"\r\n"];
335
transform({transfer_encoding,Value})->
336
["Pragma:",Value,"\r\n"];
337
transform({etag,Value})->
338
["ETag:",Value,"\r\n"];
339
transform({location,Value})->
340
["Retry-After:",Value,"\r\n"];
341
transform({server,Value})->
342
["Server:",Value,"\r\n"];
343
transform({allow,Value})->
344
["Allow:",Value,"\r\n"];
345
transform({content_encoding,Value})->
346
["Content-Encoding:",Value,"\r\n"];
347
transform({content_language,Value})->
348
["Content-Language:",Value,"\r\n"];
349
transform({retry_after,Value})->
350
["Retry-After:",Value,"\r\n"];
351
transform({server,Value})->
352
["Server:",Value,"\r\n"];
353
transform({allow,Value})->
354
["Allow:",Value,"\r\n"];
355
transform({content_encoding,Value})->
356
["Content-Encoding:",Value,"\r\n"];
357
transform({content_language,Value})->
358
["Content-Language:",Value,"\r\n"];
359
transform({content_location,Value})->
360
["Content-Location:",Value,"\r\n"];
361
transform({content_length,Value})->
362
["Content-Length:",Value,"\r\n"];
363
transform({content_MD5,Value})->
364
["Content-MD5:",Value,"\r\n"];
365
transform({content_range,Value})->
366
["Content-Range:",Value,"\r\n"];
367
transform({content_type,Value})->
368
["Content-Type:",Value,"\r\n"];
369
transform({expires,Value})->
370
["Expires:",Value,"\r\n"];
371
transform({last_modified,Value})->
372
["Last-Modified:",Value,"\r\n"].
376
%%----------------------------------------------------------------------
377
%% This is the old way of sending data it is strongly encouraged to
378
%% Leave this method and go on to the newer form of response
380
%%----------------------------------------------------------------------
382
send_response_old(#mod{socket_type = Type,
384
method = "HEAD"} = Info,
385
StatusCode, Response) ->
386
?vtrace("send_response_old(HEAD) -> entry with"
389
[StatusCode,Response]),
390
case httpd_util:split(lists:flatten(Response),"\r\n\r\n|\n\n",2) of
391
{ok, [Head, Body]} ->
393
httpd_util:header(StatusCode,Info#mod.connection) ++
394
"Content-Length:" ++ content_length(Body),
395
httpd_socket:deliver(Type, Sock, [Header,Head,"\r\n"]);
398
send_status(Info, 500, "Internal Server Error")
401
send_response_old(#mod{socket_type = Type,
402
socket = Sock} = Info,
403
StatusCode, Response) ->
404
?vtrace("send_response_old -> entry with"
407
[StatusCode,Response]),
408
case httpd_util:split(lists:flatten(Response),"\r\n\r\n|\n\n",2) of
409
{ok, [_Head, Body]} ->
411
httpd_util:header(StatusCode,Info#mod.connection) ++
412
"Content-Length:" ++ content_length(Body),
413
httpd_socket:deliver(Type, Sock, [Header, Response]);
417
httpd_util:header(StatusCode,Info#mod.connection) ++
418
"Content-Length:" ++ content_length(Body) ++ "\r\n",
419
httpd_socket:deliver(Type, Sock, [Header, Response]);
422
send_status(Info, 500, "Internal Server Error")
425
content_length(Body)->
426
integer_to_list(httpd_util:flatlength(Body))++"\r\n".
429
report_error(Mod, ConfigDB, Error) ->
430
Modules = httpd_util:lookup(ConfigDB, modules,
431
[mod_get, mod_head, mod_log]),
432
case lists:member(Mod, Modules) of
434
Mod:report_error(ConfigDB, Error);