Author: benoitc
Date: Tue Sep  7 23:35:28 2010
New Revision: 993558

URL: http://svn.apache.org/viewvc?rev=993558&view=rev
Log:
fix issue #COUCHDB-230 . now it's possible to do */test =
/db/_design/test or even example.com/test =  /db/_design/test and other
stuff already possible with vhost manager.

Modified:
    couchdb/trunk/src/couchdb/couch_httpd_vhost.erl
    couchdb/trunk/test/etap/160-vhosts.t

Modified: couchdb/trunk/src/couchdb/couch_httpd_vhost.erl
URL: 
http://svn.apache.org/viewvc/couchdb/trunk/src/couchdb/couch_httpd_vhost.erl?rev=993558&r1=993557&r2=993558&view=diff
==============================================================================
--- couchdb/trunk/src/couchdb/couch_httpd_vhost.erl (original)
+++ couchdb/trunk/src/couchdb/couch_httpd_vhost.erl Tue Sep  7 23:35:28 2010
@@ -17,7 +17,7 @@
 
 -export([start_link/0, init/1, handle_call/3, handle_info/2, handle_cast/2]).
 -export([code_change/3, terminate/2]).
--export([match_vhost/1]).
+-export([match_vhost/1, urlsplit_netloc/2]).
 -export([redirect_to_vhost/2]).
 
 -include("couch_db.hrl").
@@ -149,6 +149,8 @@ handle_call({match_vhost, MochiReq}, _Fr
         vhost_fun = Fun
     } = State,
 
+    {"/" ++ VPath, Query, Fragment} = 
mochiweb_util:urlsplit_path(MochiReq:get(raw_path)),
+    VPathParts =  string:tokens(VPath, "/"),
 
     XHost = couch_config:get("httpd", "x_forwarded_host", "X-Forwarded-Host"),
     VHost = case MochiReq:get_header_value(XHost) of
@@ -160,18 +162,25 @@ handle_call({match_vhost, MochiReq}, _Fr
         Value -> Value
     end,
     {VHostParts, VhostPort} = split_host_port(VHost),
-    MochiReq1 = case try_bind_vhost(VHosts, lists:reverse(VHostParts),
-            VhostPort) of
+    FinalMochiReq = case try_bind_vhost(VHosts, lists:reverse(VHostParts),
+            VhostPort, VPathParts) of
         no_vhost_matched -> MochiReq;
-        VhostTarget ->
+        {VhostTarget, NewPath} ->
             case vhost_global(VHostGlobals, MochiReq) of
                 true ->
                     MochiReq;
                 _Else ->
-                    Fun(MochiReq, VhostTarget)
+                    NewPath1 = mochiweb_util:urlunsplit_path({NewPath, Query,
+                                          Fragment}),
+                    MochiReq1 = mochiweb_request:new(MochiReq:get(socket),
+                                      MochiReq:get(method),
+                                      NewPath1,
+                                      MochiReq:get(version),
+                                      MochiReq:get(headers)),
+                    Fun(MochiReq1, VhostTarget)
             end
     end,
-    {reply, {ok, MochiReq1}, State}; 
+    {reply, {ok, FinalMochiReq}, State}; 
         
 % update vhosts
 handle_call(vhosts_changed, _From, State) ->
@@ -247,19 +256,25 @@ vhost_global( VhostGlobals, MochiReq) ->
 
 %% bind host
 %% first it try to bind the port then the hostname.
-try_bind_vhost([], _HostParts, _Port) ->
+try_bind_vhost([], _HostParts, _Port, _PathParts) ->
     no_vhost_matched;
-try_bind_vhost([VhostSpec|Rest], HostParts, Port) ->
-    {{VHostParts, VPort}, Path} = VhostSpec,
+try_bind_vhost([VhostSpec|Rest], HostParts, Port, PathParts) ->
+    {{VHostParts, VPort, VPath}, Path} = VhostSpec,
     case bind_port(VPort, Port) of
         ok -> 
             case bind_vhost(lists:reverse(VHostParts), HostParts, []) of
-                {ok, Bindings, Remainings} -> 
-                    Path1 = make_target(Path, Bindings, Remainings, []),
-                    "/" ++ string:join(Path1,[?SEPARATOR]);
-                fail -> try_bind_vhost(Rest, HostParts, Port)
+                {ok, Bindings, Remainings} ->
+                    case bind_path(VPath, PathParts) of
+                        {ok, PathParts1} ->
+                            Path1 = make_target(Path, Bindings, Remainings, 
[]),
+                            {make_path(Path1), make_path(PathParts1)};
+                        fail -> 
+                            try_bind_vhost(Rest, HostParts, Port,
+                                PathParts)
+                    end;
+                fail -> try_bind_vhost(Rest, HostParts, Port, PathParts)
             end;
-        fail ->  try_bind_vhost(Rest, HostParts, Port)
+        fail ->  try_bind_vhost(Rest, HostParts, Port, PathParts)
     end.
 
 %% doc: build new patch from bindings. bindings are query args
@@ -288,7 +303,7 @@ make_target([P|Rest], Bindings, Remainin
 bind_port(Port, Port) -> ok;
 bind_port(_,_) -> fail.
 
-% bind bhost
+%% bind bhost
 bind_vhost([],[], Bindings) -> {ok, Bindings, []};
 bind_vhost([?MATCH_ALL], [], _Bindings) -> fail;
 bind_vhost([?MATCH_ALL], Rest, Bindings) -> {ok, Bindings, Rest};
@@ -299,18 +314,41 @@ bind_vhost([Cname|Rest], [Cname|RestHost
     bind_vhost(Rest, RestHost, Bindings);
 bind_vhost(_, _, _) -> fail.
 
+%% bind path
+bind_path([], PathParts) ->
+    {ok, PathParts};
+bind_path(_VPathParts, []) ->
+    fail;
+bind_path([Path|VRest],[Path|Rest]) ->
+   bind_path(VRest, Rest);
+bind_path(_, _) ->
+    fail.
+
 % utilities
 
 
 %% create vhost list from ini
 make_vhosts() ->
     lists:foldl(fun({Vhost, Path}, Acc) ->
-        {H, P} = split_host_port(Vhost),
-        H1 = make_spec(H, []),
-        [{{H1,P}, split_path(Path)}|Acc]
+        [{parse_vhost(Vhost), split_path(Path)}|Acc]
     end, [], couch_config:get("vhosts")).
 
 
+parse_vhost(Vhost) ->
+    case urlsplit_netloc(Vhost, []) of
+        {[], Path} ->
+            {make_spec("*", []), 80, Path};
+        {HostPort, []} ->
+            {H, P} = split_host_port(HostPort),
+            H1 = make_spec(H, []),
+            {H1, P, []};
+        {HostPort, Path} ->
+            {H, P} = split_host_port(HostPort),
+            H1 = make_spec(H, []),
+            {H1, P, string:tokens(Path, "/")}
+    end.
+            
+
 split_host_port(HostAsString) ->
     case string:rchr(HostAsString, $:) of
         0 ->
@@ -351,3 +389,14 @@ parse_var(P) ->
         _ -> P
     end.
 
+
+% mochiweb doesn't export it.
+urlsplit_netloc("", Acc) ->
+    {lists:reverse(Acc), ""};
+urlsplit_netloc(Rest=[C | _], Acc) when C =:= $/; C =:= $?; C =:= $# ->
+    {lists:reverse(Acc), Rest};
+urlsplit_netloc([C | Rest], Acc) ->
+    urlsplit_netloc(Rest, [C | Acc]).
+
+make_path(Parts) ->
+     "/" ++ string:join(Parts,[?SEPARATOR]).

Modified: couchdb/trunk/test/etap/160-vhosts.t
URL: 
http://svn.apache.org/viewvc/couchdb/trunk/test/etap/160-vhosts.t?rev=993558&r1=993557&r2=993558&view=diff
==============================================================================
--- couchdb/trunk/test/etap/160-vhosts.t (original)
+++ couchdb/trunk/test/etap/160-vhosts.t Tue Sep  7 23:35:28 2010
@@ -54,7 +54,7 @@ config_files() ->
 main(_) ->
     test_util:init_code_path(),
 
-    etap:plan(10),
+    etap:plan(14),
     case (catch test()) of
         ok ->
             etap:end_tests();
@@ -105,13 +105,18 @@ test() ->
     ok = couch_config:set("vhosts", "example.com", "/etap-test-db", false),
     ok = couch_config:set("vhosts", "*.example.com", 
             "/etap-test-db/_design/doc1/_rewrite", false),
+    ok = couch_config:set("vhosts", "example.com/test", "/etap-test-db", 
false),
     ok = couch_config:set("vhosts", "example1.com", 
             "/etap-test-db/_design/doc1/_rewrite/", false),
     ok = couch_config:set("vhosts",":appname.:dbname.example1.com",
             "/:dbname/_design/:appname/_rewrite/", false),
     ok = couch_config:set("vhosts", ":dbname.example1.com", "/:dbname", false),
+    
     ok = couch_config:set("vhosts", "*.example2.com", "/*", false),
-
+    ok = couch_config:set("vhosts", "*/test", "/etap-test-db", false), 
+    ok = couch_config:set("vhosts", "*.example2.com/test", "/*", false),
+    ok = couch_config:set("vhosts", "*/test1", 
+            "/etap-test-db/_design/doc1/_show/test", false), 
 
     test_regular_request(),
     test_vhost_request(),
@@ -123,7 +128,11 @@ test() ->
     test_vhost_request_replace_var(),
     test_vhost_request_replace_var1(), 
     test_vhost_request_replace_wildcard(),
-    
+    test_vhost_request_path(),
+    test_vhost_request_path1(),
+    test_vhost_request_path2(),
+    test_vhost_request_path3(),
+
     %% restart boilerplate
     couch_db:close(Db),
     timer:sleep(3000),
@@ -231,3 +240,45 @@ test_vhost_request_replace_wildcard() ->
             etap:is(true, true, "should return database info");
         _Else -> false
     end.
+
+test_vhost_request_path() ->
+    Uri = server() ++ "test",
+    case ibrowse:send_req(Uri, [], get, [], [{host_header, "example.com"}]) of
+        {ok, _, _, Body} ->
+            {[{<<"db_name">>, <<"etap-test-db">>},_,_,_,_,_,_,_,_,_]}
+                = couch_util:json_decode(Body),
+            etap:is(true, true, "should return database info");
+        _Else -> false
+    end.
+
+test_vhost_request_path1() ->
+    Url = server() ++ "test/doc1?revs_info=true",
+    case ibrowse:send_req(Url, [], get, [], []) of
+        {ok, _, _, Body} ->
+            {JsonProps} = couch_util:json_decode(Body),
+            HasRevsInfo = proplists:is_defined(<<"_revs_info">>, JsonProps),
+            etap:is(HasRevsInfo, true, "should return _revs_info");
+        _Else -> false
+    end.
+
+test_vhost_request_path2() ->
+    Uri = server() ++ "test",
+    case ibrowse:send_req(Uri, [], get, [], 
[{host_header,"etap-test-db.example2.com"}]) of
+        {ok, _, _, Body} ->
+            {[{<<"db_name">>, <<"etap-test-db">>},_,_,_,_,_,_,_,_,_]}
+                = couch_util:json_decode(Body),
+            etap:is(true, true, "should return database info");
+        _Else -> false
+    end.
+
+test_vhost_request_path3() ->
+    Uri = server() ++ "test1",
+    case ibrowse:send_req(Uri, [], get, [], []) of
+        {ok, _, _, Body} ->
+            {Json} = couch_util:json_decode(Body),
+            etap:is(case proplists:get_value(<<"path">>, Json) of
+                <<"/etap-test-db/_design/doc1/_show/test">> -> true;
+                _ -> false
+            end, true, <<"path in req ok">>);
+        _Else -> false
+    end.


Reply via email to