diff options
Diffstat (limited to 'src/couch/src/couch_httpd_vhost.erl')
-rw-r--r-- | src/couch/src/couch_httpd_vhost.erl | 457 |
1 files changed, 0 insertions, 457 deletions
diff --git a/src/couch/src/couch_httpd_vhost.erl b/src/couch/src/couch_httpd_vhost.erl deleted file mode 100644 index 0bff6a36d..000000000 --- a/src/couch/src/couch_httpd_vhost.erl +++ /dev/null @@ -1,457 +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(couch_httpd_vhost). --behaviour(gen_server). --vsn(1). --behaviour(config_listener). - --compile(tuple_calls). - --export([start_link/0, reload/0, get_state/0, dispatch_host/1]). --export([urlsplit_netloc/2, redirect_to_vhost/2]). --export([host/1, split_host_port/1]). - --export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). - -% config_listener api --export([handle_config_change/5, handle_config_terminate/3]). - --include_lib("couch/include/couch_db.hrl"). - --define(SEPARATOR, $\/). --define(MATCH_ALL, {bind, '*'}). --define(RELISTEN_DELAY, 5000). - --record(vhosts_state, { - vhosts, - vhost_globals, - vhosts_fun -}). - -%% doc the vhost manager. -%% This gen_server keep state of vhosts added to the ini and try to -%% match the Host header (or forwarded) against rules built against -%% vhost list. -%% -%% Declaration of vhosts take place in the configuration file : -%% -%% [vhosts] -%% example.com = /example -%% *.example.com = /example -%% -%% The first line will rewrite the rquest to display the content of the -%% example database. This rule works only if the Host header is -%% 'example.com' and won't work for CNAMEs. Second rule on the other hand -%% match all CNAMES to example db. So www.example.com or db.example.com -%% will work. -%% -%% The wildcard ('*') should always be the last in the cnames: -%% -%% "*.db.example.com = /" will match all cname on top of db -%% examples to the root of the machine. -%% -%% -%% Rewriting Hosts to path -%% ----------------------- -%% -%% Like in the _rewrite handler you could match some variable and use -%them to create the target path. Some examples: -%% -%% [vhosts] -%% *.example.com = /* -%% :dbname.example.com = /:dbname -%% :ddocname.:dbname.example.com = /:dbname/_design/:ddocname/_rewrite -%% -%% First rule pass wildcard as dbname, second do the same but use a -%% variable name and the third one allows you to use any app with -%% @ddocname in any db with @dbname . -%% -%% You could also change the default function to handle request by -%% changing the setting `redirect_vhost_handler` in `httpd` section of -%% the Ini: -%% -%% [httpd] -%% redirect_vhost_handler = {Module, Fun} -%% -%% The function take 2 args : the mochiweb request object and the target -%%% path. - -start_link() -> - gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). - -%% @doc reload vhosts rules -reload() -> - gen_server:call(?MODULE, reload). - -get_state() -> - gen_server:call(?MODULE, get_state). - -%% @doc Try to find a rule matching current Host heade. some rule is -%% found it rewrite the Mochiweb Request else it return current Request. -dispatch_host(MochiReq) -> - case vhost_enabled() of - true -> - dispatch_host_int(MochiReq); - false -> - MochiReq - end. - -dispatch_host_int(MochiReq) -> - #vhosts_state{ - vhost_globals = VHostGlobals, - vhosts = VHosts, - vhosts_fun = Fun - } = get_state(), - - {"/" ++ VPath, Query, Fragment} = mochiweb_util:urlsplit_path(MochiReq:get(raw_path)), - VPathParts = string:tokens(VPath, "/"), - - VHost = host(MochiReq), - {VHostParts, VhostPort} = split_host_port(VHost), - FinalMochiReq = - case - try_bind_vhost( - VHosts, - lists:reverse(VHostParts), - VhostPort, - VPathParts - ) - of - no_vhost_matched -> - MochiReq; - {VhostTarget, NewPath} -> - case vhost_global(VHostGlobals, MochiReq) of - true -> - MochiReq; - _Else -> - 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, - FinalMochiReq. - -append_path("/" = _Target, "/" = _Path) -> - "/"; -append_path(Target, Path) -> - Target ++ Path. - -% default redirect vhost handler -redirect_to_vhost(MochiReq, VhostTarget) -> - Path = MochiReq:get(raw_path), - Target = append_path(VhostTarget, Path), - - couch_log:debug("Vhost Target: '~p'~n", [Target]), - - Headers = mochiweb_headers:enter( - "x-couchdb-vhost-path", - Path, - MochiReq:get(headers) - ), - - % build a new mochiweb request - MochiReq1 = mochiweb_request:new( - MochiReq:get(socket), - MochiReq:get(method), - Target, - MochiReq:get(version), - Headers - ), - % cleanup, It force mochiweb to reparse raw uri. - MochiReq1:cleanup(), - MochiReq1. - -%% if so, then it will not be rewritten, but will run as a normal couchdb request. -%* normally you'd use this for _uuids _utils and a few of the others you want to -%% keep available on vhosts. You can also use it to make databases 'global'. -vhost_global(VhostGlobals, MochiReq) -> - RawUri = MochiReq:get(raw_path), - {"/" ++ Path, _, _} = mochiweb_util:urlsplit_path(RawUri), - - Front = - case couch_httpd:partition(Path) of - {"", "", ""} -> - % Special case the root url handler - "/"; - {FirstPart, _, _} -> - FirstPart - end, - [true] == [true || V <- VhostGlobals, V == Front]. - -%% bind host -%% first it try to bind the port then the hostname. -try_bind_vhost([], _HostParts, _Port, _PathParts) -> - no_vhost_matched; -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} -> - 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, PathParts) - end. - -%% doc: build new patch from bindings. bindings are query args -%% (+ dynamic query rewritten if needed) and bindings found in -%% bind_path step. -%% TODO: merge code with rewrite. But we need to make sure we are -%% in string here. -make_target([], _Bindings, _Remaining, Acc) -> - lists:reverse(Acc); -make_target([?MATCH_ALL], _Bindings, Remaining, Acc) -> - Acc1 = lists:reverse(Acc) ++ Remaining, - Acc1; -make_target([?MATCH_ALL | _Rest], _Bindings, Remaining, Acc) -> - Acc1 = lists:reverse(Acc) ++ Remaining, - Acc1; -make_target([{bind, P} | Rest], Bindings, Remaining, Acc) -> - P2 = - case couch_util:get_value({bind, P}, Bindings) of - undefined -> "undefined"; - P1 -> P1 - end, - make_target(Rest, Bindings, Remaining, [P2 | Acc]); -make_target([P | Rest], Bindings, Remaining, Acc) -> - make_target(Rest, Bindings, Remaining, [P | Acc]). - -%% bind port -bind_port(Port, Port) -> ok; -bind_port('*', _) -> ok; -bind_port(_, _) -> fail. - -%% bind bhost -bind_vhost([], [], Bindings) -> - {ok, Bindings, []}; -bind_vhost([?MATCH_ALL], [], _Bindings) -> - fail; -bind_vhost([?MATCH_ALL], Rest, Bindings) -> - {ok, Bindings, Rest}; -bind_vhost([], _HostParts, _Bindings) -> - fail; -bind_vhost([{bind, Token} | Rest], [Match | RestHost], Bindings) -> - bind_vhost(Rest, RestHost, [{{bind, Token}, Match} | Bindings]); -bind_vhost([Cname | Rest], [Cname | RestHost], Bindings) -> - 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 - -host(MochiReq) -> - XHost = chttpd_util:get_chttpd_config( - "x_forwarded_host", "X-Forwarded-Host" - ), - case MochiReq:get_header_value(XHost) of - undefined -> - case MochiReq:get_header_value("Host") of - undefined -> []; - Value1 -> Value1 - end; - Value -> - Value - end. - -make_vhosts() -> - Vhosts = lists:foldl( - fun - ({_, ""}, Acc) -> - Acc; - ({Vhost, Path}, Acc) -> - [{parse_vhost(Vhost), split_path(Path)} | Acc] - end, - [], - config:get("vhosts") - ), - - lists:reverse(lists:usort(Vhosts)). - -parse_vhost(Vhost) -> - case urlsplit_netloc(Vhost, []) of - {[], Path} -> - {make_spec("*", []), '*', 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 -> - {split_host(HostAsString), '*'}; - N -> - HostPart = string:substr(HostAsString, 1, N - 1), - case - (catch erlang:list_to_integer( - string:substr( - HostAsString, - N + 1, - length(HostAsString) - ) - )) - of - {'EXIT', _} -> - {split_host(HostAsString), '*'}; - Port -> - {split_host(HostPart), Port} - end - end. - -split_host(HostAsString) -> - string:tokens(HostAsString, "\."). - -split_path(Path) -> - make_spec(string:tokens(Path, "/"), []). - -make_spec([], Acc) -> - lists:reverse(Acc); -make_spec(["" | R], Acc) -> - make_spec(R, Acc); -make_spec(["*" | R], Acc) -> - make_spec(R, [?MATCH_ALL | Acc]); -make_spec([P | R], Acc) -> - P1 = parse_var(P), - make_spec(R, [P1 | Acc]). - -parse_var(P) -> - case P of - ":" ++ Var -> - {bind, Var}; - _ -> - 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]). - -init(_) -> - ok = config:listen_for_changes(?MODULE, nil), - - %% load configuration - {VHostGlobals, VHosts, Fun} = load_conf(), - State = #vhosts_state{ - vhost_globals = VHostGlobals, - vhosts = VHosts, - vhosts_fun = Fun - }, - {ok, State}. - -handle_call(reload, _From, _State) -> - {VHostGlobals, VHosts, Fun} = load_conf(), - {reply, ok, #vhosts_state{ - vhost_globals = VHostGlobals, - vhosts = VHosts, - vhosts_fun = Fun - }}; -handle_call(get_state, _From, State) -> - {reply, State, State}; -handle_call(_Msg, _From, State) -> - {noreply, State}. - -handle_cast(_Msg, State) -> - {noreply, State}. - -handle_info(restart_config_listener, State) -> - ok = config:listen_for_changes(?MODULE, nil), - {noreply, State}; -handle_info(_Info, State) -> - {noreply, State}. - -terminate(_Reason, _State) -> - ok. - -code_change(_OldVsn, State, _Extra) -> - {ok, State}. - -handle_config_change("vhosts", _, _, _, _) -> - {ok, ?MODULE:reload()}; -handle_config_change(_, _, _, _, _) -> - {ok, nil}. - -handle_config_terminate(_, stop, _) -> - ok; -handle_config_terminate(_Server, _Reason, _State) -> - erlang:send_after(?RELISTEN_DELAY, whereis(?MODULE), restart_config_listener). - -load_conf() -> - %% get vhost globals - VHostGlobals = re:split( - "_utils, _uuids, _session, _users", - "\\s*,\\s*", - [{return, list}] - ), - - %% build vhosts matching rules - VHosts = make_vhosts(), - - %% build vhosts handler fun - DefaultVHostFun = "{couch_httpd_vhost, redirect_to_vhost}", - Fun = couch_httpd:make_arity_2_fun(DefaultVHostFun), - - {VHostGlobals, VHosts, Fun}. - -%% cheaply determine if there are any virtual hosts -%% configured at all. -vhost_enabled() -> - case config:get("vhosts") of - [] -> - false; - _ -> - true - end. |