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
+%%====================================================================