You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@couchdb.apache.org by cm...@apache.org on 2008/03/29 00:32:30 UTC

svn commit: r642432 [8/16] - in /incubator/couchdb/trunk: ./ bin/ build-contrib/ etc/ etc/conf/ etc/default/ etc/init/ etc/launchd/ etc/logrotate.d/ share/ share/server/ share/www/ share/www/browse/ share/www/image/ share/www/script/ share/www/style/ s...

Added: incubator/couchdb/trunk/src/couch_inets/httpd_manager.erl
URL: http://svn.apache.org/viewvc/incubator/couchdb/trunk/src/couch_inets/httpd_manager.erl?rev=642432&view=auto
==============================================================================
--- incubator/couchdb/trunk/src/couch_inets/httpd_manager.erl (added)
+++ incubator/couchdb/trunk/src/couch_inets/httpd_manager.erl Fri Mar 28 16:32:19 2008
@@ -0,0 +1,829 @@
+%% ``The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved via the world wide web at http://www.erlang.org/.
+%% 
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights 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$
+%%
+
+-module(httpd_manager).
+
+-include("httpd.hrl").
+
+-behaviour(gen_server).
+
+%% External API
+-export([start/2, start_link/2, start_link/3, stop/1, restart/1]).
+
+%% Internal API
+-export([new_connection/1, done_connection/1]).
+
+%% Module API
+-export([config_lookup/2, config_lookup/3, 
+	 config_multi_lookup/2, config_multi_lookup/3, 
+	 config_match/2, config_match/3]).
+
+%% gen_server exports
+-export([init/1, 
+	 handle_call/3, handle_cast/2, handle_info/2, 
+	 terminate/2,
+         code_change/3]).
+
+
+%% Management exports
+-export([block/2, block/3, unblock/1]).
+-export([get_admin_state/1, get_usage_state/1]).
+-export([is_busy/1,is_busy/2,is_busy_or_blocked/1,is_blocked/1]). %% ???????
+-export([get_status/1, get_status/2]).
+
+-export([c/1]).
+
+-record(state,{socket_type  = ip_comm,
+	       config_file,
+	       config_db    = null,
+	       connections, %% Current request handlers
+	       admin_state  = unblocked,
+	       blocker_ref  = undefined,
+	       blocking_tmr = undefined,
+	       status       = []}).
+
+
+c(Port) ->
+    Ref = httpd_util:make_name("httpd",undefined,Port),
+    gen_server:call(Ref, fake_close).
+
+
+%%
+%% External API
+%%
+
+start(ConfigFile, ConfigList) ->
+    Port = httpd_util:key1search(ConfigList,port,80),
+    Addr = httpd_util:key1search(ConfigList,bind_address),
+    Name = make_name(Addr,Port),
+    gen_server:start({local,Name},?MODULE,
+		     [ConfigFile, ConfigList, 15000, Addr, Port],[]).
+    
+start_link(ConfigFile, ConfigList) ->
+    start_link(ConfigFile, ConfigList, 15000).
+start_link(ConfigFile, ConfigList, AcceptTimeout) ->
+    Port = httpd_util:key1search(ConfigList,port,80),
+    Addr = httpd_util:key1search(ConfigList,bind_address),
+    Name = make_name(Addr,Port),
+    gen_server:start_link({local, Name},?MODULE,
+			  [ConfigFile, ConfigList, AcceptTimeout, Addr, Port],[]).
+    
+stop(ServerRef) ->
+    gen_server:call(ServerRef, stop).
+
+restart(ServerRef) ->
+    gen_server:call(ServerRef, restart).
+
+
+%%%----------------------------------------------------------------
+
+block(ServerRef, disturbing) ->
+    call(ServerRef,block);
+
+block(ServerRef, non_disturbing) ->
+    do_block(ServerRef, non_disturbing, infinity).
+
+block(ServerRef, Method, Timeout) ->
+    do_block(ServerRef, Method, Timeout).
+
+
+%% The reason for not using call here, is that the manager cannot
+%% _wait_ for completion of the requests. It must be able to do
+%% do other things at the same time as the blocking goes on.
+do_block(ServerRef, Method, infinity) ->
+    Ref = make_ref(),
+    cast(ServerRef, {block, Method, infinity, self(), Ref}),
+    receive
+	{block_reply, Reply, Ref} ->
+	    Reply
+    end;
+do_block(ServerRef,Method,Timeout) when Timeout > 0 ->
+    Ref = make_ref(),
+    cast(ServerRef,{block,Method,Timeout,self(),Ref}),
+    receive
+	{block_reply,Reply,Ref} ->
+	    Reply
+    end.
+
+
+%%%----------------------------------------------------------------
+
+%% unblock
+
+unblock(ServerRef) ->
+    call(ServerRef,unblock).
+
+%% get admin/usage state
+
+get_admin_state(ServerRef) ->
+    call(ServerRef,get_admin_state).
+
+get_usage_state(ServerRef) ->
+    call(ServerRef,get_usage_state).
+
+
+%% get_status
+
+get_status(ServerRef) ->
+    gen_server:call(ServerRef,get_status).
+
+get_status(ServerRef,Timeout) ->
+    gen_server:call(ServerRef,get_status,Timeout).
+
+%%
+%% Internal API
+%%
+
+
+%% new_connection
+
+new_connection(Manager) ->
+    gen_server:call(Manager, {new_connection, self()}, infinity).
+
+%% done
+
+done_connection(Manager) ->
+    gen_server:cast(Manager, {done_connection, self()}).
+
+
+%% is_busy(ServerRef) -> true | false
+%% 
+%% Tests if the server is (in usage state) busy, 
+%% i.e. has rached the heavy load limit.
+%% 
+
+is_busy(ServerRef) ->
+    gen_server:call(ServerRef,is_busy).
+    
+is_busy(ServerRef,Timeout) ->
+    gen_server:call(ServerRef,is_busy,Timeout).
+
+
+%% is_busy_or_blocked(ServerRef) -> busy | blocked | false
+%% 
+%% Tests if the server is busy (usage state), i.e. has rached,
+%% the heavy load limit, or blocked (admin state) .
+%% 
+
+is_busy_or_blocked(ServerRef) ->
+    gen_server:call(ServerRef,is_busy_or_blocked).
+    
+
+%% is_blocked(ServerRef) -> true | false
+%% 
+%% Tests if the server is blocked (admin state) .
+%% 
+
+is_blocked(ServerRef) ->
+    gen_server:call(ServerRef,is_blocked).
+    
+
+%%
+%% Module API. Theese functions are intended for use from modules only.
+%%
+
+config_lookup(Port, Query) ->
+    config_lookup(undefined, Port, Query).
+config_lookup(Addr, Port, Query) ->
+    Name = httpd_util:make_name("httpd",Addr,Port),
+    gen_server:call(whereis(Name), {config_lookup, Query}).
+
+config_multi_lookup(Port, Query) ->
+    config_multi_lookup(undefined,Port,Query).
+config_multi_lookup(Addr,Port, Query) ->
+    Name = httpd_util:make_name("httpd",Addr,Port),
+    gen_server:call(whereis(Name), {config_multi_lookup, Query}).
+
+config_match(Port, Pattern) ->
+    config_match(undefined,Port,Pattern).
+config_match(Addr, Port, Pattern) ->
+    Name = httpd_util:make_name("httpd",Addr,Port),
+    gen_server:call(whereis(Name), {config_match, Pattern}).
+
+
+%%
+%% Server call-back functions
+%%
+
+%% init
+
+init([ConfigFile, ConfigList, AcceptTimeout, Addr, Port]) ->
+    process_flag(trap_exit, true),
+    case (catch do_init(ConfigFile, ConfigList, AcceptTimeout, Addr, Port)) of
+	{error, Reason} ->
+	    String = lists:flatten(io_lib:format("Failed initiating web server: ~n~p~n~p~n",[ConfigFile,Reason])),
+	    error_logger:error_report(String),
+	    {stop, Reason};
+	{ok, State} ->
+	    {ok, State}
+    end.
+   
+
+do_init(ConfigFile, ConfigList, AcceptTimeout, Addr, Port) ->
+    ConfigDB   = do_initial_store(ConfigList),
+    SocketType = httpd_conf:config(ConfigDB),
+    case httpd_acceptor_sup:start_acceptor(SocketType, Addr,
+					   Port, ConfigDB, AcceptTimeout) of
+	{ok, _Pid} ->
+	    Status = [{max_conn,0}, {last_heavy_load,never}, 
+		      {last_connection,never}],
+	    State  = #state{socket_type = SocketType,
+			    config_file = ConfigFile,
+			    config_db   = ConfigDB,
+			    connections = [],
+			    status      = Status},
+	    {ok, State};
+	Else ->
+	    Else
+    end.
+
+
+do_initial_store(ConfigList) ->
+    case httpd_conf:store(ConfigList) of
+	{ok, ConfigDB} ->
+	    ConfigDB;
+	{error, Reason} ->
+	    throw({error, Reason})
+    end.
+
+   
+
+%% handle_call
+
+handle_call(stop, _From, State) ->
+    {stop, normal, ok, State};
+
+handle_call({config_lookup, Query}, _From, State) ->
+    Res = httpd_util:lookup(State#state.config_db, Query),
+    {reply, Res, State};
+
+handle_call({config_multi_lookup, Query}, _From, State) ->
+    Res = httpd_util:multi_lookup(State#state.config_db, Query),
+    {reply, Res, State};
+
+handle_call({config_match, Query}, _From, State) ->
+    Res = ets:match_object(State#state.config_db, Query),
+    {reply, Res, State};
+
+handle_call(get_status, _From, State) ->
+    ManagerStatus  = manager_status(self()),
+    S1 = [{current_conn,length(State#state.connections)}|State#state.status]++
+	[ManagerStatus],
+    {reply,S1,State};
+
+handle_call(is_busy, _From, State) ->
+    Reply = case get_ustate(State) of
+		busy ->
+		    true;
+		_ ->
+		    false
+	  end,
+    {reply,Reply,State};
+
+handle_call(is_busy_or_blocked, _From, State) ->
+    Reply = 
+	case get_astate(State) of
+	    unblocked ->
+		case get_ustate(State) of
+		    busy ->
+			busy;
+		    _ ->
+			false
+		end;
+	    _ ->
+		blocked
+	  end,
+    {reply,Reply,State};
+
+handle_call(is_blocked, _From, State) ->
+    Reply = 
+	case get_astate(State) of
+	    unblocked ->
+		false;
+	    _ ->
+		true
+	  end,
+    {reply,Reply,State};
+
+handle_call(get_admin_state, _From, State) ->
+    Reply = get_astate(State),
+    {reply,Reply,State};
+
+handle_call(get_usage_state, _From, State) ->
+    Reply = get_ustate(State),
+    {reply,Reply,State};
+
+handle_call(restart, _From, State) when State#state.admin_state == blocked ->
+    case handle_restart(State) of
+	{stop, Reply,S1} ->
+	    {stop, Reply, S1};
+	{_, Reply, S1} ->
+	    {reply,Reply,S1}
+    end;
+
+handle_call(restart, _From, State) ->
+    {reply,{error,{invalid_admin_state,State#state.admin_state}},State};
+
+handle_call(block, _From, State) ->
+    {Reply,S1} = handle_block(State),
+    {reply,Reply,S1};
+
+handle_call(unblock, {From,_Tag}, State) ->
+    {Reply,S1} = handle_unblock(State,From),
+    {reply, Reply, S1};
+
+handle_call({new_connection, Pid}, _From, State) ->
+    {Status, NewState} = handle_new_connection(State, Pid),
+    {reply, Status, NewState};
+
+handle_call(Request, From, State) ->
+    String = 
+	lists:flatten(
+	  io_lib:format("Unknown request "
+			"~n   ~p"
+			"~nto manager (~p)"
+			"~nfrom ~p",
+			[Request, self(), From])),
+    report_error(State,String),
+    {reply, ok, State}.
+
+
+%% handle_cast
+
+handle_cast({done_connection, Pid}, State) ->
+    S1 = handle_done_connection(State, Pid),
+    {noreply, S1};
+
+handle_cast({block, disturbing, Timeout, From, Ref}, State) ->
+    S1 = handle_block(State, Timeout, From, Ref),
+    {noreply,S1};
+
+handle_cast({block, non_disturbing, Timeout, From, Ref}, State) ->
+    S1 = handle_nd_block(State, Timeout, From, Ref),
+    {noreply,S1};
+
+handle_cast(Message, State) ->
+    String = 
+	lists:flatten(
+	  io_lib:format("Unknown message "
+			"~n   ~p"
+			"~nto manager (~p)",
+			[Message, self()])),
+    report_error(State, String),
+    {noreply, State}.
+
+%% handle_info
+
+handle_info({block_timeout, Method}, State) ->
+    S1 = handle_block_timeout(State,Method),
+    {noreply, S1};
+
+handle_info({'DOWN', Ref, process, _Object, _Info}, State) ->
+    S1 = 
+	case State#state.blocker_ref of
+	    Ref ->
+		handle_blocker_exit(State);
+	    _ ->
+		%% Not our blocker, so ignore
+		State
+	end,
+    {noreply, S1};
+
+handle_info({'EXIT', _, normal}, State) ->
+    {noreply, State};
+
+handle_info({'EXIT', _, blocked}, S) ->
+    {noreply, S};
+
+handle_info({'EXIT', Pid, Reason}, State) ->
+    S1 = check_connections(State, Pid, Reason),
+    {noreply, S1};
+
+handle_info(Info, State) ->
+    String = 
+	lists:flatten(
+	  io_lib:format("Unknown info "
+			"~n   ~p"
+			"~nto manager (~p)",
+			[Info, self()])),
+    report_error(State, String),
+    {noreply, State}.
+
+
+%% terminate
+
+terminate(_, #state{config_db = Db}) -> 
+    httpd_conf:remove_all(Db),
+    ok.
+
+
+%% code_change({down,ToVsn}, State, Extra)
+%% 
+%% NOTE:
+%% Actually upgrade from 2.5.1 to 2.5.3 and downgrade from 
+%% 2.5.3 to 2.5.1 is done with an application restart, so 
+%% these function is actually never used. The reason for keeping
+%% this stuff is only for future use.
+%%
+code_change({down,_ToVsn}, State, _Extra) ->
+    {ok,State};
+
+%% code_change(FromVsn, State, Extra)
+%%
+code_change(_FromVsn, State, _Extra) ->
+    {ok,State}.
+
+
+
+%% -------------------------------------------------------------------------
+%% check_connection
+%%
+%%
+%%
+%%
+
+check_connections(#state{connections = []} = State, _Pid, _Reason) ->
+    State;
+check_connections(#state{admin_state = shutting_down,
+			 connections = Connections} = State, Pid, Reason) ->
+    %% Could be a crashing request handler
+    case lists:delete(Pid, Connections) of
+	[] -> % Crashing request handler => block complete
+	    String = 
+		lists:flatten(
+		  io_lib:format("request handler (~p) crashed:"
+				"~n   ~p", [Pid, Reason])),
+	    report_error(State, String),
+	    demonitor_blocker(State#state.blocker_ref),
+	    {Tmr,From,Ref} = State#state.blocking_tmr,
+	    stop_block_tmr(Tmr),
+	    From ! {block_reply,ok,Ref},
+	    State#state{admin_state = blocked, connections = [],
+			blocker_ref = undefined};
+	Connections1 ->
+	    State#state{connections = Connections1}
+    end;
+check_connections(#state{connections = Connections} = State, Pid, Reason) ->
+    case lists:delete(Pid, Connections) of
+	Connections -> % Not a request handler, so ignore
+	    State;
+        NewConnections ->
+	    String = 
+		lists:flatten(
+		  io_lib:format("request handler (~p) crashed:"
+				"~n   ~p", [Pid, Reason])),
+	    report_error(State, String),
+	    State#state{connections = NewConnections}
+    end.
+
+
+%% -------------------------------------------------------------------------
+%% handle_[new | done]_connection
+%%
+%%
+%%
+%%
+
+handle_new_connection(State, Handler) ->
+    UsageState = get_ustate(State),
+    AdminState = get_astate(State),
+    handle_new_connection(UsageState, AdminState, State, Handler).
+
+handle_new_connection(busy, unblocked, State, _Handler) ->
+    Status = update_heavy_load_status(State#state.status),
+    {{reject, busy}, 
+     State#state{status = Status}};
+
+handle_new_connection(_UsageState, unblocked, State, Handler) ->
+    Connections = State#state.connections,
+    Status      = update_connection_status(State#state.status, 
+					   length(Connections)+1),
+    link(Handler),
+    {{ok, accept}, 
+     State#state{connections = [Handler|Connections], status = Status}};
+
+handle_new_connection(_UsageState, _AdminState, State, _Handler) ->
+    {{reject, blocked}, 
+     State}.
+
+handle_done_connection(#state{admin_state = shutting_down,
+			      connections = Connections} = State, Handler) ->
+    unlink(Handler),
+    case lists:delete(Handler, Connections) of
+	[] -> % Ok, block complete
+	    demonitor_blocker(State#state.blocker_ref),
+	    {Tmr,From,Ref} = State#state.blocking_tmr,
+	    stop_block_tmr(Tmr),
+	    From ! {block_reply,ok,Ref},
+	    State#state{admin_state = blocked, connections = [],
+			blocker_ref = undefined};
+	Connections1 ->
+	    State#state{connections = Connections1}
+    end;
+
+handle_done_connection(#state{connections = Connections} = State, Handler) ->
+    State#state{connections = lists:delete(Handler, Connections)}.
+    
+    
+%% -------------------------------------------------------------------------
+%% handle_block
+%%
+%%
+%%
+%%
+handle_block(#state{admin_state = AdminState} = S) ->
+    handle_block(S, AdminState).
+
+handle_block(S,unblocked) ->
+    %% Kill all connections
+    [kill_handler(Pid) || Pid <- S#state.connections],
+    {ok,S#state{connections = [], admin_state = blocked}};
+handle_block(S,blocked) ->
+    {ok,S};
+handle_block(S,shutting_down) ->
+    {{error,shutting_down},S}.
+    
+
+kill_handler(Pid) ->
+    exit(Pid, blocked).
+
+handle_block(S,Timeout,From,Ref) when Timeout >= 0 ->
+    do_block(S,Timeout,From,Ref);
+
+handle_block(S,Timeout,From,Ref) ->
+    Reply = {error,{invalid_block_request,Timeout}},
+    From ! {block_reply,Reply,Ref},
+    S.
+
+do_block(S,Timeout,From,Ref) ->
+    case S#state.connections of
+	[] ->
+	    %% Already in idle usage state => go directly to blocked
+	    From ! {block_reply,ok,Ref},
+	    S#state{admin_state = blocked};
+	_ ->
+	    %% Active or Busy usage state => go to shutting_down
+	    %% Make sure we get to know if blocker dies...
+	    MonitorRef = monitor_blocker(From),
+	    Tmr = {start_block_tmr(Timeout,disturbing),From,Ref},
+	    S#state{admin_state = shutting_down, 
+		    blocker_ref = MonitorRef, blocking_tmr = Tmr}
+    end.
+
+handle_nd_block(S,infinity,From,Ref) ->
+    do_nd_block(S,infinity,From,Ref);
+
+handle_nd_block(S,Timeout,From,Ref) when Timeout >= 0 ->
+    do_nd_block(S,Timeout,From,Ref);
+
+handle_nd_block(S,Timeout,From,Ref) ->
+    Reply = {error,{invalid_block_request,Timeout}},
+    From ! {block_reply,Reply,Ref},
+    S.
+
+do_nd_block(S,Timeout,From,Ref) ->
+    case S#state.connections of
+	[] ->
+	    %% Already in idle usage state => go directly to blocked
+	    From ! {block_reply,ok,Ref},
+	    S#state{admin_state = blocked};
+	_ ->
+	    %% Active or Busy usage state => go to shutting_down
+	    %% Make sure we get to know if blocker dies...
+	    MonitorRef = monitor_blocker(From),
+	    Tmr = {start_block_tmr(Timeout,non_disturbing),From,Ref},
+	    S#state{admin_state = shutting_down, 
+		    blocker_ref = MonitorRef, blocking_tmr = Tmr}
+    end.
+
+handle_block_timeout(S,Method) ->
+    %% Time to take this to the road...
+    demonitor_blocker(S#state.blocker_ref),
+    handle_block_timeout1(S,Method,S#state.blocking_tmr).
+
+handle_block_timeout1(S,non_disturbing,{_,From,Ref}) ->
+    From ! {block_reply,{error,timeout},Ref},
+    S#state{admin_state = unblocked, 
+	    blocker_ref = undefined, blocking_tmr = undefined};
+
+handle_block_timeout1(S,disturbing,{_,From,Ref}) ->
+    [exit(Pid,blocked) || Pid <- S#state.connections],
+
+    From ! {block_reply,ok,Ref},
+    S#state{admin_state = blocked,    connections = [], 
+	    blocker_ref = undefined, blocking_tmr = undefined};
+
+handle_block_timeout1(S,Method,{_,From,Ref}) ->
+    From ! {block_reply,{error,{unknown_block_method,Method}},Ref},
+    S#state{admin_state = blocked,    connections = [], 
+	    blocker_ref = undefined, blocking_tmr = undefined};
+
+handle_block_timeout1(S, _Method, _TmrInfo) ->
+    S#state{admin_state = unblocked,
+	    blocker_ref = undefined, blocking_tmr = undefined}.
+
+handle_unblock(S, FromA) ->
+    handle_unblock(S, FromA, S#state.admin_state).
+
+handle_unblock(S, _FromA, unblocked) ->
+    {ok,S};
+handle_unblock(S, FromA, _AdminState) ->
+    stop_block_tmr(S#state.blocking_tmr),
+    case S#state.blocking_tmr of
+	{_Tmr,FromB,Ref} ->
+	    %% Another process is trying to unblock
+	    %% Inform the blocker
+	    FromB ! {block_reply, {error,{unblocked,FromA}},Ref};
+	_ ->
+	    ok
+    end,
+    {ok,S#state{admin_state = unblocked, blocking_tmr = undefined}}.
+    
+%% The blocker died so we give up on the block.
+handle_blocker_exit(S) ->
+    {Tmr,_From,_Ref} = S#state.blocking_tmr,
+    stop_block_tmr(Tmr),
+    S#state{admin_state = unblocked,
+	    blocker_ref = undefined, blocking_tmr = undefined}.
+    
+
+
+%% -------------------------------------------------------------------------
+%% handle_restart
+%%
+%%
+%%
+%%
+handle_restart(#state{config_file = undefined} = State) ->
+    {continue, {error, undefined_config_file}, State};
+handle_restart(#state{config_db = Db, config_file = ConfigFile} = State) ->
+    {ok, Config} = httpd_conf:load(ConfigFile),
+    case (catch check_constant_values(Db, Config)) of
+	ok ->
+	    %% If something goes wrong between the remove 
+	    %% and the store where fu-ed
+	    httpd_conf:remove_all(Db),
+	    case httpd_conf:store(Config) of
+		{ok, NewConfigDB} ->
+		    {continue, ok, State#state{config_db = NewConfigDB}};
+		Error ->
+		    {stop, Error, State}
+	    end;
+	Error ->
+	    {continue, Error, State}
+    end.
+
+
+check_constant_values(Db, Config) ->
+    %% Check port number
+    Port = httpd_util:lookup(Db,port),
+    case httpd_util:key1search(Config,port) of  %% MUST be equal
+	Port ->
+	    ok;
+	OtherPort ->
+	    throw({error,{port_number_changed,Port,OtherPort}})
+    end,
+
+    %% Check bind address
+    Addr = httpd_util:lookup(Db,bind_address),
+    case httpd_util:key1search(Config,bind_address) of  %% MUST be equal
+	Addr ->
+	    ok;
+	OtherAddr ->
+	    throw({error,{addr_changed,Addr,OtherAddr}})
+    end,
+
+    %% Check socket type
+    SockType = httpd_util:lookup(Db, com_type),
+    case httpd_util:key1search(Config, com_type) of  %% MUST be equal
+	SockType ->
+	    ok;
+	OtherSockType ->
+	    throw({error,{sock_type_changed,SockType,OtherSockType}})
+    end,
+    ok.
+
+
+%% get_ustate(State) -> idle | active | busy
+%%
+%% Retrieve the usage state of the HTTP server:
+%%   0 active connection            -> idle
+%%   max_clients active connections -> busy
+%%   Otherwise                      -> active
+%%
+get_ustate(State) ->
+    get_ustate(length(State#state.connections),State).
+
+get_ustate(0,_State) ->
+    idle;
+get_ustate(ConnectionCnt,State) ->
+    ConfigDB = State#state.config_db,
+    case httpd_util:lookup(ConfigDB, max_clients, 150) of
+	ConnectionCnt ->
+	    busy;
+	_ ->
+	    active
+    end.
+
+
+get_astate(S) -> S#state.admin_state.
+
+
+%% Timer handling functions
+start_block_tmr(infinity,_) ->
+    undefined;
+start_block_tmr(T,M) ->
+    erlang:send_after(T,self(),{block_timeout,M}).
+
+stop_block_tmr(undefined) ->
+    ok;
+stop_block_tmr(Ref) ->
+    erlang:cancel_timer(Ref).
+
+
+%% Monitor blocker functions
+monitor_blocker(Pid) when pid(Pid) ->
+    case (catch erlang:monitor(process,Pid)) of
+	{'EXIT', _Reason} ->
+	    undefined;
+	MonitorRef ->
+	    MonitorRef
+    end;
+monitor_blocker(_) ->
+    undefined.
+
+demonitor_blocker(undefined) ->
+    ok;
+demonitor_blocker(Ref) ->
+    (catch erlang:demonitor(Ref)).
+
+
+%% Some status utility functions
+
+update_heavy_load_status(Status) ->
+    update_status_with_time(Status,last_heavy_load).
+
+update_connection_status(Status,ConnCount) ->
+    S1 = case lists:keysearch(max_conn,1,Status) of
+	     {value, {max_conn, C1}} when ConnCount > C1 ->
+		 lists:keyreplace(max_conn,1,Status,{max_conn,ConnCount});
+	     {value, {max_conn, _C2}} ->
+		 Status;
+	     false ->
+		 [{max_conn, ConnCount} | Status]
+	 end,
+    update_status_with_time(S1,last_connection).
+
+update_status_with_time(Status,Key) ->
+    lists:keyreplace(Key,1,Status,{Key,universal_time()}).
+
+universal_time() -> calendar:universal_time().
+
+manager_status(P) ->
+    Items = [status, message_queue_len, reductions,
+	     heap_size, stack_size],
+    {manager_status, process_status(P,Items,[])}.
+
+
+process_status(P,[],L) ->
+    [{pid,P}|lists:reverse(L)];
+process_status(P,[H|T],L) ->
+    case (catch process_info(P,H)) of
+	{H, Value} ->
+	    process_status(P,T,[{H,Value}|L]);
+	_ ->
+	    process_status(P,T,[{H,undefined}|L])
+    end.
+	
+make_name(Addr,Port) ->
+    httpd_util:make_name("httpd",Addr,Port).
+
+
+report_error(State,String) ->
+    Cdb = State#state.config_db,
+    error_logger:error_report(String),
+    mod_log:report_error(Cdb,String),
+    mod_disk_log:report_error(Cdb,String).
+        
+%%
+call(ServerRef,Request) ->
+    gen_server:call(ServerRef,Request).
+
+cast(ServerRef,Message) ->
+    gen_server:cast(ServerRef,Message).
+

Added: incubator/couchdb/trunk/src/couch_inets/httpd_misc_sup.erl
URL: http://svn.apache.org/viewvc/incubator/couchdb/trunk/src/couch_inets/httpd_misc_sup.erl?rev=642432&view=auto
==============================================================================
--- incubator/couchdb/trunk/src/couch_inets/httpd_misc_sup.erl (added)
+++ incubator/couchdb/trunk/src/couch_inets/httpd_misc_sup.erl Fri Mar 28 16:32:19 2008
@@ -0,0 +1,89 @@
+%% ``The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved via the world wide web at http://www.erlang.org/.
+%% 
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights 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$
+%%
+%%----------------------------------------------------------------------
+%% Purpose: The supervisor for auth and sec processes in the http server, 
+%%          hangs under the httpd_instance_sup_<Addr>_<Port> supervisor.
+%%----------------------------------------------------------------------
+
+-module(httpd_misc_sup).
+
+-behaviour(supervisor).
+
+%% API 
+-export([start_link/2, start_auth_server/2, stop_auth_server/2, 
+	 start_sec_server/2,  stop_sec_server/2]).
+
+%% Supervisor callback
+-export([init/1]).
+
+%%%=========================================================================
+%%%  API
+%%%=========================================================================
+
+start_link(Addr, Port) ->
+    SupName = make_name(Addr, Port),
+    supervisor:start_link({local, SupName}, ?MODULE, []).
+
+%%----------------------------------------------------------------------
+%% Function: [start|stop]_[auth|sec]_server/3
+%% Description: Starts a [auth | security] worker (child) process
+%%----------------------------------------------------------------------
+start_auth_server(Addr, Port) ->
+    start_permanent_worker(mod_auth_server, Addr, Port, [gen_server]).
+
+stop_auth_server(Addr, Port) ->
+    stop_permanent_worker(mod_auth_server, Addr, Port).
+
+
+start_sec_server(Addr, Port) ->
+    start_permanent_worker(mod_security_server, Addr, Port, [gen_server]).
+
+stop_sec_server(Addr, Port) ->
+    stop_permanent_worker(mod_security_server, Addr, Port).
+
+
+%%%=========================================================================
+%%%  Supervisor callback
+%%%=========================================================================
+init(_) -> 
+    Flags     = {one_for_one, 0, 1},
+    Workers   = [],
+    {ok, {Flags, Workers}}.
+
+%%%=========================================================================
+%%%  Internal functions
+%%%=========================================================================
+start_permanent_worker(Mod, Addr, Port, Modules) ->
+    SupName = make_name(Addr, Port),
+    Spec    = {{Mod, Addr, Port},
+	       {Mod, start_link, [Addr, Port]}, 
+	       permanent, timer:seconds(1), worker, [Mod] ++ Modules},
+    supervisor:start_child(SupName, Spec).
+
+stop_permanent_worker(Mod, Addr, Port) ->
+    SupName = make_name(Addr, Port),
+    Name    = {Mod, Addr, Port},
+    case supervisor:terminate_child(SupName, Name) of
+	ok ->
+	    supervisor:delete_child(SupName, Name);
+	Error ->
+	    Error
+    end.
+    
+make_name(Addr,Port) ->
+    httpd_util:make_name("httpd_misc_sup",Addr,Port).

Added: incubator/couchdb/trunk/src/couch_inets/httpd_request.erl
URL: http://svn.apache.org/viewvc/incubator/couchdb/trunk/src/couch_inets/httpd_request.erl?rev=642432&view=auto
==============================================================================
--- incubator/couchdb/trunk/src/couch_inets/httpd_request.erl (added)
+++ incubator/couchdb/trunk/src/couch_inets/httpd_request.erl Fri Mar 28 16:32:19 2008
@@ -0,0 +1,337 @@
+%% ``The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved via the world wide web at http://www.erlang.org/.
+%% 
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights 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$
+
+-module(httpd_request).
+
+-include("http_internal.hrl").
+-include("httpd.hrl").
+
+-export([parse/1, whole_body/2, validate/3, update_mod_data/5,
+	 body_data/2]).
+
+%% Callback API - used for example if the header/body is received a
+%% little at a time on a socket. 
+-export([parse_method/1, parse_uri/1, parse_version/1, parse_headers/1,
+	 whole_body/1]).
+
+%%%=========================================================================
+%%%  Internal application API
+%%%=========================================================================
+parse([Bin, MaxHeaderSize]) ->
+         parse_method(Bin, [], MaxHeaderSize, []).
+
+%% Functions that may be returned during the decoding process
+%% if the input data is incompleate. 
+parse_method([Bin, Method, MaxHeaderSize, Result]) ->
+    parse_method(Bin, Method, MaxHeaderSize, Result).
+
+parse_uri([Bin, URI, MaxHeaderSize, Result]) ->
+    parse_uri(Bin, URI, MaxHeaderSize, Result).
+
+parse_version([Bin, Rest, Version, MaxHeaderSize, Result]) ->
+    parse_version(<<Rest/binary, Bin/binary>>, Version, MaxHeaderSize, 
+		  Result).
+
+parse_headers([Bin, Rest, Header, Headers, MaxHeaderSize, Result]) ->
+    parse_headers(<<Rest/binary, Bin/binary>>, 
+		  Header, Headers, MaxHeaderSize, Result).
+
+whole_body([Bin, Body, Length])  ->
+    whole_body(<<Body/binary, Bin/binary>>, Length).    
+
+
+%% Separate the body for this request from a possible piplined new 
+%% request and convert the body data to "string" format.
+body_data(Headers, Body) ->    
+    ContentLength = list_to_integer(Headers#http_request_h.'content-length'),
+    case size(Body) - ContentLength of
+ 	0 ->
+ 	    {binary_to_list(Body), <<>>};
+ 	_ ->
+ 	    <<BodyThisReq:ContentLength/binary, Next/binary>> = Body,   
+ 	    {binary_to_list(BodyThisReq), Next}
+    end.
+
+%%-------------------------------------------------------------------------
+%% validate(Method, Uri, Version) -> ok | {error, {bad_request, Reason} |
+%%			     {error, {not_supported, {Method, Uri, Version}}
+%%      Method = "HEAD" | "GET" | "POST" | "TRACE"
+%%      Uri = uri()
+%%      Version = "HTTP/N.M"      
+%% Description: Checks that HTTP-request-line is valid.
+%%------------------------------------------------------------------------- 
+validate("HEAD", Uri, "HTTP/1." ++ _N) ->
+    validate_uri(Uri);
+validate("GET", Uri, []) -> %% Simple HTTP/0.9 
+    validate_uri(Uri);
+validate("GET", Uri, "HTTP/0.9") ->
+    validate_uri(Uri);
+validate("GET", Uri, "HTTP/1." ++ _N) ->
+    validate_uri(Uri);
+validate("POST", Uri, "HTTP/1." ++ _N) ->
+    validate_uri(Uri);
+validate("TRACE", Uri, "HTTP/1." ++ N) when hd(N) >= $1 ->
+    validate_uri(Uri);
+validate("PUT", Uri, "HTTP/1." ++ _N) ->
+    validate_uri(Uri);
+validate("DELETE", Uri, "HTTP/1." ++ _N) ->
+    validate_uri(Uri);
+validate(Method, Uri, Version) ->
+    {error, {not_supported, {Method, Uri, Version}}}.
+
+%%----------------------------------------------------------------------
+%% The request is passed through the server as a record of type mod 
+%% create it.
+%% ----------------------------------------------------------------------
+update_mod_data(ModData, Method, RequestURI, HTTPVersion, Headers)-> 
+    ParsedHeaders =  tagup_header(Headers),
+    PersistentConn = get_persistens(HTTPVersion, ParsedHeaders, 
+				    ModData#mod.config_db),
+    {ok, ModData#mod{data = [],
+		     method = Method,
+		     absolute_uri = format_absolute_uri(RequestURI, 
+							ParsedHeaders),
+		     request_uri = format_request_uri(RequestURI),
+		     http_version = HTTPVersion,
+		     request_line = Method ++ " " ++ RequestURI ++ 
+		     " " ++ HTTPVersion,
+		     parsed_header = ParsedHeaders,
+		     connection = PersistentConn}}.
+
+%%%========================================================================
+%%% Internal functions
+%%%========================================================================
+parse_method(<<>>, Method, MaxHeaderSize, Result) ->
+    {?MODULE, parse_method, [Method, MaxHeaderSize, Result]};
+parse_method(<<?SP, Rest/binary>>, Method, MaxHeaderSize, Result) ->
+    parse_uri(Rest, [], MaxHeaderSize,
+	      [string:strip(lists:reverse(Method)) | Result]);
+parse_method(<<Octet, Rest/binary>>, Method, MaxHeaderSize, Result) ->
+    parse_method(Rest, [Octet | Method], MaxHeaderSize, Result).
+
+parse_uri(<<>>, URI, MaxHeaderSize, Result) ->
+    {?MODULE, parse_uri, [URI, MaxHeaderSize, Result]};
+parse_uri(<<?SP, Rest/binary>>, URI, MaxHeaderSize, Result) -> 
+    parse_version(Rest, [], MaxHeaderSize, 
+		  [string:strip(lists:reverse(URI)) | Result]);
+%% Can happen if it is a simple HTTP/0.9 request e.i "GET /\r\n\r\n"
+parse_uri(<<?CR, _Rest/binary>> = Data, URI, MaxHeaderSize, Result) -> 
+    parse_version(Data, [], MaxHeaderSize, 
+		  [string:strip(lists:reverse(URI)) | Result]);
+parse_uri(<<Octet, Rest/binary>>, URI, MaxHeaderSize, Result) ->
+    parse_uri(Rest, [Octet | URI], MaxHeaderSize, Result).
+
+parse_version(<<>>, Version, MaxHeaderSize, Result) ->
+    {?MODULE, parse_version, [<<>>, Version, MaxHeaderSize, Result]};
+parse_version(<<?CR, ?LF, Rest/binary>>, Version, MaxHeaderSize, Result) ->
+    parse_headers(Rest, [], [], MaxHeaderSize, 
+		  [string:strip(lists:reverse(Version)) | Result]);
+parse_version(<<?CR>> = Data, Version, MaxHeaderSize, Result) ->
+    {?MODULE, parse_version, [Data, Version, MaxHeaderSize, Result]};
+parse_version(<<Octet, Rest/binary>>, Version, MaxHeaderSize, Result) ->
+    parse_version(Rest, [Octet | Version], MaxHeaderSize, Result).
+
+parse_headers(<<>>, Header, Headers, MaxHeaderSize, Result) ->
+    {?MODULE, parse_headers, [<<>>, Header, Headers, MaxHeaderSize, Result]};
+parse_headers(<<?CR,?LF,?CR,?LF,Body/binary>>, [], [], _, Result) ->
+     NewResult = list_to_tuple(lists:reverse([Body, {#http_request_h{}, []} |
+					      Result])),
+    {ok, NewResult};
+parse_headers(<<?CR,?LF,?CR,?LF,Body/binary>>, Header, Headers,
+	      MaxHeaderSize, Result) ->
+    HTTPHeaders = [lists:reverse(Header) | Headers],
+    Length =   lists:foldl(fun(H, Acc) -> length(H) + Acc end,
+			   0, HTTPHeaders),
+    case ((Length > MaxHeaderSize) or (MaxHeaderSize == nolimit)) of
+	true ->
+	    {error, {header_too_long, MaxHeaderSize}, 
+	     lists:nth(3, lists:reverse(Result))}; % HTTP Version
+	false ->
+	    RequestHeaderRcord = 
+		http_request:headers(HTTPHeaders, #http_request_h{}),
+	    NewResult = 
+		list_to_tuple(lists:reverse([Body, {RequestHeaderRcord, 
+						    HTTPHeaders} | Result])),
+	    {ok, NewResult}
+    end;
+parse_headers(<<?CR,?LF,?CR>> = Data, Header, Headers, 
+	      MaxHeaderSize, Result) ->
+    {?MODULE, parse_headers, [Data, Header, Headers, MaxHeaderSize, Result]};
+
+%% There where no headers, which is unlikely to happen.
+parse_headers(<<?CR,?LF>>, [], [], _, Result) ->
+     NewResult = list_to_tuple(lists:reverse([<<>>, {#http_request_h{}, []} |
+					      Result])),
+    {ok, NewResult};
+parse_headers(<<?CR,?LF>> = Data, Header, Headers, 
+	      MaxHeaderSize, Result) ->
+    {?MODULE, parse_headers, [Data, Header, Headers, MaxHeaderSize, Result]};
+parse_headers(<<?CR,?LF, Octet, Rest/binary>>, Header, Headers, 
+	      MaxHeaderSize, Result) ->
+    parse_headers(Rest, [Octet], [lists:reverse(Header) | Headers], 
+		  MaxHeaderSize, Result);
+parse_headers(<<?CR>> = Data, Header, Headers, 
+	      MaxHeaderSize, Result) ->
+    {?MODULE, parse_headers, [Data, Header, Headers, MaxHeaderSize, Result]};
+parse_headers(<<Octet, Rest/binary>>, Header, Headers, 
+	      MaxHeaderSize, Result) ->
+    parse_headers(Rest, [Octet | Header], Headers, MaxHeaderSize, Result).
+
+whole_body(Body, Length) ->
+    case size(Body) of
+	N when N < Length, Length > 0 ->
+	  {?MODULE, whole_body, [Body, Length]};
+	N when N >= Length, Length >= 0 ->  
+	    %% When a client uses pipelining trailing data
+	    %% may be part of the next request!
+	    %% Trailing data will be separated from 
+	    %% the actual body in body_data/2.
+	    {ok, Body}
+	end.
+
+%% Prevent people from trying to access directories/files
+%% relative to the ServerRoot.
+validate_uri(RequestURI) ->
+    UriNoQueryNoHex = 
+	case string:str(RequestURI, "?") of
+	    0 ->
+		(catch httpd_util:decode_hex(RequestURI));
+	    Ndx ->
+		(catch httpd_util:decode_hex(string:left(RequestURI, Ndx)))	
+	end,
+    case UriNoQueryNoHex of
+	{'EXIT',_Reason} ->
+	    {error, {bad_request, {malformed_syntax, RequestURI}}};
+	_ ->
+	    Path = format_request_uri(UriNoQueryNoHex),
+	    Path2=[X||X<-string:tokens(Path, "/"),X=/="."], %% OTP-5938
+	    validate_path( Path2,0, RequestURI)
+    end.
+
+validate_path([], _, _) ->
+    ok;
+validate_path([".." | _], 0, RequestURI) ->
+    {error, {bad_request, {forbidden, RequestURI}}};
+validate_path([".." | Rest], N, RequestURI) ->
+    validate_path(Rest, N - 1, RequestURI);
+validate_path([_ | Rest], N, RequestURI) ->
+    validate_path(Rest, N + 1, RequestURI).
+
+%%----------------------------------------------------------------------
+%% There are 3 possible forms of the reuqest URI 
+%%
+%%  1. * When the request is not for a special assset. is is instead
+%%     to the server itself
+%%
+%%  2. absoluteURI the whole servername port and asset is in the request
+%%
+%%  3. The most common form that http/1.0 used abs path that is a path
+%%     to the requested asset.
+%%----------------------------------------------------------------------
+format_request_uri("*")->
+    "*";
+format_request_uri("http://" ++ ServerAndPath) ->
+   remove_server(ServerAndPath);
+
+format_request_uri("HTTP://" ++ ServerAndPath) ->
+    remove_server(ServerAndPath);
+
+format_request_uri(ABSPath) ->
+    ABSPath.
+
+remove_server([]) ->
+    "/";
+remove_server([$\/|Url])->
+    case Url of
+	[]->
+	    "/";
+        _->
+	    [$\/|Url]
+    end;
+remove_server([_|Url]) ->
+    remove_server(Url).
+
+format_absolute_uri("http://"++ Uri, _)->
+    "HTTP://" ++ Uri;
+
+format_absolute_uri(OrigUri = "HTTP://" ++ _, _)->
+    OrigUri;
+
+format_absolute_uri(Uri,ParsedHeader)->
+    case httpd_util:key1search(ParsedHeader,"host") of
+	undefined ->
+	    nohost;
+	Host ->
+	    Host++Uri
+    end.
+
+get_persistens(HTTPVersion,ParsedHeader,ConfigDB)->
+    case httpd_util:lookup(ConfigDB, persistent_conn, true) of
+	true->
+	    case HTTPVersion of
+		%%If it is version prio to 1.1 kill the conneciton
+		"HTTP/1." ++ NList ->
+		    case httpd_util:key1search(ParsedHeader,
+					       "connection", "keep-alive") of  
+			%%if the connection isnt ordered to go down
+			%%let it live The keep-alive value is the
+			%%older http/1.1 might be older Clients that
+			%%use it.
+			"keep-alive" when hd(NList) >= 49 ->
+			    ?DEBUG("CONNECTION MODE: ~p",[true]),  
+			    true;
+			"close" ->
+			    ?DEBUG("CONNECTION MODE: ~p",[false]),  
+			    false;
+			_Connect ->
+  			    ?DEBUG("CONNECTION MODE: ~p VALUE: ~p",
+				   [false, _Connect]),  
+			    false
+		    end; 
+		_ ->
+		    ?DEBUG("CONNECTION MODE: ~p VERSION: ~p",
+			   [false, HTTPVersion]),  
+		    false
+	    end;
+	_ ->
+	    false
+    end.
+
+
+%%----------------------------------------------------------------------
+%% tagup_header
+%%
+%% Parses the header of a HTTP request and returns a key,value tuple 
+%% list containing Name and Value of each header directive as of:
+%%
+%% Content-Type: multipart/mixed -> {"Content-Type", "multipart/mixed"}
+%%
+%% But in http/1.1 the field-names are case insencitive so now it must be 
+%% Content-Type: multipart/mixed -> {"content-type", "multipart/mixed"}
+%% The standard furthermore says that leading and traling white space 
+%% is not a part of the fieldvalue and shall therefore be removed.
+%%----------------------------------------------------------------------
+tagup_header([]) ->          [];
+tagup_header([Line|Rest]) -> [tag(Line, [])|tagup_header(Rest)].
+
+tag([], Tag) ->
+    {http_util:to_lower(lists:reverse(Tag)), ""};
+tag([$:|Rest], Tag) ->
+    {http_util:to_lower(lists:reverse(Tag)), string:strip(Rest)};
+tag([Chr|Rest], Tag) ->
+    tag(Rest, [Chr|Tag]).
+

Added: incubator/couchdb/trunk/src/couch_inets/httpd_request_handler.erl
URL: http://svn.apache.org/viewvc/incubator/couchdb/trunk/src/couch_inets/httpd_request_handler.erl?rev=642432&view=auto
==============================================================================
--- incubator/couchdb/trunk/src/couch_inets/httpd_request_handler.erl (added)
+++ incubator/couchdb/trunk/src/couch_inets/httpd_request_handler.erl Fri Mar 28 16:32:19 2008
@@ -0,0 +1,516 @@
+%% ``The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved via the world wide web at http://www.erlang.org/.
+%% 
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights 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$
+%%
+%% Description: Implements a request handler process for the HTTP server.  
+%% 
+
+-module(httpd_request_handler).
+
+-behaviour(gen_server).
+
+%% Application internal API
+-export([start/2, start/3, socket_ownership_transfered/3]).
+
+%% gen_server callbacks
+-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
+	 terminate/2, code_change/3]).
+
+-include("httpd.hrl").
+-include("http_internal.hrl").
+
+-record(state, {mod,     %% #mod{}
+		manager, %% pid()
+		status,  %% accept | busy | blocked
+		mfa,     %% {Module, Function, Args} 
+		max_keep_alive_request = infinity, %% integer() | infinity
+		response_sent = false, %% true | false 
+		timeout,  %% infinity | integer() > 0
+		timer,     %% ref() - Request timer
+		headers,  %% #http_request_h{}
+		body      %% binary()
+	       }).
+
+%%====================================================================
+%% Application internal API
+%%====================================================================
+%%--------------------------------------------------------------------
+%% Function: start() -> {ok, Pid} | ignore | {error,Error}
+%% Description: Starts a httpd-request handler process. Intended to be
+%% called by the httpd acceptor process.
+%%--------------------------------------------------------------------
+start(Manager, ConfigDB) ->
+    start(Manager, ConfigDB, 15000).
+start(Manager, ConfigDB, AcceptTimeout) ->
+    proc_lib:start(?MODULE, init, [[Manager, ConfigDB,AcceptTimeout]]).
+
+%%--------------------------------------------------------------------
+%% socket_ownership_transfered(Pid, SocketType, Socket) -> void()
+%%
+%% Pid = pid()
+%% SocketType = ip_comm | ssl 
+%% Socket = socket()
+%%
+%% Description: Send a message to the request handler process
+%% confirming that the socket ownership has now sucssesfully been
+%% transfered to it. Intended to be called by the httpd acceptor
+%% process.
+%%--------------------------------------------------------------------
+socket_ownership_transfered(Pid, SocketType, Socket) ->
+    Pid ! {socket_ownership_transfered, SocketType, Socket}.
+
+%%--------------------------------------------------------------------
+%% Function: init(Args) -> _
+%%
+%% Description: Initiates the server. Obs special init that uses 
+%% gen_server:enter_loop/3. This is used instead of the normal
+%% gen_server callback init, as a more complex init than the
+%% gen_server provides is needed. 
+%%--------------------------------------------------------------------
+init([Manager, ConfigDB,AcceptTimeout]) ->
+    %% Make sure this process terminates if the httpd manager process
+    %% should die!
+    link(Manager), 
+    %% At this point the function httpd_request_handler:start/2 will return.
+    proc_lib:init_ack({ok, self()}),
+    
+    {SocketType, Socket} = await_socket_ownership_transfer(AcceptTimeout),
+    
+    Resolve = http_transport:resolve(),
+    Peername = httpd_socket:peername(SocketType, Socket),
+    InitData = #init_data{peername = Peername, resolve = Resolve},
+    Mod = #mod{config_db = ConfigDB, 
+	       socket_type = SocketType, 
+	       socket = Socket,
+	       init_data = InitData},
+
+    MaxHeaderSize = httpd_util:lookup(ConfigDB, max_header_size, 
+				      ?HTTP_MAX_HEADER_SIZE),
+    TimeOut = httpd_util:lookup(ConfigDB, keep_alive_timeout, 150000),
+    NrOfRequest = httpd_util:lookup(ConfigDB, 
+				    max_keep_alive_request, infinity),
+    
+    {_, Status} = httpd_manager:new_connection(Manager),
+    
+    
+    State = #state{mod = Mod, manager = Manager, status = Status,
+		   timeout = TimeOut, max_keep_alive_request = NrOfRequest,
+		   mfa = {httpd_request, parse, [MaxHeaderSize]}}, 
+    
+    NewState = activate_request_timeout(State),
+
+    http_transport:setopts(SocketType, Socket, [binary,{packet, 0},
+						{active, once}]),
+    gen_server:enter_loop(?MODULE, [], NewState).
+	    
+%%====================================================================
+%% gen_server callbacks
+%%====================================================================
+
+%%--------------------------------------------------------------------
+%% handle_call(Request, From, State) -> {reply, Reply, State} |
+%%                                      {reply, Reply, State, Timeout} |
+%%                                      {noreply, State} |
+%%                                      {noreply, State, Timeout} |
+%%                                      {stop, Reason, Reply, State} |
+%%                                      {stop, Reason, State}
+%% Description: Handling call messages
+%%--------------------------------------------------------------------
+handle_call(Request, From, State) ->
+    {stop, {call_api_violation, Request, From}, State}.
+
+%%--------------------------------------------------------------------
+%% handle_cast(Msg, State) -> {noreply, State} |
+%%                                      {noreply, State, Timeout} |
+%%                                      {stop, Reason, State}
+%% Description: Handling cast messages
+%%--------------------------------------------------------------------
+handle_cast(Msg, State) ->
+    {reply, {cast_api_violation, Msg}, State}.
+
+%%--------------------------------------------------------------------
+%% handle_info(Info, State) -> {noreply, State} |
+%%                                       {noreply, State, Timeout} |
+%%                                       {stop, Reason, State}
+%% Description: Handling all non call/cast messages
+%%--------------------------------------------------------------------
+handle_info({Proto, Socket, Data}, State = 
+	    #state{mfa = {Module, Function, Args},
+		   mod = #mod{socket_type = SockType, 
+			      socket = Socket} = ModData} 
+	    = State) when Proto == tcp; Proto == ssl; Proto == dummy ->
+  
+    case Module:Function([Data | Args]) of
+        {ok, Result} ->
+	    NewState = cancel_request_timeout(State),
+            handle_http_msg(Result, NewState); 
+	{error, {header_too_long, MaxHeaderSize}, Version} ->
+	    NewModData =  ModData#mod{http_version = Version},
+	    httpd_response:send_status(NewModData, 413, "Header too big"),
+	    Reason = io_lib:format("Header too big, max size is ~p~n", 
+				   [MaxHeaderSize]),
+	    error_log(Reason, NewModData),
+	    {stop, normal, State#state{response_sent = true, 
+				       mod = NewModData}};
+	NewMFA ->
+	    http_transport:setopts(SockType, Socket, [{active, once}]),
+            {noreply, State#state{mfa = NewMFA}}
+    end;
+
+%% Error cases
+handle_info({tcp_closed, _}, State) ->
+    {stop, normal, State};
+handle_info({ssl_closed, _}, State) ->
+    {stop, normal, State};
+handle_info({tcp_error, _, _} = Reason, State) ->
+    {stop, Reason, State};
+handle_info({ssl_error, _, _} = Reason, State) ->
+    {stop, Reason, State};
+
+%% Timeouts
+handle_info(timeout, #state{mod = ModData, mfa = {_, parse, _}} = State) ->
+    error_log("No request received on keep-alive connection" 
+	      "before server side timeout", ModData),
+    %% No response should be sent!
+    {stop, normal, State#state{response_sent = true}}; 
+handle_info(timeout, #state{mod = ModData} = State) ->
+    httpd_response:send_status(ModData, 408, "Request timeout"),
+    error_log("The client did not send the whole request before the"
+	      "server side timeout", ModData),
+    {stop, normal, State#state{response_sent = true}};
+
+%% Default case
+handle_info(Info, #state{mod = ModData} = State) ->
+    Error = lists:flatten(
+	      io_lib:format("Unexpected message received: ~n~p~n", [Info])),
+    error_log(Error, ModData),
+    {noreply, State}.
+
+%%--------------------------------------------------------------------
+%% terminate(Reason, State) -> void()
+%%
+%% Description: This function is called by a gen_server when it is about to
+%% terminate. It should be the opposite of Module:init/1 and do any necessary
+%% cleaning up. When it returns, the gen_server terminates with Reason.
+%% The return value is ignored.
+%%--------------------------------------------------------------------
+terminate(normal, State) ->
+    do_terminate(State);
+terminate(Reason, #state{response_sent = false, mod = ModData} = State) ->
+    httpd_response:send_status(ModData, 500, none),
+    error_log(httpd_util:reason_phrase(500), ModData),
+    terminate(Reason, State#state{response_sent = true, mod = ModData});
+terminate(_, State) ->
+    do_terminate(State).
+
+do_terminate(#state{mod = ModData, manager = Manager} = State) ->
+    catch httpd_manager:done_connection(Manager),
+    cancel_request_timeout(State),
+    httpd_socket:close(ModData#mod.socket_type, ModData#mod.socket).
+
+%%--------------------------------------------------------------------
+%% code_change(OldVsn, State, Extra) -> {ok, NewState}
+%%
+%% Description: Convert process state when code is changed
+%%--------------------------------------------------------------------
+code_change(_OldVsn, State, _Extra) ->
+    {ok, State}.
+
+%%--------------------------------------------------------------------
+%%% Internal functions
+%%--------------------------------------------------------------------
+await_socket_ownership_transfer(AcceptTimeout) ->
+    receive
+	{socket_ownership_transfered, SocketType, Socket} ->
+	    {SocketType, Socket}
+    after AcceptTimeout ->
+	    exit(accept_socket_timeout)
+    end.
+
+handle_http_msg({_, _, Version, {_, _}, _}, #state{status = busy,
+						   mod = ModData} = State) -> 
+    handle_manager_busy(State#state{mod = 
+				    ModData#mod{http_version = Version}}),
+    {stop, normal, State}; 
+
+handle_http_msg({_, _, Version, {_, _}, _}, 
+		#state{status = blocked, mod = ModData} = State) ->
+    handle_manager_blocked(State#state{mod = 
+				       ModData#mod{http_version = Version}}),
+    {stop, normal, State}; 
+
+handle_http_msg({Method, Uri, Version, {RecordHeaders, Headers}, Body},
+		#state{status = accept, mod = ModData} = State) ->        
+    case httpd_request:validate(Method, Uri, Version) of
+	ok  ->
+	    {ok, NewModData} = 
+		httpd_request:update_mod_data(ModData, Method, Uri,
+					      Version, Headers),
+      
+	    case is_host_specified_if_required(NewModData#mod.absolute_uri,
+					       RecordHeaders, Version) of
+		true ->
+		    handle_body(State#state{headers = RecordHeaders,
+					    body = Body,
+					    mod = NewModData});
+		false ->
+		    httpd_response:send_status(ModData#mod{http_version = 
+							   Version}, 
+					       400, none),
+		    {stop, normal, State#state{response_sent = true}}
+	    end;
+	{error, {not_supported, What}} ->
+	    httpd_response:send_status(ModData#mod{http_version = Version},
+				       501, {Method, Uri, Version}),
+	    Reason = io_lib:format("Not supported: ~p~n", [What]),
+	    error_log(Reason, ModData),
+	    {stop, normal, State#state{response_sent = true}};
+	{error, {bad_request, {forbidden, URI}}} ->
+	    httpd_response:send_status(ModData#mod{http_version = Version},
+				       403, URI),
+	    Reason = io_lib:format("Forbidden URI: ~p~n", [URI]),
+	    error_log(Reason, ModData),
+	    {stop, normal, State#state{response_sent = true}};
+	{error,{bad_request, {malformed_syntax, URI}}} ->
+	    httpd_response:send_status(ModData#mod{http_version = Version},
+				       400, URI),
+	    Reason = io_lib:format("Malformed syntax in URI: ~p~n", [URI]),
+	    error_log(Reason, ModData),
+	    {stop, normal, State#state{response_sent = true}}
+    end;
+handle_http_msg({ChunkedHeaders, Body}, 
+		State = #state{headers = Headers}) ->
+    NewHeaders = http_chunk:handle_headers(Headers, ChunkedHeaders),
+    handle_response(State#state{headers = NewHeaders, body = Body});
+handle_http_msg(Body, State) ->
+    handle_response(State#state{body = Body}).
+
+handle_manager_busy(#state{mod = #mod{config_db = ConfigDB}} = State) ->
+    MaxClients = httpd_util:lookup(ConfigDB, max_clients, 150),
+    Reason = io_lib:format("heavy load (>~w processes)", [MaxClients]),
+    reject_connection(State, lists:flatten(Reason)).
+
+handle_manager_blocked(State) ->
+    Reason = "Server maintenance performed, try again later",
+    reject_connection(State, Reason).
+
+reject_connection(#state{mod = ModData} = State, Reason) ->
+    httpd_response:send_status(ModData, 503, Reason),
+    {stop, normal, State#state{response_sent = true}}. 
+
+is_host_specified_if_required(nohost, #http_request_h{host = undefined}, 
+			      "HTTP/1.1") ->
+    false;
+is_host_specified_if_required(_, _, _) ->
+    true.
+
+handle_body(#state{mod = #mod{config_db = ConfigDB}} = State) ->
+    
+    MaxHeaderSize =
+	httpd_util:lookup(ConfigDB, max_header_size, ?HTTP_MAX_HEADER_SIZE),
+    MaxBodySize = httpd_util:lookup(ConfigDB, max_body_size, nolimit),
+   
+    case handle_expect(State, MaxBodySize) of 
+	ok ->
+	    handle_body(State, MaxHeaderSize, MaxBodySize);
+	Other ->
+	    Other
+    
+    end.
+	
+handle_body(#state{headers = Headers, body = Body, mod = ModData} = State,
+	    MaxHeaderSize, MaxBodySize) ->
+    case Headers#http_request_h.'transfer-encoding' of
+	"chunked" ->
+	    case http_chunk:decode(Body, MaxBodySize, MaxHeaderSize) of
+		{Module, Function, Args} ->
+		    http_transport:setopts(ModData#mod.socket_type, 
+					   ModData#mod.socket, 
+					   [{active, once}]),
+		    {noreply, State#state{mfa = 
+					  {Module, Function, Args}}};
+		{ok, {ChunkedHeaders, NewBody}} ->
+		    NewHeaders = 
+			http_chunk:handle_headers(Headers, ChunkedHeaders),
+		    handle_response(State#state{headers = NewHeaders,
+						body = NewBody})
+	    end;
+	Encoding when list(Encoding) ->
+	    httpd_response:send_status(ModData, 501, 
+				       "Unknown Transfer-Encoding"),
+	    Reason = io_lib:format("Unknown Transfer-Encoding: ~p~n", 
+				   [Encoding]),
+	    error_log(Reason, ModData),
+	    {stop, normal, State#state{response_sent = true}};
+	_ -> 
+	    Length = 
+		list_to_integer(Headers#http_request_h.'content-length'),
+	    case ((Length =< MaxBodySize) or (MaxBodySize == nolimit)) of
+		true ->
+		    case httpd_request:whole_body(Body, Length) of 
+			{Module, Function, Args} ->
+			    http_transport:setopts(ModData#mod.socket_type, 
+						   ModData#mod.socket, 
+						   [{active, once}]),
+			    {noreply, State#state{mfa = 
+						  {Module, Function, Args}}};
+			
+			{ok, NewBody} ->
+			    handle_response(
+			      State#state{headers = Headers,
+					  body = NewBody})
+		    end;
+		false ->
+		    httpd_response:send_status(ModData, 413, "Body too big"),
+		    error_log("Body too big", ModData),
+		    {stop, normal,  State#state{response_sent = true}}
+	    end
+    end.
+
+handle_expect(#state{headers = Headers, mod = 
+		     #mod{config_db = ConfigDB} = ModData} = State, 
+	      MaxBodySize) ->
+    Length = Headers#http_request_h.'content-length',
+    case expect(Headers, ModData#mod.http_version, ConfigDB) of
+	continue when MaxBodySize > Length; MaxBodySize == nolimit ->
+	    httpd_response:send_status(ModData, 100, ""),
+	    ok;
+	continue when MaxBodySize < Length ->
+	    httpd_response:send_status(ModData, 413, "Body too big"),
+	    error_log("Body too big", ModData),
+	    {stop, normal, State#state{response_sent = true}};
+	{break, Value} ->
+	    httpd_response:send_status(ModData, 417, 
+				       "Unexpected expect value"),
+	    Reason = io_lib:format("Unexpected expect value: ~p~n", [Value]),
+	    error_log(Reason, ModData),
+	    {stop, normal,  State#state{response_sent = true}};
+	no_expect_header ->
+	    ok;
+	http_1_0_expect_header ->
+	    httpd_response:send_status(ModData, 400, 
+				       "Only HTTP/1.1 Clients "
+				       "may use the Expect Header"),
+	    error_log("Client with lower version than 1.1 tried to send"
+		      "an expect header", ModData),
+	    {stop, normal, State#state{response_sent = true}}
+    end.
+
+expect(Headers, "HTTP/1.1", _) ->
+    case Headers#http_request_h.expect of
+	"100-continue" ->
+	    continue; 
+	undefined ->
+	    no_expect_header;
+	Other ->
+	    {break, Other}
+    end;
+expect(Headers, _, ConfigDB) ->
+    case Headers#http_request_h.expect of
+	undefined ->
+	    no_expect_header;
+	_ ->
+	    case httpd_util:lookup(ConfigDB, expect, continue) of
+		continue->
+		    no_expect_header;
+		_ ->
+		    http_1_0_expect_header
+	    end
+    end.
+
+handle_response(#state{body = Body, mod = ModData, headers = Headers,
+		       max_keep_alive_request = Max} = State) when Max > 0 ->
+    {NewBody, Data} = httpd_request:body_data(Headers, Body),
+    ok = httpd_response:generate_and_send_response(
+	   ModData#mod{entity_body = NewBody}),
+    handle_next_request(State#state{response_sent = true}, Data);
+
+handle_response(#state{body = Body, headers = Headers, 
+		       mod = ModData} = State) ->
+    {NewBody, _} = httpd_request:body_data(Headers, Body),
+    ok = httpd_response:generate_and_send_response(
+	   ModData#mod{entity_body = NewBody}),
+    {stop, normal, State#state{response_sent = true}}.
+
+handle_next_request(#state{mod = #mod{connection = true} = ModData,
+			  max_keep_alive_request = Max} = State, Data) ->
+    NewModData = #mod{socket_type = ModData#mod.socket_type, 
+ 		      socket = ModData#mod.socket, 
+ 		      config_db = ModData#mod.config_db, 
+ 		      init_data = ModData#mod.init_data},
+    MaxHeaderSize =
+	httpd_util:lookup(ModData#mod.config_db, 
+			  max_header_size, ?HTTP_MAX_HEADER_SIZE),
+   
+     TmpState = State#state{mod = NewModData,
+			   mfa = {httpd_request, parse, [MaxHeaderSize]},
+			    max_keep_alive_request = decrease(Max),
+			   headers = undefined, body = undefined,
+			   response_sent = false},
+    
+    NewState = activate_request_timeout(TmpState),
+
+    case Data of
+	<<>> ->
+	    http_transport:setopts(ModData#mod.socket_type,
+				   ModData#mod.socket, [{active, once}]),
+	    {noreply, NewState};
+	_ ->
+	    handle_info({dummy, ModData#mod.socket, Data}, NewState)
+    end;
+
+handle_next_request(State, _) ->
+    {stop, normal, State}.
+
+activate_request_timeout(#state{timeout = Time} = State) ->
+    Ref = erlang:send_after(Time, self(), timeout),
+    State#state{timer = Ref}.
+
+cancel_request_timeout(#state{timer = undefined} = State) ->
+    State;
+cancel_request_timeout(#state{timer = Timer} = State) ->
+    erlang:cancel_timer(Timer),
+    receive 
+	timeout ->
+	    ok
+    after 0 ->
+	    ok
+    end,
+    State#state{timer = undefined}.
+
+decrease(N) when integer(N)->
+    N-1;
+decrease(N) ->
+    N.
+
+error_log(ReasonString, #mod{socket = Socket, socket_type = SocketType,
+			     config_db = ConfigDB, 
+			     init_data = #init_data{peername = Peername}}) ->
+    Error = lists:flatten(
+	      io_lib:format("Error reading request: ~s",[ReasonString])),
+    error_log(mod_log, SocketType, Socket, ConfigDB, Peername, Error),
+    error_log(mod_disk_log, SocketType, Socket, ConfigDB, Peername, Error).
+
+error_log(Mod, SocketType, Socket, ConfigDB, Peername, String) ->
+    Modules = httpd_util:lookup(ConfigDB, modules,
+				[mod_get, mod_head, mod_log]),
+    case lists:member(Mod, Modules) of
+	true ->
+	    Mod:error_log(SocketType, Socket, ConfigDB, Peername, String);
+	_ ->
+	    ok
+    end.
+

Added: incubator/couchdb/trunk/src/couch_inets/httpd_response.erl
URL: http://svn.apache.org/viewvc/incubator/couchdb/trunk/src/couch_inets/httpd_response.erl?rev=642432&view=auto
==============================================================================
--- incubator/couchdb/trunk/src/couch_inets/httpd_response.erl (added)
+++ incubator/couchdb/trunk/src/couch_inets/httpd_response.erl Fri Mar 28 16:32:19 2008
@@ -0,0 +1,377 @@
+%% ``The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved via the world wide web at http://www.erlang.org/.
+%% 
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights 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$
+%%
+-module(httpd_response).
+-export([generate_and_send_response/1, send_status/3, send_header/3, 
+	 send_body/3, send_chunk/3, send_final_chunk/2, split_header/2,
+	 is_disable_chunked_send/1, cache_headers/1]).
+
+-include("httpd.hrl").
+-include("http_internal.hrl").
+
+-define(VMODULE,"RESPONSE").
+
+%% If peername does not exist the client already discarded the
+%% request so we do not need to send a reply.
+generate_and_send_response(#mod{init_data =
+				#init_data{peername = {_,"unknown"}}}) ->
+    ok;
+generate_and_send_response(#mod{config_db = ConfigDB} = ModData) ->
+    Modules = httpd_util:lookup(ConfigDB,modules,
+				[mod_get, mod_head, mod_log]),
+    case traverse_modules(ModData, Modules) of
+	done ->
+	    ok;
+	{proceed, Data} ->
+	    case httpd_util:key1search(Data, status) of
+		{StatusCode, PhraseArgs, _Reason} ->
+		    send_status(ModData, StatusCode, PhraseArgs),
+		    ok;		
+		undefined ->
+		    case httpd_util:key1search(Data, response) of
+			{already_sent, _StatusCode, _Size} ->
+			    ok;
+			{response, Header, Body} -> %% New way
+			    send_response(ModData, Header, Body),
+			    ok;
+			{StatusCode, Response} ->   %% Old way
+			    send_response_old(ModData, StatusCode, Response),
+			    ok;
+			undefined ->
+			    send_status(ModData, 500, none),
+			    ok
+		    end
+	    end
+    end.
+
+
+%% traverse_modules
+
+traverse_modules(ModData,[]) ->
+    {proceed,ModData#mod.data};
+traverse_modules(ModData,[Module|Rest]) ->
+    case (catch apply(Module,do,[ModData])) of
+	{'EXIT', Reason} ->
+	    String = 
+		lists:flatten(
+		  io_lib:format("traverse exit from apply: ~p:do => ~n~p",
+				[Module, Reason])),
+	    report_error(mod_log, ModData#mod.config_db, String),
+	    report_error(mod_disk_log, ModData#mod.config_db, String),
+	    done;
+	done ->
+	    done;
+	{break,NewData} ->
+	    {proceed,NewData};
+	{proceed,NewData} ->
+	    traverse_modules(ModData#mod{data=NewData},Rest)
+    end.
+
+%% send_status %%
+
+
+send_status(ModData, 100, _PhraseArgs) ->
+    send_header(ModData, 100, [{content_length, "0"}]);
+
+send_status(#mod{socket_type = SocketType, 
+		 socket      = Socket, 
+		 config_db   = ConfigDB} = ModData, StatusCode, PhraseArgs) ->
+
+    ReasonPhrase = httpd_util:reason_phrase(StatusCode),
+    Message      = httpd_util:message(StatusCode, PhraseArgs, ConfigDB),
+    Body         = get_body(ReasonPhrase, Message),
+
+    send_header(ModData, StatusCode, [{content_type, "text/html"},
+			    {content_length, integer_to_list(length(Body))}]),
+    httpd_socket:deliver(SocketType, Socket, Body).
+
+
+get_body(ReasonPhrase, Message)->
+    "<HTML>
+       <HEAD>
+           <TITLE>"++ReasonPhrase++"</TITLE>
+      </HEAD>
+      <BODY>
+      <H1>"++ReasonPhrase++"</H1>\n"++Message++"\n</BODY>
+      </HTML>\n".
+ 
+
+send_response(ModData, Header, Body) ->
+    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(ModData, 404, "The file is not availible");
+	StatusCode ->
+	    case send_header(ModData, StatusCode, lists:keydelete(code, 1,
+							       Header)) of
+		ok ->
+		    send_body(ModData, StatusCode, Body);
+		_ ->
+		    done   
+	    end
+    end.
+
+send_header(#mod{socket_type = Type, socket = Sock, 
+		 http_version = Ver,  connection = Conn} = _ModData, 
+	    StatusCode, KeyValueTupleHeaders) ->
+    Headers = create_header(lists:map(fun transform/1, KeyValueTupleHeaders)),
+    NewVer = case {Ver, StatusCode} of
+		 {[], _} ->
+		     %% May be implicit!
+		     "HTTP/0.9";
+		 {unknown, 408} ->
+		     %% This will proably never happen! It means the
+		     %% server has timed out the request without
+		     %% receiving a version for the request!  Send the
+		     %% lowest version so to ensure that the client
+		     %% will be able to handle it, probably the
+		     %% sensible thing to do!
+		     "HTTP/0.9";
+		 {undefined,_} ->
+		     "HTTP/1.0"; %% See rfc2145 2.3 last paragraph
+		 _ ->
+		     Ver
+	     end,
+    StatusLine = [NewVer, " ", io_lib:write(StatusCode), " ",
+		  httpd_util:reason_phrase(StatusCode), ?CRLF],
+    ConnectionHeader = get_connection(Conn, NewVer),
+    Head = list_to_binary([StatusLine, Headers, ConnectionHeader , ?CRLF]),
+    httpd_socket:deliver(Type, Sock, Head).
+
+send_body(#mod{socket_type = Type, socket = Socket}, _, nobody) ->
+    httpd_socket:close(Type, Socket),
+    ok;
+
+send_body(#mod{socket_type = Type, socket = Sock}, 
+	  _StatusCode, Body) when list(Body) ->
+    ok = httpd_socket:deliver(Type, Sock, Body);
+
+send_body(#mod{socket_type = Type, socket = Sock} = ModData, 
+	  StatusCode, {Fun, Args}) ->
+    case (catch apply(Fun, Args)) of
+	close ->
+	    httpd_socket:close(Type, Sock),
+	    done;
+
+	sent ->
+	    {proceed,[{response,{already_sent, StatusCode, 
+				 httpd_util:key1search(ModData#mod.data,
+						       content_length)}}]};
+	{ok, Body} ->
+	    case httpd_socket:deliver(Type, Sock, Body) of
+		ok ->
+		    {proceed,[{response,
+			       {already_sent, StatusCode, 
+				httpd_util:key1search(ModData#mod.data,
+						      content_length)}}]};
+		_ ->
+		    done
+	    end;	    
+
+	_ ->
+	    done
+    end.
+
+split_header([$: | Value], AccName) ->
+    Name = http_util:to_lower(string:strip(AccName)),
+    {lists:reverse(Name), 
+     string:strip(string:strip(string:strip(Value, right, ?LF), right, ?CR))};
+split_header([Char | Rest], AccName) ->
+    split_header(Rest, [Char | AccName]).
+
+send_chunk(_, <<>>, _) ->
+    ok;
+send_chunk(_, [], _) ->
+    ok;
+
+send_chunk(#mod{http_version = "HTTP/1.1", 
+		socket_type = Type, socket = Sock}, Response0, false) ->
+    Response = http_chunk:encode(Response0),
+    httpd_socket:deliver(Type, Sock, Response);
+
+send_chunk(#mod{socket_type = Type, socket = Sock} = _ModData, Response, _) ->
+    httpd_socket:deliver(Type, Sock, Response).
+
+send_final_chunk(#mod{http_version = "HTTP/1.1", 
+		      socket_type = Type, socket = Sock}, false) ->
+    httpd_socket:deliver(Type, Sock, http_chunk:encode_last());
+send_final_chunk(#mod{socket_type = Type, socket = Sock}, _) ->
+    httpd_socket:close(Type, Sock).
+
+is_disable_chunked_send(Db) ->
+    httpd_util:lookup(Db, disable_chunked_transfer_encoding_send, false).
+
+%% 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(_,_) ->
+    "".
+
+cache_headers(#mod{config_db = Db}) ->
+    case httpd_util:lookup(Db, script_nocache, false) of
+	true ->
+	    Date = httpd_util:rfc1123_date(),
+	    [{"cache-control", "no-cache"},
+	     {"pragma", "no-cache"},
+	     {"expires", Date}];
+	false ->
+	    []
+    end.
+
+create_header(KeyValueTupleHeaders) ->
+    NewHeaders = add_default_headers([{"date", httpd_util:rfc1123_date()},
+				      {"content-type", "text/html"},
+				      {"server", ?SERVER_SOFTWARE}], 
+				     KeyValueTupleHeaders),
+    lists:map(fun fix_header/1, NewHeaders).
+
+fix_header({Key0, Value}) ->
+    %% make sure first letter is capital
+    Words1 = string:tokens(Key0, "-"),
+    Words2 = upify(Words1, []),
+    Key    = new_key(Words2),
+    Key ++ ": " ++ Value ++ ?CRLF .
+
+new_key([]) ->
+    "";
+new_key([W]) ->
+    W;
+new_key([W1,W2]) ->
+    W1 ++ "-" ++ W2;
+new_key([W|R]) ->
+    W ++ "-" ++ new_key(R).
+    
+upify([], Acc) ->
+    lists:reverse(Acc);
+upify([Key|Rest], Acc) ->
+    upify(Rest, [upify2(Key)|Acc]).
+
+upify2([C|Rest]) when C >= $a, C =< $z ->
+    [C-($a-$A)|Rest];
+upify2(Str) ->
+    Str.
+
+add_default_headers([], Headers) ->
+    Headers;
+
+add_default_headers([Header = {Default, _} | Defaults], Headers) ->
+    case lists:keysearch(Default, 1, Headers) of
+	{value, _} ->
+	    add_default_headers(Defaults, Headers);
+	_ ->
+	    add_default_headers(Defaults, [Header | Headers])
+    end.
+
+transform({content_type, Value}) ->
+    {"content-type", Value};
+transform({accept_ranges, Value}) ->
+     {"accept-ranges", Value};
+transform({cache_control, Value}) ->
+     {"cache-control",Value};
+transform({transfer_encoding, Value}) ->
+    {"transfer-encoding", Value};
+transform({content_encoding, Value}) ->
+    {"content-encoding", Value};
+transform({content_language, Value}) ->
+    {"content-language", Value};
+transform({retry_after, Value}) ->
+    {"retry-after", Value};
+transform({content_location, Value}) ->
+    {"Content-Location:", Value};
+transform({content_length, Value}) ->
+    {"content-length", Value};
+transform({content_MD5, Value}) ->
+    {"content-md5", Value};
+transform({content_range, Value}) ->
+    {"content-range", Value};
+transform({last_modified, Value}) ->
+    {"last-modified", Value};
+transform({Field, Value}) when is_atom(Field) ->
+    {atom_to_list(Field), Value};
+transform({Field, Value}) when is_list(Field) ->
+    {Field, Value}.
+
+%%----------------------------------------------------------------------
+%% 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{method      = "HEAD"} = ModData,
+		  StatusCode, Response) ->
+    NewResponse = lists:flatten(Response),
+    
+    case httpd_util:split(NewResponse, [?CR, ?LF, ?CR, ?LF],2) of
+	{ok, [Head, Body]} ->
+	    {ok, NewHead} = handle_headers(string:tokens(Head, [?CR,?LF]), []),
+	    send_header(ModData, StatusCode, [{content_length,
+					    content_length(Body)} | NewHead]);
+	{ok, [NewResponse]} ->
+	    send_header(ModData, StatusCode, [{content_length,
+					       content_length(NewResponse)}]);
+	_Error ->
+	    send_status(ModData, 500, "Internal Server Error")
+    end;
+
+send_response_old(#mod{socket_type = Type, 
+		       socket      = Sock} = ModData,
+		  StatusCode, Response) ->
+
+    NewResponse = lists:flatten(Response),
+    
+    case httpd_util:split(NewResponse, [?CR, ?LF, ?CR, ?LF], 2) of
+	{ok, [Head, Body]} ->
+	    {ok, NewHead} = handle_headers(string:tokens(Head, 
+							 [?CR,?LF]), []),
+	    send_header(ModData, StatusCode, [{content_length,
+					       content_length(Body)} | 
+					      NewHead]),
+	    httpd_socket:deliver(Type, Sock, Body);
+	{ok, [NewResponse]} ->
+	    send_header(ModData, StatusCode, [{content_length,
+					       content_length(NewResponse)}]),
+	    httpd_socket:deliver(Type, Sock, NewResponse);
+
+	{error, _Reason} ->
+	    send_status(ModData, 500, "Internal Server Error")
+    end.
+
+content_length(Body)->
+    integer_to_list(httpd_util:flatlength(Body)).
+
+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.
+
+handle_headers([], NewHeaders) ->
+    {ok, NewHeaders};
+
+handle_headers([Header | Headers], NewHeaders) -> 
+    {FieldName, FieldValue} = split_header(Header, []),
+    handle_headers(Headers, 
+		   [{FieldName, FieldValue}| NewHeaders]).
+    	

Added: incubator/couchdb/trunk/src/couch_inets/httpd_script_env.erl
URL: http://svn.apache.org/viewvc/incubator/couchdb/trunk/src/couch_inets/httpd_script_env.erl?rev=642432&view=auto
==============================================================================
--- incubator/couchdb/trunk/src/couch_inets/httpd_script_env.erl (added)
+++ incubator/couchdb/trunk/src/couch_inets/httpd_script_env.erl Fri Mar 28 16:32:19 2008
@@ -0,0 +1,141 @@
+%% ``The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved via the world wide web at http://www.erlang.org/.
+%% 
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights 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$
+%%
+
+-module(httpd_script_env).
+
+-export([create_env/3]).
+
+-include("httpd.hrl").
+
+%%%=========================================================================
+%%%  Internal application API 
+%%%=========================================================================
+%%--------------------------------------------------------------------------
+%% create_env(ScriptType, ModData, ScriptElements) -> [{EnvVariable, Value}]
+%%                           
+%%	ScriptType = cgi | esi   
+%%	ModData = #mod{}
+%%      ScriptElements = [{Element, Value}]
+%%      Element = path_info | query_string | entity_body
+%%      Value = term()
+%%      EnvVariable = string() - cgi | atom() - esi
+%%
+%% Description: Creates a list of cgi/esi environment variables and
+%% there values.
+%%--------------------------------------------------------------------------
+create_env(ScriptType, ModData, ScriptElements) ->
+    create_basic_elements(ScriptType, ModData) 
+	++ create_http_header_elements(ScriptType, ModData#mod.parsed_header)
+	++ create_script_elements(ScriptType, ModData, ScriptElements)
+	++ create_mod_interaction_elements(ScriptType, ModData).
+
+%%%========================================================================
+%%% Internal functions
+%%%========================================================================
+create_basic_elements(esi, ModData) ->
+    {_, RemoteAddr} = (ModData#mod.init_data)#init_data.peername,
+    [{server_software, ?SERVER_SOFTWARE},
+     {server_name, (ModData#mod.init_data)#init_data.resolve},
+     {gateway_interface,?GATEWAY_INTERFACE},
+     {server_protocol, ?SERVER_PROTOCOL},
+     {server_port, httpd_util:lookup(ModData#mod.config_db,port,80)},
+     {request_method, ModData#mod.method},
+     {remote_addr, RemoteAddr},
+     {script_name, ModData#mod.request_uri}];
+
+create_basic_elements(cgi, ModData) ->
+    {_, RemoteAddr} = (ModData#mod.init_data)#init_data.peername,
+    [{"SERVER_SOFTWARE",?SERVER_SOFTWARE},
+     {"SERVER_NAME", (ModData#mod.init_data)#init_data.resolve},
+     {"GATEWAY_INTERFACE",?GATEWAY_INTERFACE},
+     {"SERVER_PROTOCOL",?SERVER_PROTOCOL},
+     {"SERVER_PORT",
+      integer_to_list(httpd_util:lookup(
+			ModData#mod.config_db, port, 80))},
+     {"REQUEST_METHOD", ModData#mod.method},
+     {"REMOTE_ADDR", RemoteAddr},
+     {"SCRIPT_NAME", ModData#mod.request_uri}].
+
+create_http_header_elements(ScriptType, Headers) ->
+    create_http_header_elements(ScriptType, Headers, []).
+
+create_http_header_elements(_, [], Acc) ->
+    Acc;
+create_http_header_elements(ScriptType, [{Name, [Value | _] = Values } | 
+					     Headers], Acc) when list(Value) ->
+    NewName = lists:map(fun(X) -> if X == $- -> $_; true -> X end end, Name),
+    Element = http_env_element(ScriptType, NewName, multi_value(Values)),
+    create_http_header_elements(ScriptType, Headers, [Element | Acc]);
+
+create_http_header_elements(ScriptType, [{Name, Value} | Headers], Acc) 
+  when list(Value) ->
+    {ok, NewName, _} = regexp:gsub(Name,"-","_"),
+    Element = http_env_element(ScriptType, NewName, Value),
+    create_http_header_elements(ScriptType, Headers, [Element | Acc]).
+
+http_env_element(cgi, VarName, Value)  ->
+    {"HTTP_"++ http_util:to_upper(VarName), Value};
+http_env_element(esi, VarName, Value)  ->
+    {list_to_atom("http_"++ http_util:to_lower(VarName)), Value}.
+
+multi_value([]) ->
+  [];
+multi_value([Value]) ->
+  Value;
+multi_value([Value | Rest]) ->
+  Value ++ ", " ++ multi_value(Rest).
+
+create_script_elements(ScriptType, ModData, ScriptElements) ->
+    lists:flatmap(fun({Element, Data}) ->
+			  create_script_elements(ScriptType,
+						 Element,
+						 Data, ModData)
+		  end, ScriptElements).
+
+create_script_elements(esi, query_string, QueryString, _) ->
+    [{query_string, QueryString}];
+create_script_elements(cgi, query_string, QueryString, _) ->
+    [{"QUERY_STRING", QueryString}];
+create_script_elements(esi, path_info, PathInfo, ModData) ->
+    Aliases = httpd_util:multi_lookup(ModData#mod.config_db, alias),
+    {_,PathTranslated,_} = 
+	mod_alias:real_name(ModData#mod.config_db, PathInfo,
+			    Aliases),
+    [{path_info, PathInfo},
+     {path_translated, PathTranslated}];
+create_script_elements(cgi, path_info, PathInfo, ModData) ->
+    Aliases = httpd_util:multi_lookup(ModData#mod.config_db, alias),
+    {_,PathTranslated,_} = 
+	mod_alias:real_name(ModData#mod.config_db, PathInfo,
+			    Aliases),
+    [{"PATH_INFO", PathInfo},
+     {"PATH_TRANSLATED", PathTranslated}];
+create_script_elements(esi, entity_body, Body, _) ->
+    [{content_length, httpd_util:flatlength(Body)}]; 
+create_script_elements(cgi, entity_body, Body, _) ->
+    [{"CONTENT_LENGTH", httpd_util:flatlength(Body)}]; 
+create_script_elements(_, _, _, _) ->
+    [].
+
+create_mod_interaction_elements(_, ModData)->
+    case httpd_util:key1search(ModData#mod.data, remote_user) of
+	undefined ->
+	    [];
+	RemoteUser ->
+	    [{remote_user, RemoteUser}]
+    end.

Added: incubator/couchdb/trunk/src/couch_inets/httpd_socket.erl
URL: http://svn.apache.org/viewvc/incubator/couchdb/trunk/src/couch_inets/httpd_socket.erl?rev=642432&view=auto
==============================================================================
--- incubator/couchdb/trunk/src/couch_inets/httpd_socket.erl (added)
+++ incubator/couchdb/trunk/src/couch_inets/httpd_socket.erl Fri Mar 28 16:32:19 2008
@@ -0,0 +1,62 @@
+%% ``The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved via the world wide web at http://www.erlang.org/.
+%% 
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights 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$
+%%
+-module(httpd_socket).
+
+%% API  (document close ?)
+-export([deliver/3,  peername/2, resolve/0,  close/2]).
+
+-include("httpd.hrl").
+
+-define(VMODULE,"SOCKET").
+-include_lib("kernel/include/inet.hrl").
+
+deliver(SocketType, Socket, IOListOrBinary)  ->
+    case http_transport:send(SocketType, Socket, IOListOrBinary) of
+	{error, _Reason} ->
+	    (catch close(SocketType, Socket)), 
+	    socket_closed;
+	_ ->
+	    ok
+    end.
+
+peername(SocketType, Socket) ->
+    http_transport:peername(SocketType, Socket).
+
+resolve() ->
+   http_transport:resolve().
+
+close(SocketType, Socket) ->
+    close_sleep(SocketType, 1000),
+    Res = 
+	case (catch http_transport:close(SocketType, Socket)) of
+	    ok ->                  ok;
+	    {error,Reason} ->      {error,Reason};
+	    {'EXIT',{noproc,_}} -> {error,closed};
+	    {'EXIT',Reason} ->     {error,Reason};
+	    Otherwise ->           {error,Otherwise}
+	end,
+    Res.
+
+%% Workaround for ssl problem when ssl does not deliver the message
+%% sent prior to the close before the close signal.
+close_sleep({ssl, _}, Time) ->
+    sleep(Time);
+close_sleep(_, _) -> 
+    ok.
+
+sleep(T) -> receive after T -> ok end.

Added: incubator/couchdb/trunk/src/couch_inets/httpd_sup.erl
URL: http://svn.apache.org/viewvc/incubator/couchdb/trunk/src/couch_inets/httpd_sup.erl?rev=642432&view=auto
==============================================================================
--- incubator/couchdb/trunk/src/couch_inets/httpd_sup.erl (added)
+++ incubator/couchdb/trunk/src/couch_inets/httpd_sup.erl Fri Mar 28 16:32:19 2008
@@ -0,0 +1,137 @@
+%% ``The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved via the world wide web at http://www.erlang.org/.
+%% 
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights 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$
+%%
+%%----------------------------------------------------------------------
+%% Purpose: The top supervisor for the http server (httpd) hangs under 
+%%          inets_sup.
+%%----------------------------------------------------------------------
+
+-module(httpd_sup).
+
+-behaviour(supervisor).
+
+%% API
+-export([start_link/1]).
+-export([start_child/1, stop_child/2]).
+
+%% Supervisor callback
+-export([init/1]).
+
+
+%%%=========================================================================
+%%%  API
+%%%=========================================================================
+start_link(HttpdServices) ->
+    supervisor:start_link({local, ?MODULE}, ?MODULE, [HttpdServices]).
+
+start_child(ConfigFile) ->
+    {ok, Spec} = httpd_child_spec(ConfigFile, 15000, []),
+    supervisor:start_child(?MODULE, Spec).
+    
+stop_child(Addr, Port) ->
+    Name = {httpd_instance_sup, Addr, Port},
+    case supervisor:terminate_child(?MODULE, Name) of
+        ok ->
+            supervisor:delete_child(?MODULE, Name);
+        Error ->
+            Error
+    end.
+    
+%%%=========================================================================
+%%%  Supervisor callback
+%%%=========================================================================
+init([HttpdServices]) ->
+    RestartStrategy = one_for_one,
+    MaxR = 10,
+    MaxT = 3600,
+    Children = child_spec(HttpdServices, []),
+    {ok, {{RestartStrategy, MaxR, MaxT}, Children}}.
+
+%%%=========================================================================
+%%%  Internal functions
+%%%=========================================================================
+%% The format of the httpd service is:
+%% httpd_service() -> {httpd,httpd()}
+%% httpd()         -> [httpd_config()] | file()
+%% httpd_config()  -> {file,file()} |
+%%                    {debug,debug()} |
+%%                    {accept_timeout,integer()}
+%% debug()         -> disable | [debug_options()]
+%% debug_options() -> {all_functions,modules()} | 
+%%                    {exported_functions,modules()} |
+%%                    {disable,modules()}
+%% modules()       -> [atom()]
+child_spec([], Acc) ->
+    Acc;
+child_spec([{httpd, HttpdService} | Rest], Acc) ->
+    NewHttpdService = mk_tuple_list(HttpdService),
+    %%    Acc2 = child_spec2(NewHttpdService,Acc),
+    NewAcc=
+	case catch child_spec2(NewHttpdService) of
+	    {ok,Acc2} ->
+		[Acc2|Acc];
+	    {error,Reason} ->
+		error_msg("failed to create child spec for ~n~p~ndue to: ~p",
+			  [HttpdService,Reason]),
+%		exit({error,Reason})
+		Acc
+	end,
+    child_spec(Rest,NewAcc).
+
+child_spec2(HttpdService) ->
+    Debug = http_util:key1search(HttpdService,debug,[]),
+    AcceptTimeout = http_util:key1search(HttpdService,accept_timeout,15000),
+    ConfigFile =
+    case http_util:key1search(HttpdService,file) of
+	undefined -> throw({error,{mandatory_conf_file_missed}});
+	File -> File
+    end,
+    httpd_util:valid_options(Debug,AcceptTimeout,ConfigFile),
+    httpd_child_spec(ConfigFile,AcceptTimeout,Debug).
+
+
+httpd_child_spec(ConfigFile,AcceptTimeout,Debug) ->
+    case httpd_conf:load(ConfigFile) of
+	{ok, ConfigList} ->
+	    Port = httpd_util:key1search(ConfigList, port, 80),
+	    Addr = httpd_util:key1search(ConfigList, bind_address),
+	    {ok, httpd_child_spec(ConfigFile, AcceptTimeout, 
+				  Debug, Addr, Port)};
+	Error ->
+	    Error
+    end.
+
+httpd_child_spec(ConfigFile, AcceptTimeout, Debug, Addr, Port) ->
+    Name = {httpd_instance_sup, Addr, Port},
+    StartFunc = {httpd_instance_sup, start_link,
+		 [ConfigFile,AcceptTimeout,Debug]},
+    Restart = permanent, 
+    Shutdown = infinity,
+    Modules = [httpd_instance_sup],
+    Type = supervisor,
+    {Name, StartFunc, Restart, Shutdown, Type, Modules}.
+
+
+mk_tuple_list([]) ->
+    [];
+mk_tuple_list([H={_,_}|T]) ->
+    [H|mk_tuple_list(T)];
+mk_tuple_list(F) ->
+    [{file,F}].
+
+error_msg(F, A) ->
+    error_logger:error_msg(F ++ "~n", A).