Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Implement path-based claims, as well as vhost-based claims

  • Loading branch information...
commit 59180800272899d080597849f53679e950d71f73 1 parent 8707f11
@tonyg authored
View
2  NOTES
@@ -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?
View
44 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,
@@ -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
@@ -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))]}]}.
View
157 src/reflect_request_queue.erl
@@ -9,9 +9,9 @@
%% 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
@@ -19,45 +19,28 @@
%% 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);
@@ -65,11 +48,11 @@ single_request(Req, Host) ->
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}} ->
@@ -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");
@@ -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)
@@ -118,27 +101,23 @@ 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} ->
@@ -146,19 +125,22 @@ handle_reverse_http(Req, 'POST', AP, [], UrlQueryFields) ->
{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"},
@@ -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} ->
@@ -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 () ->
@@ -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 ++ "\""}.
@@ -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
@@ -297,3 +259,6 @@ format_req(Req, Body) ->
_ ->
[MethodLine, Headers, Body]
end.
+
+reqparser(Config) ->
+ reversehttp:lookup(request_parser_module, Config, reqparser_vhost).
View
27 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 ++ "/".
View
24 src/reqparser_vhost.erl
@@ -0,0 +1,24 @@
+-module(reqparser_vhost).
+
+-export([analyse/2, ext_url/3]).
+
+analyse(Req, Config) ->
+ case Req:get_header_value(host) of
+ undefined ->
+ {error, 400, "Missing Host HTTP header"};
+ MixedCaseHost ->
+ Host = string:to_lower(MixedCaseHost),
+ AccessHosts = reversehttp:lookup(access_point_hosts, Config, []),
+ case lists:member(Host, AccessHosts) of
+ true ->
+ reversehttp:match_access_point(Req, Config);
+ false ->
+ {single_request, ext_url1(Host)}
+ end
+ end.
+
+ext_url(Req, _Config, HostLabel) ->
+ ext_url1(HostLabel ++ "." ++ string:to_lower(Req:get_header_value(host))).
+
+ext_url1(LcHost) ->
+ "http://" ++ LcHost ++ "/".
View
11 src/reversehttp.app
@@ -10,9 +10,12 @@
]},
{registered, []},
{mod, {reversehttp_app, []}},
- {env, [{exception_hosts, [{"localhost", ["/reversehttp"]},
- {"localhost:8000", ["/reversehttp"]},
- {"localhost.lshift.net", ["/reversehttp"]},
- {"localhost.lshift.net:8000", ["/reversehttp"]}]},
+ {env, [{reflector_config,
+ [{request_parser_module, reqparser_path},
+ {access_point_hosts, ["localhost",
+ "localhost:8000",
+ "localhost.lshift.net",
+ "localhost.lshift.net:8000"]},
+ {access_point_paths, ["/reversehttp"]}]},
{port, 8000}]},
{applications, [kernel, stdlib, crypto]}]}.
View
46 src/reversehttp.erl
@@ -6,6 +6,7 @@
-module(reversehttp).
-author('author <author@example.com>').
-export([start/0, stop/0]).
+-export([lookup/2, lookup/3, match_access_point/2]).
ensure_started(App) ->
case application:start(App) of
@@ -28,3 +29,48 @@ stop() ->
Res = application:stop(reversehttp),
application:stop(crypto),
Res.
+
+lookup(Key, AssocList) ->
+ case lists:keysearch(Key, 1, AssocList) of
+ {value, {_, Value}} ->
+ {ok, Value};
+ false ->
+ {error, not_found}
+ end.
+
+lookup(Key, AssocList, DefaultValue) ->
+ case lists:keysearch(Key, 1, AssocList) of
+ {value, {_, Value}} ->
+ Value;
+ false ->
+ DefaultValue
+ end.
+
+match_access_point(Req, Config) ->
+ Host = case Req:get_header_value(host) of
+ undefined -> lookup(canonical_host, Config, "localhost");
+ V -> V
+ end,
+ match_access_point1(mochiweb_util:urlsplit_path(Req:get(raw_path)),
+ Host,
+ lookup(access_point_paths, Config, [])).
+
+match_access_point1({Path, _QueryPart, _Fragment}, _Host, []) ->
+ %% It's a request for content from the main vhost(s). We indicate
+ %% to our caller that they should serve content as usual.
+ "/" ++ StaticPath = Path,
+ {normal, StaticPath};
+match_access_point1(Pieces = {Path, QueryPart, _Fragment}, Host, [AccessPoint | AccessPoints]) ->
+ case lists:prefix(AccessPoint, Path) of
+ true ->
+ %% The request was for one of our access points.
+ StrippedPath = string:substr(Path, length(AccessPoint) + 1),
+ PathComponents = string:tokens(StrippedPath, "/"),
+ QueryFields = mochiweb_util:parse_qs(QueryPart),
+ {access_point,
+ "http://" ++ Host ++ AccessPoint,
+ PathComponents,
+ QueryFields};
+ false ->
+ match_access_point1(Pieces, Host, AccessPoints)
+ end.
View
21 src/reversehttp_web.erl
@@ -12,21 +12,21 @@
start(Options) ->
{DocRoot, Options1} = get_option(docroot, Options),
- ExceptionHosts = case application:get_env(exception_hosts) of
- undefined -> [{"localhost", ["/reversehttp"]}];
+ ReflectorConfig = case application:get_env(reflector_config) of
+ undefined -> [{exception_hosts, [{"localhost", ["/reversehttp"]}]}];
{ok, V} -> V
end,
- error_logger:info_report({reversehttp_web, exception_hosts, ExceptionHosts}),
+ error_logger:info_report({reversehttp_web, config, ReflectorConfig}),
Loop = fun (Req) ->
- ?MODULE:loop(Req, DocRoot, ExceptionHosts)
+ ?MODULE:loop(Req, DocRoot, ReflectorConfig)
end,
mochiweb_http:start([{name, ?MODULE}, {loop, Loop} | Options1]).
stop() ->
mochiweb_http:stop(?MODULE).
-loop(Req, DocRoot, ExceptionHosts) ->
- case catch reflect_request_queue:handle(Req, ExceptionHosts) of
+loop(Req, DocRoot, ReflectorConfig) ->
+ case catch reflect_request_queue:handle(Req, ReflectorConfig) of
{'EXIT', normal} ->
ok;
{'EXIT', Reason} ->
@@ -35,14 +35,13 @@ loop(Req, DocRoot, ExceptionHosts) ->
{request, Req:dump()}}),
Req:respond({500, [], "Reflector error"}),
ok;
- exception_host ->
- loop1(Req, DocRoot);
- _ ->
+ {normal, Path} ->
+ static_content(Req, Path, DocRoot);
+ ok ->
ok
end.
-loop1(Req, DocRoot) ->
- "/" ++ Path = Req:get(path),
+static_content(Req, Path, DocRoot) ->
case Req:get(method) of
Method when Method =:= 'GET'; Method =:= 'HEAD' ->
case Path of

0 comments on commit 5918080

Please sign in to comment.
Something went wrong with that request. Please try again.