From ace6dfe0107010b57c5da0596f69cfd10fc84a38 Mon Sep 17 00:00:00 2001 From: John Christopher Anderson Date: Thu, 29 Jan 2009 22:15:48 +0000 Subject: 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: https://svn.apache.org/repos/asf/couchdb/trunk@739047 13f79535-47bb-0310-9956-ffa450edef68 --- src/couchdb/couch.app.tpl.in | 2 +- src/couchdb/couch_httpd.erl | 11 +++- src/couchdb/couch_httpd_db.erl | 3 +- src/couchdb/couch_httpd_misc_handlers.erl | 3 +- src/couchdb/couch_rep.erl | 96 ++++++++++++++++++++----------- src/couchdb/couch_server_sup.erl | 2 +- 6 files changed, 80 insertions(+), 37 deletions(-) (limited to 'src/couchdb') diff --git a/src/couchdb/couch.app.tpl.in b/src/couchdb/couch.app.tpl.in index 3b1ea02f..e0100cb4 100644 --- a/src/couchdb/couch.app.tpl.in +++ b/src/couchdb/couch.app.tpl.in @@ -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 --git a/src/couchdb/couch_httpd.erl b/src/couchdb/couch_httpd.erl index acd6af40..7c371326 100644 --- a/src/couchdb/couch_httpd.erl +++ b/src/couchdb/couch_httpd.erl @@ -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}) -> 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). diff --git a/src/couchdb/couch_httpd_db.erl b/src/couchdb/couch_httpd_db.erl index 73d90fe6..2cb4c403 100644 --- a/src/couchdb/couch_httpd_db.erl +++ b/src/couchdb/couch_httpd_db.erl @@ -261,7 +261,8 @@ db_req(#httpd{method='GET',mochi_req=MochiReq, path_parts=[DbName,<<"_design/",_ 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) -> diff --git a/src/couchdb/couch_httpd_misc_handlers.erl b/src/couchdb/couch_httpd_misc_handlers.erl index e30f7594..2ba8ef64 100644 --- a/src/couchdb/couch_httpd_misc_handlers.erl +++ b/src/couchdb/couch_httpd_misc_handlers.erl @@ -50,7 +50,8 @@ handle_utils_dir_req(#httpd{method='GET'}=Req, DocumentRoot) -> 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"). diff --git a/src/couchdb/couch_rep.erl b/src/couchdb/couch_rep.erl index 29f1fc80..ff926584 100644 --- a/src/couchdb/couch_rep.erl +++ b/src/couchdb/couch_rep.erl @@ -157,30 +157,54 @@ replicate2(Source, DbSrc, Target, DbTgt, Options) -> 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 @@ wait_result({Pid,Ref}) -> {'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 @@ enum_docs_parallel(DbS, DbT, DocInfoList) -> 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 @@ enum_docs_since(DbSource, DbTarget, StartSeq, InAcc) -> [] -> {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">>, diff --git a/src/couchdb/couch_server_sup.erl b/src/couchdb/couch_server_sup.erl index 27efc9e7..627c34a9 100644 --- a/src/couchdb/couch_server_sup.erl +++ b/src/couchdb/couch_server_sup.erl @@ -102,7 +102,7 @@ start_server(IniFiles) -> % ensure these applications are running - application:start(inets), + application:start(ibrowse), application:start(crypto), {ok, Pid} = supervisor:start_link( -- cgit v1.2.3