From dev-return-12817-apmail-couchdb-dev-archive=couchdb.apache.org@couchdb.apache.org Sat Nov 06 00:13:01 2010 Return-Path: Delivered-To: apmail-couchdb-dev-archive@www.apache.org Received: (qmail 121 invoked from network); 6 Nov 2010 00:13:00 -0000 Received: from unknown (HELO mail.apache.org) (140.211.11.3) by 140.211.11.9 with SMTP; 6 Nov 2010 00:13:00 -0000 Received: (qmail 41147 invoked by uid 500); 6 Nov 2010 00:13:31 -0000 Delivered-To: apmail-couchdb-dev-archive@couchdb.apache.org Received: (qmail 41094 invoked by uid 500); 6 Nov 2010 00:13:31 -0000 Mailing-List: contact dev-help@couchdb.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@couchdb.apache.org Delivered-To: mailing list dev@couchdb.apache.org Received: (qmail 41085 invoked by uid 99); 6 Nov 2010 00:13:31 -0000 Received: from athena.apache.org (HELO athena.apache.org) (140.211.11.136) by apache.org (qpsmtpd/0.29) with ESMTP; Sat, 06 Nov 2010 00:13:31 +0000 X-ASF-Spam-Status: No, hits=2.5 required=10.0 tests=FREEMAIL_FROM,FREEMAIL_REPLY,NORMAL_HTTP_TO_IP,RCVD_IN_DNSWL_NONE,SPF_PASS,T_TO_NO_BRKTS_FREEMAIL,WEIRD_PORT X-Spam-Check-By: apache.org Received-SPF: pass (athena.apache.org: domain of paul.joseph.davis@gmail.com designates 209.85.214.180 as permitted sender) Received: from [209.85.214.180] (HELO mail-iw0-f180.google.com) (209.85.214.180) by apache.org (qpsmtpd/0.29) with ESMTP; Sat, 06 Nov 2010 00:13:27 +0000 Received: by iwn37 with SMTP id 37so3570027iwn.11 for ; Fri, 05 Nov 2010 17:13:06 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=gamma; h=domainkey-signature:received:mime-version:received:in-reply-to :references:from:date:message-id:subject:to:content-type :content-transfer-encoding; bh=aXe5nEaXr+HjBCTiEsXHqdWvSC8EuOzl+vzww8iY7ao=; b=E1+A4D+gyEzktaN1VwJflofG1XAQUe1o3sr1s8MzGx7rDk4aniPY2HUnK/UTWMnoae sNFOwtT/plHFtxOpsGSnDKMQye+VTwH5CYbxx1JG4bb8vcvCVhhqzpL1ou9XFv97Wm5c qIH+mNrogitOfxixRJeUBOEFgFMznqw3mMBV0= DomainKey-Signature: a=rsa-sha1; c=nofws; d=gmail.com; s=gamma; h=mime-version:in-reply-to:references:from:date:message-id:subject:to :content-type:content-transfer-encoding; b=U0/KIdQv2vOKdURVP25BSMsVNligBcHi7NjhcvQLbXO4tw1duFjL2eQGDvmSB3mL2H 4Kqaj4BakBwNlNGYoF9vgqDOe7QjEyq6Ac6NTg3O0JQX8X3YeShS7krdNTCgo860XDB7 oO3TU1MuQUmtp4+5dINqZM7+nleWYpWW4KfLY= Received: by 10.42.214.146 with SMTP id ha18mr1571645icb.503.1289002386290; Fri, 05 Nov 2010 17:13:06 -0700 (PDT) MIME-Version: 1.0 Received: by 10.231.32.139 with HTTP; Fri, 5 Nov 2010 17:12:26 -0700 (PDT) In-Reply-To: References: <20101105232622.83FB523889BB@eris.apache.org> From: Paul Davis Date: Fri, 5 Nov 2010 20:12:26 -0400 Message-ID: Subject: Re: svn commit: r1031877 - in /couchdb/trunk: etc/couchdb/ share/www/script/test/ src/couchdb/ test/etap/ To: dev@couchdb.apache.org Content-Type: text/plain; charset=ISO-8859-1 Content-Transfer-Encoding: quoted-printable Filipe, Fixing now. On Fri, Nov 5, 2010 at 8:01 PM, Filipe David Manana w= rote: > Paul, > > Here: > > +stream_chunked_response(Req, ReqId, Resp) -> > + =A0 =A0receive > + =A0 =A0 =A0 =A0{ibrowse_async_response, ReqId, Chunk} -> > + =A0 =A0 =A0 =A0 =A0 =A0couch_httpd:send_chunk(Resp, Chunk), > + =A0 =A0 =A0 =A0 =A0 =A0ibrowse:stream_next(ReqId), > + =A0 =A0 =A0 =A0 =A0 =A0stream_chunked_response(Req, ReqId, Resp); > + =A0 =A0 =A0 =A0{ibrowse_async_response, ReqId, {error, Reason}} -> > + =A0 =A0 =A0 =A0 =A0 =A0throw({error, Reason}); > + =A0 =A0 =A0 =A0{ibrowse_async_response_end, ReqId} -> > + =A0 =A0 =A0 =A0 =A0 =A0couch_httpd:last_chunk(Resp) > + =A0 =A0end. > + > + > +stream_length_response(Req, ReqId, Resp) -> > + =A0 =A0receive > + =A0 =A0 =A0 =A0{ibrowse_async_response, ReqId, Chunk} -> > + =A0 =A0 =A0 =A0 =A0 =A0couch_httpd:send(Resp, Chunk), > + =A0 =A0 =A0 =A0 =A0 =A0ibrowse:stream_next(ReqId), > + =A0 =A0 =A0 =A0 =A0 =A0stream_length_response(Req, ReqId, Resp); > + =A0 =A0 =A0 =A0{ibrowse_async_response, {error, Reason}} -> > + =A0 =A0 =A0 =A0 =A0 =A0throw({error, Reason}); > + =A0 =A0 =A0 =A0{ibrowse_async_response_end, ReqId} -> > + =A0 =A0 =A0 =A0 =A0 =A0ok > + =A0 =A0end. > > The " {ibrowse_async_response, ReqId, {error, Reason}} =A0" clauses > should come before the =A0" {ibrowse_async_response, ReqId, Chunk} " > clauses, since the last always mask the former. > > Also, in the stream_length_response function you're missing the ReqId > element before the {error, Reason} element. > > Good work :D > > On Fri, Nov 5, 2010 at 11:26 PM, =A0 wrote: >> Author: davisp >> Date: Fri Nov =A05 23:26:21 2010 >> New Revision: 1031877 >> >> URL: http://svn.apache.org/viewvc?rev=3D1031877&view=3Drev >> Log: >> HTTP proxy handler. >> >> The second of two new features to replace the _externals protocols. This >> allows users to configure CouchDB to proxy requests to an external HTTP >> server. The external HTTP server is not required to be on the same host >> running CouchDB. >> >> The configuration looks like such: >> >> [httpd_global_handlers] >> _google =3D {couch_httpd_proxy, handle_proxy_req, <<"http://www.google.c= om">>} >> >> You can then hit this proxy at the url: >> >> http://127.0.0.1:5984/_google >> >> If you add any path after the proxy name, or make a request with a query >> string, those will be appended to the URL specified in the configuration= . >> >> Ie: >> >> =A0 =A0http://127.0.0.1:5984/_google/search?q=3Dplankton >> >> would translate to: >> >> =A0 =A0http://www.google.com/search?q=3Dplankton >> >> Obviously, request bodies are handled as expected. >> >> >> Added: >> =A0 =A0couchdb/trunk/src/couchdb/couch_httpd_proxy.erl >> =A0 =A0couchdb/trunk/test/etap/180-http-proxy.ini >> =A0 =A0couchdb/trunk/test/etap/180-http-proxy.t >> =A0 =A0couchdb/trunk/test/etap/test_web.erl >> Modified: >> =A0 =A0couchdb/trunk/etc/couchdb/local.ini >> =A0 =A0couchdb/trunk/share/www/script/test/basics.js >> =A0 =A0couchdb/trunk/src/couchdb/Makefile.am >> =A0 =A0couchdb/trunk/src/couchdb/couch_httpd.erl >> =A0 =A0couchdb/trunk/test/etap/ =A0 (props changed) >> =A0 =A0couchdb/trunk/test/etap/Makefile.am >> >> Modified: couchdb/trunk/etc/couchdb/local.ini >> URL: http://svn.apache.org/viewvc/couchdb/trunk/etc/couchdb/local.ini?re= v=3D1031877&r1=3D1031876&r2=3D1031877&view=3Ddiff >> =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D >> --- couchdb/trunk/etc/couchdb/local.ini (original) >> +++ couchdb/trunk/etc/couchdb/local.ini Fri Nov =A05 23:26:21 2010 >> @@ -20,6 +20,9 @@ >> =A0; the whitelist. >> =A0;config_whitelist =3D [{httpd,config_whitelist}, {log,level}, {etc,et= c}] >> >> +[httpd_global_handlers] >> +;_google =3D {couch_httpd_proxy, handle_proxy_req, <<"http://www.google= .com">>} >> + >> =A0[couch_httpd_auth] >> =A0; If you set this to true, you should also uncomment the WWW-Authenti= cate line >> =A0; above. If you don't configure a WWW-Authenticate header, CouchDB wi= ll send >> >> Modified: couchdb/trunk/share/www/script/test/basics.js >> URL: http://svn.apache.org/viewvc/couchdb/trunk/share/www/script/test/ba= sics.js?rev=3D1031877&r1=3D1031876&r2=3D1031877&view=3Ddiff >> =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D >> --- couchdb/trunk/share/www/script/test/basics.js (original) >> +++ couchdb/trunk/share/www/script/test/basics.js Fri Nov =A05 23:26:21 = 2010 >> @@ -159,8 +159,8 @@ couchTests.basics =3D function(debug) { >> =A0 var loc =3D xhr.getResponseHeader("Location"); >> =A0 T(loc, "should have a Location header"); >> =A0 var locs =3D loc.split('/'); >> - =A0T(locs[4] =3D=3D resp.id); >> - =A0T(locs[3] =3D=3D "test_suite_db"); >> + =A0T(locs[locs.length-1] =3D=3D resp.id); >> + =A0T(locs[locs.length-2] =3D=3D "test_suite_db"); >> >> =A0 // test that that POST's with an _id aren't overriden with a UUID. >> =A0 var xhr =3D CouchDB.request("POST", "/test_suite_db", { >> >> Modified: couchdb/trunk/src/couchdb/Makefile.am >> URL: http://svn.apache.org/viewvc/couchdb/trunk/src/couchdb/Makefile.am?= rev=3D1031877&r1=3D1031876&r2=3D1031877&view=3Ddiff >> =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D >> --- couchdb/trunk/src/couchdb/Makefile.am (original) >> +++ couchdb/trunk/src/couchdb/Makefile.am Fri Nov =A05 23:26:21 2010 >> @@ -50,6 +50,7 @@ source_files =3D \ >> =A0 =A0 couch_httpd_show.erl \ >> =A0 =A0 couch_httpd_view.erl \ >> =A0 =A0 couch_httpd_misc_handlers.erl \ >> + =A0 =A0couch_httpd_proxy.erl \ >> =A0 =A0 =A0 =A0couch_httpd_rewrite.erl \ >> =A0 =A0 couch_httpd_stats_handlers.erl \ >> =A0 =A0 =A0 =A0couch_httpd_vhost.erl \ >> @@ -107,6 +108,7 @@ compiled_files =3D \ >> =A0 =A0 couch_httpd_db.beam \ >> =A0 =A0 couch_httpd_auth.beam \ >> =A0 =A0 couch_httpd_oauth.beam \ >> + =A0 =A0couch_httpd_proxy.beam \ >> =A0 =A0 couch_httpd_external.beam \ >> =A0 =A0 couch_httpd_show.beam \ >> =A0 =A0 couch_httpd_view.beam \ >> >> Modified: couchdb/trunk/src/couchdb/couch_httpd.erl >> URL: http://svn.apache.org/viewvc/couchdb/trunk/src/couchdb/couch_httpd.= erl?rev=3D1031877&r1=3D1031876&r2=3D1031877&view=3Ddiff >> =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D >> --- couchdb/trunk/src/couchdb/couch_httpd.erl (original) >> +++ couchdb/trunk/src/couchdb/couch_httpd.erl Fri Nov =A05 23:26:21 2010 >> @@ -22,7 +22,7 @@ >> =A0-export([parse_form/1,json_body/1,json_body_obj/1,body/1,doc_etag/1, = make_etag/1, etag_respond/3]). >> =A0-export([primary_header_value/2,partition/1,serve_file/3,serve_file/4= , server_header/0]). >> =A0-export([start_chunked_response/3,send_chunk/2,log_request/2]). >> --export([start_response_length/4, send/2]). >> +-export([start_response_length/4, start_response/3, send/2]). >> =A0-export([start_json_response/2, start_json_response/3, end_json_respo= nse/1]). >> =A0-export([send_response/4,send_method_not_allowed/2,send_error/4, send= _redirect/2,send_chunked_error/2]). >> =A0-export([send_json/2,send_json/3,send_json/4,last_chunk/1,parse_multi= part_request/3]). >> @@ -526,6 +526,18 @@ start_response_length(#httpd{mochi_req=3DM >> =A0 =A0 end, >> =A0 =A0 {ok, Resp}. >> >> +start_response(#httpd{mochi_req=3DMochiReq}=3DReq, Code, Headers) -> >> + =A0 =A0log_request(Req, Code), >> + =A0 =A0couch_stats_collector:increment({httpd_status_cdes, Code}), >> + =A0 =A0CookieHeader =3D couch_httpd_auth:cookie_auth_header(Req, Heade= rs), >> + =A0 =A0Headers2 =3D Headers ++ server_header() ++ CookieHeader, >> + =A0 =A0Resp =3D MochiReq:start_response({Code, Headers2}), >> + =A0 =A0case MochiReq:get(method) of >> + =A0 =A0 =A0 =A0'HEAD' -> throw({http_head_abort, Resp}); >> + =A0 =A0 =A0 =A0_ -> ok >> + =A0 =A0end, >> + =A0 =A0{ok, Resp}. >> + >> =A0send(Resp, Data) -> >> =A0 =A0 Resp:send(Data), >> =A0 =A0 {ok, Resp}. >> >> Added: couchdb/trunk/src/couchdb/couch_httpd_proxy.erl >> URL: http://svn.apache.org/viewvc/couchdb/trunk/src/couchdb/couch_httpd_= proxy.erl?rev=3D1031877&view=3Dauto >> =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D >> --- couchdb/trunk/src/couchdb/couch_httpd_proxy.erl (added) >> +++ couchdb/trunk/src/couchdb/couch_httpd_proxy.erl Fri Nov =A05 23:26:2= 1 2010 >> @@ -0,0 +1,425 @@ >> +% Licensed under the Apache License, Version 2.0 (the "License"); you m= ay not >> +% use this file except in compliance with the License. You may obtain a= copy of >> +% the License at >> +% >> +% =A0 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, WIT= HOUT >> +% WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See = the >> +% License for the specific language governing permissions and limitatio= ns under >> +% the License. >> +-module(couch_httpd_proxy). >> + >> +-export([handle_proxy_req/2]). >> + >> +-include("couch_db.hrl"). >> +-include("../ibrowse/ibrowse.hrl"). >> + >> +-define(TIMEOUT, infinity). >> +-define(PKT_SIZE, 4096). >> + >> + >> +handle_proxy_req(Req, ProxyDest) -> >> + >> + =A0 =A0%% Bug in Mochiweb? >> + =A0 =A0%% Reported here: http://github.com/mochi/mochiweb/issues/issue= /16 >> + =A0 =A0erase(mochiweb_request_body_length), >> + >> + =A0 =A0Method =3D get_method(Req), >> + =A0 =A0Url =3D get_url(Req, ProxyDest), >> + =A0 =A0Version =3D get_version(Req), >> + =A0 =A0Headers =3D get_headers(Req), >> + =A0 =A0Body =3D get_body(Req), >> + =A0 =A0Options =3D [ >> + =A0 =A0 =A0 =A0{http_vsn, Version}, >> + =A0 =A0 =A0 =A0{headers_as_is, true}, >> + =A0 =A0 =A0 =A0{response_format, binary}, >> + =A0 =A0 =A0 =A0{stream_to, {self(), once}} >> + =A0 =A0], >> + =A0 =A0case ibrowse:send_req(Url, Headers, Method, Body, Options, ?TIM= EOUT) of >> + =A0 =A0 =A0 =A0{ibrowse_req_id, ReqId} -> >> + =A0 =A0 =A0 =A0 =A0 =A0stream_response(Req, ProxyDest, ReqId); >> + =A0 =A0 =A0 =A0{error, Reason} -> >> + =A0 =A0 =A0 =A0 =A0 =A0throw({error, Reason}) >> + =A0 =A0end. >> + >> + >> +get_method(#httpd{mochi_req=3DMochiReq}) -> >> + =A0 =A0case MochiReq:get(method) of >> + =A0 =A0 =A0 =A0Method when is_atom(Method) -> >> + =A0 =A0 =A0 =A0 =A0 =A0list_to_atom(string:to_lower(atom_to_list(Metho= d))); >> + =A0 =A0 =A0 =A0Method when is_list(Method) -> >> + =A0 =A0 =A0 =A0 =A0 =A0list_to_atom(string:to_lower(Method)); >> + =A0 =A0 =A0 =A0Method when is_binary(Method) -> >> + =A0 =A0 =A0 =A0 =A0 =A0list_to_atom(string:to_lower(?b2l(Method))) >> + =A0 =A0end. >> + >> + >> +get_url(Req, ProxyDest) when is_binary(ProxyDest) -> >> + =A0 =A0get_url(Req, ?b2l(ProxyDest)); >> +get_url(#httpd{mochi_req=3DMochiReq}=3DReq, ProxyDest) -> >> + =A0 =A0BaseUrl =3D case mochiweb_util:partition(ProxyDest, "/") of >> + =A0 =A0 =A0 =A0{[], "/", _} -> couch_httpd:absolute_uri(Req, ProxyDest= ); >> + =A0 =A0 =A0 =A0_ -> ProxyDest >> + =A0 =A0end, >> + =A0 =A0ProxyPrefix =3D "/" ++ ?b2l(hd(Req#httpd.path_parts)), >> + =A0 =A0RequestedPath =3D MochiReq:get(raw_path), >> + =A0 =A0case mochiweb_util:partition(RequestedPath, ProxyPrefix) of >> + =A0 =A0 =A0 =A0{[], ProxyPrefix, []} -> >> + =A0 =A0 =A0 =A0 =A0 =A0BaseUrl; >> + =A0 =A0 =A0 =A0{[], ProxyPrefix, [$/ | DestPath]} -> >> + =A0 =A0 =A0 =A0 =A0 =A0remove_trailing_slash(BaseUrl) ++ "/" ++ DestPa= th; >> + =A0 =A0 =A0 =A0{[], ProxyPrefix, DestPath} -> >> + =A0 =A0 =A0 =A0 =A0 =A0remove_trailing_slash(BaseUrl) ++ "/" ++ DestPa= th; >> + =A0 =A0 =A0 =A0_Else -> >> + =A0 =A0 =A0 =A0 =A0 =A0throw({invalid_url_path, {ProxyPrefix, Requeste= dPath}}) >> + =A0 =A0end. >> + >> +get_version(#httpd{mochi_req=3DMochiReq}) -> >> + =A0 =A0MochiReq:get(version). >> + >> + >> +get_headers(#httpd{mochi_req=3DMochiReq}) -> >> + =A0 =A0to_ibrowse_headers(mochiweb_headers:to_list(MochiReq:get(header= s)), []). >> + >> +to_ibrowse_headers([], Acc) -> >> + =A0 =A0lists:reverse(Acc); >> +to_ibrowse_headers([{K, V} | Rest], Acc) when is_atom(K) -> >> + =A0 =A0to_ibrowse_headers([{atom_to_list(K), V} | Rest], Acc); >> +to_ibrowse_headers([{K, V} | Rest], Acc) when is_list(K) -> >> + =A0 =A0case string:to_lower(K) of >> + =A0 =A0 =A0 =A0"content-length" -> >> + =A0 =A0 =A0 =A0 =A0 =A0to_ibrowse_headers(Rest, [{content_length, V} |= Acc]); >> + =A0 =A0 =A0 =A0% This appears to make ibrowse too smart. >> + =A0 =A0 =A0 =A0%"transfer-encoding" -> >> + =A0 =A0 =A0 =A0% =A0 =A0to_ibrowse_headers(Rest, [{transfer_encoding, = V} | Acc]); >> + =A0 =A0 =A0 =A0_ -> >> + =A0 =A0 =A0 =A0 =A0 =A0to_ibrowse_headers(Rest, [{K, V} | Acc]) >> + =A0 =A0end. >> + >> +get_body(#httpd{method=3D'GET'}) -> >> + =A0 =A0fun() -> eof end; >> +get_body(#httpd{method=3D'HEAD'}) -> >> + =A0 =A0fun() -> eof end; >> +get_body(#httpd{method=3D'DELETE'}) -> >> + =A0 =A0fun() -> eof end; >> +get_body(#httpd{mochi_req=3DMochiReq}) -> >> + =A0 =A0case MochiReq:get(body_length) of >> + =A0 =A0 =A0 =A0undefined -> >> + =A0 =A0 =A0 =A0 =A0 =A0<<>>; >> + =A0 =A0 =A0 =A0{unknown_transfer_encoding, Unknown} -> >> + =A0 =A0 =A0 =A0 =A0 =A0exit({unknown_transfer_encoding, Unknown}); >> + =A0 =A0 =A0 =A0chunked -> >> + =A0 =A0 =A0 =A0 =A0 =A0{fun stream_chunked_body/1, {init, MochiReq, 0}= }; >> + =A0 =A0 =A0 =A00 -> >> + =A0 =A0 =A0 =A0 =A0 =A0<<>>; >> + =A0 =A0 =A0 =A0Length when is_integer(Length) andalso Length > 0 -> >> + =A0 =A0 =A0 =A0 =A0 =A0{fun stream_length_body/1, {init, MochiReq, Len= gth}}; >> + =A0 =A0 =A0 =A0Length -> >> + =A0 =A0 =A0 =A0 =A0 =A0exit({invalid_body_length, Length}) >> + =A0 =A0end. >> + >> + >> +remove_trailing_slash(Url) -> >> + =A0 =A0rem_slash(lists:reverse(Url)). >> + >> +rem_slash([]) -> >> + =A0 =A0[]; >> +rem_slash([$\s | RevUrl]) -> >> + =A0 =A0rem_slash(RevUrl); >> +rem_slash([$\t | RevUrl]) -> >> + =A0 =A0rem_slash(RevUrl); >> +rem_slash([$\r | RevUrl]) -> >> + =A0 =A0rem_slash(RevUrl); >> +rem_slash([$\n | RevUrl]) -> >> + =A0 =A0rem_slash(RevUrl); >> +rem_slash([$/ | RevUrl]) -> >> + =A0 =A0rem_slash(RevUrl); >> +rem_slash(RevUrl) -> >> + =A0 =A0lists:reverse(RevUrl). >> + >> + >> +stream_chunked_body({init, MReq, 0}) -> >> + =A0 =A0% First chunk, do expect-continue dance. >> + =A0 =A0init_body_stream(MReq), >> + =A0 =A0stream_chunked_body({stream, MReq, 0, [], ?PKT_SIZE}); >> +stream_chunked_body({stream, MReq, 0, Buf, BRem}) -> >> + =A0 =A0% Finished a chunk, get next length. If next length >> + =A0 =A0% is 0, its time to try and read trailers. >> + =A0 =A0{CRem, Data} =3D read_chunk_length(MReq), >> + =A0 =A0case CRem of >> + =A0 =A0 =A0 =A00 -> >> + =A0 =A0 =A0 =A0 =A0 =A0BodyData =3D iolist_to_binary(lists:reverse(Buf= , Data)), >> + =A0 =A0 =A0 =A0 =A0 =A0{ok, BodyData, {trailers, MReq, [], ?PKT_SIZE}}= ; >> + =A0 =A0 =A0 =A0_ -> >> + =A0 =A0 =A0 =A0 =A0 =A0stream_chunked_body( >> + =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0{stream, MReq, CRem, [Data | Buf], BRem= -size(Data)} >> + =A0 =A0 =A0 =A0 =A0 =A0) >> + =A0 =A0end; >> +stream_chunked_body({stream, MReq, CRem, Buf, BRem}) when BRem =3D< 0 -= > >> + =A0 =A0% Time to empty our buffers to the upstream socket. >> + =A0 =A0BodyData =3D iolist_to_binary(lists:reverse(Buf)), >> + =A0 =A0{ok, BodyData, {stream, MReq, CRem, [], ?PKT_SIZE}}; >> +stream_chunked_body({stream, MReq, CRem, Buf, BRem}) -> >> + =A0 =A0% Buffer some more data from the client. >> + =A0 =A0Length =3D lists:min([CRem, BRem]), >> + =A0 =A0Socket =3D MReq:get(socket), >> + =A0 =A0NewState =3D case mochiweb_socket:recv(Socket, Length, ?TIMEOUT= ) of >> + =A0 =A0 =A0 =A0{ok, Data} when size(Data) =3D=3D CRem -> >> + =A0 =A0 =A0 =A0 =A0 =A0case mochiweb_socket:recv(Socket, 2, ?TIMEOUT) = of >> + =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0{ok, <<"\r\n">>} -> >> + =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0{stream, MReq, 0, [<<"\r\n">>, = Data | Buf], BRem-Length-2}; >> + =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0_ -> >> + =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0exit(normal) >> + =A0 =A0 =A0 =A0 =A0 =A0end; >> + =A0 =A0 =A0 =A0{ok, Data} -> >> + =A0 =A0 =A0 =A0 =A0 =A0{stream, MReq, CRem-Length, [Data | Buf], BRem-= Length}; >> + =A0 =A0 =A0 =A0_ -> >> + =A0 =A0 =A0 =A0 =A0 =A0exit(normal) >> + =A0 =A0end, >> + =A0 =A0stream_chunked_body(NewState); >> +stream_chunked_body({trailers, MReq, Buf, BRem}) when BRem =3D< 0 -> >> + =A0 =A0% Empty our buffers and send data upstream. >> + =A0 =A0BodyData =3D iolist_to_binary(lists:reverse(Buf)), >> + =A0 =A0{ok, BodyData, {trailers, MReq, [], ?PKT_SIZE}}; >> +stream_chunked_body({trailers, MReq, Buf, BRem}) -> >> + =A0 =A0% Read another trailer into the buffer or stop on an >> + =A0 =A0% empty line. >> + =A0 =A0Socket =3D MReq:get(socket), >> + =A0 =A0mochiweb_socket:setopts(Socket, [{packet, line}]), >> + =A0 =A0case mochiweb_socket:recv(Socket, 0, ?TIMEOUT) of >> + =A0 =A0 =A0 =A0{ok, <<"\r\n">>} -> >> + =A0 =A0 =A0 =A0 =A0 =A0mochiweb_socket:setopts(Socket, [{packet, raw}]= ), >> + =A0 =A0 =A0 =A0 =A0 =A0BodyData =3D iolist_to_binary(lists:reverse(Buf= , <<"\r\n">>)), >> + =A0 =A0 =A0 =A0 =A0 =A0{ok, BodyData, eof}; >> + =A0 =A0 =A0 =A0{ok, Footer} -> >> + =A0 =A0 =A0 =A0 =A0 =A0mochiweb_socket:setopts(Socket, [{packet, raw}]= ), >> + =A0 =A0 =A0 =A0 =A0 =A0NewState =3D {trailers, MReq, [Footer | Buf], B= Rem-size(Footer)}, >> + =A0 =A0 =A0 =A0 =A0 =A0stream_chunked_body(NewState); >> + =A0 =A0 =A0 =A0_ -> >> + =A0 =A0 =A0 =A0 =A0 =A0exit(normal) >> + =A0 =A0end; >> +stream_chunked_body(eof) -> >> + =A0 =A0% Tell ibrowse we're done sending data. >> + =A0 =A0eof. >> + >> + >> +stream_length_body({init, MochiReq, Length}) -> >> + =A0 =A0% Do the expect-continue dance >> + =A0 =A0init_body_stream(MochiReq), >> + =A0 =A0stream_length_body({stream, MochiReq, Length}); >> +stream_length_body({stream, _MochiReq, 0}) -> >> + =A0 =A0% Finished streaming. >> + =A0 =A0eof; >> +stream_length_body({stream, MochiReq, Length}) -> >> + =A0 =A0BufLen =3D lists:min([Length, ?PKT_SIZE]), >> + =A0 =A0case MochiReq:recv(BufLen) of >> + =A0 =A0 =A0 =A0<<>> -> eof; >> + =A0 =A0 =A0 =A0Bin -> {ok, Bin, {stream, MochiReq, Length-BufLen}} >> + =A0 =A0end. >> + >> + >> +init_body_stream(MochiReq) -> >> + =A0 =A0Expect =3D case MochiReq:get_header_value("expect") of >> + =A0 =A0 =A0 =A0undefined -> >> + =A0 =A0 =A0 =A0 =A0 =A0undefined; >> + =A0 =A0 =A0 =A0Value when is_list(Value) -> >> + =A0 =A0 =A0 =A0 =A0 =A0string:to_lower(Value) >> + =A0 =A0end, >> + =A0 =A0case Expect of >> + =A0 =A0 =A0 =A0"100-continue" -> >> + =A0 =A0 =A0 =A0 =A0 =A0MochiReq:start_raw_response({100, gb_trees:empt= y()}); >> + =A0 =A0 =A0 =A0_Else -> >> + =A0 =A0 =A0 =A0 =A0 =A0ok >> + =A0 =A0end. >> + >> + >> +read_chunk_length(MochiReq) -> >> + =A0 =A0Socket =3D MochiReq:get(socket), >> + =A0 =A0mochiweb_socket:setopts(Socket, [{packet, line}]), >> + =A0 =A0case mochiweb_socket:recv(Socket, 0, ?TIMEOUT) of >> + =A0 =A0 =A0 =A0{ok, Header} -> >> + =A0 =A0 =A0 =A0 =A0 =A0mochiweb_socket:setopts(Socket, [{packet, raw}]= ), >> + =A0 =A0 =A0 =A0 =A0 =A0Splitter =3D fun(C) -> >> + =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0C =3D/=3D $\r andalso C =3D/=3D $\n and= also C =3D/=3D $\s >> + =A0 =A0 =A0 =A0 =A0 =A0end, >> + =A0 =A0 =A0 =A0 =A0 =A0{Hex, _Rest} =3D lists:splitwith(Splitter, ?b2l= (Header)), >> + =A0 =A0 =A0 =A0 =A0 =A0{mochihex:to_int(Hex), Header}; >> + =A0 =A0 =A0 =A0_ -> >> + =A0 =A0 =A0 =A0 =A0 =A0exit(normal) >> + =A0 =A0end. >> + >> + >> +stream_response(Req, ProxyDest, ReqId) -> >> + =A0 =A0receive >> + =A0 =A0 =A0 =A0{ibrowse_async_headers, ReqId, "100", _} -> >> + =A0 =A0 =A0 =A0 =A0 =A0% ibrowse doesn't handle 100 Continue responses= which >> + =A0 =A0 =A0 =A0 =A0 =A0% means we have to discard them so the proxy cl= ient >> + =A0 =A0 =A0 =A0 =A0 =A0% doesn't get confused. >> + =A0 =A0 =A0 =A0 =A0 =A0ibrowse:stream_next(ReqId), >> + =A0 =A0 =A0 =A0 =A0 =A0stream_response(Req, ProxyDest, ReqId); >> + =A0 =A0 =A0 =A0{ibrowse_async_headers, ReqId, Status, Headers} -> >> + =A0 =A0 =A0 =A0 =A0 =A0{Source, Dest} =3D get_urls(Req, ProxyDest), >> + =A0 =A0 =A0 =A0 =A0 =A0FixedHeaders =3D fix_headers(Source, Dest, Head= ers, []), >> + =A0 =A0 =A0 =A0 =A0 =A0case body_length(FixedHeaders) of >> + =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0chunked -> >> + =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0{ok, Resp} =3D couch_httpd:star= t_chunked_response( >> + =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0Req, list_to_integer(St= atus), FixedHeaders >> + =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0), >> + =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0ibrowse:stream_next(ReqId), >> + =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0stream_chunked_response(Req, Re= qId, Resp), >> + =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0{ok, Resp}; >> + =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0Length when is_integer(Length) -> >> + =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0{ok, Resp} =3D couch_httpd:star= t_response_length( >> + =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0Req, list_to_integer(St= atus), FixedHeaders, Length >> + =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0), >> + =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0ibrowse:stream_next(ReqId), >> + =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0stream_length_response(Req, Req= Id, Resp), >> + =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0{ok, Resp}; >> + =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0_ -> >> + =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0{ok, Resp} =3D couch_httpd:star= t_response( >> + =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0Req, list_to_integer(St= atus), FixedHeaders >> + =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0), >> + =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0ibrowse:stream_next(ReqId), >> + =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0stream_length_response(Req, Req= Id, Resp), >> + =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0% XXX: MochiWeb apparently does= n't look at the >> + =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0% response to see if it must fo= rce close the >> + =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0% connection. So we help it out= here. >> + =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0erlang:put(mochiweb_request_for= ce_close, true), >> + =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0{ok, Resp} >> + =A0 =A0 =A0 =A0 =A0 =A0end >> + =A0 =A0end. >> + >> + >> +stream_chunked_response(Req, ReqId, Resp) -> >> + =A0 =A0receive >> + =A0 =A0 =A0 =A0{ibrowse_async_response, ReqId, Chunk} -> >> + =A0 =A0 =A0 =A0 =A0 =A0couch_httpd:send_chunk(Resp, Chunk), >> + =A0 =A0 =A0 =A0 =A0 =A0ibrowse:stream_next(ReqId), >> + =A0 =A0 =A0 =A0 =A0 =A0stream_chunked_response(Req, ReqId, Resp); >> + =A0 =A0 =A0 =A0{ibrowse_async_response, ReqId, {error, Reason}} -> >> + =A0 =A0 =A0 =A0 =A0 =A0throw({error, Reason}); >> + =A0 =A0 =A0 =A0{ibrowse_async_response_end, ReqId} -> >> + =A0 =A0 =A0 =A0 =A0 =A0couch_httpd:last_chunk(Resp) >> + =A0 =A0end. >> + >> + >> +stream_length_response(Req, ReqId, Resp) -> >> + =A0 =A0receive >> + =A0 =A0 =A0 =A0{ibrowse_async_response, ReqId, Chunk} -> >> + =A0 =A0 =A0 =A0 =A0 =A0couch_httpd:send(Resp, Chunk), >> + =A0 =A0 =A0 =A0 =A0 =A0ibrowse:stream_next(ReqId), >> + =A0 =A0 =A0 =A0 =A0 =A0stream_length_response(Req, ReqId, Resp); >> + =A0 =A0 =A0 =A0{ibrowse_async_response, {error, Reason}} -> >> + =A0 =A0 =A0 =A0 =A0 =A0throw({error, Reason}); >> + =A0 =A0 =A0 =A0{ibrowse_async_response_end, ReqId} -> >> + =A0 =A0 =A0 =A0 =A0 =A0ok >> + =A0 =A0end. >> + >> + >> +get_urls(Req, ProxyDest) -> >> + =A0 =A0SourceUrl =3D couch_httpd:absolute_uri(Req, "/" ++ hd(Req#httpd= .path_parts)), >> + =A0 =A0Source =3D parse_url(?b2l(iolist_to_binary(SourceUrl))), >> + =A0 =A0case (catch parse_url(ProxyDest)) of >> + =A0 =A0 =A0 =A0Dest when is_record(Dest, url) -> >> + =A0 =A0 =A0 =A0 =A0 =A0{Source, Dest}; >> + =A0 =A0 =A0 =A0_ -> >> + =A0 =A0 =A0 =A0 =A0 =A0DestUrl =3D couch_httpd:absolute_uri(Req, Proxy= Dest), >> + =A0 =A0 =A0 =A0 =A0 =A0{Source, parse_url(DestUrl)} >> + =A0 =A0end. >> + >> + >> +fix_headers(_, _, [], Acc) -> >> + =A0 =A0lists:reverse(Acc); >> +fix_headers(Source, Dest, [{K, V} | Rest], Acc) -> >> + =A0 =A0Fixed =3D case string:to_lower(K) of >> + =A0 =A0 =A0 =A0"location" -> rewrite_location(Source, Dest, V); >> + =A0 =A0 =A0 =A0"content-location" -> rewrite_location(Source, Dest, V)= ; >> + =A0 =A0 =A0 =A0"uri" -> rewrite_location(Source, Dest, V); >> + =A0 =A0 =A0 =A0"destination" -> rewrite_location(Source, Dest, V); >> + =A0 =A0 =A0 =A0"set-cookie" -> rewrite_cookie(Source, Dest, V); >> + =A0 =A0 =A0 =A0_ -> V >> + =A0 =A0end, >> + =A0 =A0fix_headers(Source, Dest, Rest, [{K, Fixed} | Acc]). >> + >> + >> +rewrite_location(Source, #url{host=3DHost, port=3DPort, protocol=3DProt= o}, Url) -> >> + =A0 =A0case (catch parse_url(Url)) of >> + =A0 =A0 =A0 =A0#url{host=3DHost, port=3DPort, protocol=3DProto} =3D Lo= cation -> >> + =A0 =A0 =A0 =A0 =A0 =A0DestLoc =3D #url{ >> + =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0protocol=3DSource#url.protocol, >> + =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0host=3DSource#url.host, >> + =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0port=3DSource#url.port, >> + =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0path=3Djoin_url_path(Source#url.path, L= ocation#url.path) >> + =A0 =A0 =A0 =A0 =A0 =A0}, >> + =A0 =A0 =A0 =A0 =A0 =A0url_to_url(DestLoc); >> + =A0 =A0 =A0 =A0#url{} -> >> + =A0 =A0 =A0 =A0 =A0 =A0Url; >> + =A0 =A0 =A0 =A0_ -> >> + =A0 =A0 =A0 =A0 =A0 =A0url_to_url(Source#url{path=3Djoin_url_path(Sour= ce#url.path, Url)}) >> + =A0 =A0end. >> + >> + >> +rewrite_cookie(_Source, _Dest, Cookie) -> >> + =A0 =A0Cookie. >> + >> + >> +parse_url(Url) when is_binary(Url) -> >> + =A0 =A0ibrowse_lib:parse_url(?b2l(Url)); >> +parse_url(Url) when is_list(Url) -> >> + =A0 =A0ibrowse_lib:parse_url(?b2l(iolist_to_binary(Url))). >> + >> + >> +join_url_path(Src, Dst) -> >> + =A0 =A0Src2 =3D case lists:reverse(Src) of >> + =A0 =A0 =A0 =A0"/" ++ RestSrc -> lists:reverse(RestSrc); >> + =A0 =A0 =A0 =A0_ -> Src >> + =A0 =A0end, >> + =A0 =A0Dst2 =3D case Dst of >> + =A0 =A0 =A0 =A0"/" ++ RestDst -> RestDst; >> + =A0 =A0 =A0 =A0_ -> Dst >> + =A0 =A0end, >> + =A0 =A0Src2 ++ "/" ++ Dst2. >> + >> + >> +url_to_url(#url{host=3DHost, port=3DPort, path=3DPath, protocol=3DProto= }) -> >> + =A0 =A0LPort =3D case {Proto, Port} of >> + =A0 =A0 =A0 =A0{http, 80} -> ""; >> + =A0 =A0 =A0 =A0{https, 443} -> ""; >> + =A0 =A0 =A0 =A0_ -> ":" ++ integer_to_list(Port) >> + =A0 =A0end, >> + =A0 =A0LPath =3D case Path of >> + =A0 =A0 =A0 =A0"/" ++ _RestPath -> Path; >> + =A0 =A0 =A0 =A0_ -> "/" ++ Path >> + =A0 =A0end, >> + =A0 =A0atom_to_list(Proto) ++ "://" ++ Host ++ LPort ++ LPath. >> + >> + >> +body_length(Headers) -> >> + =A0 =A0case is_chunked(Headers) of >> + =A0 =A0 =A0 =A0true -> chunked; >> + =A0 =A0 =A0 =A0_ -> content_length(Headers) >> + =A0 =A0end. >> + >> + >> +is_chunked([]) -> >> + =A0 =A0false; >> +is_chunked([{K, V} | Rest]) -> >> + =A0 =A0case string:to_lower(K) of >> + =A0 =A0 =A0 =A0"transfer-encoding" -> >> + =A0 =A0 =A0 =A0 =A0 =A0string:to_lower(V) =3D=3D "chunked"; >> + =A0 =A0 =A0 =A0_ -> >> + =A0 =A0 =A0 =A0 =A0 =A0is_chunked(Rest) >> + =A0 =A0end. >> + >> +content_length([]) -> >> + =A0 =A0undefined; >> +content_length([{K, V} | Rest]) -> >> + =A0 =A0case string:to_lower(K) of >> + =A0 =A0 =A0 =A0"content-length" -> >> + =A0 =A0 =A0 =A0 =A0 =A0list_to_integer(V); >> + =A0 =A0 =A0 =A0_ -> >> + =A0 =A0 =A0 =A0 =A0 =A0content_length(Rest) >> + =A0 =A0end. >> + >> >> Propchange: couchdb/trunk/test/etap/ >> ------------------------------------------------------------------------= ------ >> --- svn:ignore (original) >> +++ svn:ignore Fri Nov =A05 23:26:21 2010 >> @@ -3,4 +3,5 @@ Makefile >> =A0Makefile.in >> =A0test_util.erl >> =A0test_util.beam >> +test_web.beam >> =A0run >> >> Added: couchdb/trunk/test/etap/180-http-proxy.ini >> URL: http://svn.apache.org/viewvc/couchdb/trunk/test/etap/180-http-proxy= .ini?rev=3D1031877&view=3Dauto >> =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D >> --- couchdb/trunk/test/etap/180-http-proxy.ini (added) >> +++ couchdb/trunk/test/etap/180-http-proxy.ini Fri Nov =A05 23:26:21 201= 0 >> @@ -0,0 +1,20 @@ >> +; Licensed to the Apache Software Foundation (ASF) under one >> +; or more contributor license agreements. =A0See the NOTICE file >> +; distributed with this work for additional information >> +; regarding copyright ownership. =A0The 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. =A0You may obtain a copy of the License at >> +; >> +; =A0 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. =A0See the License for the >> +; specific language governing permissions and limitations >> +; under the License. >> + >> +[httpd_global_handlers] >> +_test =3D {couch_httpd_proxy, handle_proxy_req, <<"http://127.0.0.1:598= 5/">>} >> +_error =3D {couch_httpd_proxy, handle_proxy_req, <<"http://127.0.0.1:59= 86/">>} >> \ No newline at end of file >> >> Added: couchdb/trunk/test/etap/180-http-proxy.t >> URL: http://svn.apache.org/viewvc/couchdb/trunk/test/etap/180-http-proxy= .t?rev=3D1031877&view=3Dauto >> =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D >> --- couchdb/trunk/test/etap/180-http-proxy.t (added) >> +++ couchdb/trunk/test/etap/180-http-proxy.t Fri Nov =A05 23:26:21 2010 >> @@ -0,0 +1,357 @@ >> +#!/usr/bin/env escript >> +% Licensed under the Apache License, Version 2.0 (the "License"); you m= ay not >> +% use this file except in compliance with the License. You may obtain a= copy of >> +% the License at >> +% >> +% =A0 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, WIT= HOUT >> +% WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See = the >> +% License for the specific language governing permissions and limitatio= ns under >> +% the License. >> + >> +-record(req, {method=3Dget, path=3D"", headers=3D[], body=3D"", opts=3D= []}). >> + >> +default_config() -> >> + =A0 =A0[ >> + =A0 =A0 =A0 =A0test_util:build_file("etc/couchdb/default_dev.ini"), >> + =A0 =A0 =A0 =A0test_util:source_file("test/etap/180-http-proxy.ini") >> + =A0 =A0]. >> + >> +server() -> "http://127.0.0.1:5984/_test/". >> +proxy() -> "http://127.0.0.1:5985/". >> +external() -> "https://www.google.com/". >> + >> +main(_) -> >> + =A0 =A0test_util:init_code_path(), >> + >> + =A0 =A0etap:plan(61), >> + =A0 =A0case (catch test()) of >> + =A0 =A0 =A0 =A0ok -> >> + =A0 =A0 =A0 =A0 =A0 =A0etap:end_tests(); >> + =A0 =A0 =A0 =A0Other -> >> + =A0 =A0 =A0 =A0 =A0 =A0etap:diag("Test died abnormally: ~p", [Other]), >> + =A0 =A0 =A0 =A0 =A0 =A0etap:bail("Bad return value.") >> + =A0 =A0end, >> + =A0 =A0ok. >> + >> +check_request(Name, Req, Remote, Local) -> >> + =A0 =A0case Remote of >> + =A0 =A0 =A0 =A0no_remote -> ok; >> + =A0 =A0 =A0 =A0_ -> test_web:set_assert(Remote) >> + =A0 =A0end, >> + =A0 =A0Url =3D case proplists:lookup(url, Req#req.opts) of >> + =A0 =A0 =A0 =A0none -> server() ++ Req#req.path; >> + =A0 =A0 =A0 =A0{url, DestUrl} -> DestUrl >> + =A0 =A0end, >> + =A0 =A0Opts =3D [{headers_as_is, true} | Req#req.opts], >> + =A0 =A0Resp =3Dibrowse:send_req( >> + =A0 =A0 =A0 =A0Url, Req#req.headers, Req#req.method, Req#req.body, Opt= s >> + =A0 =A0), >> + =A0 =A0%etap:diag("ibrowse response: ~p", [Resp]), >> + =A0 =A0case Local of >> + =A0 =A0 =A0 =A0no_local -> ok; >> + =A0 =A0 =A0 =A0_ -> etap:fun_is(Local, Resp, Name) >> + =A0 =A0end, >> + =A0 =A0case {Remote, Local} of >> + =A0 =A0 =A0 =A0{no_remote, _} -> >> + =A0 =A0 =A0 =A0 =A0 =A0ok; >> + =A0 =A0 =A0 =A0{_, no_local} -> >> + =A0 =A0 =A0 =A0 =A0 =A0ok; >> + =A0 =A0 =A0 =A0_ -> >> + =A0 =A0 =A0 =A0 =A0 =A0etap:is(test_web:check_last(), was_ok, Name ++ = " - request handled") >> + =A0 =A0end, >> + =A0 =A0Resp. >> + >> +test() -> >> + =A0 =A0couch_server_sup:start_link(default_config()), >> + =A0 =A0ibrowse:start(), >> + =A0 =A0crypto:start(), >> + =A0 =A0test_web:start_link(), >> + >> + =A0 =A0test_basic(), >> + =A0 =A0test_alternate_status(), >> + =A0 =A0test_trailing_slash(), >> + =A0 =A0test_passes_header(), >> + =A0 =A0test_passes_host_header(), >> + =A0 =A0test_passes_header_back(), >> + =A0 =A0test_rewrites_location_headers(), >> + =A0 =A0test_doesnt_rewrite_external_locations(), >> + =A0 =A0test_rewrites_relative_location(), >> + =A0 =A0test_uses_same_version(), >> + =A0 =A0test_passes_body(), >> + =A0 =A0test_passes_eof_body_back(), >> + =A0 =A0test_passes_chunked_body(), >> + =A0 =A0test_passes_chunked_body_back(), >> + >> + =A0 =A0test_connect_error(), >> + >> + =A0 =A0ok. >> + >> +test_basic() -> >> + =A0 =A0Remote =3D fun(Req) -> >> + =A0 =A0 =A0 =A0'GET' =3D Req:get(method), >> + =A0 =A0 =A0 =A0"/" =3D Req:get(path), >> + =A0 =A0 =A0 =A0undefined =3D Req:get(body_length), >> + =A0 =A0 =A0 =A0undefined =3D Req:recv_body(), >> + =A0 =A0 =A0 =A0{ok, {200, [{"Content-Type", "text/plain"}], "ok"}} >> + =A0 =A0end, >> + =A0 =A0Local =3D fun({ok, "200", _, "ok"}) -> true; (_) -> false end, >> + =A0 =A0check_request("Basic proxy test", #req{}, Remote, Local). >> + >> +test_alternate_status() -> >> + =A0 =A0Remote =3D fun(Req) -> >> + =A0 =A0 =A0 =A0"/alternate_status" =3D Req:get(path), >> + =A0 =A0 =A0 =A0{ok, {201, [], "ok"}} >> + =A0 =A0end, >> + =A0 =A0Local =3D fun({ok, "201", _, "ok"}) -> true; (_) -> false end, >> + =A0 =A0Req =3D #req{path=3D"alternate_status"}, >> + =A0 =A0check_request("Alternate status", Req, Remote, Local). >> + >> +test_trailing_slash() -> >> + =A0 =A0Remote =3D fun(Req) -> >> + =A0 =A0 =A0 =A0"/trailing_slash/" =3D Req:get(path), >> + =A0 =A0 =A0 =A0{ok, {200, [], "ok"}} >> + =A0 =A0end, >> + =A0 =A0Local =3D fun({ok, "200", _, "ok"}) -> true; (_) -> false end, >> + =A0 =A0Req =3D #req{path=3D"trailing_slash/"}, >> + =A0 =A0check_request("Trailing slash", Req, Remote, Local). >> + >> +test_passes_header() -> >> + =A0 =A0Remote =3D fun(Req) -> >> + =A0 =A0 =A0 =A0"/passes_header" =3D Req:get(path), >> + =A0 =A0 =A0 =A0"plankton" =3D Req:get_header_value("X-CouchDB-Ralph"), >> + =A0 =A0 =A0 =A0{ok, {200, [], "ok"}} >> + =A0 =A0end, >> + =A0 =A0Local =3D fun({ok, "200", _, "ok"}) -> true; (_) -> false end, >> + =A0 =A0Req =3D #req{ >> + =A0 =A0 =A0 =A0path=3D"passes_header", >> + =A0 =A0 =A0 =A0headers=3D[{"X-CouchDB-Ralph", "plankton"}] >> + =A0 =A0}, >> + =A0 =A0check_request("Passes header", Req, Remote, Local). >> + >> +test_passes_host_header() -> >> + =A0 =A0Remote =3D fun(Req) -> >> + =A0 =A0 =A0 =A0"/passes_host_header" =3D Req:get(path), >> + =A0 =A0 =A0 =A0"www.google.com" =3D Req:get_header_value("Host"), >> + =A0 =A0 =A0 =A0{ok, {200, [], "ok"}} >> + =A0 =A0end, >> + =A0 =A0Local =3D fun({ok, "200", _, "ok"}) -> true; (_) -> false end, >> + =A0 =A0Req =3D #req{ >> + =A0 =A0 =A0 =A0path=3D"passes_host_header", >> + =A0 =A0 =A0 =A0headers=3D[{"Host", "www.google.com"}] >> + =A0 =A0}, >> + =A0 =A0check_request("Passes host header", Req, Remote, Local). >> + >> +test_passes_header_back() -> >> + =A0 =A0Remote =3D fun(Req) -> >> + =A0 =A0 =A0 =A0"/passes_header_back" =3D Req:get(path), >> + =A0 =A0 =A0 =A0{ok, {200, [{"X-CouchDB-Plankton", "ralph"}], "ok"}} >> + =A0 =A0end, >> + =A0 =A0Local =3D fun >> + =A0 =A0 =A0 =A0({ok, "200", Headers, "ok"}) -> >> + =A0 =A0 =A0 =A0 =A0 =A0lists:member({"X-CouchDB-Plankton", "ralph"}, H= eaders); >> + =A0 =A0 =A0 =A0(_) -> >> + =A0 =A0 =A0 =A0 =A0 =A0false >> + =A0 =A0end, >> + =A0 =A0Req =3D #req{path=3D"passes_header_back"}, >> + =A0 =A0check_request("Passes header back", Req, Remote, Local). >> + >> +test_rewrites_location_headers() -> >> + =A0 =A0etap:diag("Testing location header rewrites."), >> + =A0 =A0do_rewrite_tests([ >> + =A0 =A0 =A0 =A0{"Location", proxy() ++ "foo/bar", server() ++ "foo/bar= "}, >> + =A0 =A0 =A0 =A0{"Content-Location", proxy() ++ "bing?q=3D2", server() = ++ "bing?q=3D2"}, >> + =A0 =A0 =A0 =A0{"Uri", proxy() ++ "zip#frag", server() ++ "zip#frag"}, >> + =A0 =A0 =A0 =A0{"Destination", proxy(), server()} >> + =A0 =A0]). >> + >> +test_doesnt_rewrite_external_locations() -> >> + =A0 =A0etap:diag("Testing no rewrite of external locations."), >> + =A0 =A0do_rewrite_tests([ >> + =A0 =A0 =A0 =A0{"Location", external() ++ "search", external() ++ "sea= rch"}, >> + =A0 =A0 =A0 =A0{"Content-Location", external() ++ "s?q=3D2", external(= ) ++ "s?q=3D2"}, >> + =A0 =A0 =A0 =A0{"Uri", external() ++ "f#f", external() ++ "f#f"}, >> + =A0 =A0 =A0 =A0{"Destination", external() ++ "f?q=3D2#f", external() += + "f?q=3D2#f"} >> + =A0 =A0]). >> + >> +test_rewrites_relative_location() -> >> + =A0 =A0etap:diag("Testing relative rewrites."), >> + =A0 =A0do_rewrite_tests([ >> + =A0 =A0 =A0 =A0{"Location", "/foo", server() ++ "foo"}, >> + =A0 =A0 =A0 =A0{"Content-Location", "bar", server() ++ "bar"}, >> + =A0 =A0 =A0 =A0{"Uri", "/zing?q=3D3", server() ++ "zing?q=3D3"}, >> + =A0 =A0 =A0 =A0{"Destination", "bing?q=3Dstuff#yay", server() ++ "bing= ?q=3Dstuff#yay"} >> + =A0 =A0]). >> + >> +do_rewrite_tests(Tests) -> >> + =A0 =A0lists:foreach(fun({Header, Location, Url}) -> >> + =A0 =A0 =A0 =A0do_rewrite_test(Header, Location, Url) >> + =A0 =A0end, Tests). >> + >> +do_rewrite_test(Header, Location, Url) -> >> + =A0 =A0Remote =3D fun(Req) -> >> + =A0 =A0 =A0 =A0"/rewrite_test" =3D Req:get(path), >> + =A0 =A0 =A0 =A0{ok, {302, [{Header, Location}], "ok"}} >> + =A0 =A0end, >> + =A0 =A0Local =3D fun >> + =A0 =A0 =A0 =A0({ok, "302", Headers, "ok"}) -> >> + =A0 =A0 =A0 =A0 =A0 =A0etap:is( >> + =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0couch_util:get_value(Header, Headers), >> + =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0Url, >> + =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0"Header rewritten correctly." >> + =A0 =A0 =A0 =A0 =A0 =A0), >> + =A0 =A0 =A0 =A0 =A0 =A0true; >> + =A0 =A0 =A0 =A0(_) -> >> + =A0 =A0 =A0 =A0 =A0 =A0false >> + =A0 =A0end, >> + =A0 =A0Req =3D #req{path=3D"rewrite_test"}, >> + =A0 =A0Label =3D "Rewrite test for ", >> + =A0 =A0check_request(Label ++ Header, Req, Remote, Local). >> + >> +test_uses_same_version() -> >> + =A0 =A0Remote =3D fun(Req) -> >> + =A0 =A0 =A0 =A0"/uses_same_version" =3D Req:get(path), >> + =A0 =A0 =A0 =A0{1, 0} =3D Req:get(version), >> + =A0 =A0 =A0 =A0{ok, {200, [], "ok"}} >> + =A0 =A0end, >> + =A0 =A0Local =3D fun({ok, "200", _, "ok"}) -> true; (_) -> false end, >> + =A0 =A0Req =3D #req{ >> + =A0 =A0 =A0 =A0path=3D"uses_same_version", >> + =A0 =A0 =A0 =A0opts=3D[{http_vsn, {1, 0}}] >> + =A0 =A0}, >> + =A0 =A0check_request("Uses same version", Req, Remote, Local). >> + >> +test_passes_body() -> >> + =A0 =A0Remote =3D fun(Req) -> >> + =A0 =A0 =A0 =A0'PUT' =3D Req:get(method), >> + =A0 =A0 =A0 =A0"/passes_body" =3D Req:get(path), >> + =A0 =A0 =A0 =A0<<"Hooray!">> =3D Req:recv_body(), >> + =A0 =A0 =A0 =A0{ok, {201, [], "ok"}} >> + =A0 =A0end, >> + =A0 =A0Local =3D fun({ok, "201", _, "ok"}) -> true; (_) -> false end, >> + =A0 =A0Req =3D #req{ >> + =A0 =A0 =A0 =A0method=3Dput, >> + =A0 =A0 =A0 =A0path=3D"passes_body", >> + =A0 =A0 =A0 =A0body=3D"Hooray!" >> + =A0 =A0}, >> + =A0 =A0check_request("Passes body", Req, Remote, Local). >> + >> +test_passes_eof_body_back() -> >> + =A0 =A0BodyChunks =3D [<<"foo">>, <<"bar">>, <<"bazinga">>], >> + =A0 =A0Remote =3D fun(Req) -> >> + =A0 =A0 =A0 =A0'GET' =3D Req:get(method), >> + =A0 =A0 =A0 =A0"/passes_eof_body" =3D Req:get(path), >> + =A0 =A0 =A0 =A0{raw, {200, [{"Connection", "close"}], BodyChunks}} >> + =A0 =A0end, >> + =A0 =A0Local =3D fun({ok, "200", _, "foobarbazinga"}) -> true; (_) -> = false end, >> + =A0 =A0Req =3D #req{path=3D"passes_eof_body"}, >> + =A0 =A0check_request("Passes eof body", Req, Remote, Local). >> + >> +test_passes_chunked_body() -> >> + =A0 =A0BodyChunks =3D [<<"foo">>, <<"bar">>, <<"bazinga">>], >> + =A0 =A0Remote =3D fun(Req) -> >> + =A0 =A0 =A0 =A0'POST' =3D Req:get(method), >> + =A0 =A0 =A0 =A0"/passes_chunked_body" =3D Req:get(path), >> + =A0 =A0 =A0 =A0RecvBody =3D fun >> + =A0 =A0 =A0 =A0 =A0 =A0({Length, Chunk}, [Chunk | Rest]) -> >> + =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0Length =3D size(Chunk), >> + =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0Rest; >> + =A0 =A0 =A0 =A0 =A0 =A0({0, []}, []) -> >> + =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0ok >> + =A0 =A0 =A0 =A0end, >> + =A0 =A0 =A0 =A0ok =3D Req:stream_body(1024*1024, RecvBody, BodyChunks)= , >> + =A0 =A0 =A0 =A0{ok, {201, [], "ok"}} >> + =A0 =A0end, >> + =A0 =A0Local =3D fun({ok, "201", _, "ok"}) -> true; (_) -> false end, >> + =A0 =A0Req =3D #req{ >> + =A0 =A0 =A0 =A0method=3Dpost, >> + =A0 =A0 =A0 =A0path=3D"passes_chunked_body", >> + =A0 =A0 =A0 =A0headers=3D[{"Transfer-Encoding", "chunked"}], >> + =A0 =A0 =A0 =A0body=3Dmk_chunked_body(BodyChunks) >> + =A0 =A0}, >> + =A0 =A0check_request("Passes chunked body", Req, Remote, Local). >> + >> +test_passes_chunked_body_back() -> >> + =A0 =A0Name =3D "Passes chunked body back", >> + =A0 =A0Remote =3D fun(Req) -> >> + =A0 =A0 =A0 =A0'GET' =3D Req:get(method), >> + =A0 =A0 =A0 =A0"/passes_chunked_body_back" =3D Req:get(path), >> + =A0 =A0 =A0 =A0BodyChunks =3D [<<"foo">>, <<"bar">>, <<"bazinga">>], >> + =A0 =A0 =A0 =A0{chunked, {200, [{"Transfer-Encoding", "chunked"}], Bod= yChunks}} >> + =A0 =A0end, >> + =A0 =A0Req =3D #req{ >> + =A0 =A0 =A0 =A0path=3D"passes_chunked_body_back", >> + =A0 =A0 =A0 =A0opts=3D[{stream_to, self()}] >> + =A0 =A0}, >> + >> + =A0 =A0Resp =3D check_request(Name, Req, Remote, no_local), >> + >> + =A0 =A0etap:fun_is( >> + =A0 =A0 =A0 =A0fun({ibrowse_req_id, _}) -> true; (_) -> false end, >> + =A0 =A0 =A0 =A0Resp, >> + =A0 =A0 =A0 =A0"Received an ibrowse request id." >> + =A0 =A0), >> + =A0 =A0{_, ReqId} =3D Resp, >> + >> + =A0 =A0% Grab headers from response >> + =A0 =A0receive >> + =A0 =A0 =A0 =A0{ibrowse_async_headers, ReqId, "200", Headers} -> >> + =A0 =A0 =A0 =A0 =A0 =A0etap:is( >> + =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0proplists:get_value("Transfer-Encoding"= , Headers), >> + =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0"chunked", >> + =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0"Response included the Transfer-Encodin= g: chunked header" >> + =A0 =A0 =A0 =A0 =A0 =A0), >> + =A0 =A0 =A0 =A0ibrowse:stream_next(ReqId) >> + =A0 =A0after 1000 -> >> + =A0 =A0 =A0 =A0throw({error, timeout}) >> + =A0 =A0end, >> + >> + =A0 =A0% Check body received >> + =A0 =A0% TODO: When we upgrade to ibrowse >=3D 2.0.0 this check needs = to >> + =A0 =A0% =A0 =A0 =A0 check that the chunks returned are what we sent f= rom the >> + =A0 =A0% =A0 =A0 =A0 Remote test. >> + =A0 =A0etap:diag("TODO: UPGRADE IBROWSE"), >> + =A0 =A0etap:is(recv_body(ReqId, []), <<"foobarbazinga">>, "Decoded chu= nked body."), >> + >> + =A0 =A0% Check test_web server. >> + =A0 =A0etap:is(test_web:check_last(), was_ok, Name ++ " - request hand= led"). >> + >> +test_connect_error() -> >> + =A0 =A0Local =3D fun({ok, "500", _Headers, _Body}) -> true; (_) -> fal= se end, >> + =A0 =A0Req =3D #req{opts=3D[{url, "http://127.0.0.1:5984/_error"}]}, >> + =A0 =A0check_request("Connect error", Req, no_remote, Local). >> + >> + >> +mk_chunked_body(Chunks) -> >> + =A0 =A0mk_chunked_body(Chunks, []). >> + >> +mk_chunked_body([], Acc) -> >> + =A0 =A0iolist_to_binary(lists:reverse(Acc, "0\r\n\r\n")); >> +mk_chunked_body([Chunk | Rest], Acc) -> >> + =A0 =A0Size =3D to_hex(size(Chunk)), >> + =A0 =A0mk_chunked_body(Rest, ["\r\n", Chunk, "\r\n", Size | Acc]). >> + >> +to_hex(Val) -> >> + =A0 =A0to_hex(Val, []). >> + >> +to_hex(0, Acc) -> >> + =A0 =A0Acc; >> +to_hex(Val, Acc) -> >> + =A0 =A0to_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) -> >> + =A0 =A0receive >> + =A0 =A0 =A0 =A0{ibrowse_async_response, ReqId, Data} -> >> + =A0 =A0 =A0 =A0 =A0 =A0recv_body(ReqId, [Data | Acc]); >> + =A0 =A0 =A0 =A0{ibrowse_async_response_end, ReqId} -> >> + =A0 =A0 =A0 =A0 =A0 =A0iolist_to_binary(lists:reverse(Acc)); >> + =A0 =A0 =A0 =A0Else -> >> + =A0 =A0 =A0 =A0 =A0 =A0throw({error, unexpected_mesg, Else}) >> + =A0 =A0after 5000 -> >> + =A0 =A0 =A0 =A0throw({error, timeout}) >> + =A0 =A0end. >> >> Modified: couchdb/trunk/test/etap/Makefile.am >> URL: http://svn.apache.org/viewvc/couchdb/trunk/test/etap/Makefile.am?re= v=3D1031877&r1=3D1031876&r2=3D1031877&view=3Ddiff >> =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D >> --- couchdb/trunk/test/etap/Makefile.am (original) >> +++ couchdb/trunk/test/etap/Makefile.am Fri Nov =A05 23:26:21 2010 >> @@ -11,7 +11,7 @@ >> =A0## the License. >> >> =A0noinst_SCRIPTS =3D run >> -noinst_DATA =3D test_util.beam >> +noinst_DATA =3D test_util.beam test_web.beam >> >> =A0%.beam: %.erl >> =A0 =A0 =A0 =A0$(ERLC) $< >> @@ -27,6 +27,7 @@ DISTCLEANFILES =3D temp.* >> >> =A0EXTRA_DIST =3D \ >> =A0 =A0 =A0 =A0run.tpl \ >> + =A0 =A0 =A0 test_web.erl \ >> =A0 =A0 001-load.t \ >> =A0 =A0 002-icu-driver.t \ >> =A0 =A0 010-file-basics.t \ >> @@ -77,4 +78,6 @@ EXTRA_DIST =3D \ >> =A0 =A0 172-os-daemon-errors.4.es \ >> =A0 =A0 172-os-daemon-errors.t \ >> =A0 =A0 =A0 =A0173-os-daemon-cfg-register.es \ >> - =A0 =A0 =A0 173-os-daemon-cfg-register.t >> + =A0 =A0 =A0 173-os-daemon-cfg-register.t \ >> + =A0 =A0 =A0 180-http-proxy.ini \ >> + =A0 =A0 =A0 180-http-proxy.t >> >> Added: couchdb/trunk/test/etap/test_web.erl >> URL: http://svn.apache.org/viewvc/couchdb/trunk/test/etap/test_web.erl?r= ev=3D1031877&view=3Dauto >> =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D >> --- couchdb/trunk/test/etap/test_web.erl (added) >> +++ couchdb/trunk/test/etap/test_web.erl Fri Nov =A05 23:26:21 2010 >> @@ -0,0 +1,99 @@ >> +% Licensed under the Apache License, Version 2.0 (the "License"); you m= ay not >> +% use this file except in compliance with the License. You may obtain a= copy of >> +% the License at >> +% >> +% =A0 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, WIT= HOUT >> +% WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See = the >> +% License for the specific language governing permissions and limitatio= ns 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() -> >> + =A0 =A0gen_server:start({local, ?HANDLER}, ?MODULE, [], []), >> + =A0 =A0mochiweb_http:start([ >> + =A0 =A0 =A0 =A0{name, ?SERVER}, >> + =A0 =A0 =A0 =A0{loop, {?MODULE, loop}}, >> + =A0 =A0 =A0 =A0{port, 5985} >> + =A0 =A0]). >> + >> +loop(Req) -> >> + =A0 =A0%etap:diag("Handling request: ~p", [Req]), >> + =A0 =A0case gen_server:call(?HANDLER, {check_request, Req}) of >> + =A0 =A0 =A0 =A0{ok, RespInfo} -> >> + =A0 =A0 =A0 =A0 =A0 =A0{ok, Req:respond(RespInfo)}; >> + =A0 =A0 =A0 =A0{raw, {Status, Headers, BodyChunks}} -> >> + =A0 =A0 =A0 =A0 =A0 =A0Resp =3D Req:start_response({Status, Headers}), >> + =A0 =A0 =A0 =A0 =A0 =A0lists:foreach(fun(C) -> Resp:send(C) end, BodyC= hunks), >> + =A0 =A0 =A0 =A0 =A0 =A0erlang:put(mochiweb_request_force_close, true), >> + =A0 =A0 =A0 =A0 =A0 =A0{ok, Resp}; >> + =A0 =A0 =A0 =A0{chunked, {Status, Headers, BodyChunks}} -> >> + =A0 =A0 =A0 =A0 =A0 =A0Resp =3D Req:respond({Status, Headers, chunked}= ), >> + =A0 =A0 =A0 =A0 =A0 =A0timer:sleep(500), >> + =A0 =A0 =A0 =A0 =A0 =A0lists:foreach(fun(C) -> Resp:write_chunk(C) end= , BodyChunks), >> + =A0 =A0 =A0 =A0 =A0 =A0Resp:write_chunk([]), >> + =A0 =A0 =A0 =A0 =A0 =A0{ok, Resp}; >> + =A0 =A0 =A0 =A0{error, Reason} -> >> + =A0 =A0 =A0 =A0 =A0 =A0etap:diag("Error: ~p", [Reason]), >> + =A0 =A0 =A0 =A0 =A0 =A0Body =3D lists:flatten(io_lib:format("Error: ~p= ", [Reason])), >> + =A0 =A0 =A0 =A0 =A0 =A0{ok, Req:respond({200, [], Body})} >> + =A0 =A0end. >> + >> +get_port() -> >> + =A0 =A0mochiweb_socket_server:get(?SERVER, port). >> + >> +set_assert(Fun) -> >> + =A0 =A0ok =3D gen_server:call(?HANDLER, {set_assert, Fun}). >> + >> +check_last() -> >> + =A0 =A0gen_server:call(?HANDLER, last_status). >> + >> +init(_) -> >> + =A0 =A0{ok, nil}. >> + >> +terminate(_Reason, _State) -> >> + =A0 =A0ok. >> + >> +handle_call({check_request, Req}, _From, State) when is_function(State,= 1) -> >> + =A0 =A0Resp2 =3D case (catch State(Req)) of >> + =A0 =A0 =A0 =A0{ok, Resp} -> {reply, {ok, Resp}, was_ok}; >> + =A0 =A0 =A0 =A0{raw, Resp} -> {reply, {raw, Resp}, was_ok}; >> + =A0 =A0 =A0 =A0{chunked, Resp} -> {reply, {chunked, Resp}, was_ok}; >> + =A0 =A0 =A0 =A0Error -> {reply, {error, Error}, not_ok} >> + =A0 =A0end, >> + =A0 =A0Req:cleanup(), >> + =A0 =A0Resp2; >> +handle_call({check_request, _Req}, _From, _State) -> >> + =A0 =A0{reply, {error, no_assert_function}, not_ok}; >> +handle_call(last_status, _From, State) when is_atom(State) -> >> + =A0 =A0{reply, State, nil}; >> +handle_call(last_status, _From, State) -> >> + =A0 =A0{reply, {error, not_checked}, State}; >> +handle_call({set_assert, Fun}, _From, nil) -> >> + =A0 =A0{reply, ok, Fun}; >> +handle_call({set_assert, _}, _From, State) -> >> + =A0 =A0{reply, {error, assert_function_set}, State}; >> +handle_call(Msg, _From, State) -> >> + =A0 =A0{reply, {ignored, Msg}, State}. >> + >> +handle_cast(Msg, State) -> >> + =A0 =A0etap:diag("Ignoring cast message: ~p", [Msg]), >> + =A0 =A0{noreply, State}. >> + >> +handle_info(Msg, State) -> >> + =A0 =A0etap:diag("Ignoring info message: ~p", [Msg]), >> + =A0 =A0{noreply, State}. >> + >> +code_change(_OldVsn, State, _Extra) -> >> + =A0 =A0{ok, State}. >> >> >> > > > > -- > Filipe David Manana, > fdmanana@gmail.com, fdmanana@apache.org > > "Reasonable men adapt themselves to the world. > =A0Unreasonable men adapt the world to themselves. > =A0That's why all progress depends on unreasonable men." >