You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@couchdb.apache.org by kx...@apache.org on 2015/12/03 00:02:21 UTC

[21/50] couchdb commit: updated refs/heads/1.x.x to 921006f

Port 180-http-proxy.t etap test suite to eunit


Project: http://git-wip-us.apache.org/repos/asf/couchdb/repo
Commit: http://git-wip-us.apache.org/repos/asf/couchdb/commit/3e545431
Tree: http://git-wip-us.apache.org/repos/asf/couchdb/tree/3e545431
Diff: http://git-wip-us.apache.org/repos/asf/couchdb/diff/3e545431

Branch: refs/heads/1.x.x
Commit: 3e545431dc1bfb751a10a731514dd9d5dd0726d1
Parents: 96b15f9
Author: Alexander Shorin <kx...@apache.org>
Authored: Wed Jun 4 12:54:44 2014 +0400
Committer: Alexander Shorin <kx...@apache.org>
Committed: Thu Dec 3 00:51:55 2015 +0300

----------------------------------------------------------------------
 test/couchdb/Makefile.am                  |   5 +-
 test/couchdb/couchdb_http_proxy_tests.erl | 554 +++++++++++++++++++++++++
 test/couchdb/test_web.erl                 | 112 +++++
 test/etap/180-http-proxy.ini              |  20 -
 test/etap/180-http-proxy.t                | 376 -----------------
 test/etap/Makefile.am                     |   5 +-
 test/etap/test_web.erl                    |  99 -----
 7 files changed, 671 insertions(+), 500 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/couchdb/blob/3e545431/test/couchdb/Makefile.am
----------------------------------------------------------------------
diff --git a/test/couchdb/Makefile.am b/test/couchdb/Makefile.am
index 95f1b77..d664a58 100644
--- a/test/couchdb/Makefile.am
+++ b/test/couchdb/Makefile.am
@@ -19,7 +19,8 @@ all:
 	mkdir -p temp/
 	$(ERLC) -Wall -I$(top_srcdir)/src -I$(top_srcdir)/test/couchdb/include \
 			-o $(top_builddir)/test/couchdb/ebin/ $(ERLC_FLAGS) ${TEST} \
-			$(top_srcdir)/test/couchdb/test_request.erl
+			$(top_srcdir)/test/couchdb/test_request.erl \
+			$(top_srcdir)/test/couchdb/test_web.erl
 	chmod +x run
 	chmod +x $(top_builddir)/test/couchdb/fixtures/os_daemon_configer.escript
 
@@ -41,12 +42,14 @@ eunit_files = \
     couch_work_queue_tests.erl \
     couchdb_attachments_tests.erl \
     couchdb_file_compression_tests.erl \
+    couchdb_http_proxy_tests.erl \
     couchdb_modules_load_tests.erl \
     couchdb_os_daemons_tests.erl \
     couchdb_update_conflicts_tests.erl \
     couchdb_vhosts_tests.erl \
     couchdb_views_tests.erl \
     test_request.erl \
+    test_web.erl \
     include/couch_eunit.hrl
 
 fixture_files = \

http://git-wip-us.apache.org/repos/asf/couchdb/blob/3e545431/test/couchdb/couchdb_http_proxy_tests.erl
----------------------------------------------------------------------
diff --git a/test/couchdb/couchdb_http_proxy_tests.erl b/test/couchdb/couchdb_http_proxy_tests.erl
new file mode 100644
index 0000000..03ceca7
--- /dev/null
+++ b/test/couchdb/couchdb_http_proxy_tests.erl
@@ -0,0 +1,554 @@
+% 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.
+
+-module(couchdb_http_proxy_tests).
+
+-include("couch_eunit.hrl").
+
+-record(req, {method=get, path="", headers=[], body="", opts=[]}).
+
+-define(CONFIG_FIXTURE_TEMP,
+    begin
+        FileName = filename:join([?TEMPDIR, ?tempfile() ++ ".ini"]),
+        {ok, Fd} = file:open(FileName, write),
+        ok = file:truncate(Fd),
+        ok = file:close(Fd),
+        FileName
+    end).
+-define(TIMEOUT, 5000).
+
+
+start() ->
+    % we have to write any config changes to temp ini file to not loose them
+    % when supervisor will kill all children due to reaching restart threshold
+    % (each httpd_global_handlers changes causes couch_httpd restart)
+    couch_server_sup:start_link(?CONFIG_CHAIN ++ [?CONFIG_FIXTURE_TEMP]),
+    % 49151 is IANA Reserved, let's assume no one is listening there
+    with_process_restart(couch_httpd, fun() ->
+        couch_config:set("httpd_global_handlers", "_error",
+            "{couch_httpd_proxy, handle_proxy_req, <<\"http://127.0.0.1:49151/\">>}"
+        )
+    end),
+    ok.
+
+stop(_) ->
+    couch_server_sup:stop(),
+    ok.
+
+setup() ->
+    {ok, Pid} = test_web:start_link(),
+    Value = lists:flatten(io_lib:format(
+        "{couch_httpd_proxy, handle_proxy_req, ~p}",
+        [list_to_binary(proxy_url())])),
+    with_process_restart(couch_httpd, fun() ->
+        couch_config:set("httpd_global_handlers", "_test", Value)
+    end),
+    Pid.
+
+teardown(Pid) ->
+    erlang:monitor(process, Pid),
+    test_web:stop(),
+    receive
+        {'DOWN', _, _, Pid, _} ->
+            ok
+    after ?TIMEOUT ->
+        throw({timeout, test_web_stop})
+    end.
+
+
+http_proxy_test_() ->
+    {
+        "HTTP Proxy handler tests",
+        {
+            setup,
+            fun start/0, fun stop/1,
+            {
+                foreach,
+                fun setup/0, fun teardown/1,
+                [
+                    fun should_proxy_basic_request/1,
+                    fun should_return_alternative_status/1,
+                    fun should_respect_trailing_slash/1,
+                    fun should_proxy_headers/1,
+                    fun should_proxy_host_header/1,
+                    fun should_pass_headers_back/1,
+                    fun should_use_same_protocol_version/1,
+                    fun should_proxy_body/1,
+                    fun should_proxy_body_back/1,
+                    fun should_proxy_chunked_body/1,
+                    fun should_proxy_chunked_body_back/1,
+                    fun should_rewrite_location_header/1,
+                    fun should_not_rewrite_external_locations/1,
+                    fun should_rewrite_relative_location/1,
+                    fun should_refuse_connection_to_backend/1
+                ]
+            }
+
+        }
+    }.
+
+
+should_proxy_basic_request(_) ->
+    Remote = fun(Req) ->
+        'GET' = Req:get(method),
+        "/" = Req:get(path),
+        0 = Req:get(body_length),
+        <<>> = Req:recv_body(),
+        {ok, {200, [{"Content-Type", "text/plain"}], "ok"}}
+    end,
+    Local = fun
+        ({ok, "200", _, "ok"}) ->
+            true;
+        (_) ->
+            false
+    end,
+    ?_test(check_request(#req{}, Remote, Local)).
+
+should_return_alternative_status(_) ->
+    Remote = fun(Req) ->
+        "/alternate_status" = Req:get(path),
+        {ok, {201, [], "ok"}}
+    end,
+    Local = fun
+        ({ok, "201", _, "ok"}) ->
+            true;
+        (_) ->
+            false
+    end,
+    Req = #req{path = "/alternate_status"},
+    ?_test(check_request(Req, Remote, Local)).
+
+should_respect_trailing_slash(_) ->
+    Remote = fun(Req) ->
+        "/trailing_slash/" = Req:get(path),
+        {ok, {200, [], "ok"}}
+    end,
+    Local = fun
+        ({ok, "200", _, "ok"}) ->
+            true;
+        (_) ->
+            false
+    end,
+    Req = #req{path="/trailing_slash/"},
+    ?_test(check_request(Req, Remote, Local)).
+
+should_proxy_headers(_) ->
+    Remote = fun(Req) ->
+        "/passes_header" = Req:get(path),
+        "plankton" = Req:get_header_value("X-CouchDB-Ralph"),
+        {ok, {200, [], "ok"}}
+    end,
+    Local = fun
+        ({ok, "200", _, "ok"}) ->
+            true;
+        (_) ->
+            false
+    end,
+    Req = #req{
+        path="/passes_header",
+        headers=[{"X-CouchDB-Ralph", "plankton"}]
+    },
+    ?_test(check_request(Req, Remote, Local)).
+
+should_proxy_host_header(_) ->
+    Remote = fun(Req) ->
+        "/passes_host_header" = Req:get(path),
+        "www.google.com" = Req:get_header_value("Host"),
+        {ok, {200, [], "ok"}}
+    end,
+    Local = fun
+        ({ok, "200", _, "ok"}) ->
+            true;
+        (_) ->
+            false
+    end,
+    Req = #req{
+        path="/passes_host_header",
+        headers=[{"Host", "www.google.com"}]
+    },
+    ?_test(check_request(Req, Remote, Local)).
+
+should_pass_headers_back(_) ->
+    Remote = fun(Req) ->
+        "/passes_header_back" = Req:get(path),
+        {ok, {200, [{"X-CouchDB-Plankton", "ralph"}], "ok"}}
+    end,
+    Local = fun
+        ({ok, "200", Headers, "ok"}) ->
+            lists:member({"X-CouchDB-Plankton", "ralph"}, Headers);
+        (_) ->
+            false
+    end,
+    Req = #req{path="/passes_header_back"},
+    ?_test(check_request(Req, Remote, Local)).
+
+should_use_same_protocol_version(_) ->
+    Remote = fun(Req) ->
+        "/uses_same_version" = Req:get(path),
+        {1, 0} = Req:get(version),
+        {ok, {200, [], "ok"}}
+    end,
+    Local = fun
+        ({ok, "200", _, "ok"}) ->
+            true;
+        (_) ->
+            false
+    end,
+    Req = #req{
+        path="/uses_same_version",
+        opts=[{http_vsn, {1, 0}}]
+    },
+    ?_test(check_request(Req, Remote, Local)).
+
+should_proxy_body(_) ->
+    Remote = fun(Req) ->
+        'PUT' = Req:get(method),
+        "/passes_body" = Req:get(path),
+        <<"Hooray!">> = Req:recv_body(),
+        {ok, {201, [], "ok"}}
+    end,
+    Local = fun
+        ({ok, "201", _, "ok"}) ->
+            true;
+        (_) ->
+            false
+    end,
+    Req = #req{
+        method=put,
+        path="/passes_body",
+        body="Hooray!"
+    },
+    ?_test(check_request(Req, Remote, Local)).
+
+should_proxy_body_back(_) ->
+    BodyChunks = [<<"foo">>, <<"bar">>, <<"bazinga">>],
+    Remote = fun(Req) ->
+        'GET' = Req:get(method),
+        "/passes_eof_body" = Req:get(path),
+        {raw, {200, [{"Connection", "close"}], BodyChunks}}
+    end,
+    Local = fun
+        ({ok, "200", _, "foobarbazinga"}) ->
+            true;
+        (_) ->
+            false
+    end,
+    Req = #req{path="/passes_eof_body"},
+    ?_test(check_request(Req, Remote, Local)).
+
+should_proxy_chunked_body(_) ->
+    BodyChunks = [<<"foo">>, <<"bar">>, <<"bazinga">>],
+    Remote = fun(Req) ->
+        'POST' = Req:get(method),
+        "/passes_chunked_body" = Req:get(path),
+        RecvBody = fun
+            ({Length, Chunk}, [Chunk | Rest]) ->
+                Length = size(Chunk),
+                Rest;
+            ({0, []}, []) ->
+                ok
+        end,
+        ok = Req:stream_body(1024 * 1024, RecvBody, BodyChunks),
+        {ok, {201, [], "ok"}}
+    end,
+    Local = fun
+        ({ok, "201", _, "ok"}) ->
+            true;
+        (_) ->
+            false
+    end,
+    Req = #req{
+        method=post,
+        path="/passes_chunked_body",
+        headers=[{"Transfer-Encoding", "chunked"}],
+        body=chunked_body(BodyChunks)
+    },
+    ?_test(check_request(Req, Remote, Local)).
+
+should_proxy_chunked_body_back(_) ->
+    ?_test(begin
+        Remote = fun(Req) ->
+            'GET' = Req:get(method),
+            "/passes_chunked_body_back" = Req:get(path),
+            BodyChunks = [<<"foo">>, <<"bar">>, <<"bazinga">>],
+            {chunked, {200, [{"Transfer-Encoding", "chunked"}], BodyChunks}}
+        end,
+        Req = #req{
+            path="/passes_chunked_body_back",
+            opts=[{stream_to, self()}]
+        },
+
+        Resp = check_request(Req, Remote, no_local),
+        ?assertMatch({ibrowse_req_id, _}, Resp),
+        {_, ReqId} = Resp,
+
+        % Grab headers from response
+        receive
+            {ibrowse_async_headers, ReqId, "200", Headers} ->
+                ?assertEqual("chunked",
+                             proplists:get_value("Transfer-Encoding", Headers)),
+            ibrowse:stream_next(ReqId)
+        after 1000 ->
+            throw({error, timeout})
+        end,
+
+        ?assertEqual(<<"foobarbazinga">>, recv_body(ReqId, [])),
+        ?assertEqual(was_ok, test_web:check_last())
+    end).
+
+should_refuse_connection_to_backend(_) ->
+    Local = fun
+        ({ok, "500", _, _}) ->
+            true;
+        (_) ->
+            false
+    end,
+    Req = #req{opts=[{url, server_url("/_error")}]},
+    ?_test(check_request(Req, no_remote, Local)).
+
+should_rewrite_location_header(_) ->
+    {
+        "Testing location header rewrites",
+        do_rewrite_tests([
+            {"Location", proxy_url() ++ "/foo/bar",
+                         server_url() ++ "/foo/bar"},
+            {"Content-Location", proxy_url() ++ "/bing?q=2",
+                                 server_url() ++ "/bing?q=2"},
+            {"Uri", proxy_url() ++ "/zip#frag",
+                    server_url() ++ "/zip#frag"},
+            {"Destination", proxy_url(),
+                            server_url() ++ "/"}
+        ])
+    }.
+
+should_not_rewrite_external_locations(_) ->
+    {
+        "Testing no rewrite of external locations",
+        do_rewrite_tests([
+            {"Location", external_url() ++ "/search",
+                         external_url() ++ "/search"},
+            {"Content-Location", external_url() ++ "/s?q=2",
+                                 external_url() ++ "/s?q=2"},
+            {"Uri", external_url() ++ "/f#f",
+                    external_url() ++ "/f#f"},
+            {"Destination", external_url() ++ "/f?q=2#f",
+                            external_url() ++ "/f?q=2#f"}
+        ])
+    }.
+
+should_rewrite_relative_location(_) ->
+    {
+        "Testing relative rewrites",
+        do_rewrite_tests([
+            {"Location", "/foo",
+                         server_url() ++ "/foo"},
+            {"Content-Location", "bar",
+                                 server_url() ++ "/bar"},
+            {"Uri", "/zing?q=3",
+                    server_url() ++ "/zing?q=3"},
+            {"Destination", "bing?q=stuff#yay",
+                            server_url() ++ "/bing?q=stuff#yay"}
+        ])
+    }.
+
+
+do_rewrite_tests(Tests) ->
+    lists:map(fun({Header, Location, Url}) ->
+        should_rewrite_header(Header, Location, Url)
+    end, Tests).
+
+should_rewrite_header(Header, Location, Url) ->
+    Remote = fun(Req) ->
+        "/rewrite_test" = Req:get(path),
+        {ok, {302, [{Header, Location}], "ok"}}
+    end,
+    Local = fun
+        ({ok, "302", Headers, "ok"}) ->
+            ?assertEqual(Url, couch_util:get_value(Header, Headers)),
+            true;
+        (E) ->
+            ?debugFmt("~p", [E]),
+            false
+    end,
+    Req = #req{path="/rewrite_test"},
+    {Header, ?_test(check_request(Req, Remote, Local))}.
+
+
+server_url() ->
+    server_url("/_test").
+
+server_url(Resource) ->
+    Addr = couch_config:get("httpd", "bind_address"),
+    Port = integer_to_list(mochiweb_socket_server:get(couch_httpd, port)),
+    lists:concat(["http://", Addr, ":", Port, Resource]).
+
+proxy_url() ->
+    "http://127.0.0.1:" ++ integer_to_list(test_web:get_port()).
+
+external_url() ->
+    "https://google.com".
+
+check_request(Req, Remote, Local) ->
+    case Remote of
+        no_remote ->
+            ok;
+        _ ->
+            test_web:set_assert(Remote)
+    end,
+    Url = case proplists:lookup(url, Req#req.opts) of
+        none ->
+            server_url() ++ Req#req.path;
+        {url, DestUrl} ->
+            DestUrl
+    end,
+    Opts = [{headers_as_is, true} | Req#req.opts],
+    Resp =ibrowse:send_req(
+        Url, Req#req.headers, Req#req.method, Req#req.body, Opts
+    ),
+    %?debugFmt("ibrowse response: ~p", [Resp]),
+    case Local of
+        no_local ->
+            ok;
+        _ ->
+            ?assert(Local(Resp))
+    end,
+    case {Remote, Local} of
+        {no_remote, _} ->
+            ok;
+        {_, no_local} ->
+            ok;
+        _ ->
+            ?assertEqual(was_ok, test_web:check_last())
+    end,
+    Resp.
+
+chunked_body(Chunks) ->
+    chunked_body(Chunks, []).
+
+chunked_body([], Acc) ->
+    iolist_to_binary(lists:reverse(Acc, "0\r\n\r\n"));
+chunked_body([Chunk | Rest], Acc) ->
+    Size = to_hex(size(Chunk)),
+    chunked_body(Rest, ["\r\n", Chunk, "\r\n", Size | Acc]).
+
+to_hex(Val) ->
+    to_hex(Val, []).
+
+to_hex(0, Acc) ->
+    Acc;
+to_hex(Val, Acc) ->
+    to_hex(Val div 16, [hex_char(Val rem 16) | Acc]).
+
+hex_char(V) when V < 10 -> $0 + V;
+hex_char(V) -> $A + V - 10.
+
+recv_body(ReqId, Acc) ->
+    receive
+        {ibrowse_async_response, ReqId, Data} ->
+            recv_body(ReqId, [Data | Acc]);
+        {ibrowse_async_response_end, ReqId} ->
+            iolist_to_binary(lists:reverse(Acc));
+        Else ->
+            throw({error, unexpected_mesg, Else})
+    after ?TIMEOUT ->
+        throw({error, timeout})
+    end.
+
+
+%% Copy from couch test_util @ master branch
+
+now_us() ->
+    {MegaSecs, Secs, MicroSecs} = now(),
+    (MegaSecs * 1000000 + Secs) * 1000000 + MicroSecs.
+
+stop_sync(Name) ->
+    stop_sync(Name, shutdown).
+stop_sync(Name, Reason) ->
+    stop_sync(Name, Reason, 5000).
+stop_sync(Name, Reason, Timeout) when is_atom(Name) ->
+    stop_sync(whereis(Name), Reason, Timeout);
+stop_sync(Pid, Reason, Timeout) when is_atom(Reason) and is_pid(Pid) ->
+    stop_sync(Pid, fun() -> exit(Pid, Reason) end, Timeout);
+stop_sync(Pid, Fun, Timeout) when is_function(Fun) and is_pid(Pid) ->
+    MRef = erlang:monitor(process, Pid),
+    try
+        begin
+            catch unlink(Pid),
+            Res = (catch Fun()),
+            receive
+            {'DOWN', MRef, _, _, _} ->
+                Res
+            after Timeout ->
+                timeout
+            end
+        end
+    after
+        erlang:demonitor(MRef, [flush])
+    end;
+stop_sync(_, _, _) -> error(badarg).
+
+stop_sync_throw(Name, Error) ->
+    stop_sync_throw(Name, shutdown, Error).
+stop_sync_throw(Name, Reason, Error) ->
+    stop_sync_throw(Name, Reason, Error, 5000).
+stop_sync_throw(Pid, Fun, Error, Timeout) ->
+    case stop_sync(Pid, Fun, Timeout) of
+        timeout ->
+            throw(Error);
+        Else ->
+            Else
+    end.
+
+with_process_restart(Name) ->
+    {Pid, true} = with_process_restart(
+        fun() -> exit(whereis(Name), shutdown) end, Name),
+    Pid.
+with_process_restart(Name, Fun) ->
+    with_process_restart(Name, Fun, 5000).
+with_process_restart(Name, Fun, Timeout) ->
+    ok = stop_sync(Name, Fun),
+    case wait_process(Name, Timeout) of
+    timeout ->
+        timeout;
+    Pid ->
+        Pid
+    end.
+
+wait_process(Name) ->
+    wait_process(Name, 5000).
+wait_process(Name, Timeout) ->
+    wait(fun() ->
+       case whereis(Name) of
+       undefined ->
+          wait;
+       Pid ->
+          Pid
+       end
+    end, Timeout).
+
+wait(Fun) ->
+    wait(Fun, 5000, 50).
+wait(Fun, Timeout) ->
+    wait(Fun, Timeout, 50).
+wait(Fun, Timeout, Delay) ->
+    Now = now_us(),
+    wait(Fun, Timeout * 1000, Delay, Now, Now).
+wait(_Fun, Timeout, _Delay, Started, Prev) when Prev - Started > Timeout ->
+    timeout;
+wait(Fun, Timeout, Delay, Started, _Prev) ->
+    case Fun() of
+    wait ->
+        ok = timer:sleep(Delay),
+        wait(Fun, Timeout, Delay, Started, now_us());
+    Else ->
+        Else
+    end.

http://git-wip-us.apache.org/repos/asf/couchdb/blob/3e545431/test/couchdb/test_web.erl
----------------------------------------------------------------------
diff --git a/test/couchdb/test_web.erl b/test/couchdb/test_web.erl
new file mode 100644
index 0000000..1de2cd1
--- /dev/null
+++ b/test/couchdb/test_web.erl
@@ -0,0 +1,112 @@
+% 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.
+
+-module(test_web).
+-behaviour(gen_server).
+
+-include("couch_eunit.hrl").
+
+-export([start_link/0, stop/0, loop/1, get_port/0, set_assert/1, check_last/0]).
+-export([init/1, terminate/2, code_change/3]).
+-export([handle_call/3, handle_cast/2, handle_info/2]).
+
+-define(SERVER, test_web_server).
+-define(HANDLER, test_web_handler).
+-define(DELAY, 500).
+
+start_link() ->
+    gen_server:start({local, ?HANDLER}, ?MODULE, [], []),
+    mochiweb_http:start([
+        {name, ?SERVER},
+        {loop, {?MODULE, loop}},
+        {port, 0}
+    ]).
+
+loop(Req) ->
+    %?debugFmt("Handling request: ~p", [Req]),
+    case gen_server:call(?HANDLER, {check_request, Req}) of
+        {ok, RespInfo} ->
+            {ok, Req:respond(RespInfo)};
+        {raw, {Status, Headers, BodyChunks}} ->
+            Resp = Req:start_response({Status, Headers}),
+            lists:foreach(fun(C) -> Resp:send(C) end, BodyChunks),
+            erlang:put(mochiweb_request_force_close, true),
+            {ok, Resp};
+        {chunked, {Status, Headers, BodyChunks}} ->
+            Resp = Req:respond({Status, Headers, chunked}),
+            timer:sleep(?DELAY),
+            lists:foreach(fun(C) -> Resp:write_chunk(C) end, BodyChunks),
+            Resp:write_chunk([]),
+            {ok, Resp};
+        {error, Reason} ->
+            ?debugFmt("Error: ~p", [Reason]),
+            Body = lists:flatten(io_lib:format("Error: ~p", [Reason])),
+            {ok, Req:respond({200, [], Body})}
+    end.
+
+get_port() ->
+    mochiweb_socket_server:get(?SERVER, port).
+
+set_assert(Fun) ->
+    ?assertEqual(ok, gen_server:call(?HANDLER, {set_assert, Fun})).
+
+check_last() ->
+    gen_server:call(?HANDLER, last_status).
+
+init(_) ->
+    {ok, nil}.
+
+terminate(_Reason, _State) ->
+    ok.
+
+stop() ->
+    gen_server:cast(?SERVER, stop).
+
+
+handle_call({check_request, Req}, _From, State) when is_function(State, 1) ->
+    Resp2 = case (catch State(Req)) of
+        {ok, Resp} ->
+            {reply, {ok, Resp}, was_ok};
+        {raw, Resp} ->
+            {reply, {raw, Resp}, was_ok};
+        {chunked, Resp} ->
+            {reply, {chunked, Resp}, was_ok};
+        Error ->
+            {reply, {error, Error}, not_ok}
+    end,
+    Req:cleanup(),
+    Resp2;
+handle_call({check_request, _Req}, _From, _State) ->
+    {reply, {error, no_assert_function}, not_ok};
+handle_call(last_status, _From, State) when is_atom(State) ->
+    {reply, State, nil};
+handle_call(last_status, _From, State) ->
+    {reply, {error, not_checked}, State};
+handle_call({set_assert, Fun}, _From, nil) ->
+    {reply, ok, Fun};
+handle_call({set_assert, _}, _From, State) ->
+    {reply, {error, assert_function_set}, State};
+handle_call(Msg, _From, State) ->
+    {reply, {ignored, Msg}, State}.
+
+handle_cast(stop, State) ->
+    {stop, normal, State};
+handle_cast(Msg, State) ->
+    ?debugFmt("Ignoring cast message: ~p", [Msg]),
+    {noreply, State}.
+
+handle_info(Msg, State) ->
+    ?debugFmt("Ignoring info message: ~p", [Msg]),
+    {noreply, State}.
+
+code_change(_OldVsn, State, _Extra) ->
+    {ok, State}.

http://git-wip-us.apache.org/repos/asf/couchdb/blob/3e545431/test/etap/180-http-proxy.ini
----------------------------------------------------------------------
diff --git a/test/etap/180-http-proxy.ini b/test/etap/180-http-proxy.ini
deleted file mode 100644
index 3e2ba13..0000000
--- a/test/etap/180-http-proxy.ini
+++ /dev/null
@@ -1,20 +0,0 @@
-; Licensed to the Apache Software Foundation (ASF) under one
-; or more contributor license agreements.  See the NOTICE file
-; distributed with this work for additional information
-; regarding copyright ownership.  The ASF licenses this file
-; to you 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.
-
-; 49151 is IANA Reserved, let's assume no one is listening there
-[httpd_global_handlers]
-_error = {couch_httpd_proxy, handle_proxy_req, <<"http://127.0.0.1:49151/">>}

http://git-wip-us.apache.org/repos/asf/couchdb/blob/3e545431/test/etap/180-http-proxy.t
----------------------------------------------------------------------
diff --git a/test/etap/180-http-proxy.t b/test/etap/180-http-proxy.t
deleted file mode 100755
index da67603..0000000
--- a/test/etap/180-http-proxy.t
+++ /dev/null
@@ -1,376 +0,0 @@
-#!/usr/bin/env escript
-% 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.
-
--record(req, {method=get, path="", headers=[], body="", opts=[]}).
-
-server() ->
-    lists:concat([
-        "http://127.0.0.1:",
-        mochiweb_socket_server:get(couch_httpd, port),
-        "/_test/"
-    ]).
-
-proxy() ->
-    "http://127.0.0.1:" ++ integer_to_list(test_web:get_port()) ++ "/".
-
-external() -> "https://www.google.com/".
-
-main(_) ->
-    test_util:init_code_path(),
-
-    etap:plan(61),
-    case (catch test()) of
-        ok ->
-            etap:end_tests();
-        Other ->
-            etap:diag("Test died abnormally: ~p", [Other]),
-            etap:bail("Bad return value.")
-    end,
-    ok.
-
-check_request(Name, Req, Remote, Local) ->
-    case Remote of
-        no_remote -> ok;
-        _ -> test_web:set_assert(Remote)
-    end,
-    Url = case proplists:lookup(url, Req#req.opts) of
-        none -> server() ++ Req#req.path;
-        {url, DestUrl} -> DestUrl
-    end,
-    Opts = [{headers_as_is, true} | Req#req.opts],
-    Resp =ibrowse:send_req(
-        Url, Req#req.headers, Req#req.method, Req#req.body, Opts
-    ),
-    %etap:diag("ibrowse response: ~p", [Resp]),
-    case Local of
-        no_local -> ok;
-        _ -> etap:fun_is(Local, Resp, Name)
-    end,
-    case {Remote, Local} of
-        {no_remote, _} ->
-            ok;
-        {_, no_local} ->
-            ok;
-        _ ->
-            etap:is(test_web:check_last(), was_ok, Name ++ " - request handled")
-    end,
-    Resp.
-
-test() ->
-    ExtraConfig = [test_util:source_file("test/etap/180-http-proxy.ini")],
-    couch_server_sup:start_link(test_util:config_files() ++ ExtraConfig),
-    ibrowse:start(),
-    crypto:start(),
-
-    % start the test_web server on a random port
-    test_web:start_link(),
-    Url = lists:concat([
-        "{couch_httpd_proxy, handle_proxy_req, <<\"http://127.0.0.1:",
-        test_web:get_port(),
-        "/\">>}"
-    ]),
-    couch_config:set("httpd_global_handlers", "_test", Url, false),
-
-    % let couch_httpd restart
-    timer:sleep(100),
-
-    test_basic(),
-    test_alternate_status(),
-    test_trailing_slash(),
-    test_passes_header(),
-    test_passes_host_header(),
-    test_passes_header_back(),
-    test_rewrites_location_headers(),
-    test_doesnt_rewrite_external_locations(),
-    test_rewrites_relative_location(),
-    test_uses_same_version(),
-    test_passes_body(),
-    test_passes_eof_body_back(),
-    test_passes_chunked_body(),
-    test_passes_chunked_body_back(),
-
-    test_connect_error(),
-    
-    ok.
-
-test_basic() ->
-    Remote = fun(Req) ->
-        'GET' = Req:get(method),
-        "/" = Req:get(path),
-        0 = Req:get(body_length),
-        <<>> = Req:recv_body(),
-        {ok, {200, [{"Content-Type", "text/plain"}], "ok"}}
-    end,
-    Local = fun({ok, "200", _, "ok"}) -> true; (_) -> false end,
-    check_request("Basic proxy test", #req{}, Remote, Local).
-
-test_alternate_status() ->
-    Remote = fun(Req) ->
-        "/alternate_status" = Req:get(path),
-        {ok, {201, [], "ok"}}
-    end,
-    Local = fun({ok, "201", _, "ok"}) -> true; (_) -> false end,
-    Req = #req{path="alternate_status"},
-    check_request("Alternate status", Req, Remote, Local).
-
-test_trailing_slash() ->
-    Remote = fun(Req) ->
-        "/trailing_slash/" = Req:get(path),
-        {ok, {200, [], "ok"}}
-    end,
-    Local = fun({ok, "200", _, "ok"}) -> true; (_) -> false end,
-    Req = #req{path="trailing_slash/"},
-    check_request("Trailing slash", Req, Remote, Local).
-
-test_passes_header() ->
-    Remote = fun(Req) ->
-        "/passes_header" = Req:get(path),
-        "plankton" = Req:get_header_value("X-CouchDB-Ralph"),
-        {ok, {200, [], "ok"}}
-    end,
-    Local = fun({ok, "200", _, "ok"}) -> true; (_) -> false end,
-    Req = #req{
-        path="passes_header",
-        headers=[{"X-CouchDB-Ralph", "plankton"}]
-    },
-    check_request("Passes header", Req, Remote, Local).
-
-test_passes_host_header() ->
-    Remote = fun(Req) ->
-        "/passes_host_header" = Req:get(path),
-        "www.google.com" = Req:get_header_value("Host"),
-        {ok, {200, [], "ok"}}
-    end,
-    Local = fun({ok, "200", _, "ok"}) -> true; (_) -> false end,
-    Req = #req{
-        path="passes_host_header",
-        headers=[{"Host", "www.google.com"}]
-    },
-    check_request("Passes host header", Req, Remote, Local).
-
-test_passes_header_back() ->
-    Remote = fun(Req) ->
-        "/passes_header_back" = Req:get(path),
-        {ok, {200, [{"X-CouchDB-Plankton", "ralph"}], "ok"}}
-    end,
-    Local = fun
-        ({ok, "200", Headers, "ok"}) ->
-            lists:member({"X-CouchDB-Plankton", "ralph"}, Headers);
-        (_) ->
-            false
-    end,
-    Req = #req{path="passes_header_back"},
-    check_request("Passes header back", Req, Remote, Local).
-
-test_rewrites_location_headers() ->
-    etap:diag("Testing location header rewrites."),
-    do_rewrite_tests([
-        {"Location", proxy() ++ "foo/bar", server() ++ "foo/bar"},
-        {"Content-Location", proxy() ++ "bing?q=2", server() ++ "bing?q=2"},
-        {"Uri", proxy() ++ "zip#frag", server() ++ "zip#frag"},
-        {"Destination", proxy(), server()}
-    ]).
-
-test_doesnt_rewrite_external_locations() ->
-    etap:diag("Testing no rewrite of external locations."),
-    do_rewrite_tests([
-        {"Location", external() ++ "search", external() ++ "search"},
-        {"Content-Location", external() ++ "s?q=2", external() ++ "s?q=2"},
-        {"Uri", external() ++ "f#f", external() ++ "f#f"},
-        {"Destination", external() ++ "f?q=2#f", external() ++ "f?q=2#f"}
-    ]).
-
-test_rewrites_relative_location() ->
-    etap:diag("Testing relative rewrites."),
-    do_rewrite_tests([
-        {"Location", "/foo", server() ++ "foo"},
-        {"Content-Location", "bar", server() ++ "bar"},
-        {"Uri", "/zing?q=3", server() ++ "zing?q=3"},
-        {"Destination", "bing?q=stuff#yay", server() ++ "bing?q=stuff#yay"}
-    ]).
-
-do_rewrite_tests(Tests) ->
-    lists:foreach(fun({Header, Location, Url}) ->
-        do_rewrite_test(Header, Location, Url)
-    end, Tests).
-    
-do_rewrite_test(Header, Location, Url) ->
-    Remote = fun(Req) ->
-        "/rewrite_test" = Req:get(path),
-        {ok, {302, [{Header, Location}], "ok"}}
-    end,
-    Local = fun
-        ({ok, "302", Headers, "ok"}) ->
-            etap:is(
-                couch_util:get_value(Header, Headers),
-                Url,
-                "Header rewritten correctly."
-            ),
-            true;
-        (_) ->
-            false
-    end,
-    Req = #req{path="rewrite_test"},
-    Label = "Rewrite test for ",
-    check_request(Label ++ Header, Req, Remote, Local).
-
-test_uses_same_version() ->
-    Remote = fun(Req) ->
-        "/uses_same_version" = Req:get(path),
-        {1, 0} = Req:get(version),
-        {ok, {200, [], "ok"}}
-    end,
-    Local = fun({ok, "200", _, "ok"}) -> true; (_) -> false end,
-    Req = #req{
-        path="uses_same_version",
-        opts=[{http_vsn, {1, 0}}]
-    },
-    check_request("Uses same version", Req, Remote, Local).
-
-test_passes_body() ->
-    Remote = fun(Req) ->
-        'PUT' = Req:get(method),
-        "/passes_body" = Req:get(path),
-        <<"Hooray!">> = Req:recv_body(),
-        {ok, {201, [], "ok"}}
-    end,
-    Local = fun({ok, "201", _, "ok"}) -> true; (_) -> false end,
-    Req = #req{
-        method=put,
-        path="passes_body",
-        body="Hooray!"
-    },
-    check_request("Passes body", Req, Remote, Local).
-
-test_passes_eof_body_back() ->
-    BodyChunks = [<<"foo">>, <<"bar">>, <<"bazinga">>],
-    Remote = fun(Req) ->
-        'GET' = Req:get(method),
-        "/passes_eof_body" = Req:get(path),
-        {raw, {200, [{"Connection", "close"}], BodyChunks}}
-    end,
-    Local = fun({ok, "200", _, "foobarbazinga"}) -> true; (_) -> false end,
-    Req = #req{path="passes_eof_body"},
-    check_request("Passes eof body", Req, Remote, Local).
-
-test_passes_chunked_body() ->
-    BodyChunks = [<<"foo">>, <<"bar">>, <<"bazinga">>],
-    Remote = fun(Req) ->
-        'POST' = Req:get(method),
-        "/passes_chunked_body" = Req:get(path),
-        RecvBody = fun
-            ({Length, Chunk}, [Chunk | Rest]) ->
-                Length = size(Chunk),
-                Rest;
-            ({0, []}, []) ->
-                ok
-        end,
-        ok = Req:stream_body(1024*1024, RecvBody, BodyChunks),
-        {ok, {201, [], "ok"}}
-    end,
-    Local = fun({ok, "201", _, "ok"}) -> true; (_) -> false end,
-    Req = #req{
-        method=post,
-        path="passes_chunked_body",
-        headers=[{"Transfer-Encoding", "chunked"}],
-        body=mk_chunked_body(BodyChunks)
-    },
-    check_request("Passes chunked body", Req, Remote, Local).
-
-test_passes_chunked_body_back() ->
-    Name = "Passes chunked body back",
-    Remote = fun(Req) ->
-        'GET' = Req:get(method),
-        "/passes_chunked_body_back" = Req:get(path),
-        BodyChunks = [<<"foo">>, <<"bar">>, <<"bazinga">>],
-        {chunked, {200, [{"Transfer-Encoding", "chunked"}], BodyChunks}}
-    end,
-    Req = #req{
-        path="passes_chunked_body_back",
-        opts=[{stream_to, self()}]
-    },
-
-    Resp = check_request(Name, Req, Remote, no_local),
-
-    etap:fun_is(
-        fun({ibrowse_req_id, _}) -> true; (_) -> false end,
-        Resp,
-        "Received an ibrowse request id."
-    ),
-    {_, ReqId} = Resp,
-    
-    % Grab headers from response
-    receive
-        {ibrowse_async_headers, ReqId, "200", Headers} ->
-            etap:is(
-                proplists:get_value("Transfer-Encoding", Headers),
-                "chunked",
-                "Response included the Transfer-Encoding: chunked header"
-            ),
-        ibrowse:stream_next(ReqId)
-    after 1000 ->
-        throw({error, timeout})
-    end,
-    
-    % Check body received
-    % TODO: When we upgrade to ibrowse >= 2.0.0 this check needs to
-    %       check that the chunks returned are what we sent from the
-    %       Remote test.
-    etap:diag("TODO: UPGRADE IBROWSE"),
-    etap:is(recv_body(ReqId, []), <<"foobarbazinga">>, "Decoded chunked body."),
-
-    % Check test_web server.
-    etap:is(test_web:check_last(), was_ok, Name ++ " - request handled").
-
-test_connect_error() ->
-    Local = fun({ok, "500", _Headers, _Body}) -> true; (_) -> false end,
-    Url = lists:concat([
-        "http://127.0.0.1:",
-        mochiweb_socket_server:get(couch_httpd, port),
-        "/_error"
-    ]),
-    Req = #req{opts=[{url, Url}]},
-    check_request("Connect error", Req, no_remote, Local).
-
-
-mk_chunked_body(Chunks) ->
-    mk_chunked_body(Chunks, []).
-
-mk_chunked_body([], Acc) ->
-    iolist_to_binary(lists:reverse(Acc, "0\r\n\r\n"));
-mk_chunked_body([Chunk | Rest], Acc) ->
-    Size = to_hex(size(Chunk)),
-    mk_chunked_body(Rest, ["\r\n", Chunk, "\r\n", Size | Acc]).
-
-to_hex(Val) ->
-    to_hex(Val, []).
-
-to_hex(0, Acc) ->
-    Acc;
-to_hex(Val, Acc) ->
-    to_hex(Val div 16, [hex_char(Val rem 16) | Acc]).
-
-hex_char(V) when V < 10 -> $0 + V;
-hex_char(V) -> $A + V - 10.
-
-recv_body(ReqId, Acc) ->
-    receive
-        {ibrowse_async_response, ReqId, Data} ->
-            recv_body(ReqId, [Data | Acc]);
-        {ibrowse_async_response_end, ReqId} ->
-            iolist_to_binary(lists:reverse(Acc));
-        Else ->
-            throw({error, unexpected_mesg, Else})
-    after 5000 ->
-        throw({error, timeout})
-    end.

http://git-wip-us.apache.org/repos/asf/couchdb/blob/3e545431/test/etap/Makefile.am
----------------------------------------------------------------------
diff --git a/test/etap/Makefile.am b/test/etap/Makefile.am
index 05a7870..b92faf1 100644
--- a/test/etap/Makefile.am
+++ b/test/etap/Makefile.am
@@ -11,7 +11,7 @@
 ## the License.
 
 noinst_SCRIPTS = run
-noinst_DATA = test_util.beam test_web.beam
+noinst_DATA = test_util.beam
 
 %.beam: %.erl
 	$(ERLC) $<
@@ -32,8 +32,6 @@ fixture_files = \
     fixtures/test.couch
 
 tap_files = \
-    180-http-proxy.ini \
-    180-http-proxy.t \
     190-json-stream-parse.t \
     200-view-group-no-db-leaks.t \
     201-view-group-shutdown.t \
@@ -45,6 +43,5 @@ tap_files = \
 
 EXTRA_DIST = \
     run.tpl \
-    test_web.erl \
     $(fixture_files) \
     $(tap_files)

http://git-wip-us.apache.org/repos/asf/couchdb/blob/3e545431/test/etap/test_web.erl
----------------------------------------------------------------------
diff --git a/test/etap/test_web.erl b/test/etap/test_web.erl
deleted file mode 100644
index ed78651..0000000
--- a/test/etap/test_web.erl
+++ /dev/null
@@ -1,99 +0,0 @@
-% 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.
-
--module(test_web).
--behaviour(gen_server).
-
--export([start_link/0, loop/1, get_port/0, set_assert/1, check_last/0]).
--export([init/1, terminate/2, code_change/3]).
--export([handle_call/3, handle_cast/2, handle_info/2]).
-
--define(SERVER, test_web_server).
--define(HANDLER, test_web_handler).
-
-start_link() ->
-    gen_server:start({local, ?HANDLER}, ?MODULE, [], []),
-    mochiweb_http:start([
-        {name, ?SERVER},
-        {loop, {?MODULE, loop}},
-        {port, 0}
-    ]).
-
-loop(Req) ->
-    %etap:diag("Handling request: ~p", [Req]),
-    case gen_server:call(?HANDLER, {check_request, Req}) of
-        {ok, RespInfo} ->
-            {ok, Req:respond(RespInfo)};
-        {raw, {Status, Headers, BodyChunks}} ->
-            Resp = Req:start_response({Status, Headers}),
-            lists:foreach(fun(C) -> Resp:send(C) end, BodyChunks),
-            erlang:put(mochiweb_request_force_close, true),
-            {ok, Resp};
-        {chunked, {Status, Headers, BodyChunks}} ->
-            Resp = Req:respond({Status, Headers, chunked}),
-            timer:sleep(500),
-            lists:foreach(fun(C) -> Resp:write_chunk(C) end, BodyChunks),
-            Resp:write_chunk([]),
-            {ok, Resp};
-        {error, Reason} ->
-            etap:diag("Error: ~p", [Reason]),
-            Body = lists:flatten(io_lib:format("Error: ~p", [Reason])),
-            {ok, Req:respond({200, [], Body})}
-    end.
-
-get_port() ->
-    mochiweb_socket_server:get(?SERVER, port).
-
-set_assert(Fun) ->
-    ok = gen_server:call(?HANDLER, {set_assert, Fun}).
-
-check_last() ->
-    gen_server:call(?HANDLER, last_status).
-
-init(_) ->
-    {ok, nil}.
-
-terminate(_Reason, _State) ->
-    ok.
-
-handle_call({check_request, Req}, _From, State) when is_function(State, 1) ->
-    Resp2 = case (catch State(Req)) of
-        {ok, Resp} -> {reply, {ok, Resp}, was_ok};
-        {raw, Resp} -> {reply, {raw, Resp}, was_ok};
-        {chunked, Resp} -> {reply, {chunked, Resp}, was_ok};
-        Error -> {reply, {error, Error}, not_ok}
-    end,
-    Req:cleanup(),
-    Resp2;
-handle_call({check_request, _Req}, _From, _State) ->
-    {reply, {error, no_assert_function}, not_ok};
-handle_call(last_status, _From, State) when is_atom(State) ->
-    {reply, State, nil};
-handle_call(last_status, _From, State) ->
-    {reply, {error, not_checked}, State};
-handle_call({set_assert, Fun}, _From, nil) ->
-    {reply, ok, Fun};
-handle_call({set_assert, _}, _From, State) ->
-    {reply, {error, assert_function_set}, State};
-handle_call(Msg, _From, State) ->
-    {reply, {ignored, Msg}, State}.
-
-handle_cast(Msg, State) ->
-    etap:diag("Ignoring cast message: ~p", [Msg]),
-    {noreply, State}.
-
-handle_info(Msg, State) ->
-    etap:diag("Ignoring info message: ~p", [Msg]),
-    {noreply, State}.
-
-code_change(_OldVsn, State, _Extra) ->
-    {ok, State}.