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 [6/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/http_uri.erl
URL: http://svn.apache.org/viewvc/incubator/couchdb/trunk/src/couch_inets/http_uri.erl?rev=642432&view=auto
==============================================================================
--- incubator/couchdb/trunk/src/couch_inets/http_uri.erl (added)
+++ incubator/couchdb/trunk/src/couch_inets/http_uri.erl Fri Mar 28 16:32:19 2008
@@ -0,0 +1,113 @@
+% ``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(http_uri).
+
+-export([parse/1]).
+
+%%%=========================================================================
+%%%  API
+%%%=========================================================================
+parse(AbsURI) ->
+    case parse_scheme(AbsURI) of
+	{error, Reason} ->
+	    {error, Reason};
+	{Scheme, Rest} ->
+	    case (catch parse_uri_rest(Scheme, Rest)) of
+		{UserInfo, Host, Port, Path, Query} ->
+		    {Scheme, UserInfo, Host, Port, Path, Query};
+		_  ->
+		    {error, {malformed_url, AbsURI}}    
+	    end
+    end.
+
+%%%========================================================================
+%%% Internal functions
+%%%========================================================================
+parse_scheme(AbsURI) ->
+    case split_uri(AbsURI, ":", {error, no_scheme}, 1, 1) of
+	{error, no_scheme} ->
+	    {error, no_scheme};
+	{StrScheme, Rest} ->
+	    case list_to_atom(http_util:to_lower(StrScheme)) of
+		Scheme when Scheme == http; Scheme == https ->
+		    {Scheme, Rest};
+		Scheme ->
+		    {error, {not_supported_scheme, Scheme}}
+	    end
+    end.
+
+parse_uri_rest(Scheme, "//" ++ URIPart) ->
+
+    {Authority, PathQuery} = 
+	case split_uri(URIPart, "/", URIPart, 1, 0) of
+	    Split = {_, _} ->
+		Split;
+	    URIPart ->
+		case split_uri(URIPart, "\\?", URIPart, 1, 0) of
+		    Split = {_, _} ->
+			Split;
+		    URIPart ->
+			{URIPart,""}
+		end
+	end,
+    
+    {UserInfo, HostPort} = split_uri(Authority, "@", {"", Authority}, 1, 1),
+    {Host, Port} = parse_host_port(Scheme, HostPort),
+    {Path, Query} = parse_path_query(PathQuery),
+    {UserInfo, Host, Port, Path, Query}.
+
+
+parse_path_query(PathQuery) ->
+    {Path, Query} =  split_uri(PathQuery, "\\?", {PathQuery, ""}, 1, 0),
+    {path(Path), Query}.
+    
+
+parse_host_port(Scheme,"[" ++ HostPort) -> %ipv6
+    DefaultPort = default_port(Scheme),
+    {Host, ColonPort} = split_uri(HostPort, "\\]", {HostPort, ""}, 1, 1),
+    {_, Port} = split_uri(ColonPort, ":", {"", DefaultPort}, 0, 1),
+    {Host, int_port(Port)};
+
+parse_host_port(Scheme, HostPort) ->
+    DefaultPort = default_port(Scheme),
+    {Host, Port} = split_uri(HostPort, ":", {HostPort, DefaultPort}, 1, 1),
+    {Host, int_port(Port)}.
+    
+split_uri(UriPart, SplitChar, NoMatchResult, SkipLeft, SkipRight) ->
+    case regexp:first_match(UriPart, SplitChar) of
+	{match, Match, _} ->
+	    {string:substr(UriPart, 1, Match - SkipLeft),
+	     string:substr(UriPart, Match + SkipRight, length(UriPart))}; 
+	nomatch ->
+	    NoMatchResult
+    end.
+
+default_port(http) ->
+    80;
+default_port(https) ->
+    443.
+
+int_port(Port) when is_integer(Port) ->
+    Port;
+int_port(Port) when is_list(Port) ->
+    list_to_integer(Port).
+
+path("") ->
+    "/";
+path(Path) ->
+    Path.

Added: incubator/couchdb/trunk/src/couch_inets/http_util.erl
URL: http://svn.apache.org/viewvc/incubator/couchdb/trunk/src/couch_inets/http_util.erl?rev=642432&view=auto
==============================================================================
--- incubator/couchdb/trunk/src/couch_inets/http_util.erl (added)
+++ incubator/couchdb/trunk/src/couch_inets/http_util.erl Fri Mar 28 16:32:19 2008
@@ -0,0 +1,171 @@
+%% ``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(http_util).
+
+-export([key1search/2, key1search/3,
+	 to_upper/1, to_lower/1, convert_netscapecookie_date/1,
+	 hexlist_to_integer/1, integer_to_hexlist/1, 
+	 convert_month/1, is_hostname/1]).
+
+%%%=========================================================================
+%%%  Internal application API
+%%%=========================================================================
+key1search(TupleList,Key) ->
+    key1search(TupleList,Key,undefined).
+
+key1search(TupleList,Key,Undefined) ->
+    case lists:keysearch(Key,1,TupleList) of
+	{value,{Key,Value}} ->
+	    Value;
+	false ->
+	    Undefined
+    end.
+
+to_upper(Str) ->
+    to_upper(Str, []).
+
+to_upper([C|Cs], Acc) when C >= $a, C =< $z ->
+    to_upper(Cs, [C-($a-$A)| Acc]);
+to_upper([C|Cs], Acc) ->
+    to_upper(Cs, [C | Acc]);
+to_upper([], Acc) ->
+    lists:reverse(Acc).
+
+to_lower(Str) ->
+    to_lower(Str, []).
+to_lower([C|Cs], Acc) when C >= $A, C =< $Z ->
+    to_lower(Cs, [C+($a-$A)| Acc]);
+to_lower([C|Cs], Acc) ->
+    to_lower(Cs, [C| Acc]);
+to_lower([], Acc) ->
+    lists:reverse(Acc).
+
+convert_netscapecookie_date([_D,_A,_Y, $,, _SP,
+			     D1,D2,_DA,
+			     M,O,N,_DA,
+			     Y1,Y2,Y3,Y4,_SP,
+			     H1,H2,_Col,
+			     M1,M2,_Col,
+			     S1,S2|_Rest]) -> 
+    Year=list_to_integer([Y1,Y2,Y3,Y4]),
+    Day=list_to_integer([D1,D2]),
+    Month=convert_month([M,O,N]),
+    Hour=list_to_integer([H1,H2]),
+    Min=list_to_integer([M1,M2]),
+    Sec=list_to_integer([S1,S2]),
+    {{Year,Month,Day},{Hour,Min,Sec}};
+
+convert_netscapecookie_date([_D,_A,_Y, _SP,
+			     D1,D2,_DA,
+			     M,O,N,_DA,
+			     Y1,Y2,Y3,Y4,_SP,
+			     H1,H2,_Col,
+			     M1,M2,_Col,
+			     S1,S2|_Rest]) -> 
+    Year=list_to_integer([Y1,Y2,Y3,Y4]),
+    Day=list_to_integer([D1,D2]),
+    Month=convert_month([M,O,N]),
+    Hour=list_to_integer([H1,H2]),
+    Min=list_to_integer([M1,M2]),
+    Sec=list_to_integer([S1,S2]),
+    {{Year,Month,Day},{Hour,Min,Sec}}.
+
+hexlist_to_integer([])->
+    empty;
+%%When the string only contains one value its eaasy done.
+%% 0-9
+hexlist_to_integer([Size]) when Size >= 48 , Size =< 57 ->
+   Size - 48;
+%% A-F
+hexlist_to_integer([Size]) when Size >= 65 , Size =< 70 ->
+    Size - 55;
+%% a-f
+hexlist_to_integer([Size]) when Size >= 97 , Size =< 102 ->
+    Size - 87;
+hexlist_to_integer([_Size]) ->
+    not_a_num;
+
+hexlist_to_integer(Size) ->
+    Len = string:span(Size, "1234567890abcdefABCDEF"),
+    hexlist_to_integer2(Size, 16 bsl (4 *(Len-2)),0).
+
+integer_to_hexlist(Num)->
+    integer_to_hexlist(Num, get_size(Num), []).
+
+convert_month("Jan") -> 1;
+convert_month("Feb") -> 2;
+convert_month("Mar") -> 3; 
+convert_month("Apr") -> 4;
+convert_month("May") -> 5;
+convert_month("Jun") -> 6;
+convert_month("Jul") -> 7;
+convert_month("Aug") -> 8;
+convert_month("Sep") -> 9;
+convert_month("Oct") -> 10;
+convert_month("Nov") -> 11;
+convert_month("Dec") -> 12.
+
+is_hostname(Dest) ->
+    inet_parse:domain(Dest).
+
+%%%========================================================================
+%%% Internal functions
+%%%========================================================================
+hexlist_to_integer2([],_Pos,Sum)->
+    Sum;
+hexlist_to_integer2([HexVal | HexString], Pos, Sum) 
+  when HexVal >= 48, HexVal =< 57 ->
+    hexlist_to_integer2(HexString, Pos bsr 4, Sum + ((HexVal-48) * Pos));
+
+hexlist_to_integer2([HexVal | HexString], Pos, Sum) 
+  when HexVal >= 65, HexVal =<70 ->
+    hexlist_to_integer2(HexString, Pos bsr 4, Sum + ((HexVal-55) * Pos));
+
+hexlist_to_integer2([HexVal | HexString], Pos, Sum)
+  when HexVal>=97, HexVal=<102 ->
+    hexlist_to_integer2(HexString, Pos bsr 4, Sum + ((HexVal-87) * Pos));
+
+hexlist_to_integer2(_AfterHexString, _Pos, Sum)->
+    Sum.
+
+integer_to_hexlist(Num, Pot, Res) when Pot<0 ->
+    convert_to_ascii([Num | Res]);
+
+integer_to_hexlist(Num,Pot,Res) ->
+    Position = (16 bsl (Pot*4)),
+    PosVal = Num div Position,
+    integer_to_hexlist(Num - (PosVal*Position), Pot-1, [PosVal | Res]).
+
+get_size(Num)->
+    get_size(Num, 0).
+
+get_size(Num, Pot) when Num < (16 bsl(Pot *4))  ->
+    Pot-1;
+
+get_size(Num, Pot) ->
+    get_size(Num, Pot+1).
+
+convert_to_ascii(RevesedNum) ->
+    convert_to_ascii(RevesedNum, []).
+
+convert_to_ascii([], Num)->
+    Num;
+convert_to_ascii([Num | Reversed], Number) when Num > -1, Num < 10 ->
+    convert_to_ascii(Reversed, [Num + 48 | Number]);
+convert_to_ascii([Num | Reversed], Number) when Num > 9, Num < 16 ->
+    convert_to_ascii(Reversed, [Num + 55 | Number]).

Added: incubator/couchdb/trunk/src/couch_inets/httpc_handler.erl
URL: http://svn.apache.org/viewvc/incubator/couchdb/trunk/src/couch_inets/httpc_handler.erl?rev=642432&view=auto
==============================================================================
--- incubator/couchdb/trunk/src/couch_inets/httpc_handler.erl (added)
+++ incubator/couchdb/trunk/src/couch_inets/httpc_handler.erl Fri Mar 28 16:32:19 2008
@@ -0,0 +1,953 @@
+% ``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(httpc_handler).
+
+-behaviour(gen_server).
+
+-include("httpc_internal.hrl").
+-include("http_internal.hrl").
+
+%%--------------------------------------------------------------------
+%% Application API
+-export([start_link/2, send/2, cancel/2, stream/3]).
+
+%% gen_server callbacks
+-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
+	 terminate/2, code_change/3]).
+
+-record(timers, {request_timers = [], % [ref()]
+		 pipeline_timer % ref()
+	      }).
+
+-record(state, {request,        % #request{}
+                session,        % #tcp_session{} 
+                status_line,    % {Version, StatusCode, ReasonPharse}
+                headers,        % #http_response_h{}
+                body,           % binary()
+                mfa,            % {Moduel, Function, Args}
+                pipeline = queue:new(),% queue() 
+		status = new,          % new | pipeline | close | ssl_tunnel
+		canceled = [],	       % [RequestId]
+                max_header_size = nolimit,   % nolimit | integer() 
+                max_body_size = nolimit,     % nolimit | integer()
+		options,                     % #options{}
+		timers = #timers{}           % #timers{}
+               }).
+
+%%====================================================================
+%% External functions
+%%====================================================================
+%%--------------------------------------------------------------------
+%% Function: start() -> {ok, Pid}
+%%
+%% Description: Starts a http-request handler process. Intended to be
+%% called by the httpc manager process. 
+%%
+%% Note: Uses proc_lib and gen_server:enter_loop so that waiting
+%% for gen_tcp:connect to timeout in init/1 will not
+%% block the httpc manager process in odd cases such as trying to call
+%% a server that does not exist. (See OTP-6735) The only API function
+%% sending messages to the handler process that can be called before
+%% init has compleated is cancel and that is not a problem! (Send and
+%% stream will not be called before the first request has been sent and
+%% the reply or part of it has arrived.)
+%%--------------------------------------------------------------------
+start_link(Request, ProxyOptions) ->
+    {ok, proc_lib:spawn_link(?MODULE, init, [[Request, ProxyOptions]])}.
+
+%%--------------------------------------------------------------------
+%% Function: send(Request, Pid) -> ok 
+%%	Request = #request{}
+%%      Pid = pid() - the pid of the http-request handler process.
+%%
+%% Description: Uses this handlers session to send a request. Intended
+%% to be called by the httpc manager process.
+%%--------------------------------------------------------------------
+send(Request, Pid) ->
+    call(Request, Pid, 5000).
+
+%%--------------------------------------------------------------------
+%% Function: cancel(RequestId, Pid) -> ok
+%%	RequestId = ref()
+%%      Pid = pid() -  the pid of the http-request handler process.
+%%
+%% Description: Cancels a request. Intended to be called by the httpc
+%% manager process.
+%%--------------------------------------------------------------------
+cancel(RequestId, Pid) ->
+    cast({cancel, RequestId}, Pid).
+
+%%--------------------------------------------------------------------
+%% Function: stream(BodyPart, Request, Code) -> _
+%%	BodyPart = binary()
+%%      Request = #request{}
+%%      Code = integer()
+%%
+%% Description: Stream the HTTP body to the caller process (client) 
+%%              or to a file. Note that the data that has been stream
+%%              does not have to be saved. (We do not want to use up
+%%              memory in vain.)
+%%--------------------------------------------------------------------
+%% Request should not be streamed
+stream(BodyPart, Request = #request{stream = none}, _) ->
+    {BodyPart, Request};
+
+stream(BodyPart, Request = #request{stream = self}, 200) -> % Stream to caller
+    httpc_response:send(Request#request.from, 
+			{Request#request.id, stream, BodyPart}),
+    {<<>>, Request};
+
+stream(BodyPart, Request = #request{stream = Filename}, 200)
+  when is_list(Filename) -> % Stream to file
+    case file:open(Filename, [write, raw, append, delayed_write]) of
+	{ok, Fd} ->
+	    stream(BodyPart, Request#request{stream = Fd}, 200);
+	{error, Reason} ->
+	    exit({stream_to_file_failed, Reason})
+    end;
+
+stream(BodyPart, Request = #request{stream = Fd}, 200) -> % Stream to file
+    case file:write(Fd, BodyPart) of
+	ok ->
+	    {<<>>, Request};
+	{error, Reason} ->
+	    exit({stream_to_file_failed, Reason})
+    end;
+stream(BodyPart, Request,_) -> % only 200 responses can be streamed
+    {BodyPart, Request}.
+
+%%====================================================================
+%% Server functions
+%%====================================================================
+
+%%--------------------------------------------------------------------
+%% Function: init([Request, Session]) -> {ok, State} | 
+%%                       {ok, State, Timeout} | ignore |{stop, Reason}
+%%
+%% Description: Initiates the httpc_handler process 
+%%
+%% Note: The init function may not fail, that will kill the
+%% httpc_manager process. We could make the httpc_manager more comlex
+%% but we do not want that so errors will be handled by the process
+%% sending an init_error message to itself.
+%%--------------------------------------------------------------------
+init([Request, Options]) ->
+    process_flag(trap_exit, true),
+    handle_verbose(Options#options.verbose),
+    Address = handle_proxy(Request#request.address, Options#options.proxy),
+    {ok, State} =
+	case {Address /= Request#request.address, Request#request.scheme} of
+	    {true, https} ->
+		Error = https_through_proxy_is_not_currently_supported,
+		self() ! {init_error, 
+			  Error, httpc_response:error(Request, Error)},
+		{ok, #state{request = Request, options = Options,
+			    status = ssl_tunnel}};
+	    %% This is what we should do if and when ssl supports 
+	    %% "socket upgrading"
+	    %%send_ssl_tunnel_request(Address, Request,
+	    %%		    #state{options = Options,
+	    %%		   status = ssl_tunnel});
+	    {_, _} ->
+		send_first_request(Address, Request, #state{options = Options})
+	end,
+    gen_server:enter_loop(?MODULE, [], State).
+
+%%--------------------------------------------------------------------
+%% Function: handle_call(Request, From, State) -> {reply, Reply, State} |
+%%          {reply, Reply, State, Timeout} |
+%%          {noreply, State}               |
+%%          {noreply, State, Timeout}      |
+%%          {stop, Reason, Reply, State}   | (terminate/2 is called)
+%%          {stop, Reason, State}            (terminate/2 is called)
+%% Description: Handling call messages
+%%--------------------------------------------------------------------
+handle_call(Request, _, State = #state{session = Session =
+				       #tcp_session{socket = Socket},
+				       timers = Timers,
+				       options = Options}) ->
+    Address = handle_proxy(Request#request.address, Options#options.proxy),
+
+    case httpc_request:send(Address, Request, Socket) of
+        ok ->
+	    %% Activate the request time out for the new request
+	    NewState = activate_request_timeout(State#state{request =
+							     Request}),
+
+	    ClientClose = httpc_request:is_client_closing(
+			    Request#request.headers),
+            case State#state.request of
+                #request{} -> %% Old request no yet finished
+		    %% Make sure to use the new value of timers in state
+		    NewTimers = NewState#state.timers,
+                    NewPipeline = queue:in(Request, State#state.pipeline),
+		    NewSession = 
+			Session#tcp_session{pipeline_length = 
+					    %% Queue + current
+					    queue:len(NewPipeline) + 1,
+					    client_close = ClientClose},
+		    httpc_manager:insert_session(NewSession),
+                    {reply, ok, State#state{pipeline = NewPipeline,
+					    session = NewSession,
+					    timers = NewTimers}};
+		undefined ->
+		    %% Note: tcp-message reciving has already been
+		    %% activated by handle_pipeline/2. Also
+		    %% the parsing-function #state.mfa is initiated
+		    %% by handle_pipeline/2.
+		    cancel_timer(Timers#timers.pipeline_timer, 
+				 timeout_pipeline),
+		    NewSession = 
+			Session#tcp_session{pipeline_length = 1,
+					    client_close = ClientClose},
+		    httpc_manager:insert_session(NewSession),
+		    {reply, ok, 
+		     NewState#state{request = Request,
+				    session = NewSession,
+				    timers = 
+				    Timers#timers{pipeline_timer =
+						  undefined}}}
+	    end;
+	{error, Reason} ->
+	    {reply, {pipline_failed, Reason}, State}
+    end.
+%%--------------------------------------------------------------------
+%% Function: handle_cast(Msg, State) -> {noreply, State} |
+%%          {noreply, State, Timeout} |
+%%          {stop, Reason, State}            (terminate/2 is called)
+%% Description: Handling cast messages
+%%--------------------------------------------------------------------
+
+%% When the request in process has been canceld the handler process is
+%% stopped and the pipelined requests will be reissued. This is is
+%% based on the assumption that it is proably cheaper to reissue the
+%% requests than to wait for a potentiall large response that we then
+%% only throw away. This of course is not always true maybe we could
+%% do something smarter here?! If the request canceled is not
+%% the one handled right now the same effect will take place in
+%% handle_pipeline/2 when the canceled request is on turn.
+handle_cast({cancel, RequestId}, State = #state{request = Request =
+						#request{id = RequestId}}) ->
+    httpc_manager:request_canceled(RequestId),
+    {stop, normal, 
+     State#state{canceled = [RequestId | State#state.canceled],
+		 request = Request#request{from = answer_sent}}};
+handle_cast({cancel, RequestId}, State) ->
+    httpc_manager:request_canceled(RequestId),
+    {noreply, State#state{canceled = [RequestId | State#state.canceled]}}.
+
+%%--------------------------------------------------------------------
+%% Function: handle_info(Info, State) -> {noreply, State} |
+%%          {noreply, State, Timeout} |
+%%          {stop, Reason, State}            (terminate/2 is called)
+%% Description: Handling all non call/cast messages
+%%--------------------------------------------------------------------
+handle_info({Proto, _Socket, Data}, State = 
+	    #state{mfa = {Module, Function, Args}, 
+		   request = #request{method = Method, 
+				      stream = Stream} = Request, 
+		   session = Session, status_line = StatusLine}) 
+  when Proto == tcp; Proto == ssl; Proto == httpc_handler ->
+
+    case Module:Function([Data | Args]) of
+        {ok, Result} ->
+            handle_http_msg(Result, State); 
+        {_, whole_body, _} when Method == head ->
+	    handle_response(State#state{body = <<>>}); 
+	{Module, whole_body, [Body, Length]} ->
+	    {_, Code, _} = StatusLine,
+	    {NewBody, NewRequest} = stream(Body, Request, Code),
+	    %% When we stream we will not keep the already
+	    %% streamed data, that would be a waste of memory.
+	    NewLength = case Stream of
+			    none ->   
+				Length;
+			    _ ->
+				Length - size(Body)
+			end,
+	    http_transport:setopts(socket_type(Session#tcp_session.scheme), 
+                                   Session#tcp_session.socket, 
+				   [{active, once}]),    
+            {noreply, State#state{mfa = {Module, whole_body, 
+					 [NewBody, NewLength]},
+				  request = NewRequest}};
+	NewMFA ->
+	    http_transport:setopts(socket_type(Session#tcp_session.scheme), 
+                                   Session#tcp_session.socket, 
+				   [{active, once}]),
+            {noreply, State#state{mfa = NewMFA}}
+    end;
+
+%% The Server may close the connection to indicate that the
+%% whole body is now sent instead of sending an lengh
+%% indicator.
+handle_info({tcp_closed, _}, State = #state{mfa = {_, whole_body, Args}}) ->
+    handle_response(State#state{body = hd(Args)}); 
+handle_info({ssl_closed, _}, State = #state{mfa = {_, whole_body, Args}}) ->
+    handle_response(State#state{body = hd(Args)}); 
+
+%%% Server closes idle pipeline
+handle_info({tcp_closed, _}, State = #state{request = undefined}) ->
+    {stop, normal, State};
+handle_info({ssl_closed, _}, State = #state{request = undefined}) ->
+    {stop, normal, State};
+
+%%% Error cases
+handle_info({tcp_closed, _}, State) ->
+    {stop, session_remotly_closed, State};
+handle_info({ssl_closed, _}, State) ->
+    {stop, session_remotly_closed, State};
+handle_info({tcp_error, _, _} = Reason, State) ->
+    {stop, Reason, State};
+handle_info({ssl_error, _, _} = Reason, State) ->
+    {stop, Reason, State};
+
+%%% Timeouts
+%% Internaly, to a request handling process, a request time out is
+%% seen as a canceld request.
+handle_info({timeout, RequestId}, State = 
+	    #state{request = Request = #request{id = RequestId}}) ->
+    httpc_response:send(Request#request.from, 
+		       httpc_response:error(Request,timeout)),
+    {stop, normal, 
+     State#state{canceled = [RequestId | State#state.canceled],
+		 request = Request#request{from = answer_sent}}};
+handle_info({timeout, RequestId}, State = #state{request = Request}) ->
+    httpc_response:send(Request#request.from, 
+		       httpc_response:error(Request,timeout)),
+    {noreply, State#state{canceled = [RequestId | State#state.canceled]}};
+
+handle_info(timeout_pipeline, State = #state{request = undefined}) ->
+    {stop, normal, State};
+
+%% Setting up the connection to the server somehow failed. 
+handle_info({init_error, _, ClientErrMsg},
+	    State = #state{request = Request}) ->
+    NewState = answer_request(Request, ClientErrMsg, State),
+    {stop, normal, NewState};
+
+%%% httpc_manager process dies. 
+handle_info({'EXIT', _, _}, State = #state{request = undefined}) ->
+    {stop, normal, State};
+%%Try to finish the current request anyway,
+%% there is a fairly high probability that it can be done successfully.
+%% Then close the connection, hopefully a new manager is started that
+%% can retry requests in the pipeline.
+handle_info({'EXIT', _, _}, State) ->
+    {noreply, State#state{status = close}}.
+    
+%%--------------------------------------------------------------------
+%% Function: terminate(Reason, State) -> _  (ignored by gen_server)
+%% Description: Shutdown the httpc_handler
+%%--------------------------------------------------------------------
+terminate(normal, #state{session = undefined}) ->
+    ok;  %% Init error there is no socket to be closed.
+terminate(normal, #state{request = Request, 
+		    session = #tcp_session{id = undefined,
+					   socket = Socket}}) ->  
+    %% Init error sending, no session information has been setup but
+    %% there is a socket that needs closing.
+    http_transport:close(socket_type(Request), Socket);
+
+terminate(_, State = #state{session = Session, request = undefined,
+			   timers = Timers}) -> 
+    catch httpc_manager:delete_session(Session#tcp_session.id),
+    
+    case queue:is_empty(State#state.pipeline) of 
+	false ->
+	    retry_pipline(queue:to_list(State#state.pipeline), State);
+	true ->
+	    ok
+    end,
+    cancel_timer(Timers#timers.pipeline_timer, timeout_pipeline),
+    http_transport:close(socket_type(Session#tcp_session.scheme),
+			 Session#tcp_session.socket);
+
+terminate(Reason, State = #state{request = Request})-> 
+    NewState = case Request#request.from of
+		   answer_sent ->
+		       State;
+		   _ ->
+		       answer_request(Request, 
+				      httpc_response:error(Request, Reason), 
+				      State)
+	       end,
+    terminate(Reason, NewState#state{request = undefined}).
+
+
+%%--------------------------------------------------------------------
+%% Func: code_change(_OldVsn, State, Extra) -> {ok, NewState}
+%% Purpose: Convert process state when code is changed
+%%--------------------------------------------------------------------
+code_change(_, State, _Extra) ->
+    {ok, State}.
+
+%%--------------------------------------------------------------------
+%%% Internal functions
+%%--------------------------------------------------------------------
+send_first_request(Address, Request, State) ->
+    Ipv6 = (State#state.options)#options.ipv6,
+    SocketType = socket_type(Request),
+    TimeOut = (Request#request.settings)#http_options.timeout,
+    case http_transport:connect(SocketType, Address, Ipv6, TimeOut) of
+	{ok, Socket} ->
+	    case httpc_request:send(Address, Request, Socket) of
+		ok ->
+		    ClientClose = 
+			httpc_request:is_client_closing(
+			  Request#request.headers),
+		    Session =
+			#tcp_session{id = {Request#request.address, self()},
+				     scheme = Request#request.scheme,
+				     socket = Socket,
+				     client_close = ClientClose},
+		    TmpState = State#state{request = Request, 
+					   session = Session, 
+					   mfa = 
+					   {httpc_response, parse,
+					    [State#state.max_header_size]},
+					   status_line = undefined,
+					   headers = undefined,
+					   body = undefined,
+					   status = new},
+		    http_transport:setopts(SocketType, 
+					   Socket, [{active, once}]),
+		    NewState = activate_request_timeout(TmpState),
+		    {ok, NewState};
+		{error, Reason} -> 
+		    %% Commented out in wait of ssl support to avoid
+		    %% dialyzer warning
+		    %%case State#state.status of
+		    %%	new -> % Called from init/1
+		    self() ! {init_error, error_sending, 
+			      httpc_response:error(Request, Reason)},
+		    {ok, State#state{request = Request,
+				     session = 
+				     #tcp_session{socket = Socket}}}
+		    %%ssl_tunnel -> % Not called from init/1
+		    %%  NewState = 
+		    %%	answer_request(Request, 
+		    %%httpc_response:error(Request, 
+		    %%Reason),
+		    %%			       State),
+		    %%	    {stop, normal, NewState}
+		    %%    end
+	    end;
+	{error, Reason} -> 
+	    %% Commented out in wait of ssl support to avoid
+	    %% dialyzer warning
+	    %% case State#state.status of
+	    %%	new -> % Called from init/1
+	    self() ! {init_error, error_connecting, 
+		      httpc_response:error(Request, Reason)},
+	    {ok, State#state{request = Request}}
+	    %%	ssl_tunnel -> % Not called from init/1
+	    %%    NewState = 
+	    %%	answer_request(Request, 
+	    %%		       httpc_response:error(Request, 
+	    %%					    Reason),
+	    %%		       State),
+	    %%    {stop, normal, NewState}
+	    %%end
+    end.
+
+handle_http_msg({Version, StatusCode, ReasonPharse, Headers, Body}, 
+		State = #state{request = Request}) ->
+    
+    case Headers#http_response_h.'content-type' of
+        "multipart/byteranges" ++ _Param ->
+            exit(not_yet_implemented);
+        _ ->
+	    start_stream({{Version, StatusCode, ReasonPharse}, Headers}, 
+			 Request),
+            handle_http_body(Body, 
+			     State#state{status_line = {Version, 
+							StatusCode,
+							ReasonPharse},
+					 headers = Headers})
+    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 = #state{status_line = {_,Code, _}}) ->
+    {NewBody, NewRequest}= stream(Body, State#state.request, Code),
+    handle_response(State#state{body = NewBody, request = NewRequest}).
+
+handle_http_body(<<>>, State = #state{request = #request{method = head}}) ->
+    handle_response(State#state{body = <<>>});
+
+handle_http_body(Body, State = #state{headers = Headers, session = Session,
+				      max_body_size = MaxBodySize,
+				      status_line = {_,Code, _},
+				      request = Request}) ->
+    case Headers#http_response_h.'transfer-encoding' of
+        "chunked" ->
+	    case http_chunk:decode(Body, State#state.max_body_size, 
+				   State#state.max_header_size, 
+				   {Code, Request}) of
+		{Module, Function, Args} ->
+		    http_transport:setopts(socket_type(
+					     Session#tcp_session.scheme), 
+					   Session#tcp_session.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) ->
+	    NewState = answer_request(Request, 
+				      httpc_response:error(Request, 
+							  unknown_encoding),
+				     State),
+	    {stop, normal, NewState};
+        _ ->
+            Length =
+                list_to_integer(Headers#http_response_h.'content-length'),
+            case ((Length =< MaxBodySize) or (MaxBodySize == nolimit)) of
+                true ->
+                    case httpc_response:whole_body(Body, Length) of
+                        {ok, Body} ->
+			    {NewBody, NewRequest}= stream(Body, Request, Code),
+			    handle_response(State#state{body = NewBody,
+							request = NewRequest});
+                        MFA ->
+                            http_transport:setopts(
+			      socket_type(Session#tcp_session.scheme), 
+			      Session#tcp_session.socket, 
+			      [{active, once}]),
+			    {noreply, State#state{mfa = MFA}}
+		    end;
+                false ->
+		    NewState = 
+			answer_request(Request,
+				       httpc_response:error(Request, 
+							   body_too_big),
+				       State),
+                    {stop, normal, NewState}
+            end
+    end.
+
+%%% Normaly I do not comment out code, I throw it away. But this might
+%%% actually be used on day if ssl is improved.
+%% handle_response(State = #state{status = ssl_tunnel,
+%% 			       request = Request,
+%% 			       options = Options,
+%% 			       session = #tcp_session{socket = Socket,
+%% 						      scheme = Scheme},
+%% 			       status_line = {_, 200, _}}) ->
+%%     %%% Insert code for upgrading the socket if and when ssl supports this.  
+%%     Address = handle_proxy(Request#request.address, Options#options.proxy),   
+%%     send_first_request(Address, Request, State);
+%% handle_response(State = #state{status = ssl_tunnel,
+%% 			      request = Request}) ->
+%%     NewState = answer_request(Request,
+%% 			      httpc_response:error(Request,
+%% 						   ssl_proxy_tunnel_failed),
+%% 			      State),
+%%                     {stop, normal, NewState};
+
+handle_response(State = #state{status = new}) ->
+   handle_response(try_to_enable_pipline(State));
+
+handle_response(State = #state{request = Request,
+			       status = Status,
+			       session = Session, 
+			       status_line = StatusLine,
+			       headers = Headers, 
+			       body = Body,
+			       options = Options}) when Status =/= new ->
+
+    handle_cookies(Headers, Request, Options),
+    case httpc_response:result({StatusLine, Headers, Body}, Request) of
+	%% 100-continue
+	continue -> 
+	    %% Send request body
+	    {_, RequestBody} = Request#request.content,
+	    http_transport:send(socket_type(Session#tcp_session.scheme), 
+					    Session#tcp_session.socket, 
+				RequestBody),
+	    %% Wait for next response
+	    http_transport:setopts(socket_type(Session#tcp_session.scheme), 
+				   Session#tcp_session.socket, 
+				   [{active, once}]),
+	    {noreply, 
+	     State#state{mfa = {httpc_response, parse,
+				[State#state.max_header_size]},
+			 status_line = undefined,
+			 headers = undefined,
+			 body = undefined
+			}};
+	%% Ignore unexpected 100-continue response and receive the
+	%% actual response that the server will send right away. 
+	{ignore, Data} -> 
+	    NewState = State#state{mfa = 
+				   {httpc_response, parse,
+				    [State#state.max_header_size]},
+				   status_line = undefined,
+				   headers = undefined,
+				   body = undefined},
+	    handle_info({httpc_handler, dummy, Data}, NewState);
+	%% On a redirect or retry the current request becomes 
+	%% obsolete and the manager will create a new request 
+	%% with the same id as the current.
+	{redirect, NewRequest, Data}->
+	    ok = httpc_manager:redirect_request(NewRequest),
+	    handle_pipeline(State#state{request = undefined}, Data);
+	{retry, TimeNewRequest, Data}->
+	    ok = httpc_manager:retry_request(TimeNewRequest),
+	    handle_pipeline(State#state{request = undefined}, Data);
+	{ok, Msg, Data} ->
+	    end_stream(StatusLine, Request),
+	    NewState = answer_request(Request, Msg, State),
+	    handle_pipeline(NewState, Data); 
+	{stop, Msg} ->
+	    end_stream(StatusLine, Request),
+	    NewState = answer_request(Request, Msg, State),
+	    {stop, normal, NewState}
+    end.
+
+handle_cookies(_,_, #options{cookies = disabled}) ->
+    ok;
+%% User wants to verify the cookies before they are stored,
+%% so the user will have to call a store command.
+handle_cookies(_,_, #options{cookies = verify}) ->
+    ok;
+handle_cookies(Headers, Request, #options{cookies = enabled}) ->
+    {Host, _ } = Request#request.address,
+    Cookies = http_cookie:cookies(Headers#http_response_h.other, 
+				  Request#request.path, Host),
+    httpc_manager:store_cookies(Cookies, Request#request.address).
+
+%% This request could not be pipelined
+handle_pipeline(State = #state{status = close}, _) ->
+    {stop, normal, State};
+
+handle_pipeline(State = #state{status = pipeline, session = Session}, 
+		Data) ->
+    case queue:out(State#state.pipeline) of
+	{empty, _} ->
+	    %% The server may choose too teminate an idle pipeline
+	    %% in this case we want to receive the close message
+	    %% at once and not when trying to pipline the next
+	    %% request.
+	    http_transport:setopts(socket_type(Session#tcp_session.scheme), 
+				   Session#tcp_session.socket, 
+				   [{active, once}]),
+	    %% If a pipeline that has been idle for some time is not
+	    %% closed by the server, the client may want to close it.
+	    NewState = activate_pipeline_timeout(State),
+	    NewSession = Session#tcp_session{pipeline_length = 0},
+	    httpc_manager:insert_session(NewSession),
+	    {noreply, 
+	     NewState#state{request = undefined, 
+			    mfa = {httpc_response, parse,
+				   [NewState#state.max_header_size]},
+			    status_line = undefined,
+			    headers = undefined,
+			    body = undefined
+			   }
+	    };
+	{{value, NextRequest}, Pipeline} ->    
+	    case lists:member(NextRequest#request.id, 
+			      State#state.canceled) of		
+		true ->
+		    %% See comment for handle_cast({cancel, RequestId})
+		    {stop, normal, 
+		     State#state{request = 
+				 NextRequest#request{from = answer_sent}}};
+		false ->
+		    NewSession = 
+			Session#tcp_session{pipeline_length =
+					    %% Queue + current
+					    queue:len(Pipeline) + 1},
+		    httpc_manager:insert_session(NewSession),
+		    NewState = 
+			State#state{pipeline = Pipeline,
+				    request = NextRequest,
+				    mfa = {httpc_response, parse,
+					   [State#state.max_header_size]},
+				    status_line = undefined,
+				    headers = undefined,
+				    body = undefined},
+		    case Data of
+			<<>> ->
+			    http_transport:setopts(
+			      socket_type(Session#tcp_session.scheme), 
+			      Session#tcp_session.socket, 
+			      [{active, once}]),
+			    {noreply, NewState};
+			_ ->
+			    %% If we already received some bytes of
+			    %% the next response
+			    handle_info({httpc_handler, dummy, Data}, 
+					NewState) 
+		    end
+	    end
+    end.
+
+call(Msg, Pid, Timeout) ->
+    gen_server:call(Pid, Msg, Timeout).
+
+cast(Msg, Pid) ->
+    gen_server:cast(Pid, Msg).
+
+activate_request_timeout(State = #state{request = Request}) ->
+    Time = (Request#request.settings)#http_options.timeout,
+    case Time of
+	infinity ->
+	    State;
+	_ ->
+	    Ref = erlang:send_after(Time, self(), 
+				    {timeout, Request#request.id}),
+	    State#state
+	      {timers = 
+	       #timers{request_timers = 
+		       [{Request#request.id, Ref}|
+			(State#state.timers)#timers.request_timers]}}
+    end.
+
+activate_pipeline_timeout(State = #state{options = 
+					 #options{pipeline_timeout = 
+						  infinity}}) ->
+    State;
+activate_pipeline_timeout(State = #state{options = 
+					 #options{pipeline_timeout = Time}}) ->
+    Ref = erlang:send_after(Time, self(), timeout_pipeline),
+    State#state{timers = #timers{pipeline_timer = Ref}}.
+
+is_pipeline_capable_server("HTTP/1." ++ N, _) when hd(N) >= $1 ->
+    true;
+is_pipeline_capable_server("HTTP/1.0", 
+			   #http_response_h{connection = "keep-alive"}) ->
+    true;
+is_pipeline_capable_server(_,_) ->
+    false.
+
+is_keep_alive_connection(Headers, Session) ->
+    (not ((Session#tcp_session.client_close) or  
+	  httpc_response:is_server_closing(Headers))).
+
+try_to_enable_pipline(State = #state{session = Session, 
+				     request = #request{method = Method},
+				     status_line = {Version, _, _},
+				     headers = Headers}) ->
+    case (is_pipeline_capable_server(Version, Headers)) and  
+	(is_keep_alive_connection(Headers, Session)) and 
+	(httpc_request:is_idempotent(Method)) of
+	true ->
+	    httpc_manager:insert_session(Session),
+	    State#state{status = pipeline};
+	false ->
+	    State#state{status = close}
+    end.
+
+answer_request(Request, Msg, State = #state{timers = Timers}) ->    
+    httpc_response:send(Request#request.from, Msg),
+    RequestTimers = Timers#timers.request_timers,
+    TimerRef =
+	http_util:key1search(RequestTimers, Request#request.id, undefined),
+    Timer = {Request#request.id, TimerRef},
+    cancel_timer(TimerRef, {timeout, Request#request.id}),
+    State#state{request = Request#request{from = answer_sent},
+		timers = 
+		Timers#timers{request_timers =
+			      lists:delete(Timer, RequestTimers)}}.
+cancel_timer(undefined, _) ->
+    ok;
+cancel_timer(Timer, TimeoutMsg) ->
+    erlang:cancel_timer(Timer),
+    receive 
+	TimeoutMsg ->
+	    ok
+    after 0 ->
+	    ok
+    end.
+
+retry_pipline([], _) ->
+    ok;
+
+retry_pipline([Request |PipeLine],  State = #state{timers = Timers}) ->
+    NewState =
+	case (catch httpc_manager:retry_request(Request)) of
+	    ok ->
+		RequestTimers = Timers#timers.request_timers,
+		Timer = {_, TimerRef} =
+		    http_util:key1search(RequestTimers, Request#request.id, 
+					  {undefined, undefined}),
+		cancel_timer(TimerRef, {timeout, Request#request.id}),
+		State#state{timers = Timers#timers{request_timers =
+					  lists:delete(Timer,
+						       RequestTimers)}};
+	    Error ->
+		answer_request(Request#request.from,
+			       httpc_response:error(Request, Error), State) 
+	end,
+    retry_pipline(PipeLine, NewState).
+
+%%% Check to see if the given {Host,Port} tuple is in the NoProxyList
+%%% Returns an eventually updated {Host,Port} tuple, with the proxy address
+handle_proxy(HostPort = {Host, _Port}, {Proxy, NoProxy}) ->
+    case Proxy of
+	undefined ->
+	    HostPort;
+	Proxy ->
+	    case is_no_proxy_dest(Host, NoProxy) of
+		true ->
+		    HostPort;
+		false ->
+		    Proxy
+	    end
+    end.
+
+is_no_proxy_dest(_, []) ->
+    false;
+is_no_proxy_dest(Host, [ "*." ++ NoProxyDomain | NoProxyDests]) ->    
+    
+    case is_no_proxy_dest_domain(Host, NoProxyDomain) of
+	true ->
+	    true;
+	false ->
+	    is_no_proxy_dest(Host, NoProxyDests)
+    end;
+
+is_no_proxy_dest(Host, [NoProxyDest | NoProxyDests]) ->
+    IsNoProxyDest = case http_util:is_hostname(NoProxyDest) of
+			true ->
+			    fun is_no_proxy_host_name/2;
+			false ->
+			    fun is_no_proxy_dest_address/2
+		    end,
+    
+    case IsNoProxyDest(Host, NoProxyDest) of
+	true ->
+	    true;
+	false ->
+	    is_no_proxy_dest(Host, NoProxyDests)
+    end.
+
+is_no_proxy_host_name(Host, Host) ->
+    true;
+is_no_proxy_host_name(_,_) ->
+    false.
+
+is_no_proxy_dest_domain(Dest, DomainPart) ->
+    lists:suffix(DomainPart, Dest).
+
+is_no_proxy_dest_address(Dest, Dest) ->
+    true;
+is_no_proxy_dest_address(Dest, AddressPart) ->
+    lists:prefix(AddressPart, Dest).
+
+socket_type(#request{scheme = http}) ->
+    ip_comm;
+socket_type(#request{scheme = https, settings = Settings}) ->
+    {ssl, Settings#http_options.ssl};
+socket_type(http) ->
+    ip_comm;
+socket_type(https) ->
+    {ssl, []}. %% Dummy value ok for ex setops that does not use this value
+
+start_stream(_, #request{stream = none}) ->
+    ok;
+start_stream({{_, 200, _}, Headers}, Request = #request{stream = self}) ->
+    Msg = httpc_response:stream_start(Headers, Request),
+    httpc_response:send(Request#request.from, Msg);
+start_stream(_, _) ->
+    ok.
+
+%% Note the end stream message is handled by httpc_response and will
+%% be sent by answer_request
+end_stream(_, #request{stream = none}) ->
+    ok;
+end_stream(_, #request{stream = self}) ->
+    ok;
+end_stream({_,200,_}, #request{stream = Fd}) ->
+    case file:close(Fd) of 
+	ok ->
+	    ok;
+	{error, enospc} -> % Could be due to delayed_write
+	    file:close(Fd)
+    end;
+end_stream(_, _) ->
+    ok.
+
+handle_verbose(verbose) ->
+    dbg:p(self(), [r]);
+handle_verbose(debug) ->
+    dbg:p(self(), [call]),
+    dbg:tp(?MODULE, [{'_', [], [{return_trace}]}]);
+handle_verbose(trace) ->
+    dbg:p(self(), [call]),
+    dbg:tpl(?MODULE, [{'_', [], [{return_trace}]}]);
+handle_verbose(_) ->
+    ok.    
+
+%%% Normaly I do not comment out code, I throw it away. But this might
+%%% actually be used on day if ssl is improved.
+%% send_ssl_tunnel_request(Address, Request = #request{address = {Host, Port}}, 
+%% 			State) ->
+%%     %% A ssl tunnel request is a special http request that looks like
+%%     %% CONNECT host:port HTTP/1.1
+%%     SslTunnelRequest = #request{method = connect, scheme = http,
+%% 				headers = 
+%% 				#http_request_h{
+%% 				  host = Host, 
+%% 				  address = Address, 
+%% 				  path = Host ++ ":",
+%% 				  pquery = integer_to_list(Port),
+%% 				  other = [{ "Proxy-Connection", "keep-alive"}]},
+%%     Ipv6 = (State#state.options)#options.ipv6,
+%%     SocketType = socket_type(SslTunnelRequest),
+%%     case http_transport:connect(SocketType, 
+%%                                 SslTunnelRequest#request.address, Ipv6) of
+%% 	{ok, Socket} ->
+%% 	    case httpc_request:send(Address, SslTunnelRequest, Socket) of
+%% 		ok ->
+%% 		    Session = #tcp_session{id = 
+%% 					   {SslTunnelRequest#request.address,
+%% 					    self()},
+%% 					   scheme = 
+%% 					   SslTunnelRequest#request.scheme,
+%% 					   socket = Socket},
+%% 		    NewState = State#state{mfa = 
+%% 					   {httpc_response, parse,
+%% 					    [State#state.max_header_size]},
+%% 					   request = Request,
+%% 					   session = Session},
+%% 		    http_transport:setopts(socket_type(
+%%                                           SslTunnelRequest#request.scheme), 
+%% 					   Socket, 
+%% 					   [{active, once}]),
+%% 		    {ok, NewState};
+%% 		{error, Reason} -> 
+%% 		    self() ! {init_error, error_sending, 
+%% 			      httpc_response:error(Request, Reason)},
+%% 		    {ok, State#state{request = Request,
+%% 				     session = #tcp_session{socket = 
+%% 							    Socket}}}
+%% 	    end;
+%% 	{error, Reason} ->
+%% 	    self() ! {init_error, error_connecting, 
+%% 		      httpc_response:error(Request, Reason)},
+%% 	    {ok, State#state{request = Request}}
+%%     end.

Added: incubator/couchdb/trunk/src/couch_inets/httpc_internal.hrl
URL: http://svn.apache.org/viewvc/incubator/couchdb/trunk/src/couch_inets/httpc_internal.hrl?rev=642432&view=auto
==============================================================================
--- incubator/couchdb/trunk/src/couch_inets/httpc_internal.hrl (added)
+++ incubator/couchdb/trunk/src/couch_inets/httpc_internal.hrl Fri Mar 28 16:32:19 2008
@@ -0,0 +1,87 @@
+%% ``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$
+%%
+
+-define(HTTP_REQUEST_TIMEOUT, infinity).
+-define(HTTP_PIPELINE_TIMEOUT, 0).
+-define(HTTP_PIPELINE_LENGTH, 2).
+-define(HTTP_MAX_TCP_SESSIONS, 2).
+-define(HTTP_MAX_REDIRECTS, 4).
+
+%%% HTTP Client per request settings
+-record(http_options,{
+	  %% Milliseconds before a request times out
+	  timeout = ?HTTP_REQUEST_TIMEOUT,  
+	  %% bool() - True if automatic redirection on 30X responses.
+	  autoredirect = true, 
+	  ssl = [], % Ssl socket options
+	  proxy_auth, % {User, Password} = {strring(), string()} 
+	  relaxed = false % bool() true if not strictly standard compliant
+	 }).
+
+%%% HTTP Client per profile setting. Currently there is only one profile.
+-record(options, {
+	  proxy =  {undefined, []}, % {{ProxyHost, ProxyPort}, [NoProxy]},
+	  pipeline_timeout = ?HTTP_PIPELINE_TIMEOUT,
+	  max_pipeline_length = ?HTTP_PIPELINE_LENGTH,
+	  max_sessions =  ?HTTP_MAX_TCP_SESSIONS,
+	  cookies = disabled, % enabled | disabled | verify
+	  ipv6 = enabled, % enabled | disabled
+	  verbose = false
+	 }).
+
+%%% All data associated to a specific HTTP request
+-record(request,{
+	  id,            % ref() - Request Id
+	  from,          % pid() - Caller
+	  redircount = 0,% Number of redirects made for this request
+	  scheme,        % http | https 
+	  address,       % ({Host,Port}) Destination Host and Port
+	  path,          % string() - Path of parsed URL
+	  pquery,        % string() - Rest of parsed URL
+	  method,        % atom() - HTTP request Method
+	  headers,       % #http_request_h{}
+	  content,       % {ContentType, Body} - Current HTTP request
+	  settings,      % #http_options{} - User defined settings
+	  abs_uri,       % string() ex: "http://www.erlang.org"
+	  userinfo,      % string() - optinal "<userinfo>@<host>:<port>"
+	  stream,	 % Boolean() - stream async reply?
+	  headers_as_is  % Boolean() - workaround for servers that does
+	  %% not honor the http standard, can also be used for testing purposes.
+	 }).               
+
+-record(tcp_session,{
+	  id,           % {{Host, Port}, HandlerPid}
+	  client_close, % true | false
+	  scheme,       % http (HTTP/TCP) | https (HTTP/SSL/TCP)
+	  socket,       % Open socket, used by connection
+	  pipeline_length = 1 % Current length of pipeline 
+	 }).
+
+-record(http_cookie,{
+	  domain,
+	  domain_default = false,
+	  name,
+	  value,
+	  comment,
+	  max_age = session,
+	  path, 
+	  path_default = false,
+	  secure = false,
+	  version = "0" 
+	 }).
+

Added: incubator/couchdb/trunk/src/couch_inets/httpc_manager.erl
URL: http://svn.apache.org/viewvc/incubator/couchdb/trunk/src/couch_inets/httpc_manager.erl?rev=642432&view=auto
==============================================================================
--- incubator/couchdb/trunk/src/couch_inets/httpc_manager.erl (added)
+++ incubator/couchdb/trunk/src/couch_inets/httpc_manager.erl Fri Mar 28 16:32:19 2008
@@ -0,0 +1,475 @@
+% ``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(httpc_manager).
+
+-behaviour(gen_server).
+
+-include("httpc_internal.hrl").
+-include("http_internal.hrl").
+
+%% Application API
+-export([start_link/1, request/1, cancel_request/1,
+	 request_canceled/1, retry_request/1, redirect_request/1,
+	 insert_session/1, delete_session/1, set_options/1, store_cookies/2,
+	 cookies/1]).
+
+%% gen_server callbacks
+-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2,
+	 code_change/3]).
+
+-record(state, {
+	  cancel = [],	 % [{RequestId, HandlerPid, ClientPid}]  
+	  handler_db,    % ets() - Entry: {Requestid, HandlerPid, ClientPid}
+	  cookie_db,     % {ets(), dets()} - {session_cookie_db, cookie_db} 
+	  options = #options{}
+	 }).
+
+%%====================================================================
+%% Application API
+%%====================================================================
+%%--------------------------------------------------------------------
+%% Function: start_link() -> {ok, Pid}
+%%
+%% Description: Starts the http request manger process. (Started by
+%% the intes supervisor.)
+%%--------------------------------------------------------------------
+start_link({default, CookieDir}) ->
+    gen_server:start_link({local, ?MODULE}, ?MODULE, 
+			  [{http_default_cookie_db, CookieDir}], []). 
+
+%%--------------------------------------------------------------------
+%% Function: request() -> {ok, Requestid} | {error, Reason}
+%%	Request = #request{}
+%%
+%% Description: Sends a request to the httpc manager process.
+%%--------------------------------------------------------------------
+request(Request) ->
+    call({request, Request}, infinity).
+
+%%--------------------------------------------------------------------
+%% Function: retry_request(Request) -> _
+%%	Request = #request{}
+%%
+%% Description: Resends a request to the httpc manager process, intended
+%% to be called by the httpc handler process if it has to terminate with
+%% a non empty pipeline.
+%%--------------------------------------------------------------------
+retry_request(Request) ->
+    cast({retry_or_redirect_request, Request}).
+
+%%--------------------------------------------------------------------
+%% Function: redirect_request(Request) -> _
+%%	Request = #request{}
+%%
+%% Description: Sends an atoumatic redirect request to the httpc
+%% manager process, intended to be called by the httpc handler process
+%% when the automatic redirect option is set.
+%%--------------------------------------------------------------------
+redirect_request(Request) ->
+    cast({retry_or_redirect_request, Request}).
+
+%%--------------------------------------------------------------------
+%% Function: cancel_request(RequestId) -> ok
+%%	RequestId - ref()
+%%
+%% Description: Cancels the request with <RequestId>.
+%%--------------------------------------------------------------------
+cancel_request(RequestId) ->
+    call({cancel_request, RequestId}, infinity).
+
+%%--------------------------------------------------------------------
+%% Function: request_canceled(RequestId) -> ok
+%%	RequestId - ref()
+%%
+%% Description: Confirms that a request has been canceld. Intended to
+%% be called by the httpc handler process.
+%%--------------------------------------------------------------------
+request_canceled(RequestId) ->
+    cast({request_canceled, RequestId}).
+
+%%--------------------------------------------------------------------
+%% Function: insert_session(Session) -> _
+%%	Session - #tcp_session{}
+%%
+%% Description: Inserts session information into the httpc manager table
+%% httpc_manager_session_db. Intended to be called by the httpc request
+%% handler process.
+%%--------------------------------------------------------------------
+insert_session(Session) ->
+    ets:insert(httpc_manager_session_db, Session).
+
+%%--------------------------------------------------------------------
+%% Function: delete_session(SessionId) -> _
+%%	SessionId -  {{Host, Port}, HandlerPid}
+%% 
+%% Description: Deletes session information from the httpc manager table
+%% httpc_manager_session_db. Intended to be called by the httpc request
+%% handler process.
+%%--------------------------------------------------------------------
+delete_session(SessionId) ->
+    ets:delete(httpc_manager_session_db, SessionId).
+
+%%--------------------------------------------------------------------
+%% Function: set_options(Options) -> ok
+%%
+%% Options = [Option]
+%% Option = {proxy, {Proxy, [NoProxy]}} | {max_pipeline_length, integer()} |
+%%          {max_sessions, integer()} | {pipeline_timeout, integer()}
+%% Proxy = {Host, Port}
+%% NoProxy - [Domain | HostName | IPAddress]     
+%% Max - integer() 
+%% 
+%% Description: Sets the options to be used by the client.
+%%--------------------------------------------------------------------
+set_options(Options) ->
+    cast({set_options, Options}).
+
+%%--------------------------------------------------------------------
+%% Function: store_cookies(Cookies, Address) -> ok
+%%
+%% Cookies = [Cookie]
+%% Cookie = #http_cookie{}
+%% 
+%% Description: Stores cookies from the server.
+%%--------------------------------------------------------------------
+store_cookies([], _) ->
+    ok;
+store_cookies(Cookies, Address) ->
+    cast({store_cookies, {Cookies, Address}}).
+
+%%--------------------------------------------------------------------
+%% Function: cookies(Url) -> ok
+%%
+%% Url = string()
+%%
+%% Description: Retrieves the cookies that  
+%%--------------------------------------------------------------------
+cookies(Url) ->
+    call({cookies, Url}, infinity).
+
+%%====================================================================
+%% gen_server callback functions
+%%====================================================================
+
+%%--------------------------------------------------------------------
+%% Function: init([Request, Session]) -> {ok, State} | 
+%%                       {ok, State, Timeout} | ignore |{stop, Reason}
+%% Description: Initiates the httpc_manger process
+%%--------------------------------------------------------------------
+init([CookiesConf|_Options]) ->
+    process_flag(trap_exit, true),
+    ets:new(httpc_manager_session_db, 
+	    [public, set, named_table, {keypos, #tcp_session.id}]),
+    {ok, #state{handler_db = ets:new(handler_db, [protected, set]),
+		cookie_db = 
+		http_cookie:open_cookie_db({CookiesConf, 
+					    http_session_cookie_db})
+	       }}.
+
+%%--------------------------------------------------------------------
+%% Function: handle_call(Request, From, State) -> {reply, Reply, State} |
+%%          {reply, Reply, State, Timeout} |
+%%          {noreply, State}               |
+%%          {noreply, State, Timeout}      |
+%%          {stop, Reason, Reply, State}   | (terminate/2 is called)
+%%          {stop, Reason, State}            (terminate/2 is called)
+%% Description: Handling call messages
+%%--------------------------------------------------------------------
+handle_call({request, Request}, _, State) ->
+    case (catch handle_request(Request, State)) of
+	{reply, Msg, NewState} ->
+	    {reply, Msg, NewState};
+	Error ->
+	    {stop, Error, httpc_response:error(Request, Error), State}
+    end;
+	
+handle_call({cancel_request, RequestId}, From, State) ->
+    case ets:lookup(State#state.handler_db, RequestId) of
+	[] ->
+	    ok, %% Nothing to cancel
+	      {reply, ok, State};
+	[{_, Pid, _}] ->
+	    httpc_handler:cancel(RequestId, Pid),
+	    {noreply, State#state{cancel = 
+				  [{RequestId, Pid, From} |
+				   State#state.cancel]}}
+    end;
+
+handle_call({cookies, Url}, _, State) ->
+    case http_uri:parse(Url) of
+	{Scheme, _, Host, Port, Path, _} ->
+	    CookieHeaders = 
+		http_cookie:header(Scheme, {Host, Port}, 
+				   Path, State#state.cookie_db),
+	    {reply, CookieHeaders, State};
+	Msg ->
+	    {reply, Msg, State}
+    end;
+
+handle_call(Msg, From, State) ->
+    error_logger:error_report("HTTPC_MANAGER recived unkown call: ~p"
+			      "from: ~p~n", [Msg, From]),
+    {reply, {error, 'API_violation'}, State}.
+
+%%--------------------------------------------------------------------
+%% Function: handle_cast(Msg, State) -> {noreply, State} |
+%%          {noreply, State, Timeout} |
+%%          {stop, Reason, State}            (terminate/2 is called)
+%% Description: Handling cast messages
+%%--------------------------------------------------------------------
+handle_cast({retry_or_redirect_request, {Time, Request}}, State) ->
+    {ok, _} = timer:apply_after(Time, ?MODULE, retry_request, [Request]),
+    {noreply, State};
+
+handle_cast({retry_or_redirect_request, Request}, State) ->
+    case (catch handle_request(Request, State)) of
+	{reply, {ok, _}, NewState} ->
+	    {noreply, NewState};
+	Error  ->
+	    httpc_response:error(Request, Error),
+	    {stop, Error, State}
+    end;
+
+handle_cast({request_canceled, RequestId}, State) ->
+    ets:delete(State#state.handler_db, RequestId),
+    case lists:keysearch(RequestId, 1, State#state.cancel) of
+	{value, Entry = {RequestId, _, From}} ->
+	    gen_server:reply(From, ok),
+	    {noreply, 
+	     State#state{cancel = lists:delete(Entry, State#state.cancel)}};
+	_ ->
+	   {noreply, State}
+    end;
+handle_cast({set_options, Options}, State = #state{options = OldOptions}) ->
+    NewOptions = 
+	#options{proxy = 
+		 http_util:key1search(Options, proxy,
+				       OldOptions#options.proxy),
+		 pipeline_timeout = 
+		 http_util:key1search(Options, pipeline_timeout, 
+				       OldOptions#options.pipeline_timeout),
+		 max_pipeline_length =
+		 http_util:key1search(Options, max_pipeline_length, 
+				       OldOptions#options.max_pipeline_length),
+		 max_sessions = 
+		 http_util:key1search(Options, max_sessions, 
+				       OldOptions#options.max_sessions),
+		 cookies = http_util:key1search(Options, cookies, 
+						 OldOptions#options.cookies),
+		 ipv6 = http_util:key1search(Options, ipv6, 
+					     OldOptions#options.ipv6),
+		 verbose = http_util:key1search(Options, verbose, 
+						OldOptions#options.verbose)
+		}, 
+    case {OldOptions#options.verbose, NewOptions#options.verbose} of
+	{Same, Same} ->
+	    ok;
+	{_, false} ->
+	    dbg:stop();
+	{false, Level}  ->
+	    dbg:tracer(),
+	    handle_verbose(Level);
+	{_, Level} ->
+	    dbg:stop(),
+	    dbg:tracer(),
+	    handle_verbose(Level)
+    end,
+
+    {noreply, State#state{options = NewOptions}};
+
+handle_cast({store_cookies, _}, 
+	    State = #state{options = #options{cookies = disabled}}) ->
+    {noreply, State};
+
+handle_cast({store_cookies, {Cookies, _}}, State) ->
+    ok = do_store_cookies(Cookies, State),
+    {noreply, State};
+
+handle_cast(Msg, State) ->
+    error_logger:error_report("HTTPC_MANAGER recived unkown cast: ~p", [Msg]),
+    {noreply, State}.
+
+%%--------------------------------------------------------------------
+%% Function: handle_info(Info, State) -> {noreply, State} |
+%%          {noreply, State, Timeout} |
+%%          {stop, Reason, State}            (terminate/2 is called)
+%% Description: Handling all non call/cast messages
+%%---------------------------------------------------------
+handle_info({'EXIT', _, _}, State) ->
+    %% Handled in DOWN
+    {noreply, State};
+handle_info({'DOWN', _, _, Pid, _}, State) ->
+    ets:match_delete(State#state.handler_db, {'_', Pid, '_'}),
+
+    %% If there where any canceled request, handled by the
+    %% the process that now has terminated, the
+    %% cancelation can be viewed as sucessfull!
+    NewCanceldList = 
+	lists:foldl(fun(Entry = {_, HandlerPid, From}, Acc)  ->
+			    case HandlerPid of
+				Pid ->
+				    gen_server:reply(From, ok),
+				    lists:delete(Entry, Acc);
+				_ ->
+				    Acc
+			    end 
+		    end, State#state.cancel, State#state.cancel),
+    {noreply, State#state{cancel = NewCanceldList}};    
+handle_info(Info, State) ->
+    error_logger:error_report("Unknown message in "
+			      "httpc_manager:handle_info ~p~n", [Info]),
+    {noreply, State}. 
+%%--------------------------------------------------------------------
+%% Function: terminate(Reason, State) -> _  (ignored by gen_server)
+%% Description: Shutdown the httpc_handler
+%%--------------------------------------------------------------------
+terminate(_, State) ->
+    http_cookie:close_cookie_db(State#state.cookie_db),
+    ets:delete(httpc_manager_session_db),
+    ets:delete(State#state.handler_db).
+
+%%--------------------------------------------------------------------
+%% Func: code_change(_OldVsn, State, Extra) -> {ok, NewState}
+%% Purpose: Convert process state when code is changed
+%%--------------------------------------------------------------------
+code_change(_OldVsn, State, _Extra) ->
+    {ok, State}.
+
+%%--------------------------------------------------------------------
+%% Internal functions
+%%--------------------------------------------------------------------
+handle_request(Request, State) ->
+    NewRequest = handle_cookies(generate_request_id(Request), State),
+    case select_session(Request#request.method, 
+			Request#request.address, 
+			Request#request.scheme, State) of
+	{ok, HandlerPid} ->
+	    pipeline(NewRequest, HandlerPid, State);
+	no_connection ->
+	    start_handler(NewRequest, State);
+	{no_session,  OpenSessions} when OpenSessions 
+	< State#options.max_sessions ->
+	    start_handler(NewRequest, State);
+	{no_session, _} ->
+	    %% Do not start any more persistent connections
+	    %% towards this server.
+	    NewHeaders = 
+		(NewRequest#request.headers)#http_request_h{connection
+							    = "close"},
+	    start_handler(NewRequest#request{headers = NewHeaders}, State)
+    end,
+    {reply, {ok, NewRequest#request.id}, State}.
+
+select_session(Method, HostPort, Scheme, #state{options = 
+						#options{max_pipeline_length =
+							 Max}}) ->
+    case httpc_request:is_idempotent(Method) of
+	true ->
+	    Candidates = ets:match(httpc_manager_session_db,
+				   {'_', {HostPort, '$1'}, 
+				    false, Scheme, '_', '$2'}),
+	    select_session(Candidates, Max);
+	false ->
+	    no_connection
+    end.
+    
+select_session(Candidates, MaxPipeline) ->
+    case Candidates of 
+	[] ->
+	  no_connection; 
+	_ ->
+	    NewCandidates = 
+		lists:foldl(
+		  fun([Pid, PipelineLength], Acc) when 
+			    PipelineLength =< MaxPipeline ->
+			  [{Pid, PipelineLength} | Acc];
+		     (_, Acc) ->
+			  Acc
+		  end, [], Candidates),
+	    
+	    case lists:keysort(2, NewCandidates) of
+		[] ->
+		    {no_session, length(Candidates)};
+		[{HandlerPid, _} | _] ->
+		    {ok, HandlerPid}
+	    end
+    end.
+	    
+pipeline(Request, HandlerPid, State) ->
+    case (catch httpc_handler:send(Request, HandlerPid)) of
+	ok ->
+	    ets:insert(State#state.handler_db, {Request#request.id, 
+						HandlerPid,
+						Request#request.from});
+	_  -> %timeout pipelining failed 
+	    start_handler(Request, State)
+    end.
+
+start_handler(Request, State) ->
+    {ok, Pid} = httpc_handler:start_link(Request, State#state.options),
+    ets:insert(State#state.handler_db, {Request#request.id, 
+					Pid, Request#request.from}),
+    erlang:monitor(process, Pid).
+
+generate_request_id(Request) ->
+    case Request#request.id of
+	undefined ->
+	    RequestId = make_ref(),
+	    Request#request{id = RequestId};
+	_ ->
+	    %% This is an automatic redirect or a retryed pipelined
+	    %% request keep the old id.
+	    Request
+    end.
+
+handle_cookies(Request, #state{options = #options{cookies = disabled}}) ->
+    Request;
+handle_cookies(Request = #request{scheme = Scheme, address = Address,
+				  path = Path, headers = 
+		 Headers = #http_request_h{other = Other}}, 
+	       #state{cookie_db = Db}) ->
+    case http_cookie:header(Scheme, Address, Path, Db) of
+	{"cookie", ""} ->
+	    Request;
+	CookieHeader ->
+	    NewHeaders = 
+		Headers#http_request_h{other = [CookieHeader | Other]},
+	    Request#request{headers = NewHeaders}
+    end.
+
+do_store_cookies([], _) ->
+    ok;
+do_store_cookies([Cookie | Cookies], State) ->
+    ok = http_cookie:insert(Cookie, State#state.cookie_db),
+    do_store_cookies(Cookies, State).
+
+call(Msg, Timeout) ->
+    gen_server:call(?MODULE, Msg, Timeout).
+
+cast(Msg) ->
+   gen_server:cast(?MODULE, Msg).
+
+handle_verbose(debug) ->
+    dbg:p(self(), [call]),
+    dbg:tp(?MODULE, [{'_', [], [{return_trace}]}]);
+handle_verbose(trace) ->
+    dbg:p(self(), [call]),
+    dbg:tpl(?MODULE, [{'_', [], [{return_trace}]}]);
+handle_verbose(_) ->
+    ok.  
+

Added: incubator/couchdb/trunk/src/couch_inets/httpc_request.erl
URL: http://svn.apache.org/viewvc/incubator/couchdb/trunk/src/couch_inets/httpc_request.erl?rev=642432&view=auto
==============================================================================
--- incubator/couchdb/trunk/src/couch_inets/httpc_request.erl (added)
+++ incubator/couchdb/trunk/src/couch_inets/httpc_request.erl Fri Mar 28 16:32:19 2008
@@ -0,0 +1,193 @@
+%% ``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(httpc_request).
+
+-include("http_internal.hrl").
+-include("httpc_internal.hrl").
+
+%% We will not make the change to use base64 in stdlib in inets just yet.
+%% it will be included in the next major release of inets. 
+-compile({nowarn_deprecated_function, {http_base_64, encode, 1}}).
+
+%%% Internal API
+-export([send/3, is_idempotent/1, is_client_closing/1]).
+
+%%%=========================================================================
+%%%  Internal application API
+%%%=========================================================================
+%%-------------------------------------------------------------------------
+%% send(MaybeProxy, Request) ->
+%%      MaybeProxy - {Host, Port}
+%%      Host = string()
+%%      Port = integer()
+%%	Request - #request{}
+%%	Socket - socket()
+%%      CookieSupport - enabled | disabled | verify
+%%                                   
+%% Description: Composes and sends a HTTP-request. 
+%%-------------------------------------------------------------------------
+send(SendAddr, #request{method = Method, scheme = Scheme,
+			path = Path, pquery = Query, headers = Headers,
+			content = Content, address = Address, 
+			abs_uri = AbsUri, headers_as_is = HeadersAsIs,
+			settings = HttpOptions, 
+			userinfo = UserInfo},
+     Socket) -> 
+    
+    TmpHeaders = handle_user_info(UserInfo, Headers),
+
+    {TmpHeaders2, Body} = post_data(Method, TmpHeaders, Content, HeadersAsIs),
+    
+    {NewHeaders, Uri} = case Address of
+			    SendAddr ->
+				{TmpHeaders2, Path ++ Query};
+			    _Proxy ->
+				TmpHeaders3 =
+				    handle_proxy(HttpOptions, TmpHeaders2),
+				{TmpHeaders3, AbsUri}
+			end,
+
+    FinalHeaders = case NewHeaders of
+		       HeaderList when is_list(HeaderList) ->
+			   headers(HeaderList, []);
+		       _  ->
+			   http_request:http_headers(NewHeaders)
+		   end,
+    
+    Message = 
+	lists:append([method(Method), " ", Uri, " HTTP/1.1", ?CRLF, 
+		      FinalHeaders, ?CRLF, Body]),
+    
+    http_transport:send(socket_type(Scheme), Socket, Message).
+
+%%-------------------------------------------------------------------------
+%% is_idempotent(Method) ->
+%% Method = atom()
+%%                                   
+%% Description: Checks if Methode is considered idempotent.
+%%-------------------------------------------------------------------------
+
+%% In particular, the convention has been established that the GET and
+%% HEAD methods SHOULD NOT have the significance of taking an action
+%% other than retrieval. These methods ought to be considered "safe".
+is_idempotent(head) -> 
+    true;
+is_idempotent(get) ->
+    true;
+%% Methods can also have the property of "idempotence" in that (aside
+%% from error or expiration issues) the side-effects of N > 0
+%% identical requests is the same as for a single request.
+is_idempotent(put) -> 
+    true;
+is_idempotent(delete) ->
+    true;
+%% Also, the methods OPTIONS and TRACE SHOULD NOT have side effects,
+%% and so are inherently idempotent.
+is_idempotent(trace) ->
+    true;
+is_idempotent(options) ->
+    true;
+is_idempotent(_) ->
+    false.
+
+%%-------------------------------------------------------------------------
+%% is_client_closing(Headers) ->
+%% Headers = #http_request_h{}
+%%                                   
+%% Description: Checks if the client has supplied a "Connection: close" header.
+%%-------------------------------------------------------------------------
+is_client_closing(Headers) ->
+    case Headers#http_request_h.connection of
+	"close" ->
+	    true;
+	 _ ->
+	    false
+    end.
+
+%%%========================================================================
+%%% Internal functions
+%%%========================================================================
+post_data(Method, Headers, {ContentType, Body}, HeadersAsIs) 
+  when Method == post; Method == put ->
+    ContentLength = body_length(Body),	      
+    NewBody = case Headers#http_request_h.expect of
+		  "100-continue" ->
+		      "";
+		  _ ->
+		      Body
+	      end,
+    
+    NewHeaders = case HeadersAsIs of
+		     [] ->
+			 Headers#http_request_h{'content-type' = 
+						ContentType, 
+						'content-length' = 
+						ContentLength};
+		     _ ->
+			 HeadersAsIs
+		 end,
+    
+    {NewHeaders, NewBody};
+
+post_data(_, Headers, _, []) ->
+    {Headers, ""};
+post_data(_, _, _, HeadersAsIs = [_|_]) ->
+    {HeadersAsIs, ""}.
+
+body_length(Body) when is_binary(Body) ->
+   integer_to_list(size(Body));
+
+body_length(Body) when is_list(Body) ->
+  integer_to_list(length(Body)).
+
+method(Method) ->
+    http_util:to_upper(atom_to_list(Method)).
+
+socket_type(http) ->
+    ip_comm;
+socket_type(https) ->
+    {ssl, []}.
+
+headers([], Headers) ->
+    lists:flatten(Headers);
+headers([{Key,Value} | Rest], Headers) ->
+    Header = Key ++ ": " ++ Value ++ ?CRLF,
+    headers(Rest, [Header | Headers]).
+
+handle_proxy(_, Headers) when is_list(Headers) ->
+    Headers; %% Headers as is option was specified
+handle_proxy(HttpOptions, Headers) ->
+    case HttpOptions#http_options.proxy_auth of
+	undefined ->
+	    Headers;
+	{User, Password} ->
+	    UserPasswd = http_base_64:encode(User ++ ":" ++ Password),
+	    Headers#http_request_h{'proxy-authorization' = 
+				   "Basic " ++ UserPasswd}
+    end.
+
+handle_user_info([], Headers) ->
+    Headers;
+handle_user_info(UserInfo, Headers) ->
+    case string:tokens(UserInfo, ":") of
+	[User, Passwd] ->
+	    UserPasswd = http_base_64:encode(User ++ ":" ++ Passwd),
+	    Headers#http_request_h{authorization = "Basic " ++ UserPasswd};
+	_ ->
+	    Headers
+    end.

Added: incubator/couchdb/trunk/src/couch_inets/httpc_response.erl
URL: http://svn.apache.org/viewvc/incubator/couchdb/trunk/src/couch_inets/httpc_response.erl?rev=642432&view=auto
==============================================================================
--- incubator/couchdb/trunk/src/couch_inets/httpc_response.erl (added)
+++ incubator/couchdb/trunk/src/couch_inets/httpc_response.erl Fri Mar 28 16:32:19 2008
@@ -0,0 +1,320 @@
+%% ``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(httpc_response).
+
+-include("http_internal.hrl").
+-include("httpc_internal.hrl").
+
+%% API
+-export([parse/1, result/2, send/2, error/2, is_server_closing/1, 
+	 stream_start/2]).
+
+%% Callback API - used for example if the header/body is received a
+%% little at a time on a socket. 
+-export([parse_version/1, parse_status_code/1, parse_reason_phrase/1,
+	 parse_headers/1, whole_body/1, whole_body/2]).
+
+%%%=========================================================================
+%%%  API
+%%%=========================================================================
+
+parse([Bin, MaxHeaderSize]) ->
+    parse_version(Bin, [], MaxHeaderSize, []).
+
+whole_body([Bin, Body, Length])  ->
+    whole_body(<<Body/binary, Bin/binary>>, Length).
+
+%% Functions that may be returned during the decoding process
+%% if the input data is incompleate. 
+parse_version([Bin, Version, MaxHeaderSize, Result]) ->
+    parse_version(Bin, Version, MaxHeaderSize, Result).
+
+parse_status_code([Bin, Code, MaxHeaderSize, Result]) ->
+    parse_status_code(Bin, Code, MaxHeaderSize, Result).
+
+parse_reason_phrase([Bin, Rest, Phrase, MaxHeaderSize, Result]) ->
+    parse_reason_phrase(<<Rest/binary, Bin/binary>>, Phrase, 
+			MaxHeaderSize, Result).
+
+parse_headers([Bin, Rest,Header, Headers, MaxHeaderSize, Result]) ->
+    parse_headers(<<Rest/binary, Bin/binary>>, Header, Headers, 
+		  MaxHeaderSize, Result).
+    
+whole_body(Body, Length) ->
+    case size(Body) of
+	N when N < Length, N > 0  ->
+	    {?MODULE, whole_body, [Body, Length]};
+	%% OBS!  The Server may close the connection to indicate that the
+	%% whole body is now sent instead of sending a lengh
+	%% indicator.In this case the lengh indicator will be
+	%% -1.
+	N when N >= Length, Length >= 0 -> 
+	    %% Potential trailing garbage will be thrown away in
+	    %% format_response/1 Some servers may send a 100-continue
+	    %% response without the client requesting it through an
+	    %% expect header in this case the trailing bytes may be
+	    %% part of the real response message.
+	    {ok, Body};
+	_ -> %% Length == -1
+	    {?MODULE, whole_body, [Body, Length]} 
+    end.
+
+%%-------------------------------------------------------------------------
+%% result(Response, Request) ->
+%%   Response - {StatusLine, Headers, Body}
+%%   Request - #request{}
+%%   Session - #tcp_session{}
+%%                                   
+%% Description: Checks the status code ...
+%%-------------------------------------------------------------------------
+result(Response = {{_,200,_}, _, _}, 
+       Request = #request{stream = Stream}) when Stream =/= none ->
+    stream_end(Response, Request);
+
+result(Response = {{_,100,_}, _, _}, Request) ->
+    status_continue(Response, Request);
+
+%% In redirect loop
+result(Response = {{_, Code, _}, _, _}, Request =
+       #request{redircount = Redirects,
+		settings = #http_options{autoredirect = true}}) 
+  when Code div 100 == 3, Redirects > ?HTTP_MAX_REDIRECTS ->
+    transparent(Response, Request);
+
+%% multiple choices 
+result(Response = {{_, 300, _}, _, _}, 
+       Request = #request{settings = 
+			  #http_options{autoredirect = 
+					true}}) ->
+    redirect(Response, Request);
+
+result(Response = {{_, Code, _}, _, _}, 
+       Request = #request{settings = 
+			  #http_options{autoredirect = true},
+			  method = head}) when Code == 301;
+					       Code == 302;
+					       Code == 303;
+					       Code == 307 ->
+    redirect(Response, Request);
+result(Response = {{_, Code, _}, _, _}, 
+       Request = #request{settings = 
+			  #http_options{autoredirect = true},
+			  method = get}) when Code == 301;
+					      Code == 302;
+					      Code == 303;
+					      Code == 307 ->
+    redirect(Response, Request);
+
+
+result(Response = {{_,503,_}, _, _}, Request) ->
+    status_service_unavailable(Response, Request);
+result(Response = {{_,Code,_}, _, _}, Request) when (Code div 100) == 5 ->
+    status_server_error_50x(Response, Request);
+
+result(Response, Request) -> 
+    transparent(Response, Request).
+
+send(To, Msg) -> 
+    To ! {http, Msg}.
+
+%%%========================================================================
+%%% Internal functions
+%%%========================================================================
+parse_version(<<>>, Version, MaxHeaderSize, Result) ->
+    {?MODULE, parse_version, [Version, MaxHeaderSize,Result]};
+parse_version(<<?SP, Rest/binary>>, Version, MaxHeaderSize, Result) ->
+    parse_status_code(Rest, [], MaxHeaderSize,
+		      [lists:reverse(Version) | Result]);	
+parse_version(<<Octet, Rest/binary>>, Version, MaxHeaderSize, Result) ->
+    parse_version(Rest, [Octet | Version], MaxHeaderSize,Result).
+
+parse_status_code(<<>>, StatusCodeStr, MaxHeaderSize, Result) -> 
+    {?MODULE, parse_status_code, [StatusCodeStr, MaxHeaderSize, Result]};
+parse_status_code(<<?SP, Rest/binary>>, StatusCodeStr, 
+		  MaxHeaderSize, Result) ->
+    parse_reason_phrase(Rest, [], MaxHeaderSize, 
+			[list_to_integer(lists:reverse(StatusCodeStr)) | 
+			 Result]);
+parse_status_code(<<Octet, Rest/binary>>, StatusCodeStr, 
+		  MaxHeaderSize,Result) ->
+    parse_status_code(Rest, [Octet | StatusCodeStr], MaxHeaderSize, Result).
+
+parse_reason_phrase(<<>>, Phrase, MaxHeaderSize, Result) ->
+    {?MODULE, parse_reason_phrase, [<<>>, Phrase, MaxHeaderSize,Result]};
+parse_reason_phrase(<<?CR, ?LF, Rest/binary>>, Phrase, 
+		    MaxHeaderSize, Result) ->
+    parse_headers(Rest, [], [], MaxHeaderSize,
+		  [lists:reverse(Phrase) | Result]); 
+parse_reason_phrase(<<?CR>> = Data, Phrase, MaxHeaderSize, Result) ->
+    {?MODULE, parse_reason_phrase, [Data, Phrase, MaxHeaderSize,Result]};
+parse_reason_phrase(<<Octet, Rest/binary>>, Phrase, MaxHeaderSize, Result) ->
+    parse_reason_phrase(Rest, [Octet | Phrase], MaxHeaderSize, Result).
+
+parse_headers(<<>>, Header, Headers, MaxHeaderSize, Result) -> 
+    {?MODULE, parse_headers, [<<>>, Header, Headers, MaxHeaderSize, Result]};
+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 ->   
+	    ResponseHeaderRcord = 
+		http_response:headers(HTTPHeaders, #http_response_h{}),
+	    {ok, list_to_tuple(
+		   lists:reverse([Body, ResponseHeaderRcord | Result]))};
+ 	false ->
+	    throw({error, {header_too_long, MaxHeaderSize, 
+			   MaxHeaderSize-Length}})
+    end;
+parse_headers(<<?CR,?LF,?CR>> = Data, Header, Headers, 
+	      MaxHeaderSize, Result) ->
+    {?MODULE, parse_headers, [Data, Header, Headers, MaxHeaderSize, Result]};
+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).
+
+
+%% RFC2616, Section 10.1.1
+%% Note:
+%% - Only act on the 100 status if the request included the
+%%   "Expect:100-continue" header, otherwise just ignore this response.
+status_continue(_, #request{headers = 
+			    #http_request_h{expect = "100-continue"}}) ->  
+    continue;
+
+status_continue({_,_, Data}, _) ->
+    %% The data in the body in this case is actually part of the real
+    %% response sent after the "fake" 100-continue.
+    {ignore, Data}.
+
+status_service_unavailable(Response = {_, Headers, _}, Request) ->
+    case Headers#http_response_h.'retry-after' of 
+	undefined ->
+	    status_server_error_50x(Response, Request);
+	Time when length(Time) < 3 -> % Wait only 99 s or less 
+	    NewTime = list_to_integer(Time) * 100, % time in ms
+	    {_, Data} =  format_response(Response),
+	    {retry, {NewTime, Request}, Data};
+	_ ->
+	    status_server_error_50x(Response, Request)
+    end.
+
+status_server_error_50x(Response, Request) ->
+    {Msg, _} =  format_response(Response),
+    {stop, {Request#request.id, Msg}}.
+
+
+redirect(Response = {StatusLine, Headers, Body}, Request) ->
+    {_, Data} =  format_response(Response),
+    case Headers#http_response_h.location of
+	undefined ->
+	    transparent(Response, Request);
+	RedirUrl ->
+	    case http_uri:parse(RedirUrl) of
+		{error, no_scheme} when
+		(Request#request.settings)#http_options.relaxed ->
+		    NewLocation = fix_relative_uri(Request, RedirUrl),
+		    redirect({StatusLine, Headers#http_response_h{
+					    location = NewLocation},
+			      Body}, Request);
+		{error, Reason} ->
+		    {ok, error(Request, Reason), Data};
+		%% Automatic redirection
+		{Scheme, _, Host, Port, Path,  Query} -> 
+		    NewHeaders = 
+			(Request#request.headers)#http_request_h{host = 
+								 Host},
+		    NewRequest = 
+			Request#request{redircount = 
+					Request#request.redircount+1,
+					scheme = Scheme,
+					headers = NewHeaders,
+					address = {Host,Port},
+					path = Path,
+					pquery = Query,
+					abs_uri =
+					atom_to_list(Scheme) ++ "://" ++
+                                        Host ++ ":" ++ 
+					integer_to_list(Port) ++
+					Path ++ Query},
+		    {redirect, NewRequest, Data}
+	    end
+    end.
+
+%%% Guessing that we received a relative URI, fix it to become an absoluteURI
+fix_relative_uri(Request, RedirUrl) ->
+    {Server, Port} = Request#request.address,
+    Path = Request#request.path,
+    atom_to_list(Request#request.scheme) ++ "://" ++ Server ++ ":" ++ Port
+	++ Path ++ RedirUrl.
+    
+error(#request{id = Id}, Reason) ->
+    {Id, {error, Reason}}.
+
+transparent(Response, Request) ->    
+    {Msg, Data} =  format_response(Response),
+    {ok, {Request#request.id, Msg}, Data}.
+
+stream_start(Headers, Request) ->
+    {Request#request.id, stream_start,  http_response:header_list(Headers)}.
+
+stream_end(Response, Request = #request{stream = self}) -> 
+    {{_, Headers, _}, Data} =  format_response(Response),
+    {ok, {Request#request.id, stream_end, Headers}, Data};
+stream_end(Response, Request) ->
+    {_, Data} =  format_response(Response),
+    {ok, {Request#request.id, saved_to_file}, Data}.
+
+is_server_closing(Headers) when record(Headers,http_response_h) ->
+    case Headers#http_response_h.connection of
+	"close" ->
+	    true;
+	_ ->
+	    false
+    end.
+
+format_response({StatusLine, Headers, Body = <<>>}) ->
+    {{StatusLine, http_response:header_list(Headers), Body}, <<>>};
+
+format_response({StatusLine, Headers, Body}) ->
+    Length = list_to_integer(Headers#http_response_h.'content-length'),
+    {NewBody, Data} = 
+	case Length of
+	    0 ->
+		{Body, <<>>};
+	    -1 -> % When no lenght indicator is provided
+		{Body, <<>>};
+	    Length when Length =< size(Body) ->
+		<<BodyThisReq:Length/binary, Next/binary>> = Body,
+		{BodyThisReq, Next};
+	    _ -> %% Connection prematurely ended. 
+		{Body, <<>>}
+	end,
+    {{StatusLine, http_response:header_list(Headers), NewBody}, Data}.
+