%%% BEGIN openflax/serve.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 OpenFlax HTTP serving functions. %% %% @end -module(openflax.serve). -vsn('$Id: serve.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([serve/3, handle_response/4]). -import(erlang). -import(lists). -import(dict). %% @spec serve(http(), conf(), master_dict()) -> conf() %% @doc Serves a resource to the client. %% Determines which resource the client wanted, then sends it to the %% user agent. Before and after this determination, notifies a watching %% process (if any). serve(Http, Conf, MasterDict) -> % Notify the watching process, if any, of a newly opened connection Ref = erlang:make_ref(), notify_watchers(connection_open, Ref, Conf), % Determine which resource the user agent wanted / can get % First, get the dispatching rules from the cfg_openflax_dispatch setting {ok, RequestDispatch} = openflax.conf:get_value(cfg_openflax_dispatch, Conf), % Now resolve them to find an initial response() {Response, DispatchConf} = openflax.dispatch:resolve_request(RequestDispatch, openflax.conf:get_string(sreq_basic_resource, Conf), openflax.conf:get_string(sreq_basic_arguments, Conf), openflax.conf:get_string(sreq_remote_address, Conf)), % openflax.app:debug(response, Response), % merge in settings specified in the dispatch rule Conf0 = openflax.conf:merge(Conf, DispatchConf), % now handle the reponse(), possibly many times as it switches handlers {ok, Conf1} = handle_response(Response, Conf0, Http, MasterDict), % Notify the watching process that the connection is now closed notify_watchers(connection_closed, Ref, Conf1), Conf1. notify_watchers(State, Ref, Conf) -> case openflax.conf:get_value(cfg_openflax_watchers, Conf) of {ok, Pids} when is_list(Pids)-> lists:foreach(fun(Pid) -> Pid ! {State, Ref, Conf} end, Pids); _ -> ok end. %% @spec handle_response(response(), conf(), http(), master_dict()) -> {ok, conf()} %% @doc Selects an appropriate action from the given response() %% and acts on it (sends it to the user agent, etc.) handle_response(Response, Conf, Http, MasterDict) -> % openflax.app:debug(handling_response, Response), handle_response0(Response, Conf, Http, MasterDict). handle_response0(bad_request, Conf, Http, MasterDict) -> openflax.http.response:error(Http, Conf, "400 Bad Request", MasterDict); handle_response0(not_found, Conf, Http, MasterDict) -> openflax.http.response:error(Http, Conf, "404 Not Found", MasterDict); handle_response0(method_not_allowed, Conf, Http, MasterDict) -> % take out some insurance, to remain conformant Conf0 = case openflax.conf:get_string(res_allow, Conf) of "" -> openflax.conf:put_value(res_allow, "GET, HEAD", Conf); _ -> Conf end, openflax.http.response:error(Http, Conf0, "405 Method Not Allowed", MasterDict); handle_response0(request_entity_too_large, Conf, Http, MasterDict) -> openflax.http.response:error(Http, Conf, "413 Request Entity Too Large", MasterDict); handle_response0(expectation_failed, Conf, Http, MasterDict) -> openflax.http.response:error(Http, Conf, "417 Expectation Failed", MasterDict); handle_response0(server_error, Conf, Http, MasterDict) -> openflax.http.response:error(Http, Conf, "500 Server Error", MasterDict); handle_response0(not_implemented, Conf, Http, MasterDict) -> openflax.http.response:error(Http, Conf, "501 Not Implemented", MasterDict); handle_response0({moved_permanently, NewLoc}, Conf, Http, _MasterDict) -> openflax.http.response:moved(Http, Conf, "301 Moved Permanently", NewLoc); handle_response0({moved_temporarily, NewLoc}, Conf, Http, _MasterDict) -> openflax.http.response:moved(Http, Conf, "302 Found", NewLoc); handle_response0({permanent_new_domain, NewDomain}, Conf, Http, MasterDict) -> NewURI = "http://" ++ NewDomain ++ openflax.conf:get_string(sreq_uri, Conf), handle_response({moved_permanently, NewURI}, Conf, Http, MasterDict); handle_response0({content, Content}, Conf, Http, _MasterDict) -> % openflax.app:debug(sending_content, lists:flatten(Content)), Size = integer_to_list(lists:flatlength(Content)), Conf0 = openflax.conf:put_value(res_content_length, Size, Conf), openflax.http.response:send_headers(Http, Conf0), openflax.http.response:send_data(Http, Conf0, Content), {ok, Conf0}; handle_response0({stream, Pid, Size}, Conf, Http, MasterDict) -> handle_stream(Pid, Size, Conf, Http, MasterDict); handle_response0(Module, Conf, Http, MasterDict) when is_atom(Module) -> % openflax.app:debug(handling_response, Module), {NextResponse, Conf0} = launch(Module, Http, Conf, MasterDict), % openflax.app:debug(next_response, NextResponse), handle_response(NextResponse, Conf0, Http, MasterDict). %% @spec handle_stream(pid(), size(), conf(), http(), master_dict()) -> %% {ok, conf()} %% @doc Handles a response streamed through messages. handle_stream(Pid, Size, Conf, Http, _MasterDict) -> Conf1 = case Size of unknown -> Conf0 = openflax.conf:put_value(sres_keep_alive, false, Conf), openflax.conf:put_value(sres_non_cacheable, true, Conf0); _ -> openflax.conf:put_value(res_content_length, integer_to_list(Size), Conf) end, openflax.http.response:send_headers(Http, Conf1), Http ! {self(), start_stream, {Pid, Size}}, Pid ! {Http, stream_open}, receive {Http, end_stream} -> {ok, Conf1} end. %% @spec launch(module(), http(), conf(), master_dict()) -> {response(), conf()} %% @doc Launches a handler module. launch(Module, _Http, Conf, MasterDict) -> case dict:find(Module, MasterDict) of {ok, ModuleConf} -> % combine settings thus far with per-module settings, and serve % note that the per-module settings do *not* override settings thus far! case catch Module:serve(openflax.conf:merge(ModuleConf, Conf)) of {'EXIT', Reason} -> openflax.app:debug(handler_exited, Reason), {server_error, Conf}; {Response, Conf0} -> {Response, Conf0} end; error -> % error - module is specified in dispatch, but not listed in config {not_found, Conf} end. %%% END of openflax/serve.erl %%%