Skip to content

Commit

Permalink
Implement path-based claims, as well as vhost-based claims
Browse files Browse the repository at this point in the history
  • Loading branch information
tonyg committed Jun 17, 2009
1 parent 8707f11 commit 5918080
Show file tree
Hide file tree
Showing 8 changed files with 190 additions and 142 deletions.
2 changes: 0 additions & 2 deletions NOTES
Expand Up @@ -16,8 +16,6 @@ When considering policies for unsubscription, perhaps include a
generation-number with a subscription? That way, flapping can be
avoided by auto-unsubscribing only a given generation...

Support non-vhost-based claims

Investigate the use of global:register_name to replace the ets table
- works, but slow; mnesia better?

Expand Down
44 changes: 15 additions & 29 deletions src/reflect_meta.erl
@@ -1,33 +1,25 @@
-module(reflect_meta).

-export([handle/4]).
-export([handle/5]).

-define(RPC_TIMEOUT, 10000).

handle(Req, AP, [], _QueryFields) ->
handle(Req, _Config, AccessUrl, [], _QueryFields) ->
reply_with(Req, "Access Point",
gen_info(Req, AP) ++
[{h2, <<"Registered Labels">>},
gen_info(Req, AccessUrl) ++
[{h2, <<"Registered Delegations">>},
{table, [{class, "meta-table"}],
[{tr, [], [{th, X} ||
X <- [<<"Label root">>, <<"Details">>]]}] ++
[format_vhost(AP, X) || X <- all_vhosts()]}]).
X <- [<<"Root URL">>, <<"Details">>]]}] ++
[format_vhost(X) || X <- all_vhosts()]}]);
handle(Req, _Config, _AccessUrl, _PathComponents, _QueryFields) ->
Req:not_found().

gen_info(Req, AP) ->
gen_info(Req, AccessUrl) ->
[{h2, <<"General Info">>},
{p, [<<"Access point info: ">>, hlink(apurl(Req, AP))]},
{p, [<<"Access point info: ">>, hlink(AccessUrl)]},
{p, [<<"Site root: ">>, hlink("http://" ++ Req:get_header_value(host) ++ "/")]}].

apurl(Req, AP) ->
"http://" ++ Req:get_header_value(host) ++ AP.

vhroot(VHost) ->
"http://" ++ VHost ++ "/".

%% vhurl(AP, VHost) ->
%% [Label | Labels] = string:tokens(VHost, "."),
%% "http://" ++ string:join(Labels, ".") ++ AP ++ "/" ++ Label.

template(Title, BodyElts) ->
{html,
[{head,
Expand Down Expand Up @@ -61,12 +53,6 @@ all_vhosts() ->
%% (N = {reflect_vhost_manager, request, H, RN}) <- global:registered_names(),
%% H =:= HostLabel ].

keyget(K, L, Def) ->
case lists:keysearch(K, 1, L) of
{value, {_, V}} -> V;
false -> Def
end.

explain_status(infinity, PollerCount, _RequestCount) ->
integer_to_list(PollerCount) ++
case PollerCount of
Expand All @@ -80,10 +66,10 @@ explain_status(ExpiryMs, _PollerCount, RequestCount) ->
_ -> "ms; " ++ integer_to_list(RequestCount) ++ " request(s) waiting"
end.

format_vhost(_AP, VHost) ->
format_vhost(VHost) ->
Info = reflect_vhost_manager:info(VHost),
{tr, [],
[{td, [], [hlink(vhroot(VHost))]},
{td, [], [explain_status(keyget(expiry_ms, Info, 0),
keyget(poller_count, Info, 0),
keyget(request_count, Info, 0))]}]}.
[{td, [], [hlink(VHost)]},
{td, [], [explain_status(reversehttp:lookup(expiry_ms, Info, 0),
reversehttp:lookup(poller_count, Info, 0),
reversehttp:lookup(request_count, Info, 0))]}]}.
157 changes: 61 additions & 96 deletions src/reflect_request_queue.erl
Expand Up @@ -9,67 +9,50 @@
%% A reversehttp request can be one of the following:
%%
%% - a request for real content from the main vhost
%% - a request to set up a virtual host
%% - a poll for requests sent to a virtual host
%% - a reply to a request sent to a virtual host
%% - a request to set up a delegated portion of URL space
%% - a poll for requests sent to a delegated portion of URL space
%% - a reply to a request sent to a delegated portion of URL space
%% - a tunnelled outbound request to be relayed
%%
%% First and foremost, if it's a request for a virtual host, we need
%% to pass it through unmolested. Otherwise, we get to examine the
%% request further to see which of the other four categories it falls
%% into.

handle(Req, ExceptionHosts) ->
case Req:get_header_value(host) of
undefined ->
error(Req, 400, "Missing Host HTTP header");
MixedCaseHost ->
Host = string:to_lower(MixedCaseHost),
case lists:keysearch(Host, 1, ExceptionHosts) of
{value, {_, AccessPoints}} ->
%% The request was for one of our configured
%% access-point hosts. Now, check the raw_path to
%% see if it matches an access-point path.
handle_exception_host(Req, AccessPoints);
false ->
single_request(Req, Host)
end
end.

%%--------------------------------------------------------------------

handle_exception_host(Req, AccessPoints) ->
{Path, QueryPart, _Fragment} = mochiweb_util:urlsplit_path(Req:get(raw_path)),
case find_access_point(Path, AccessPoints) of
{ok, AccessPoint, PathComponents} ->
%% The request was for one of our access points.
QueryFields = mochiweb_util:parse_qs(QueryPart),
handle(Req, Config) ->
Mod = reqparser(Config),
case Mod:analyse(Req, Config) of
{error, Code, ExplanatoryBodyText} ->
error(Req, Code, ExplanatoryBodyText),
ok;
{single_request, ExternalAppUrlPrefix} ->
single_request(Req, ExternalAppUrlPrefix),
ok;
{normal, Path} ->
{normal, Path};
{access_point, AccessUrl, PathComponents, QueryFields} ->
handle_reverse_http(Req,
Config,
Req:get(method),
AccessPoint,
AccessUrl,
PathComponents,
QueryFields);
{error, not_found} ->
%% It's a request for content from the main vhost(s). The
%% special token exception_host is passed back to our
%% caller to indicate that they should serve content as
%% usual.
exception_host
QueryFields),
ok
end.

single_request(Req, Host) ->
single_request(Req, ExternalAppUrlPrefix) ->
case request_host(Req) of
{error, Reason} ->
error(Req, 400, "Could not determine your IP address", Reason);
{ok, RequestHost} ->
FormattedRequest = format_req(Req, Req:recv_body()),
Msg = #poll_response{requesting_client = RequestHost,
formatted_request = FormattedRequest},
case reflect_vhost_manager:request(Host, Msg) of
case reflect_vhost_manager:request(ExternalAppUrlPrefix, Msg) of
{error, not_found} ->
error(Req, 404, "Virtual host not found", Host);
error(Req, 404, "Delegation not found", ExternalAppUrlPrefix);
{error, noproc} ->
error(Req, 503, "Virtual host manager crashed", Msg);
error(Req, 503, "Delegation manager crashed", Msg);
{error, {timeout, poller}} ->
error(Req, 504, "No available servers");
{error, {timeout, downstream}} ->
Expand All @@ -89,7 +72,7 @@ single_request(Req, Host) ->
end
end.

handle_reverse_http(Req, 'POST', _AP, ["_relay", HostAndPort], _QueryFields) ->
handle_reverse_http(Req, _Config, 'POST', _AccessUrl, ["_relay", HostAndPort], _QueryFields) ->
case extract_host_and_port(HostAndPort) of
{error, _} ->
error(Req, 400, "Bad host:port in relay request");
Expand All @@ -109,7 +92,7 @@ handle_reverse_http(Req, 'POST', _AP, ["_relay", HostAndPort], _QueryFields) ->
end
end;

handle_reverse_http(Req, 'POST', AP, [], UrlQueryFields) ->
handle_reverse_http(Req, Config, 'POST', AccessUrl, [], UrlQueryFields) ->
BodyQueryFields = case Req:recv_body() of
undefined -> [];
V -> mochiweb_util:parse_qs(V)
Expand All @@ -118,47 +101,46 @@ handle_reverse_http(Req, 'POST', AP, [], UrlQueryFields) ->
case lists:keysearch("name", 1, QueryFields) of
{value, {_, MixedCaseHostLabel}} ->
HostLabel = string:to_lower(MixedCaseHostLabel),
Token = case lists:keysearch("token", 1, QueryFields) of
{value, {_, T}} -> T;
false -> random_id(HostLabel)
end,
LeaseSecondsStr = case lists:keysearch("lease", 1, QueryFields) of
{value, {_, L}} -> L;
false -> "0"
end,
Token = reversehttp:lookup("token", QueryFields, random_id(HostLabel)),
LeaseSecondsStr = reversehttp:lookup("lease", QueryFields, "0"),
case catch list_to_integer(LeaseSecondsStr) of
{'EXIT', _} ->
error(Req, 400, "Invalid lease seconds setting", LeaseSecondsStr);
LeaseSeconds ->
ReqName = random_id({HostLabel, Token}),
Headers = [link_header(format_request_url(Req,
AP, HostLabel, Token, ReqName),
"first"),
link_header(format_ext_vhost_url(Req, HostLabel),
"related"),
{"Location", format_int_vhost_url(Req, AP, HostLabel, Token)}],
Host = expand_host_label(Req, HostLabel),
case reflect_vhost_manager:configure(Host, Token, LeaseSeconds) of
Mod = reqparser(Config),
ExternalAppUrlPrefix = Mod:ext_url(Req, Config, HostLabel),
FirstUrl = format_request_url(AccessUrl, HostLabel, Token, ReqName),
IntUrl = format_int_vhost_url(AccessUrl, HostLabel, Token),
Headers = [link_header(FirstUrl, "first"),
link_header(ExternalAppUrlPrefix, "related"),
{"Location", IntUrl}],
case reflect_vhost_manager:configure(ExternalAppUrlPrefix,
Token,
LeaseSeconds) of
{ok, existing, _Pid} ->
Req:respond({204, Headers, []});
{ok, new, _Pid} ->
Req:respond({201, Headers, []});
{error, bad_token} ->
error(Req, 403, "Bad token", HostLabel);
{error, Reason} ->
error(Req, 502, "Could not configure vhost manager", {Host, Reason})
error(Req, 502, "Could not configure delegation manager",
{ExternalAppUrlPrefix, Reason})
end
end;
false ->
error(Req, 400, "Missing label parameter", QueryFields)
end;

handle_reverse_http(Req, 'GET', AP, [HostLabel, Token, ReqName], _QueryFields) ->
handle_reverse_http(Req, Config, 'GET', AccessUrl, [HostLabel, Token, ReqName], _QueryFields) ->
NextReqName = random_id({HostLabel, Token}),
Headers = [link_header(format_request_url(Req, AP, HostLabel, Token, NextReqName), "next")],
Host = expand_host_label(Req, HostLabel),
Headers = [link_header(format_request_url(AccessUrl, HostLabel, Token, NextReqName),
"next")],
Mod = reqparser(Config),
ExternalAppUrlPrefix = Mod:ext_url(Req, Config, HostLabel),
case reflect_vhost_manager:poll(
Host, Token, ReqName,
ExternalAppUrlPrefix, Token, ReqName,
fun (#poll_response{requesting_client = RC, formatted_request = FR}) ->
Req:respond({200,
Headers ++ [{'Content-type', "message/http"},
Expand All @@ -167,7 +149,7 @@ handle_reverse_http(Req, 'GET', AP, [HostLabel, Token, ReqName], _QueryFields) -
ok
end) of
{error, noproc} ->
error(Req, 503, "Virtual host manager crashed", HostLabel);
error(Req, 503, "Delegation manager crashed", HostLabel);
{error, timeout} ->
Req:respond({204, Headers, []});
{error, bad_token} ->
Expand All @@ -178,7 +160,7 @@ handle_reverse_http(Req, 'GET', AP, [HostLabel, Token, ReqName], _QueryFields) -
ok
end;

handle_reverse_http(Req, 'POST', _AP, [HostLabel, _Token, ReqName], _QueryFields) ->
handle_reverse_http(Req, _Config, 'POST', _AccessUrl, [HostLabel, _Token, ReqName], _QueryFields) ->
case reflect_vhost_manager:respond(
ReqName,
fun () ->
Expand All @@ -195,35 +177,27 @@ handle_reverse_http(Req, 'POST', _AP, [HostLabel, _Token, ReqName], _QueryFields
Req:respond({202, [], []})
end;

handle_reverse_http(Req, 'GET', AP, PathComponents, QueryFields) ->
handle_meta(Req, AP, PathComponents, QueryFields);
handle_reverse_http(Req, Config, 'GET', AccessUrl, PathComponents, QueryFields) ->
handle_meta(Req, Config, AccessUrl, PathComponents, QueryFields);

handle_reverse_http(Req, Method, AP, PathComponents, QueryFields) ->
error(Req, 400, "Bad Reverse-HTTP Request", {Method, AP, PathComponents, QueryFields}).
handle_reverse_http(Req, _Config, Method, AccessUrl, PathComponents, QueryFields) ->
error(Req, 400, "Bad Reverse-HTTP Request", {Method, AccessUrl, PathComponents, QueryFields}).

-ifdef(ENABLE_META).
handle_meta(Req, AP, PathComponents, QueryFields) ->
reflect_meta:handle(Req, AP, PathComponents, QueryFields).
handle_meta(Req, Config, AccessUrl, PathComponents, QueryFields) ->
reflect_meta:handle(Req, Config, AccessUrl, PathComponents, QueryFields).
-else.
handle_meta(Req, AP, PathComponents, QueryFields) ->
handle_meta(_Req, _Config, _AccessUrl, _PathComponents, _QueryFields) ->
ok.
-endif.

%%--------------------------------------------------------------------

expand_host_label(Req, HostLabel) ->
HostLabel ++ "." ++ string:to_lower(Req:get_header_value(host)).

format_ext_vhost_url(Req, HostLabel) ->
"http://" ++ expand_host_label(Req, HostLabel) ++ "/".
format_int_vhost_url(AccessUrl, HostLabel, Token) ->
AccessUrl ++ "/" ++ HostLabel ++ "/" ++ Token.

format_int_vhost_url(Req, AccessPoint, HostLabel, Token) ->
"http://" ++ Req:get_header_value(host) ++
AccessPoint ++ "/" ++ HostLabel ++ "/" ++ Token.

format_request_url(Req, AccessPoint, HostLabel, Token, ReqName) ->
"http://" ++ Req:get_header_value(host) ++
AccessPoint ++ "/" ++ HostLabel ++ "/" ++ Token ++ "/" ++ ReqName.
format_request_url(AccessUrl, HostLabel, Token, ReqName) ->
AccessUrl ++ "/" ++ HostLabel ++ "/" ++ Token ++ "/" ++ ReqName.

link_header(Url, Rel) ->
{'Link', "<" ++ Url ++ ">; rel=\"" ++ Rel ++ "\""}.
Expand All @@ -241,18 +215,6 @@ error(Req, StatusCode, ExplanatoryBodyText, ExtraInfo) ->
ExtraInfo}),
Req:respond({StatusCode, [], integer_to_list(StatusCode) ++ " " ++ ExplanatoryBodyText}).

find_access_point(_Path, []) ->
{error, not_found};
find_access_point(Path, [AccessPoint | AccessPoints]) ->
case lists:prefix(AccessPoint, Path) of
true ->
StrippedPath = string:substr(Path, length(AccessPoint) + 1),
PathComponents = string:tokens(StrippedPath, "/"),
{ok, AccessPoint, PathComponents};
false ->
find_access_point(Path, AccessPoints)
end.

request_host(Req) ->
Sock = Req:get(socket),
case inet:peername(Sock) of
Expand Down Expand Up @@ -297,3 +259,6 @@ format_req(Req, Body) ->
_ ->
[MethodLine, Headers, Body]
end.

reqparser(Config) ->
reversehttp:lookup(request_parser_module, Config, reqparser_vhost).
27 changes: 27 additions & 0 deletions src/reqparser_path.erl
@@ -0,0 +1,27 @@
-module(reqparser_path).

-export([analyse/2, ext_url/3]).

analyse(Req, Config) ->
case reversehttp:match_access_point(Req, Config) of
V = {access_point, _, _, _} ->
V;
{normal, Path} ->
case extract_label(Path) of
{ok, HostLabel} ->
{single_request, ext_url(Req, Config, HostLabel)};
{error, no_slash} ->
{normal, Path}
end
end.

extract_label(Path) ->
case string:chr(Path, $/) of
0 ->
{error, no_slash};
N ->
{ok, string:left(Path, N - 1)}
end.

ext_url(Req, _Config, HostLabel) ->
"http://" ++ string:to_lower(Req:get_header_value(host)) ++ "/" ++ HostLabel ++ "/".

0 comments on commit 5918080

Please sign in to comment.