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 <yoan.bl...@gmail.com> * Paul Carey <paul.p.ca...@gmail.com> * Benoit Chesneau <bchesn...@gmail.com> + * Jason Davies <ja...@jasondavies.com> * Paul Joseph Davis <paul.joseph.da...@gmail.com> + * Maximillian Dornseif <m...@hudora.de> * Michael Gottesman <gotte...@reed.edu> * Michael Hendricks <mich...@ndrix.org> * Till Klampaeckel <t...@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%|cou...@version@/ebin|g" \ - -e "s|%couchdbebindir%|mochiweb-r82/ebin|g" \ + -e "s|%couchdbebindir%|cou...@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 <chandrashekhar.mullapar...@t-mobile.co.uk> +%%% Description : Load balancer process for HTTP client connections. +%%% +%%% Created : 11 Oct 2003 by Chandrashekhar Mullaparthi <chandrashekhar.mullapar...@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 <chandrashekhar.mullapar...@t-mobile.co.uk> +%%% Description : +%%% +%%% Created : 15 Oct 2003 by Chandrashekhar Mullaparthi <chandrashekhar.mullapar...@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 +%%====================================================================