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).