<?xml version="1.0" encoding="UTF-8"?>
<commit>
  <added type="array">
    <added>
      <filename>src/ibrowse/Makefile.am</filename>
    </added>
    <added>
      <filename>src/ibrowse/ibrowse.app</filename>
    </added>
    <added>
      <filename>src/ibrowse/ibrowse.erl</filename>
    </added>
    <added>
      <filename>src/ibrowse/ibrowse.hrl</filename>
    </added>
    <added>
      <filename>src/ibrowse/ibrowse_app.erl</filename>
    </added>
    <added>
      <filename>src/ibrowse/ibrowse_http_client.erl</filename>
    </added>
    <added>
      <filename>src/ibrowse/ibrowse_lb.erl</filename>
    </added>
    <added>
      <filename>src/ibrowse/ibrowse_lib.erl</filename>
    </added>
    <added>
      <filename>src/ibrowse/ibrowse_sup.erl</filename>
    </added>
    <added>
      <filename>src/ibrowse/ibrowse_test.erl</filename>
    </added>
  </added>
  <modified type="array">
    <modified>
      <diff>@@ -28,6 +28,8 @@ Database Core:
    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:
 </diff>
      <filename>CHANGES</filename>
    </modified>
    <modified>
      <diff>@@ -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
 </diff>
      <filename>Makefile.am</filename>
    </modified>
    <modified>
      <diff>@@ -21,3 +21,9 @@ This product also includes the following third-party components:
  * 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</diff>
      <filename>NOTICE</filename>
    </modified>
    <modified>
      <diff>@@ -12,7 +12,9 @@ Some of these people are:
  * Yoan Blanc &lt;yoan.blanc@gmail.com&gt;
  * Paul Carey &lt;paul.p.carey@gmail.com&gt;
  * Benoit Chesneau &lt;bchesneau@gmail.com&gt;
+ * Jason Davies &lt;jason@jasondavies.com&gt;
  * Paul Joseph Davis &lt;paul.joseph.davis@gmail.com&gt;
+ * Maximillian Dornseif &lt;md@hudora.de&gt;
  * Michael Gottesman &lt;gottesmm@reed.edu&gt;
  * Michael Hendricks &lt;michael@ndrix.org&gt;
  * Till Klampaeckel &lt;till@klampaeckel.de&gt;</diff>
      <filename>THANKS</filename>
    </modified>
    <modified>
      <diff>@@ -28,8 +28,9 @@ couchdb: couchdb.tpl
 	    -e &quot;s|%ICU_CONFIG%|$(ICU_CONFIG)|g&quot; \
 	    -e &quot;s|%bindir%|@bindir@|g&quot; \
 	    -e &quot;s|%localerlanglibdir%|@localerlanglibdir@|g&quot; \
-	    -e &quot;s|%mochiwebebindir%|couch-@version@/ebin|g&quot; \
-	    -e &quot;s|%couchdbebindir%|mochiweb-r82/ebin|g&quot; \
+	    -e &quot;s|%couchdbebindir%|couch-@version@/ebin|g&quot; \
+	    -e &quot;s|%mochiwebebindir%|mochiweb-r82/ebin|g&quot; \
+	    -e &quot;s|%ibrowseebindir%|ibrowse-1.4.1/ebin|g&quot; \
 	    -e &quot;s|%defaultini%|default.ini|g&quot; \
 	    -e &quot;s|%localini%|local.ini|g&quot; \
 	    -e &quot;s|%localconfdir%|@localconfdir@|g&quot; \</diff>
      <filename>bin/Makefile.am</filename>
    </modified>
    <modified>
      <diff>@@ -183,11 +183,12 @@ start_couchdb () {
         %ERL% $interactive_option -smp auto -sasl errlog_type error +K true \
         -pa %localerlanglibdir%/%couchdbebindir% \
             %localerlanglibdir%/%mochiwebebindir% \
-        -eval \&quot;application:load(inets)\&quot; \
+            %localerlanglibdir%/%ibrowseebindir% \
+        -eval \&quot;application:load(ibrowse)\&quot; \
         -eval \&quot;application:load(crypto)\&quot; \
         -eval \&quot;application:load(couch)\&quot; \
         -eval \&quot;crypto:start()\&quot; \
-        -eval \&quot;inets:start()\&quot; \
+        -eval \&quot;ibrowse:start()\&quot; \
         -eval \&quot;couch_server:start([$start_arguments]), receive done -&gt; done end.\&quot; &quot;
     if test &quot;$BACKGROUND_BOOLEAN&quot; = &quot;true&quot; \
         -a &quot;$RECURSED_BOOLEAN&quot; = &quot;false&quot;; then</diff>
      <filename>bin/couchdb.tpl.in</filename>
    </modified>
    <modified>
      <diff>@@ -267,6 +267,7 @@ AC_CONFIG_FILES([etc/Makefile])
 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])</diff>
      <filename>configure.ac</filename>
    </modified>
    <modified>
      <diff>@@ -2134,6 +2134,19 @@ var tests = {
             T(docA._rev == docB._rev);
           };
         },
+
+        design_docs_test: new function() {
+          // make sure design docs replicate properly
+          this.init = function(dbA, dbB) {
+            dbA.save({ _id:&quot;_design/test&quot; });
+          };
+
+          this.afterAB1 = function() {
+            var docA = dbA.open(&quot;_design/test&quot;);
+            var docB = dbB.open(&quot;_design/test&quot;);
+            T(docA._rev == docB._rev);
+          };
+        },
       
         attachments_test: new function () {
           // Test attachments</diff>
      <filename>share/www/script/couch_tests.js</filename>
    </modified>
    <modified>
      <diff>@@ -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]}]}.</diff>
      <filename>src/couchdb/couch.app.tpl.in</filename>
    </modified>
    <modified>
      <diff>@@ -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 @@ qs(#httpd{mochi_req=MochiReq}) -&gt;
 path(#httpd{mochi_req=MochiReq}) -&gt;
     MochiReq:get(path).
 
+absolute_uri(#httpd{mochi_req=MochiReq}, Path) -&gt;
+    Host = case MochiReq:get_header_value(&quot;Host&quot;) of
+        undefined -&gt;
+            {ok, {Address, Port}} = inet:sockname(MochiReq:get(socket)),
+            inet_parse:ntoa(Address) ++ &quot;:&quot; ++ integer_to_list(Port);
+        Value -&gt; Value
+    end,
+    &quot;http://&quot; ++ Host ++ Path.
+
 unquote(UrlEncodedString) -&gt;
     mochiweb_util:unquote(UrlEncodedString).
 </diff>
      <filename>src/couchdb/couch_httpd.erl</filename>
    </modified>
    <modified>
      <diff>@@ -261,7 +261,8 @@ db_req(#httpd{method='GET',mochi_req=MochiReq, path_parts=[DbName,&lt;&lt;&quot;_design/&quot;,_
     PathFront = &quot;/&quot; ++ couch_httpd:quote(binary_to_list(DbName)) ++ &quot;/&quot;,
     RawSplit = regexp:split(MochiReq:get(raw_path),&quot;_design%2F&quot;),
     {ok, [PathFront|PathTail]} = RawSplit,
-    RedirectTo = PathFront ++ &quot;_design/&quot; ++ mochiweb_util:join(PathTail, &quot;%2F&quot;),
+    RedirectTo = couch_httpd:absolute_uri(Req, PathFront ++ &quot;_design/&quot; ++ 
+        mochiweb_util:join(PathTail, &quot;%2F&quot;)),
     couch_httpd:send_response(Req, 301, [{&quot;Location&quot;, RedirectTo}], &lt;&lt;&gt;&gt;);
 
 db_req(#httpd{path_parts=[_DbName,&lt;&lt;&quot;_design&quot;&gt;&gt;,Name]}=Req, Db) -&gt;</diff>
      <filename>src/couchdb/couch_httpd_db.erl</filename>
    </modified>
    <modified>
      <diff>@@ -50,7 +50,8 @@ handle_utils_dir_req(#httpd{method='GET'}=Req, DocumentRoot) -&gt;
         couch_httpd:serve_file(Req, RelativePath, DocumentRoot);
     {_ActionKey, &quot;&quot;, _RelativePath} -&gt;
         % GET /_utils
-        couch_httpd:send_response(Req, 301, [{&quot;Location&quot;, &quot;/_utils/&quot;}], &lt;&lt;&gt;&gt;)
+        Headers = [{&quot;Location&quot;, couch_httpd:absolute_uri(Req, &quot;/_utils/&quot;)}],
+        couch_httpd:send_response(Req, 301, Headers, &lt;&lt;&gt;&gt;)
     end;
 handle_utils_dir_req(Req, _) -&gt;
     send_method_not_allowed(Req, &quot;GET,HEAD&quot;).</diff>
      <filename>src/couchdb/couch_httpd_misc_handlers.erl</filename>
    </modified>
    <modified>
      <diff>@@ -157,30 +157,54 @@ replicate2(Source, DbSrc, Target, DbTgt, Options) -&gt;
     end.
 
 pull_rep(DbTarget, DbSource, SourceSeqNum) -&gt;
-    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) -&gt;
     do_http_request(Url, Action, Headers, []).
 
 do_http_request(Url, Action, Headers, JsonBody) -&gt;
-    ?LOG_DEBUG(&quot;couch_rep HTTP client request:&quot;, []),
-    ?LOG_DEBUG(&quot;\tAction: ~p&quot;, [Action]),
-    ?LOG_DEBUG(&quot;\tUrl: ~p&quot;, [Url]),
-    Request =
+    do_http_request(Url, Action, Headers, JsonBody, 10).
+
+do_http_request(Url, Action, _Headers, _JsonBody, 0) -&gt;
+    ?LOG_ERROR(&quot;couch_rep HTTP ~p request failed after 10 retries: ~p&quot;, 
+        [Action, Url]);
+do_http_request(Url, Action, Headers, JsonBody, Retries) -&gt;
+    ?LOG_DEBUG(&quot;couch_rep HTTP ~p request: ~p&quot;, [Action, Url]),
+    Body =
     case JsonBody of
     [] -&gt;
-        {Url, Headers};
+        &lt;&lt;&gt;&gt;;
     _ -&gt;
-        {Url, Headers, &quot;application/json; charset=utf-8&quot;, iolist_to_binary(?JSON_ENCODE(JsonBody))}
+        iolist_to_binary(?JSON_ENCODE(JsonBody))
     end,
-    {ok, {{_, ResponseCode,_},_Headers, ResponseBody}} = http:request(Action, Request, [], []),
-    if
-    ResponseCode &gt;= 200, ResponseCode &lt; 500 -&gt;
-        ?JSON_DECODE(ResponseBody)
+    Options = [
+        {content_type, &quot;application/json; charset=utf-8&quot;},
+        {max_pipeline_size, 101},
+        {transfer_encoding, {chunked, 65535}}
+    ],
+    case ibrowse:send_req(Url, Headers, Action, Body, Options) of
+    {ok, Status, ResponseHeaders, ResponseBody} -&gt;
+        ResponseCode = list_to_integer(Status),
+        if
+        ResponseCode &gt;= 200, ResponseCode &lt; 300 -&gt;
+            ?JSON_DECODE(ResponseBody);
+        ResponseCode &gt;= 300, ResponseCode &lt; 400 -&gt;
+            RedirectUrl = mochiweb_headers:get_value(&quot;Location&quot;, 
+                mochiweb_headers:make(ResponseHeaders)),
+            do_http_request(RedirectUrl, Action, Headers, JsonBody, Retries-1);
+        ResponseCode &gt;= 400, ResponseCode &lt; 500 -&gt; 
+            ?JSON_DECODE(ResponseBody);        
+        ResponseCode == 500 -&gt;
+            ?LOG_INFO(&quot;retrying couch_rep HTTP ~p request due to 500 error: ~p&quot;,
+                [Action, Url]),
+            do_http_request(Url, Action, Headers, JsonBody, Retries - 1)
+        end;
+    {error, Reason} -&gt;
+        ?LOG_INFO(&quot;retrying couch_rep HTTP ~p request due to {error, ~p}: ~p&quot;, 
+            [Action, Reason, Url]),
+        do_http_request(Url, Action, Headers, JsonBody, Retries - 1)
     end.
 
 save_docs_buffer(DbTarget, DocsBuffer, []) -&gt;
@@ -223,20 +247,17 @@ wait_result({Pid,Ref}) -&gt;
     {'DOWN', Ref, _, _, Reason} -&gt; exit(Reason)
 end.
 
-enum_docs_parallel(DbS, DbT, DocInfoList) -&gt;
-    UpdateSeqs = [D#doc_info.update_seq || D &lt;- DocInfoList],
+enum_docs_parallel(DbS, DbT, InfoList) -&gt;
+    UpdateSeqs = [Seq || {_, Seq, _, _} &lt;- InfoList],
     SaveDocsPid = spawn_link(fun() -&gt; save_docs_buffer(DbT,[],UpdateSeqs) end),
     
-    Stats = pmap(fun(SrcDocInfo) -&gt;
-        #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}]} -&gt;
+    Stats = pmap(fun({Id, Seq, SrcRevs, MissingRevs}) -&gt;
+        case MissingRevs of
+        [] -&gt;
+            SaveDocsPid ! {self(), skip, Seq},
+            receive got_it -&gt; ok end,
+            [{missing_checked, length(SrcRevs)}];
+        _ -&gt;
             {ok, DocResults} = open_doc_revs(DbS, Id, MissingRevs, [latest]),
             
             % only save successful reads
@@ -247,13 +268,9 @@ enum_docs_parallel(DbS, DbT, DocInfoList) -&gt;
             receive got_it -&gt; ok end,
             [{missing_checked, length(SrcRevs)},
              {missing_found, length(MissingRevs)},
-             {docs_read, length(Docs)}];
-        {ok, []} -&gt;
-            SaveDocsPid ! {self(), skip, Seq},
-            receive got_it -&gt; ok end,
-            [{missing_checked, length(SrcRevs)}]
-        end    
-    end, DocInfoList),
+             {docs_read, length(Docs)}]
+        end
+    end, InfoList),
     
     SaveDocsPid ! {self(), shutdown},
     
@@ -345,7 +362,22 @@ enum_docs_since(DbSource, DbTarget, StartSeq, InAcc) -&gt;
     [] -&gt;
         {ok, InAcc};
     _ -&gt;
-        Stats = enum_docs_parallel(DbSource, DbTarget, DocInfoList),
+        UpdateSeqs = [D#doc_info.update_seq || D &lt;- DocInfoList],
+        SrcRevsList = lists:map(fun(SrcDocInfo) -&gt;
+            #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}) -&gt;
+            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 = [
             {&lt;&lt;&quot;missing_checked&quot;&gt;&gt;, </diff>
      <filename>src/couchdb/couch_rep.erl</filename>
    </modified>
    <modified>
      <diff>@@ -102,7 +102,7 @@ start_server(IniFiles) -&gt;
    
 
     % ensure these applications are running
-    application:start(inets),
+    application:start(ibrowse),
     application:start(crypto),
 
     {ok, Pid} = supervisor:start_link(</diff>
      <filename>src/couchdb/couch_server_sup.erl</filename>
    </modified>
    <modified>
      <diff>@@ -24,6 +24,7 @@ run: ../bin/couchdb.tpl
 	    -e &quot;s|%localerlanglibdir%|$(abs_top_srcdir)/src|g&quot; \
 	    -e &quot;s|%mochiwebebindir%|mochiweb|g&quot; \
 	    -e &quot;s|%couchdbebindir%|couchdb|g&quot; \
+	    -e &quot;s|%ibrowseebindir%|ibrowse|g&quot; \
 	    -e &quot;s|%defaultini%|default_dev.ini|g&quot; \
 	    -e &quot;s|%localini%|local_dev.ini|g&quot; \
 	    -e &quot;s|%localerlanglibdir%|$(abs_top_srcdir)/src|g&quot; \</diff>
      <filename>utils/Makefile.am</filename>
    </modified>
  </modified>
  <removed type="array"/>
  <parents type="array">
    <parent>
      <id>28a4dab3fce36fe47b86e4b1e2efc7cb92b21c0b</id>
    </parent>
  </parents>
  <author>
    <name>jchris</name>
    <email>jchris@13f79535-47bb-0310-9956-ffa450edef68</email>
  </author>
  <url>http://github.com/halorgium/couchdb/commit/3d89be3a57ee0e4951558f5816b25628bd5ed73e</url>
  <id>3d89be3a57ee0e4951558f5816b25628bd5ed73e</id>
  <committed-date>2009-01-29T14:15:48-08:00</committed-date>
  <authored-date>2009-01-29T14:15:48-08:00</authored-date>
  <message>Replacement of inets with ibrowse. Fixes COUCHDB-179 and enhances replication.
Thanks Jason Davies and Adam Kocoloski for the fix, Maximillian Dornseif for reporting.


git-svn-id: http://svn.apache.org/repos/asf/couchdb/trunk@739047 13f79535-47bb-0310-9956-ffa450edef68</message>
  <tree>bbcaedf64e20a580214f1b1f74cd40473073279c</tree>
  <committer>
    <name>jchris</name>
    <email>jchris@13f79535-47bb-0310-9956-ffa450edef68</email>
  </committer>
</commit>
