You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@couchdb.apache.org by jc...@apache.org on 2009/01/29 23:15:49 UTC

svn commit: r739047 [1/2] - in /couchdb/trunk: ./ bin/ share/www/script/ src/couchdb/ src/ibrowse/ utils/

Author: jchris
Date: Thu Jan 29 22:15:48 2009
New Revision: 739047

URL: http://svn.apache.org/viewvc?rev=739047&view=rev
Log:
Replacement of inets with ibrowse. Fixes COUCHDB-179 and enhances replication.
Thanks Jason Davies and Adam Kocoloski for the fix, Maximillian Dornseif for reporting.

Added:
    couchdb/trunk/src/ibrowse/
    couchdb/trunk/src/ibrowse/Makefile.am   (with props)
    couchdb/trunk/src/ibrowse/ibrowse.app
    couchdb/trunk/src/ibrowse/ibrowse.erl
    couchdb/trunk/src/ibrowse/ibrowse.hrl
    couchdb/trunk/src/ibrowse/ibrowse_app.erl
    couchdb/trunk/src/ibrowse/ibrowse_http_client.erl
    couchdb/trunk/src/ibrowse/ibrowse_lb.erl
    couchdb/trunk/src/ibrowse/ibrowse_lib.erl
    couchdb/trunk/src/ibrowse/ibrowse_sup.erl
    couchdb/trunk/src/ibrowse/ibrowse_test.erl
Modified:
    couchdb/trunk/CHANGES
    couchdb/trunk/Makefile.am
    couchdb/trunk/NOTICE
    couchdb/trunk/THANKS
    couchdb/trunk/bin/Makefile.am
    couchdb/trunk/bin/couchdb.tpl.in
    couchdb/trunk/configure.ac
    couchdb/trunk/share/www/script/couch_tests.js
    couchdb/trunk/src/couchdb/couch.app.tpl.in
    couchdb/trunk/src/couchdb/couch_httpd.erl
    couchdb/trunk/src/couchdb/couch_httpd_db.erl
    couchdb/trunk/src/couchdb/couch_httpd_misc_handlers.erl
    couchdb/trunk/src/couchdb/couch_rep.erl
    couchdb/trunk/src/couchdb/couch_server_sup.erl
    couchdb/trunk/utils/Makefile.am

Modified: couchdb/trunk/CHANGES
URL: http://svn.apache.org/viewvc/couchdb/trunk/CHANGES?rev=739047&r1=739046&r2=739047&view=diff
==============================================================================
--- couchdb/trunk/CHANGES (original)
+++ couchdb/trunk/CHANGES Thu Jan 29 22:15:48 2009
@@ -28,6 +28,8 @@
    dramatically. The fix keeps only one document in the write queue at a time.
  * Fix for databases sometimes incorrectly reporting that they contain 0
    documents after compaction.
+ * CouchDB now uses ibrowse instead of inets for its internal HTTP client
+   implementation. This means better replication stability.
 
 HTTP Interface:
 

Modified: couchdb/trunk/Makefile.am
URL: http://svn.apache.org/viewvc/couchdb/trunk/Makefile.am?rev=739047&r1=739046&r2=739047&view=diff
==============================================================================
--- couchdb/trunk/Makefile.am (original)
+++ couchdb/trunk/Makefile.am Thu Jan 29 22:15:48 2009
@@ -10,7 +10,7 @@
 ## License for the specific language governing permissions and limitations
 ## under the License.
 
-SUBDIRS = bin etc src/couchdb src/mochiweb share test var utils
+SUBDIRS = bin etc src/couchdb src/ibrowse src/mochiweb share test var utils
 
 localdoc_DATA = AUTHORS.gz BUGS.gz CHANGES.gz NEWS.gz README.gz THANKS.gz
 

Modified: couchdb/trunk/NOTICE
URL: http://svn.apache.org/viewvc/couchdb/trunk/NOTICE?rev=739047&r1=739046&r2=739047&view=diff
==============================================================================
--- couchdb/trunk/NOTICE (original)
+++ couchdb/trunk/NOTICE Thu Jan 29 22:15:48 2009
@@ -21,3 +21,9 @@
  * MochiWeb (http://code.google.com/p/mochiweb/)
 
    Copyright 2007, Mochi Media Coporation
+
+ * ibrowse
+ (http://jungerl.cvs.sourceforge.net/viewvc/jungerl/jungerl/lib/ibrowse/)
+
+   Copyright 2008, Chandrashekhar Mullaparthi
+   This ASF redistribution is consistent with the terms of the BSD License.
\ No newline at end of file

Modified: couchdb/trunk/THANKS
URL: http://svn.apache.org/viewvc/couchdb/trunk/THANKS?rev=739047&r1=739046&r2=739047&view=diff
==============================================================================
--- couchdb/trunk/THANKS (original)
+++ couchdb/trunk/THANKS Thu Jan 29 22:15:48 2009
@@ -12,7 +12,9 @@
  * Yoan Blanc <yo...@gmail.com>
  * Paul Carey <pa...@gmail.com>
  * Benoit Chesneau <bc...@gmail.com>
+ * Jason Davies <ja...@jasondavies.com>
  * Paul Joseph Davis <pa...@gmail.com>
+ * Maximillian Dornseif <md...@hudora.de>
  * Michael Gottesman <go...@reed.edu>
  * Michael Hendricks <mi...@ndrix.org>
  * Till Klampaeckel <ti...@klampaeckel.de>

Modified: couchdb/trunk/bin/Makefile.am
URL: http://svn.apache.org/viewvc/couchdb/trunk/bin/Makefile.am?rev=739047&r1=739046&r2=739047&view=diff
==============================================================================
--- couchdb/trunk/bin/Makefile.am (original)
+++ couchdb/trunk/bin/Makefile.am Thu Jan 29 22:15:48 2009
@@ -28,8 +28,9 @@
 	    -e "s|%ICU_CONFIG%|$(ICU_CONFIG)|g" \
 	    -e "s|%bindir%|@bindir@|g" \
 	    -e "s|%localerlanglibdir%|@localerlanglibdir@|g" \
-	    -e "s|%mochiwebebindir%|couch-@version@/ebin|g" \
-	    -e "s|%couchdbebindir%|mochiweb-r82/ebin|g" \
+	    -e "s|%couchdbebindir%|couch-@version@/ebin|g" \
+	    -e "s|%mochiwebebindir%|mochiweb-r82/ebin|g" \
+	    -e "s|%ibrowseebindir%|ibrowse-1.4.1/ebin|g" \
 	    -e "s|%defaultini%|default.ini|g" \
 	    -e "s|%localini%|local.ini|g" \
 	    -e "s|%localconfdir%|@localconfdir@|g" \

Modified: couchdb/trunk/bin/couchdb.tpl.in
URL: http://svn.apache.org/viewvc/couchdb/trunk/bin/couchdb.tpl.in?rev=739047&r1=739046&r2=739047&view=diff
==============================================================================
--- couchdb/trunk/bin/couchdb.tpl.in (original)
+++ couchdb/trunk/bin/couchdb.tpl.in Thu Jan 29 22:15:48 2009
@@ -183,11 +183,12 @@
         %ERL% $interactive_option -smp auto -sasl errlog_type error +K true \
         -pa %localerlanglibdir%/%couchdbebindir% \
             %localerlanglibdir%/%mochiwebebindir% \
-        -eval \"application:load(inets)\" \
+            %localerlanglibdir%/%ibrowseebindir% \
+        -eval \"application:load(ibrowse)\" \
         -eval \"application:load(crypto)\" \
         -eval \"application:load(couch)\" \
         -eval \"crypto:start()\" \
-        -eval \"inets:start()\" \
+        -eval \"ibrowse:start()\" \
         -eval \"couch_server:start([$start_arguments]), receive done -> done end.\" "
     if test "$BACKGROUND_BOOLEAN" = "true" \
         -a "$RECURSED_BOOLEAN" = "false"; then

Modified: couchdb/trunk/configure.ac
URL: http://svn.apache.org/viewvc/couchdb/trunk/configure.ac?rev=739047&r1=739046&r2=739047&view=diff
==============================================================================
--- couchdb/trunk/configure.ac (original)
+++ couchdb/trunk/configure.ac Thu Jan 29 22:15:48 2009
@@ -267,6 +267,7 @@
 AC_CONFIG_FILES([share/Makefile])
 AC_CONFIG_FILES([src/couchdb/couch.app.tpl])
 AC_CONFIG_FILES([src/couchdb/Makefile])
+AC_CONFIG_FILES([src/ibrowse/Makefile])
 AC_CONFIG_FILES([src/mochiweb/Makefile])
 AC_CONFIG_FILES([test/Makefile])
 AC_CONFIG_FILES([utils/Makefile])

Modified: couchdb/trunk/share/www/script/couch_tests.js
URL: http://svn.apache.org/viewvc/couchdb/trunk/share/www/script/couch_tests.js?rev=739047&r1=739046&r2=739047&view=diff
==============================================================================
--- couchdb/trunk/share/www/script/couch_tests.js [utf-8] (original)
+++ couchdb/trunk/share/www/script/couch_tests.js [utf-8] Thu Jan 29 22:15:48 2009
@@ -2134,6 +2134,19 @@
             T(docA._rev == docB._rev);
           };
         },
+
+        design_docs_test: new function() {
+          // make sure design docs replicate properly
+          this.init = function(dbA, dbB) {
+            dbA.save({ _id:"_design/test" });
+          };
+
+          this.afterAB1 = function() {
+            var docA = dbA.open("_design/test");
+            var docB = dbB.open("_design/test");
+            T(docA._rev == docB._rev);
+          };
+        },
       
         attachments_test: new function () {
           // Test attachments

Modified: couchdb/trunk/src/couchdb/couch.app.tpl.in
URL: http://svn.apache.org/viewvc/couchdb/trunk/src/couchdb/couch.app.tpl.in?rev=739047&r1=739046&r2=739047&view=diff
==============================================================================
--- couchdb/trunk/src/couchdb/couch.app.tpl.in (original)
+++ couchdb/trunk/src/couchdb/couch.app.tpl.in Thu Jan 29 22:15:48 2009
@@ -24,4 +24,4 @@
                            couch_view,
                            couch_query_servers,
                            couch_db_update_notifier_sup]},
-              {applications,[kernel,stdlib,crypto,inets,mochiweb]}]}.
+              {applications,[kernel,stdlib,crypto,ibrowse,mochiweb]}]}.

Modified: couchdb/trunk/src/couchdb/couch_httpd.erl
URL: http://svn.apache.org/viewvc/couchdb/trunk/src/couchdb/couch_httpd.erl?rev=739047&r1=739046&r2=739047&view=diff
==============================================================================
--- couchdb/trunk/src/couchdb/couch_httpd.erl (original)
+++ couchdb/trunk/src/couchdb/couch_httpd.erl Thu Jan 29 22:15:48 2009
@@ -15,7 +15,7 @@
 
 -export([start_link/0, stop/0, handle_request/3]).
 
--export([header_value/2,header_value/3,qs_value/2,qs_value/3,qs/1,path/1]).
+-export([header_value/2,header_value/3,qs_value/2,qs_value/3,qs/1,path/1,absolute_uri/2]).
 -export([verify_is_server_admin/1,unquote/1,quote/1,recv/2]).
 -export([parse_form/1,json_body/1,body/1,doc_etag/1, make_etag/1, etag_respond/3]).
 -export([primary_header_value/2,partition/1,serve_file/3]).
@@ -242,6 +242,15 @@
 path(#httpd{mochi_req=MochiReq}) ->
     MochiReq:get(path).
 
+absolute_uri(#httpd{mochi_req=MochiReq}, Path) ->
+    Host = case MochiReq:get_header_value("Host") of
+        undefined ->
+            {ok, {Address, Port}} = inet:sockname(MochiReq:get(socket)),
+            inet_parse:ntoa(Address) ++ ":" ++ integer_to_list(Port);
+        Value -> Value
+    end,
+    "http://" ++ Host ++ Path.
+
 unquote(UrlEncodedString) ->
     mochiweb_util:unquote(UrlEncodedString).
 

Modified: couchdb/trunk/src/couchdb/couch_httpd_db.erl
URL: http://svn.apache.org/viewvc/couchdb/trunk/src/couchdb/couch_httpd_db.erl?rev=739047&r1=739046&r2=739047&view=diff
==============================================================================
--- couchdb/trunk/src/couchdb/couch_httpd_db.erl (original)
+++ couchdb/trunk/src/couchdb/couch_httpd_db.erl Thu Jan 29 22:15:48 2009
@@ -261,7 +261,8 @@
     PathFront = "/" ++ couch_httpd:quote(binary_to_list(DbName)) ++ "/",
     RawSplit = regexp:split(MochiReq:get(raw_path),"_design%2F"),
     {ok, [PathFront|PathTail]} = RawSplit,
-    RedirectTo = PathFront ++ "_design/" ++ mochiweb_util:join(PathTail, "%2F"),
+    RedirectTo = couch_httpd:absolute_uri(Req, PathFront ++ "_design/" ++ 
+        mochiweb_util:join(PathTail, "%2F")),
     couch_httpd:send_response(Req, 301, [{"Location", RedirectTo}], <<>>);
 
 db_req(#httpd{path_parts=[_DbName,<<"_design">>,Name]}=Req, Db) ->

Modified: couchdb/trunk/src/couchdb/couch_httpd_misc_handlers.erl
URL: http://svn.apache.org/viewvc/couchdb/trunk/src/couchdb/couch_httpd_misc_handlers.erl?rev=739047&r1=739046&r2=739047&view=diff
==============================================================================
--- couchdb/trunk/src/couchdb/couch_httpd_misc_handlers.erl (original)
+++ couchdb/trunk/src/couchdb/couch_httpd_misc_handlers.erl Thu Jan 29 22:15:48 2009
@@ -50,7 +50,8 @@
         couch_httpd:serve_file(Req, RelativePath, DocumentRoot);
     {_ActionKey, "", _RelativePath} ->
         % GET /_utils
-        couch_httpd:send_response(Req, 301, [{"Location", "/_utils/"}], <<>>)
+        Headers = [{"Location", couch_httpd:absolute_uri(Req, "/_utils/")}],
+        couch_httpd:send_response(Req, 301, Headers, <<>>)
     end;
 handle_utils_dir_req(Req, _) ->
     send_method_not_allowed(Req, "GET,HEAD").

Modified: couchdb/trunk/src/couchdb/couch_rep.erl
URL: http://svn.apache.org/viewvc/couchdb/trunk/src/couchdb/couch_rep.erl?rev=739047&r1=739046&r2=739047&view=diff
==============================================================================
--- couchdb/trunk/src/couchdb/couch_rep.erl (original)
+++ couchdb/trunk/src/couchdb/couch_rep.erl Thu Jan 29 22:15:48 2009
@@ -157,30 +157,54 @@
     end.
 
 pull_rep(DbTarget, DbSource, SourceSeqNum) ->
-    http:set_options([{max_pipeline_length, 101}, {pipeline_timeout, 5000}]),
     {ok, {NewSeq, Stats}} = 
         enum_docs_since(DbSource, DbTarget, SourceSeqNum, {SourceSeqNum, []}),
-    http:set_options([{max_pipeline_length, 2}, {pipeline_timeout, 0}]),
     {NewSeq, Stats}.
 
 do_http_request(Url, Action, Headers) ->
     do_http_request(Url, Action, Headers, []).
 
 do_http_request(Url, Action, Headers, JsonBody) ->
-    ?LOG_DEBUG("couch_rep HTTP client request:", []),
-    ?LOG_DEBUG("\tAction: ~p", [Action]),
-    ?LOG_DEBUG("\tUrl: ~p", [Url]),
-    Request =
+    do_http_request(Url, Action, Headers, JsonBody, 10).
+
+do_http_request(Url, Action, _Headers, _JsonBody, 0) ->
+    ?LOG_ERROR("couch_rep HTTP ~p request failed after 10 retries: ~p", 
+        [Action, Url]);
+do_http_request(Url, Action, Headers, JsonBody, Retries) ->
+    ?LOG_DEBUG("couch_rep HTTP ~p request: ~p", [Action, Url]),
+    Body =
     case JsonBody of
     [] ->
-        {Url, Headers};
+        <<>>;
     _ ->
-        {Url, Headers, "application/json; charset=utf-8", iolist_to_binary(?JSON_ENCODE(JsonBody))}
+        iolist_to_binary(?JSON_ENCODE(JsonBody))
     end,
-    {ok, {{_, ResponseCode,_},_Headers, ResponseBody}} = http:request(Action, Request, [], []),
-    if
-    ResponseCode >= 200, ResponseCode < 500 ->
-        ?JSON_DECODE(ResponseBody)
+    Options = [
+        {content_type, "application/json; charset=utf-8"},
+        {max_pipeline_size, 101},
+        {transfer_encoding, {chunked, 65535}}
+    ],
+    case ibrowse:send_req(Url, Headers, Action, Body, Options) of
+    {ok, Status, ResponseHeaders, ResponseBody} ->
+        ResponseCode = list_to_integer(Status),
+        if
+        ResponseCode >= 200, ResponseCode < 300 ->
+            ?JSON_DECODE(ResponseBody);
+        ResponseCode >= 300, ResponseCode < 400 ->
+            RedirectUrl = mochiweb_headers:get_value("Location", 
+                mochiweb_headers:make(ResponseHeaders)),
+            do_http_request(RedirectUrl, Action, Headers, JsonBody, Retries-1);
+        ResponseCode >= 400, ResponseCode < 500 -> 
+            ?JSON_DECODE(ResponseBody);        
+        ResponseCode == 500 ->
+            ?LOG_INFO("retrying couch_rep HTTP ~p request due to 500 error: ~p",
+                [Action, Url]),
+            do_http_request(Url, Action, Headers, JsonBody, Retries - 1)
+        end;
+    {error, Reason} ->
+        ?LOG_INFO("retrying couch_rep HTTP ~p request due to {error, ~p}: ~p", 
+            [Action, Reason, Url]),
+        do_http_request(Url, Action, Headers, JsonBody, Retries - 1)
     end.
 
 save_docs_buffer(DbTarget, DocsBuffer, []) ->
@@ -223,20 +247,17 @@
     {'DOWN', Ref, _, _, Reason} -> exit(Reason)
 end.
 
-enum_docs_parallel(DbS, DbT, DocInfoList) ->
-    UpdateSeqs = [D#doc_info.update_seq || D <- DocInfoList],
+enum_docs_parallel(DbS, DbT, InfoList) ->
+    UpdateSeqs = [Seq || {_, Seq, _, _} <- InfoList],
     SaveDocsPid = spawn_link(fun() -> save_docs_buffer(DbT,[],UpdateSeqs) end),
     
-    Stats = pmap(fun(SrcDocInfo) ->
-        #doc_info{id=Id,
-            rev=Rev,
-            conflict_revs=Conflicts,
-            deleted_conflict_revs=DelConflicts,
-            update_seq=Seq} = SrcDocInfo,
-        SrcRevs = [Rev | Conflicts] ++ DelConflicts,
-        
-        case get_missing_revs(DbT, [{Id, SrcRevs}]) of
-        {ok, [{Id, MissingRevs}]} ->
+    Stats = pmap(fun({Id, Seq, SrcRevs, MissingRevs}) ->
+        case MissingRevs of
+        [] ->
+            SaveDocsPid ! {self(), skip, Seq},
+            receive got_it -> ok end,
+            [{missing_checked, length(SrcRevs)}];
+        _ ->
             {ok, DocResults} = open_doc_revs(DbS, Id, MissingRevs, [latest]),
             
             % only save successful reads
@@ -247,13 +268,9 @@
             receive got_it -> ok end,
             [{missing_checked, length(SrcRevs)},
              {missing_found, length(MissingRevs)},
-             {docs_read, length(Docs)}];
-        {ok, []} ->
-            SaveDocsPid ! {self(), skip, Seq},
-            receive got_it -> ok end,
-            [{missing_checked, length(SrcRevs)}]
-        end    
-    end, DocInfoList),
+             {docs_read, length(Docs)}]
+        end
+    end, InfoList),
     
     SaveDocsPid ! {self(), shutdown},
     
@@ -345,7 +362,22 @@
     [] ->
         {ok, InAcc};
     _ ->
-        Stats = enum_docs_parallel(DbSource, DbTarget, DocInfoList),
+        UpdateSeqs = [D#doc_info.update_seq || D <- DocInfoList],
+        SrcRevsList = lists:map(fun(SrcDocInfo) ->
+            #doc_info{id=Id,
+                rev=Rev,
+                conflict_revs=Conflicts,
+                deleted_conflict_revs=DelConflicts
+            } = SrcDocInfo,
+            SrcRevs = [Rev | Conflicts] ++ DelConflicts,
+            {Id, SrcRevs}
+        end, DocInfoList),        
+        {ok, MissingRevsList} = get_missing_revs(DbTarget, SrcRevsList),
+        InfoList = lists:map(fun({{Id, SrcRevs}, Seq}) ->
+            MissingRevs = proplists:get_value(Id, MissingRevsList, []),
+            {Id, Seq, SrcRevs, MissingRevs}
+        end, lists:zip(SrcRevsList, UpdateSeqs)),
+        Stats = enum_docs_parallel(DbSource, DbTarget, InfoList),
         OldStats = element(2, InAcc),
         TotalStats = [
             {<<"missing_checked">>, 

Modified: couchdb/trunk/src/couchdb/couch_server_sup.erl
URL: http://svn.apache.org/viewvc/couchdb/trunk/src/couchdb/couch_server_sup.erl?rev=739047&r1=739046&r2=739047&view=diff
==============================================================================
--- couchdb/trunk/src/couchdb/couch_server_sup.erl (original)
+++ couchdb/trunk/src/couchdb/couch_server_sup.erl Thu Jan 29 22:15:48 2009
@@ -102,7 +102,7 @@
    
 
     % ensure these applications are running
-    application:start(inets),
+    application:start(ibrowse),
     application:start(crypto),
 
     {ok, Pid} = supervisor:start_link(

Added: couchdb/trunk/src/ibrowse/Makefile.am
URL: http://svn.apache.org/viewvc/couchdb/trunk/src/ibrowse/Makefile.am?rev=739047&view=auto
==============================================================================
--- couchdb/trunk/src/ibrowse/Makefile.am (added)
+++ couchdb/trunk/src/ibrowse/Makefile.am Thu Jan 29 22:15:48 2009
@@ -0,0 +1,47 @@
+## Licensed under the Apache License, Version 2.0 (the "License"); you may not
+## use this file except in compliance with the License.  You may obtain a copy
+## of the License at
+##
+##   http://www.apache.org/licenses/LICENSE-2.0
+##
+## Unless required by applicable law or agreed to in writing, software
+## distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+## WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+## License for the specific language governing permissions and limitations under
+## the License.
+
+ibrowseebindir = $(localerlanglibdir)/ibrowse-1.4.1/ebin
+
+ibrowse_file_collection = \
+    ibrowse.erl \
+    ibrowse_app.erl \
+    ibrowse_http_client.erl \
+    ibrowse_lb.erl \
+    ibrowse_lib.erl \
+    ibrowse_sup.erl \
+    ibrowse_test.erl
+
+ibrowseebin_static_file = ibrowse.app
+
+ibrowseebin_make_generated_file_list = \
+    ibrowse.beam \
+    ibrowse_app.beam \
+    ibrowse_http_client.beam \
+    ibrowse_lb.beam \
+    ibrowse_lib.beam \
+    ibrowse_sup.beam \
+    ibrowse_test.beam
+
+ibrowseebin_DATA = \
+    $(ibrowseebin_static_file) \
+    $(ibrowseebin_make_generated_file_list)
+
+EXTRA_DIST =  \
+    $(ibrowse_file_collection) \
+    $(ibrowseebin_static_file)
+
+CLEANFILES = \
+    $(ibrowseebin_make_generated_file_list)
+
+%.beam: %.erl
+	$(ERLC) $<

Propchange: couchdb/trunk/src/ibrowse/Makefile.am
------------------------------------------------------------------------------
    svn:eol-style = native

Added: couchdb/trunk/src/ibrowse/ibrowse.app
URL: http://svn.apache.org/viewvc/couchdb/trunk/src/ibrowse/ibrowse.app?rev=739047&view=auto
==============================================================================
--- couchdb/trunk/src/ibrowse/ibrowse.app (added)
+++ couchdb/trunk/src/ibrowse/ibrowse.app Thu Jan 29 22:15:48 2009
@@ -0,0 +1,13 @@
+{application, ibrowse,
+        [{description, "HTTP client application"},
+         {vsn, "1.4.1"},
+         {modules, [ ibrowse, 
+		     ibrowse_http_client, 
+		     ibrowse_app, 
+		     ibrowse_sup, 
+		     ibrowse_lib,
+		     ibrowse_lb ]},
+         {registered, []},
+         {applications, [kernel,stdlib,sasl]},
+	 {env, []},
+	 {mod, {ibrowse_app, []}}]}.

Added: couchdb/trunk/src/ibrowse/ibrowse.erl
URL: http://svn.apache.org/viewvc/couchdb/trunk/src/ibrowse/ibrowse.erl?rev=739047&view=auto
==============================================================================
--- couchdb/trunk/src/ibrowse/ibrowse.erl (added)
+++ couchdb/trunk/src/ibrowse/ibrowse.erl Thu Jan 29 22:15:48 2009
@@ -0,0 +1,628 @@
+%%%-------------------------------------------------------------------
+%%% File    : ibrowse.erl
+%%% Author  : Chandrashekhar Mullaparthi <ch...@t-mobile.co.uk>
+%%% Description : Load balancer process for HTTP client connections.
+%%%
+%%% Created : 11 Oct 2003 by Chandrashekhar Mullaparthi <ch...@t-mobile.co.uk>
+%%%-------------------------------------------------------------------
+%% @author Chandrashekhar Mullaparthi <chandrashekhar dot mullaparthi at gmail dot com>
+%% @copyright 2005-2008 Chandrashekhar Mullaparthi
+%% @version 1.4
+%% @doc The ibrowse application implements an HTTP 1.1 client. This
+%% module implements the API of the HTTP client. There is one named
+%% process called 'ibrowse' which assists in load balancing and maintaining configuration. There is one load balancing process per unique webserver. There is
+%% one process to handle one TCP connection to a webserver
+%% (implemented in the module ibrowse_http_client). Multiple connections to a
+%% webserver are setup based on the settings for each webserver. The
+%% ibrowse process also determines which connection to pipeline a
+%% certain request on.  The functions to call are send_req/3,
+%% send_req/4, send_req/5, send_req/6.
+%%
+%% <p>Here are a few sample invocations.</p>
+%%
+%% <code>
+%% ibrowse:send_req("http://intranet/messenger/", [], get). 
+%% <br/><br/>
+%% 
+%% ibrowse:send_req("http://www.google.com/", [], get, [], 
+%% 		 [{proxy_user, "XXXXX"},
+%% 		  {proxy_password, "XXXXX"},
+%% 		  {proxy_host, "proxy"},
+%% 		  {proxy_port, 8080}], 1000). 
+%% <br/><br/>
+%%
+%%ibrowse:send_req("http://www.erlang.org/download/otp_src_R10B-3.tar.gz", [], get, [],
+%% 		 [{proxy_user, "XXXXX"},
+%% 		  {proxy_password, "XXXXX"},
+%% 		  {proxy_host, "proxy"},
+%% 		  {proxy_port, 8080},
+%% 		  {save_response_to_file, true}], 1000).
+%% <br/><br/>
+%%
+%% ibrowse:send_req("http://www.erlang.org", [], head).
+%%
+%% <br/><br/>
+%% ibrowse:send_req("http://www.sun.com", [], options).
+%%
+%% <br/><br/>
+%% ibrowse:send_req("http://www.bbc.co.uk", [], trace).
+%%
+%% <br/><br/>
+%% ibrowse:send_req("http://www.google.com", [], get, [], 
+%%                   [{stream_to, self()}]).
+%% </code>
+%%
+%% <p>A driver exists which implements URL encoding in C, but the
+%% speed achieved using only erlang has been good enough, so the
+%% driver isn't actually used.</p>
+
+-module(ibrowse).
+-vsn('$Id: ibrowse.erl,v 1.7 2008/05/21 15:28:11 chandrusf Exp $ ').
+
+-behaviour(gen_server).
+%%--------------------------------------------------------------------
+%% Include files
+%%--------------------------------------------------------------------
+
+%%--------------------------------------------------------------------
+%% External exports
+-export([start_link/0, start/0, stop/0]).
+
+%% gen_server callbacks
+-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
+	 terminate/2, code_change/3]).
+
+%% API interface
+-export([
+	 rescan_config/0,
+	 rescan_config/1,
+	 get_config_value/1,
+	 get_config_value/2,
+	 spawn_worker_process/2,
+	 spawn_link_worker_process/2,
+	 stop_worker_process/1,
+	 send_req/3,
+	 send_req/4,
+	 send_req/5,
+	 send_req/6,
+	 send_req_direct/4,
+	 send_req_direct/5,
+	 send_req_direct/6,
+	 send_req_direct/7,
+	 set_max_sessions/3,
+	 set_max_pipeline_size/3,
+	 set_dest/3,
+	 trace_on/0,
+	 trace_off/0,
+	 trace_on/2,
+	 trace_off/2,
+	 show_dest_status/2
+	]).
+
+-ifdef(debug).
+-compile(export_all).
+-endif.
+
+-import(ibrowse_lib, [
+		      parse_url/1,
+		      printable_date/0,
+		      get_value/2,
+		      get_value/3,
+		      do_trace/2
+		     ]).
+		      
+-record(state, {trace = false}).
+
+-include("ibrowse.hrl").
+
+-define(DEF_MAX_SESSIONS,10).
+-define(DEF_MAX_PIPELINE_SIZE,10).
+
+%%====================================================================
+%% External functions
+%%====================================================================
+%%--------------------------------------------------------------------
+%% Function: start_link/0
+%% Description: Starts the server
+%%--------------------------------------------------------------------
+%% @doc Starts the ibrowse process linked to the calling process. Usually invoked by the supervisor ibrowse_sup
+%% @spec start_link() -> {ok, pid()}
+start_link() ->
+    gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
+
+%% @doc Starts the ibrowse process without linking. Useful when testing using the shell
+start() ->
+    gen_server:start({local, ?MODULE}, ?MODULE, [], [{debug, []}]).
+
+%% @doc Stop the ibrowse process. Useful when testing using the shell.
+stop() ->
+    catch gen_server:call(ibrowse, stop).
+
+%% @doc This is the basic function to send a HTTP request.
+%% The Status return value indicates the HTTP status code returned by the webserver
+%% @spec send_req(Url::string(), Headers::headerList(), Method::method()) -> response()
+%% headerList() = [{header(), value()}]
+%% header() = atom() | string()
+%% value() = term()
+%% method() = get | post | head | options | put | delete | trace | mkcol | propfind | proppatch | lock | unlock | move | copy
+%% Status = string()
+%% ResponseHeaders = [respHeader()]
+%% respHeader() = {headerName(), headerValue()}
+%% headerName() = string()
+%% headerValue() = string()
+%% response() = {ok, Status, ResponseHeaders, ResponseBody} | {error, Reason}
+%% ResponseBody = string() | {file, Filename}
+%% Reason = term()
+send_req(Url, Headers, Method) ->
+    send_req(Url, Headers, Method, [], []).
+
+%% @doc Same as send_req/3. 
+%% If a list is specified for the body it has to be a flat list. The body can also be a fun/0 or a fun/1. <br/>
+%% If fun/0, the connection handling process will repeatdely call the fun until it returns an error or eof. <pre>Fun() = {ok, Data} | eof</pre><br/>
+%% If fun/1, the connection handling process will repeatedly call the fun with the supplied state until it returns an error or eof. <pre>Fun(State) = {ok, Data} | {ok, Data, NewState} | eof</pre>
+%% @spec send_req(Url, Headers, Method::method(), Body::body()) -> response()
+%% body() = [] | string() | binary() | fun_arity_0() | {fun_arity_1(), initial_state()}
+%% initial_state() = term()
+send_req(Url, Headers, Method, Body) ->
+    send_req(Url, Headers, Method, Body, []).
+
+%% @doc Same as send_req/4. 
+%% For a description of SSL Options, look in the ssl manpage. If the
+%% HTTP Version to use is not specified, the default is 1.1.
+%% <br/>
+%% <p>The <code>host_header</code> is useful in the case where ibrowse is
+%% connecting to a component such as <a
+%% href="http://www.stunnel.org">stunnel</a> which then sets up a
+%% secure connection to a webserver. In this case, the URL supplied to
+%% ibrowse must have the stunnel host/port details, but that won't
+%% make sense to the destination webserver. This option can then be
+%% used to specify what should go in the <code>Host</code> header in
+%% the request.</p>
+%% <ul>
+%% <li>When both the options <code>save_response_to_file</code> and <code>stream_to</code> 
+%% are specified, the former takes precedence.</li>
+%%
+%% <li>For the <code>save_response_to_file</code> option, the response body is saved to
+%% file only if the status code is in the 200-299 range. If not, the response body is returned
+%% as a string.</li>
+%% <li>Whenever an error occurs in the processing of a request, ibrowse will return as much
+%% information as it has, such as HTTP Status Code and HTTP Headers. When this happens, the response
+%% is of the form <code>{error, {Reason, {stat_code, StatusCode}, HTTP_headers}}</code></li>
+%% </ul>
+%% @spec send_req(Url::string(), Headers::headerList(), Method::method(), Body::body(), Options::optionList()) -> response()
+%% optionList() = [option()]
+%% option() = {max_sessions, integer()}        |
+%%          {max_pipeline_size, integer()}     |
+%%          {trace, boolean()}                 | 
+%%          {is_ssl, boolean()}                |
+%%          {ssl_options, [SSLOpt]}            |
+%%          {pool_name, atom()}                |
+%%          {proxy_host, string()}             |
+%%          {proxy_port, integer()}            |
+%%          {proxy_user, string()}             |
+%%          {proxy_password, string()}         |
+%%          {use_absolute_uri, boolean()}      |
+%%          {basic_auth, {username(), password()}} |
+%%          {cookie, string()}                 |
+%%          {content_length, integer()}        |
+%%          {content_type, string()}           |
+%%          {save_response_to_file, srtf()}    |
+%%          {stream_to, process()}             |
+%%          {http_vsn, {MajorVsn, MinorVsn}}   |
+%%          {host_header, string()}            |
+%%          {transfer_encoding, {chunked, ChunkSize}}
+%% 
+%% process() = pid() | atom()
+%% username() = string()
+%% password() = string()
+%% SSLOpt = term()
+%% ChunkSize = integer()
+%% srtf() = boolean() | filename()
+%% filename() = string()
+%% 
+send_req(Url, Headers, Method, Body, Options) ->
+    send_req(Url, Headers, Method, Body, Options, 30000).
+
+%% @doc Same as send_req/5. 
+%% All timeout values are in milliseconds.
+%% @spec send_req(Url, Headers::headerList(), Method::method(), Body::body(), Options::optionList(), Timeout) -> response()
+%% Timeout = integer() | infinity
+send_req(Url, Headers, Method, Body, Options, Timeout) ->
+    case catch parse_url(Url) of
+	#url{host = Host,
+	     port = Port} = Parsed_url ->
+	    Lb_pid = case ets:lookup(ibrowse_lb, {Host, Port}) of
+			 [] ->
+			     get_lb_pid(Parsed_url);
+			 [#lb_pid{pid = Lb_pid_1}] ->
+			     Lb_pid_1
+		     end,
+	    Max_sessions = get_max_sessions(Host, Port, Options),
+	    Max_pipeline_size = get_max_pipeline_size(Host, Port, Options),
+	    Options_1 = merge_options(Host, Port, Options),
+	    {SSLOptions, IsSSL} =
+		case get_value(is_ssl, Options_1, false) of
+		    false -> {[], false};
+		    true -> {get_value(ssl_options, Options_1), true}
+		end,
+	    case ibrowse_lb:spawn_connection(Lb_pid, Parsed_url,
+					     Max_sessions, 
+					     Max_pipeline_size,
+					     {SSLOptions, IsSSL}) of
+		{ok, Conn_Pid} ->
+		    do_send_req(Conn_Pid, Parsed_url, Headers,
+				Method, Body, Options_1, Timeout);
+		Err ->
+		    Err
+	    end;
+	Err ->
+	    {error, {url_parsing_failed, Err}}
+    end.
+
+merge_options(Host, Port, Options) ->
+    Config_options = get_config_value({options, Host, Port}, []),
+    lists:foldl(
+      fun({Key, Val}, Acc) ->
+			case lists:keysearch(Key, 1, Options) of
+			    false ->
+				[{Key, Val} | Acc];
+			    _ ->
+				Acc
+			end
+      end, Options, Config_options).
+
+get_lb_pid(Url) ->
+    gen_server:call(?MODULE, {get_lb_pid, Url}).
+
+get_max_sessions(Host, Port, Options) ->
+    get_value(max_sessions, Options,
+	      get_config_value({max_sessions, Host, Port}, ?DEF_MAX_SESSIONS)).
+
+get_max_pipeline_size(Host, Port, Options) ->
+    get_value(max_pipeline_size, Options,
+	      get_config_value({max_pipeline_size, Host, Port}, ?DEF_MAX_PIPELINE_SIZE)).
+
+%% @doc Deprecated. Use set_max_sessions/3 and set_max_pipeline_size/3
+%% for achieving the same effect.
+set_dest(Host, Port, [{max_sessions, Max} | T]) ->
+    set_max_sessions(Host, Port, Max),
+    set_dest(Host, Port, T);
+set_dest(Host, Port, [{max_pipeline_size, Max} | T]) ->
+    set_max_pipeline_size(Host, Port, Max),
+    set_dest(Host, Port, T);
+set_dest(Host, Port, [{trace, Bool} | T]) when Bool == true; Bool == false ->
+    ibrowse ! {trace, true, Host, Port},
+    set_dest(Host, Port, T);
+set_dest(_Host, _Port, [H | _]) ->
+    exit({invalid_option, H});
+set_dest(_, _, []) ->
+    ok.
+    
+%% @doc Set the maximum number of connections allowed to a specific Host:Port.
+%% @spec set_max_sessions(Host::string(), Port::integer(), Max::integer()) -> ok
+set_max_sessions(Host, Port, Max) when is_integer(Max), Max > 0 ->
+    gen_server:call(?MODULE, {set_config_value, {max_sessions, Host, Port}, Max}).
+
+%% @doc Set the maximum pipeline size for each connection to a specific Host:Port.
+%% @spec set_max_pipeline_size(Host::string(), Port::integer(), Max::integer()) -> ok
+set_max_pipeline_size(Host, Port, Max) when is_integer(Max), Max > 0 ->
+    gen_server:call(?MODULE, {set_config_value, {max_pipeline_size, Host, Port}, Max}).
+
+do_send_req(Conn_Pid, Parsed_url, Headers, Method, Body, Options, Timeout) ->
+    case catch ibrowse_http_client:send_req(Conn_Pid, Parsed_url,
+					    Headers, Method, Body,
+					    Options, Timeout) of
+	{'EXIT', {timeout, _}} ->
+	    {error, req_timedout};
+	{'EXIT', Reason} ->
+	    {error, {'EXIT', Reason}};
+	Ret ->
+	    Ret
+    end.
+
+%% @doc Creates a HTTP client process to the specified Host:Port which
+%% is not part of the load balancing pool. This is useful in cases
+%% where some requests to a webserver might take a long time whereas
+%% some might take a very short time. To avoid getting these quick
+%% requests stuck in the pipeline behind time consuming requests, use
+%% this function to get a handle to a connection process. <br/>
+%% <b>Note:</b> Calling this function only creates a worker process. No connection
+%% is setup. The connection attempt is made only when the first
+%% request is sent via any of the send_req_direct/4,5,6,7 functions.<br/>
+%% <b>Note:</b> It is the responsibility of the calling process to control
+%% pipeline size on such connections.
+%%
+%% @spec spawn_worker_process(Host::string(), Port::integer()) -> {ok, pid()}
+spawn_worker_process(Host, Port) ->
+    ibrowse_http_client:start({Host, Port}).
+
+%% @doc Same as spawn_worker_process/2 except the the calling process
+%% is linked to the worker process which is spawned.
+spawn_link_worker_process(Host, Port) ->
+    ibrowse_http_client:start_link({Host, Port}).
+
+%% @doc Terminate a worker process spawned using
+%% spawn_worker_process/2 or spawn_link_worker_process/2. Requests in
+%% progress will get the error response <pre>{error, closing_on_request}</pre>
+%% @spec stop_worker_process(Conn_pid::pid()) -> ok
+stop_worker_process(Conn_pid) ->
+    ibrowse_http_client:stop(Conn_pid).
+
+%% @doc Same as send_req/3 except that the first argument is the PID
+%% returned by spawn_worker_process/2 or spawn_link_worker_process/2
+send_req_direct(Conn_pid, Url, Headers, Method) ->
+    send_req_direct(Conn_pid, Url, Headers, Method, [], []).
+
+%% @doc Same as send_req/4 except that the first argument is the PID
+%% returned by spawn_worker_process/2 or spawn_link_worker_process/2
+send_req_direct(Conn_pid, Url, Headers, Method, Body) ->
+    send_req_direct(Conn_pid, Url, Headers, Method, Body, []).
+
+%% @doc Same as send_req/5 except that the first argument is the PID
+%% returned by spawn_worker_process/2 or spawn_link_worker_process/2
+send_req_direct(Conn_pid, Url, Headers, Method, Body, Options) ->
+    send_req_direct(Conn_pid, Url, Headers, Method, Body, Options, 30000).
+
+%% @doc Same as send_req/6 except that the first argument is the PID
+%% returned by spawn_worker_process/2 or spawn_link_worker_process/2
+send_req_direct(Conn_pid, Url, Headers, Method, Body, Options, Timeout) ->
+    case catch parse_url(Url) of
+	#url{host = Host,
+	     port = Port} = Parsed_url ->
+	    Options_1 = merge_options(Host, Port, Options),
+	    case do_send_req(Conn_pid, Parsed_url, Headers, Method, Body, Options_1, Timeout) of
+		{error, {'EXIT', {noproc, _}}} ->
+		    {error, worker_is_dead};
+		Ret ->
+		    Ret
+	    end;
+	Err ->
+	    {error, {url_parsing_failed, Err}}
+    end.
+    
+%% @doc Turn tracing on for the ibrowse process
+trace_on() ->
+    ibrowse ! {trace, true}.
+%% @doc Turn tracing off for the ibrowse process
+trace_off() ->
+    ibrowse ! {trace, false}.
+
+%% @doc Turn tracing on for all connections to the specified HTTP
+%% server. Host is whatever is specified as the domain name in the URL
+%% @spec trace_on(Host, Port) -> term() 
+%% Host = string() 
+%% Port = integer()
+trace_on(Host, Port) ->
+    ibrowse ! {trace, true, Host, Port}.
+
+%% @doc Turn tracing OFF for all connections to the specified HTTP
+%% server.
+%% @spec trace_off(Host, Port) -> term()
+trace_off(Host, Port) ->
+    ibrowse ! {trace, false, Host, Port}.
+
+%% @doc Shows some internal information about load balancing to a
+%% specified Host:Port. Info about workers spawned using
+%% spawn_worker_process/2 or spawn_link_worker_process/2 is not
+%% included.
+show_dest_status(Host, Port) ->
+    case ets:lookup(ibrowse_lb, {Host, Port}) of
+	[] ->
+	    no_active_processes;
+	[#lb_pid{pid = Lb_pid}] ->
+	    io:format("Load Balancer Pid     : ~p~n", [Lb_pid]),
+	    io:format("LB process msg q size : ~p~n", [(catch process_info(Lb_pid, message_queue_len))]),
+	    case lists:dropwhile(
+		   fun(Tid) ->
+			   ets:info(Tid, owner) /= Lb_pid
+		   end, ets:all()) of
+		[] ->
+		    io:format("Couldn't locate ETS table for ~p~n", [Lb_pid]);
+		[Tid | _] ->
+		    First = ets:first(Tid),
+		    Last = ets:last(Tid),
+		    Size = ets:info(Tid, size),
+		    io:format("LB ETS table id       : ~p~n", [Tid]),
+		    io:format("Num Connections       : ~p~n", [Size]),
+		    case Size of
+			0 ->
+			    ok;
+			_ ->
+			    {First_p_sz, _} = First,
+			    {Last_p_sz, _} = Last,
+			    io:format("Smallest pipeline     : ~1000.p~n", [First_p_sz]),
+			    io:format("Largest pipeline      : ~1000.p~n", [Last_p_sz])
+		    end
+	    end
+    end.
+
+%% @doc Clear current configuration for ibrowse and load from the file
+%% ibrowse.conf in the IBROWSE_EBIN/../priv directory. Current
+%% configuration is cleared only if the ibrowse.conf file is readable
+%% using file:consult/1
+rescan_config() ->
+    gen_server:call(?MODULE, rescan_config).
+
+%% Clear current configuration for ibrowse and load from the specified
+%% file. Current configuration is cleared only if the specified
+%% file is readable using file:consult/1
+rescan_config(File) when is_list(File) ->
+    gen_server:call(?MODULE, {rescan_config, File}).
+
+%%====================================================================
+%% Server functions
+%%====================================================================
+
+%%--------------------------------------------------------------------
+%% Function: init/1
+%% Description: Initiates the server
+%% Returns: {ok, State}          |
+%%          {ok, State, Timeout} |
+%%          ignore               |
+%%          {stop, Reason}
+%%--------------------------------------------------------------------
+init(_) ->
+    process_flag(trap_exit, true),
+    State = #state{},
+    put(my_trace_flag, State#state.trace),
+    put(ibrowse_trace_token, "ibrowse"),
+    ets:new(ibrowse_lb, [named_table, public, {keypos, 2}]),
+    ets:new(ibrowse_conf, [named_table, protected, {keypos, 2}]),
+    import_config(),
+    {ok, #state{}}.
+
+import_config() ->
+    case code:priv_dir(ibrowse) of
+	{error, _} = Err ->
+	    Err;
+	PrivDir ->
+	    Filename = filename:join(PrivDir, "ibrowse.conf"),
+	    import_config(Filename)
+    end.
+
+import_config(Filename) ->
+    case file:consult(Filename) of
+	{ok, Terms} ->
+	    ets:delete_all_objects(ibrowse_conf),
+	    Fun = fun({dest, Host, Port, MaxSess, MaxPipe, Options}) 
+		     when list(Host), integer(Port),
+		     integer(MaxSess), MaxSess > 0,
+		     integer(MaxPipe), MaxPipe > 0, list(Options) ->
+			  I = [{{max_sessions, Host, Port}, MaxSess},
+			       {{max_pipeline_size, Host, Port}, MaxPipe},
+			       {{options, Host, Port}, Options}],
+			  lists:foreach(
+			    fun({X, Y}) ->
+				    ets:insert(ibrowse_conf,
+					       #ibrowse_conf{key = X, 
+							     value = Y})
+			    end, I);
+		     ({K, V}) ->
+			  ets:insert(ibrowse_conf,
+				     #ibrowse_conf{key = K,
+						   value = V});
+		     (X) ->
+			  io:format("Skipping unrecognised term: ~p~n", [X])
+		  end,
+	    lists:foreach(Fun, Terms);
+	Err ->
+	    Err
+    end.
+
+%% @doc Internal export
+get_config_value(Key) ->
+    [#ibrowse_conf{value = V}] = ets:lookup(ibrowse_conf, Key),
+    V.
+
+%% @doc Internal export
+get_config_value(Key, DefVal) ->
+    case ets:lookup(ibrowse_conf, Key) of
+	[] ->
+	    DefVal;
+	[#ibrowse_conf{value = V}] ->
+	    V
+    end.
+
+set_config_value(Key, Val) ->
+    ets:insert(ibrowse_conf, #ibrowse_conf{key = Key, value = Val}).
+%%--------------------------------------------------------------------
+%% Function: handle_call/3
+%% Description: Handling call messages
+%% Returns: {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)
+%%--------------------------------------------------------------------
+handle_call({get_lb_pid, #url{host = Host, port = Port} = Url}, _From, State) ->
+    Pid = do_get_connection(Url, ets:lookup(ibrowse_lb, {Host, Port})),
+    {reply, Pid, State};
+
+handle_call(stop, _From, State) ->
+    do_trace("IBROWSE shutting down~n", []),
+    {stop, normal, ok, State};
+
+handle_call({set_config_value, Key, Val}, _From, State) ->
+    set_config_value(Key, Val),
+    {reply, ok, State};
+
+handle_call(rescan_config, _From, State) ->
+    Ret = (catch import_config()),
+    {reply, Ret, State};
+
+handle_call({rescan_config, File}, _From, State) ->
+    Ret = (catch import_config(File)),
+    {reply, Ret, State};
+
+handle_call(Request, _From, State) ->
+    Reply = {unknown_request, Request},
+    {reply, Reply, State}.
+
+%%--------------------------------------------------------------------
+%% Function: handle_cast/2
+%% Description: Handling cast messages
+%% Returns: {noreply, State}          |
+%%          {noreply, State, Timeout} |
+%%          {stop, Reason, State}            (terminate/2 is called)
+%%--------------------------------------------------------------------
+
+handle_cast(_Msg, State) ->
+    {noreply, State}.
+
+%%--------------------------------------------------------------------
+%% Function: handle_info/2
+%% Description: Handling all non call/cast messages
+%% Returns: {noreply, State}          |
+%%          {noreply, State, Timeout} |
+%%          {stop, Reason, State}            (terminate/2 is called)
+%%--------------------------------------------------------------------
+handle_info({trace, Bool}, State) ->
+    put(my_trace_flag, Bool),
+    {noreply, State};
+
+handle_info({trace, Bool, Host, Port}, State) ->
+    Fun = fun(#lb_pid{host_port = {H, P}, pid = Pid}, _)
+	     when H == Host,
+		  P == Port ->
+		  catch Pid ! {trace, Bool};
+	     (#client_conn{key = {H, P, Pid}}, _)
+	     when H == Host,
+		  P == Port ->
+		  catch Pid ! {trace, Bool};
+	     (_, Acc) ->
+		  Acc
+	  end,
+    ets:foldl(Fun, undefined, ibrowse_lb),
+    ets:insert(ibrowse_conf, #ibrowse_conf{key = {trace, Host, Port},
+					   value = Bool}),
+    {noreply, State};
+		     
+handle_info(_Info, State) ->
+    {noreply, State}.
+
+%%--------------------------------------------------------------------
+%% Function: terminate/2
+%% Description: Shutdown the server
+%% Returns: any (ignored by gen_server)
+%%--------------------------------------------------------------------
+terminate(_Reason, _State) ->
+    ok.
+
+%%--------------------------------------------------------------------
+%% Func: code_change/3
+%% Purpose: Convert process state when code is changed
+%% Returns: {ok, NewState}
+%%--------------------------------------------------------------------
+code_change(_OldVsn, State, _Extra) ->
+    {ok, State}.
+
+%%--------------------------------------------------------------------
+%%% Internal functions
+%%--------------------------------------------------------------------
+do_get_connection(#url{host = Host, port = Port}, []) ->
+    {ok, Pid} = ibrowse_lb:start_link([Host, Port]),
+    ets:insert(ibrowse_lb, #lb_pid{host_port = {Host, Port}, pid = Pid}),
+    Pid;
+do_get_connection(_Url, [#lb_pid{pid = Pid}]) ->
+    Pid.

Added: couchdb/trunk/src/ibrowse/ibrowse.hrl
URL: http://svn.apache.org/viewvc/couchdb/trunk/src/ibrowse/ibrowse.hrl?rev=739047&view=auto
==============================================================================
--- couchdb/trunk/src/ibrowse/ibrowse.hrl (added)
+++ couchdb/trunk/src/ibrowse/ibrowse.hrl Thu Jan 29 22:15:48 2009
@@ -0,0 +1,12 @@
+-ifndef(IBROWSE_HRL).
+-define(IBROWSE_HRL, "ibrowse.hrl").
+
+-record(url, {abspath, host, port, username, password, path, protocol}).
+
+-record(lb_pid, {host_port, pid}).
+
+-record(client_conn, {key, cur_pipeline_size = 0, reqs_served = 0}).
+
+-record(ibrowse_conf, {key, value}).
+
+-endif.

Added: couchdb/trunk/src/ibrowse/ibrowse_app.erl
URL: http://svn.apache.org/viewvc/couchdb/trunk/src/ibrowse/ibrowse_app.erl?rev=739047&view=auto
==============================================================================
--- couchdb/trunk/src/ibrowse/ibrowse_app.erl (added)
+++ couchdb/trunk/src/ibrowse/ibrowse_app.erl Thu Jan 29 22:15:48 2009
@@ -0,0 +1,64 @@
+%%%-------------------------------------------------------------------
+%%% File    : ibrowse_app.erl
+%%% Author  : Chandrashekhar Mullaparthi <ch...@t-mobile.co.uk>
+%%% Description : 
+%%%
+%%% Created : 15 Oct 2003 by Chandrashekhar Mullaparthi <ch...@t-mobile.co.uk>
+%%%-------------------------------------------------------------------
+-module(ibrowse_app).
+-vsn('$Id: ibrowse_app.erl,v 1.1 2005/05/05 22:28:28 chandrusf Exp $ ').
+
+-behaviour(application).
+%%--------------------------------------------------------------------
+%% Include files
+%%--------------------------------------------------------------------
+
+%%--------------------------------------------------------------------
+%% External exports
+%%--------------------------------------------------------------------
+-export([
+	 start/2,
+	 stop/1
+        ]).
+
+%%--------------------------------------------------------------------
+%% Internal exports
+%%--------------------------------------------------------------------
+-export([
+        ]).
+
+%%--------------------------------------------------------------------
+%% Macros
+%%--------------------------------------------------------------------
+
+%%--------------------------------------------------------------------
+%% Records
+%%--------------------------------------------------------------------
+
+%%====================================================================
+%% External functions
+%%====================================================================
+%%--------------------------------------------------------------------
+%% Func: start/2
+%% Returns: {ok, Pid}        |
+%%          {ok, Pid, State} |
+%%          {error, Reason}   
+%%--------------------------------------------------------------------
+start(_Type, _StartArgs) ->
+    case ibrowse_sup:start_link() of
+	{ok, Pid} -> 
+	    {ok, Pid};
+	Error ->
+	    Error
+    end.
+
+%%--------------------------------------------------------------------
+%% Func: stop/1
+%% Returns: any 
+%%--------------------------------------------------------------------
+stop(_State) ->
+    ok.
+
+%%====================================================================
+%% Internal functions
+%%====================================================================