%%% BEGIN openflax/http/response.erl %%%
%%%
%%% openflax - Open Source web server for Erlang/OTP
%%% Copyright (c)2004 Cat's Eye Technologies. All rights reserved.
%%%
%%% Redistribution and use in source and binary forms, with or without
%%% modification, are permitted provided that the following conditions
%%% are met:
%%%
%%% Redistributions of source code must retain the above copyright
%%% notice, this list of conditions and the following disclaimer.
%%%
%%% Redistributions in binary form must reproduce the above copyright
%%% notice, this list of conditions and the following disclaimer in
%%% the documentation and/or other materials provided with the
%%% distribution.
%%%
%%% Neither the name of Cat's Eye Technologies nor the names of its
%%% contributors may be used to endorse or promote products derived
%%% from this software without specific prior written permission.
%%%
%%% THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
%%% CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
%%% INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
%%% MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
%%% DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE
%%% LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
%%% OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
%%% PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
%%% OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
%%% ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
%%% OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
%%% OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
%%% POSSIBILITY OF SUCH DAMAGE.
%% @doc HTTP response generation for OpenFlax.
%%
%%
This module exports functions which are called
%% exclusively from openflax.serve to send an HTTP
%% response back to the user agent via the HTTP service.
%%
%% @end
-module(openflax.http.response).
-vsn('$Id: response.erl 31 2004-04-23 07:00:11Z catseye $').
-author('catseye@catseye.mb.ca').
-copyright('Copyright (c)2004 Cat`s Eye Technologies. All rights reserved.').
-export([send_headers/2, send_data/3]).
-export([moved/4, error/4]).
-import(lists).
%% @spec send_headers(http(), conf()) -> ok | {error, Reason}
%% @doc Sends an HTTP response header through the HTTP service.
%% Note that after doing this, you should not send any body
%% if the Request-Method is HEAD.
send_headers(Http, Conf) ->
Conf0 = add_missing_headers(Conf),
send_headers0(Http, Conf0).
send_headers0(Http, Conf) ->
Response = openflax.conf:get_string(sres_response_code, Conf),
Http ! {self(), send_response_line, Response},
ResHeaders = openflax.conf:fold(fun(Key, Value, Acc) ->
case catch atom_to_list(Key) of
"res_" ++ _ ->
[{Key, Value} | Acc];
_ ->
Acc
end
end, [], Conf),
% Get cache-control from dictionary, if present
% Assume resource is cacheable, if not
NoCacheHeaderFields = case openflax.conf:get_value(sres_non_cacheable, Conf) of
{ok, true} ->
[{res_pragma, "no-cache"},
{res_cache_control, "no-cache"}];
_ ->
[]
end,
% Get persistent connection options from dictionary, if present
% Assume connection close (no persistent connection,) if not
PersistencyHeaderFields = case
{openflax.conf:get_value(sres_keep_alive, Conf),
openflax.conf:get_string(req_connection, Conf)} of
{_, "close"} -> % other side wants us to close
[{res_connection, "close"}];
{{ok, true}, _} ->
[{res_connection, "Keep-Alive"}];
_ ->
[{res_connection, "close"}]
end,
HeaderFields = lists:reverse([
{res_date, openflax.string:from_datetime_rfc_1123()}
] ++ NoCacheHeaderFields
++ PersistencyHeaderFields
++ ResHeaders),
% openflax.app:debug(sending_headers, HeaderFields),
Http ! {self(), send_headers, HeaderFields},
ok.
%% @spec send_data(http(), conf(), string()) -> ok | {error, Reason}
%% @doc Sends arbitrary text or data to the HTTP service.
%% Sends nothing if the Request-Method was HEAD.
send_data(Http, Conf, Data) ->
case openflax.conf:get_string(sreq_method, Conf) of
"HEAD" ->
ok;
_ ->
Http ! {self(), send_data, Data},
ok
end.
%% @spec moved(http(), conf(), response(), uri()) -> {ok, conf()} | {error, Reason}
%% @doc Returns a redirection response (typically 301 or 302) to the user agent
%% via the HTTP service.
moved(Http, Conf, Response, URI) ->
URI0 = qualify_uri(URI, Conf),
Conf0 = openflax.conf:put_value(res_content_length, "0", Conf),
Conf1 = openflax.conf:put_value(res_content_type, "text/html", Conf0),
Conf2 = openflax.conf:put_value(res_location, URI0, Conf1),
Conf3 = openflax.conf:put_value(sres_keep_alive, false, Conf2),
Conf4 = openflax.conf:put_value(sres_response_code, Response, Conf3),
send_headers(Http, Conf4),
{ok, Conf3}.
%% @spec error(http(), conf(), response(), master_dict()) -> {ok, conf()}
%% @doc Returns an error response. If an error-handling module is configured,
%% serve_error/3 will attempt to use it to generate an
%% error response page. Otherwise, a (very plain) stock response is offered.
%% TODO: pass MasterDict, ensure the given module is actually configured.
error(Http, Conf, Response, MasterDict) ->
Conf0 = openflax.conf:put_value(sres_response_code, Response, Conf),
case openflax.conf:get_value(cfg_openflax_error_handler, Conf0) of
{ok, Module} ->
case openflax.conf:get_value(cfg_openflax_error_mode, Conf0) of
{ok, true} ->
% an error has already occurred. Let's not go into a loop!
error0(Http, Conf0);
_ ->
Conf1 = openflax.conf:put_value(cfg_openflax_error_mode, true, Conf0),
openflax.serve:handle_response(Module, Conf1, Http, MasterDict)
end;
_ ->
error0(Http, Conf0)
end.
error0(Http, Conf) ->
Response = openflax.conf:get_string(sres_response_code, Conf),
Body = "" ++ Response ++ ""
"" ++ Response ++ "
",
Conf1 = openflax.conf:put_value(sres_response_code, Response, Conf),
Conf2 = openflax.conf:put_value(res_content_length,
integer_to_list(lists:flatlength(Body)), Conf1),
Conf3 = openflax.conf:put_value(res_content_type, "text/html", Conf2),
send_headers(Http, Conf3),
send_data(Http, Conf3, Body),
{ok, Conf3}.
%% qualify_uri(URI::string(), conf()) -> string()
qualify_uri("/" ++ RestOfURI, Conf) ->
Host = openflax.conf:get_string(sreq_host, Conf),
Port = openflax.conf:get_string(sreq_port, Conf),
case Port of
"80" ->
"http://" ++ Host ++ "/" ++ RestOfURI;
_ ->
"http://" ++ Host ++ ":" ++ Port ++ "/" ++ RestOfURI
end;
qualify_uri("http://" ++ RestOfURI, _Conf) ->
"http://" ++ RestOfURI;
qualify_uri(URI, Conf) ->
qualify_uri("/" ++ URI, Conf).
default(Conf, Key, Value) ->
case openflax.conf:get_value(Key, Conf) of
{ok, _} ->
Conf;
_ ->
openflax.conf:put_value(Key, Value, Conf)
end.
add_missing_headers(Conf) ->
Conf0 = default(Conf, sres_response_code, "200 OK"),
Conf1 = default(Conf0, res_content_type, "application/octet-stream"),
Conf2 = default(Conf1, res_last_modified,
openflax.string:from_datetime_rfc_1123()),
Conf3 = default(Conf2, res_server,
"OpenFlax/" ++ openflax.conf:get_string(cfg_openflax_version, Conf2)),
Conf3.
%%% END of openflax/http/response.erl %%%