%% ``Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%% http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
%%
%% The Initial Developer of the Original Code is Ericsson Utvecklings AB.
%% Portions created by Ericsson are Copyright 1999, Ericsson Utvecklings
%% AB. All Rights Reserved.''
%%
%% $Id: httpd_response.erl,v 1.1 2008/12/17 09:53:34 mikpe Exp $
%%
-module(httpd_response).
-export([send/1, send_status/3, send_status/5]).
%%code is the key for the statuscode ex: 200 404 ...
-define(HTTP11HEADERFIELDS,[content_length, accept_ranges, cache_control, date,
pragma, trailer, transfer_encoding, etag, location,
retry_after, server, allow,
content_encoding, content_language,
content_location, content_MD5, content_range,
content_type, expires, last_modified]).
-define(HTTP10HEADERFIELDS,[content_length, date, pragma, transfer_encoding,
location, server, allow, content_encoding,
content_type, last_modified]).
-define(PROCEED_RESPONSE(StatusCode, Info),
{proceed,
[{response,{already_sent, StatusCode,
httpd_util:key1search(Info#mod.data,content_length)}}]}).
-include("httpd.hrl").
-define(VMODULE,"RESPONSE").
-include("httpd_verbosity.hrl").
%% send
send(#mod{config_db = ConfigDB} = Info) ->
?vtrace("send -> Request line: ~p", [Info#mod.request_line]),
Modules = httpd_util:lookup(ConfigDB,modules,[mod_get, mod_head, mod_log]),
case traverse_modules(Info, Modules) of
done ->
Info;
{proceed, Data} ->
case httpd_util:key1search(Data, status) of
{StatusCode, PhraseArgs, Reason} ->
?vdebug("send -> proceed/status: ~n"
"~n StatusCode: ~p"
"~n PhraseArgs: ~p"
"~n Reason: ~p",
[StatusCode, PhraseArgs, Reason]),
send_status(Info, StatusCode, PhraseArgs),
Info;
undefined ->
case httpd_util:key1search(Data, response) of
{already_sent, StatusCode, Size} ->
?vtrace("send -> already sent: "
"~n StatusCode: ~p"
"~n Size: ~p",
[StatusCode, Size]),
Info;
{response, Header, Body} -> %% New way
send_response(Info, Header, Body),
Info;
{StatusCode, Response} -> %% Old way
send_response_old(Info, StatusCode, Response),
Info;
undefined ->
?vtrace("send -> undefined response", []),
send_status(Info, 500, none),
Info
end
end
end.
%% traverse_modules
traverse_modules(Info,[]) ->
{proceed,Info#mod.data};
traverse_modules(Info,[Module|Rest]) ->
case (catch apply(Module,do,[Info])) of
{'EXIT', Reason} ->
?vlog("traverse_modules -> exit reason: ~p",[Reason]),
String =
lists:flatten(
io_lib:format("traverse exit from apply: ~p:do => ~n~p",
[Module, Reason])),
report_error(mod_log, Info#mod.config_db, String),
report_error(mod_disk_log, Info#mod.config_db, String),
done;
done ->
done;
{break,NewData} ->
{proceed,NewData};
{proceed,NewData} ->
traverse_modules(Info#mod{data=NewData},Rest)
end.
%% send_status %%
send_status(#mod{socket_type = SocketType,
socket = Socket,
connection = Conn} = Info, 100, _PhraseArgs) ->
?DEBUG("send_status -> StatusCode: ~p~n",[100]),
Header = httpd_util:header(100, Conn),
httpd_socket:deliver(SocketType, Socket,
[Header, "Content-Length:0\r\n\r\n"]);
send_status(#mod{socket_type = SocketType,
socket = Socket,
config_db = ConfigDB} = Info, StatusCode, PhraseArgs) ->
send_status(SocketType, Socket, StatusCode, PhraseArgs, ConfigDB).
send_status(SocketType, Socket, StatusCode, PhraseArgs, ConfigDB) ->
?DEBUG("send_status -> ~n"
" StatusCode: ~p~n"
" PhraseArgs: ~p",
[StatusCode, PhraseArgs]),
Header = httpd_util:header(StatusCode, "text/html", false),
ReasonPhrase = httpd_util:reason_phrase(StatusCode),
Message = httpd_util:message(StatusCode, PhraseArgs, ConfigDB),
Body = get_body(ReasonPhrase, Message),
Header1 =
Header ++
"Content-Length:" ++
integer_to_list(length(Body)) ++
"\r\n\r\n",
httpd_socket:deliver(SocketType, Socket, [Header1, Body]).
get_body(ReasonPhrase, Message)->
"
"++ReasonPhrase++"
"++ReasonPhrase++"
\n"++Message++"\n
\n".
%%% Create a response from the Key/Val tuples In the Head List
%%% Body is a tuple {body,Fun(),Args}
%% send_response
%% Allowed Fields
% HTTP-Version StatusCode Reason-Phrase
% *((general-headers
% response-headers
% entity-headers)CRLF)
% CRLF
% ?(BODY)
% General Header fields
% ======================
% Cache-Control cache_control
% Connection %%Is set dependiong on the request
% Date
% Pramga
% Trailer
% Transfer-Encoding
% Response Header field
% =====================
% Accept-Ranges
% (Age) Mostly for proxys
% Etag
% Location
% (Proxy-Authenticate) Only for proxies
% Retry-After
% Server
% Vary
% WWW-Authenticate
%
% Entity Header Fields
% ====================
% Allow
% Content-Encoding
% Content-Language
% Content-Length
% Content-Location
% Content-MD5
% Content-Range
% Content-Type
% Expires
% Last-Modified
send_response(Info, Header, Body) ->
?vtrace("send_response -> (new) entry with"
"~n Header: ~p", [Header]),
case httpd_util:key1search(Header, code) of
undefined ->
%% No status code
%% Ooops this must be very bad:
%% generate a 404 content not availible
send_status(Info, 404, "The file is not availible");
StatusCode ->
case send_header(Info, StatusCode, Header) of
ok ->
send_body(Info, StatusCode, Body);
Error ->
?vlog("head delivery failure: ~p", [Error]),
done
end
end.
send_header(#mod{socket_type = Type, socket = Sock,
http_version = Ver, connection = Conn} = Info,
StatusCode, Head0) ->
?vtrace("send_haeder -> entry with"
"~n Ver: ~p"
"~n Conn: ~p", [Ver, Conn]),
Head1 = create_header(Ver, Head0),
StatusLine = [Ver, " ",
io_lib:write(StatusCode), " ",
httpd_util:reason_phrase(StatusCode), "\r\n"],
Connection = get_connection(Conn, Ver),
Head = list_to_binary([StatusLine, Head1, Connection,"\r\n"]),
?vtrace("deliver head", []),
httpd_socket:deliver(Type, Sock, Head).
send_body(_, _, nobody) ->
?vtrace("send_body -> no body", []),
ok;
send_body(#mod{socket_type = Type, socket = Sock},
StatusCode, Body) when list(Body) ->
?vtrace("deliver body of size ~p", [length(Body)]),
httpd_socket:deliver(Type, Sock, Body);
send_body(#mod{socket_type = Type, socket = Sock} = Info,
StatusCode, {Fun, Args}) ->
case (catch apply(Fun, Args)) of
close ->
httpd_socket:close(Type, Sock),
done;
sent ->
?PROCEED_RESPONSE(StatusCode, Info);
{ok, Body} ->
?vtrace("deliver body", []),
case httpd_socket:deliver(Type, Sock, Body) of
ok ->
?PROCEED_RESPONSE(StatusCode, Info);
Error ->
?vlog("body delivery failure: ~p", [Error]),
done
end;
Error ->
?vlog("failure of apply(~p,~p): ~p", [Fun, Args, Error]),
done
end;
send_body(I, S, B) ->
?vinfo("BAD ARGS: "
"~n I: ~p"
"~n S: ~p"
"~n B: ~p", [I, S, B]),
exit({bad_args, {I, S, B}}).
%% Return a HTTP-header field that indicates that the
%% connection will be inpersistent
get_connection(true,"HTTP/1.0")->
"Connection:close\r\n";
get_connection(false,"HTTP/1.1") ->
"Connection:close\r\n";
get_connection(_,_) ->
"".
create_header("HTTP/1.1", Data) ->
create_header1(?HTTP11HEADERFIELDS, Data);
create_header(_, Data) ->
create_header1(?HTTP10HEADERFIELDS, Data).
create_header1(Fields, Data) ->
?DEBUG("create_header() -> "
"~n Fields :~p~n Data: ~p ~n", [Fields, Data]),
mapfilter(fun(Field)->
transform({Field, httpd_util:key1search(Data, Field)})
end, Fields, undefined).
%% Do a map and removes the values that evaluates to RemoveVal
mapfilter(Fun,List,RemoveVal)->
mapfilter(Fun,List,[],RemoveVal).
mapfilter(Fun,[],[RemoveVal|Acc],RemoveVal)->
Acc;
mapfilter(Fun,[],Acc,_RemoveVal)->
Acc;
mapfilter(Fun,[Elem|Rest],[RemoveVal|Acc],RemoveVal)->
mapfilter(Fun,Rest,[Fun(Elem)|Acc],RemoveVal);
mapfilter(Fun,[Elem|Rest],Acc,RemoveVal)->
mapfilter(Fun,Rest,[Fun(Elem)|Acc],RemoveVal).
transform({content_type,undefined})->
["Content-Type:text/plain\r\n"];
transform({date,undefined})->
["Date:",httpd_util:rfc1123_date(),"\r\n"];
transform({date,RFCDate})->
["Date:",RFCDate,"\r\n"];
transform({_Key,undefined})->
undefined;
transform({accept_ranges,Value})->
["Accept-Ranges:",Value,"\r\n"];
transform({cache_control,Value})->
["Cache-Control:",Value,"\r\n"];
transform({pragma,Value})->
["Pragma:",Value,"\r\n"];
transform({trailer,Value})->
["Trailer:",Value,"\r\n"];
transform({transfer_encoding,Value})->
["Pragma:",Value,"\r\n"];
transform({etag,Value})->
["ETag:",Value,"\r\n"];
transform({location,Value})->
["Retry-After:",Value,"\r\n"];
transform({server,Value})->
["Server:",Value,"\r\n"];
transform({allow,Value})->
["Allow:",Value,"\r\n"];
transform({content_encoding,Value})->
["Content-Encoding:",Value,"\r\n"];
transform({content_language,Value})->
["Content-Language:",Value,"\r\n"];
transform({retry_after,Value})->
["Retry-After:",Value,"\r\n"];
transform({server,Value})->
["Server:",Value,"\r\n"];
transform({allow,Value})->
["Allow:",Value,"\r\n"];
transform({content_encoding,Value})->
["Content-Encoding:",Value,"\r\n"];
transform({content_language,Value})->
["Content-Language:",Value,"\r\n"];
transform({content_location,Value})->
["Content-Location:",Value,"\r\n"];
transform({content_length,Value})->
["Content-Length:",Value,"\r\n"];
transform({content_MD5,Value})->
["Content-MD5:",Value,"\r\n"];
transform({content_range,Value})->
["Content-Range:",Value,"\r\n"];
transform({content_type,Value})->
["Content-Type:",Value,"\r\n"];
transform({expires,Value})->
["Expires:",Value,"\r\n"];
transform({last_modified,Value})->
["Last-Modified:",Value,"\r\n"].
%%----------------------------------------------------------------------
%% This is the old way of sending data it is strongly encouraged to
%% Leave this method and go on to the newer form of response
%% OTP-4408
%%----------------------------------------------------------------------
send_response_old(#mod{socket_type = Type,
socket = Sock,
method = "HEAD"} = Info,
StatusCode, Response) ->
?vtrace("send_response_old(HEAD) -> entry with"
"~n StatusCode: ~p"
"~n Response: ~p",
[StatusCode,Response]),
case httpd_util:split(lists:flatten(Response),"\r\n\r\n|\n\n",2) of
{ok, [Head, Body]} ->
Header =
httpd_util:header(StatusCode,Info#mod.connection) ++
"Content-Length:" ++ content_length(Body),
httpd_socket:deliver(Type, Sock, [Header,Head,"\r\n"]);
Error ->
send_status(Info, 500, "Internal Server Error")
end;
send_response_old(#mod{socket_type = Type,
socket = Sock} = Info,
StatusCode, Response) ->
?vtrace("send_response_old -> entry with"
"~n StatusCode: ~p"
"~n Response: ~p",
[StatusCode,Response]),
case httpd_util:split(lists:flatten(Response),"\r\n\r\n|\n\n",2) of
{ok, [_Head, Body]} ->
Header =
httpd_util:header(StatusCode,Info#mod.connection) ++
"Content-Length:" ++ content_length(Body),
httpd_socket:deliver(Type, Sock, [Header, Response]);
{ok, Body} ->
Header =
httpd_util:header(StatusCode,Info#mod.connection) ++
"Content-Length:" ++ content_length(Body) ++ "\r\n",
httpd_socket:deliver(Type, Sock, [Header, Response]);
{error, Reason} ->
send_status(Info, 500, "Internal Server Error")
end.
content_length(Body)->
integer_to_list(httpd_util:flatlength(Body))++"\r\n".
report_error(Mod, ConfigDB, Error) ->
Modules = httpd_util:lookup(ConfigDB, modules,
[mod_get, mod_head, mod_log]),
case lists:member(Mod, Modules) of
true ->
Mod:report_error(ConfigDB, Error);
_ ->
ok
end.