Skip to content
Newer
Older
100644 298 lines (267 sloc) 12.1 KB
2ede90c @tonyg Initial git commit from old mercurial repository.
authored Mar 8, 2009
1 -module(reflect_request_queue).
2
3 -export([handle/2]).
4
5 -define(ENABLE_META, true).
6
7 -record(poll_response, {requesting_client, formatted_request}).
8
9 %% A reversehttp request can be one of the following:
10 %%
11 %% - a request for real content from the main vhost
12 %% - a request to set up a virtual host
13 %% - a poll for requests sent to a virtual host
14 %% - a reply to a request sent to a virtual host
15 %% - a tunnelled outbound request to be relayed
16 %%
17 %% First and foremost, if it's a request for a virtual host, we need
18 %% to pass it through unmolested. Otherwise, we get to examine the
19 %% request further to see which of the other four categories it falls
20 %% into.
21
22 handle(Req, ExceptionHosts) ->
23 case Req:get_header_value(host) of
24 undefined ->
25 error(Req, 400, "Missing Host HTTP header");
26 Host ->
27 case lists:keysearch(Host, 1, ExceptionHosts) of
28 {value, {_, AccessPoints}} ->
29 %% The request was for one of our configured
30 %% access-point hosts. Now, check the raw_path to
31 %% see if it matches an access-point path.
32 handle_exception_host(Req, AccessPoints);
33 false ->
34 single_request(Req, Host)
35 end
36 end.
37
38 %%--------------------------------------------------------------------
39
40 handle_exception_host(Req, AccessPoints) ->
41 {Path, QueryPart, _Fragment} = mochiweb_util:urlsplit_path(Req:get(raw_path)),
42 case find_access_point(Path, AccessPoints) of
43 {ok, AccessPoint, PathComponents} ->
44 %% The request was for one of our access points.
45 QueryFields = mochiweb_util:parse_qs(QueryPart),
46 handle_reverse_http(Req,
47 Req:get(method),
48 AccessPoint,
49 PathComponents,
50 QueryFields);
51 {error, not_found} ->
52 %% It's a request for content from the main vhost(s). The
53 %% special token exception_host is passed back to our
54 %% caller to indicate that they should serve content as
55 %% usual.
56 exception_host
57 end.
58
59 single_request(Req, Host) ->
60 case request_host(Req) of
61 {error, Reason} ->
62 error(Req, 400, "Could not determine your IP address", Reason);
63 {ok, RequestHost} ->
64 FormattedRequest = format_req(Req, Req:recv_body()),
65 Msg = #poll_response{requesting_client = RequestHost,
66 formatted_request = FormattedRequest},
67 case reflect_vhost_manager:request(Host, Msg) of
68 {error, not_found} ->
69 error(Req, 404, "Virtual host not found", Host);
70 {error, noproc} ->
71 error(Req, 503, "Virtual host manager crashed", Msg);
72 {error, {timeout, poller}} ->
73 error(Req, 504, "No available servers");
74 {error, {timeout, downstream}} ->
75 error(Req, 504, "Timeout waiting for response from downstream");
76 {error, Reason} ->
77 error(Req, 500, "Internal contract error",
78 {reflect_vhost_manager, request, Msg, Reason});
79 {ok, undefined} ->
80 error(Req, 502, "Missing response from downstream");
81 {ok, Response} ->
82 case check_response(Response) of
83 {error, Reason} ->
84 error(Req, 502, "Bad response from downstream", Reason);
85 {ok, MochiResponse} ->
86 Req:respond(MochiResponse)
87 end
88 end
89 end.
90
91 handle_reverse_http(Req, 'POST', _AP, ["_relay", HostAndPort], _QueryFields) ->
92 case extract_host_and_port(HostAndPort) of
93 {error, _} ->
94 error(Req, 400, "Bad host:port in relay request");
95 {ok, Host, Port} ->
96 case Req:get_header_value('content-type') of
97 undefined ->
98 error(Req, 415, "Missing Content-type header");
99 "message/http" ++ _ ->
100 case relay_http:relay(Host, list_to_integer(Port), Req:recv_body()) of
101 {ok, Response} ->
102 Req:respond({200, [{'Content-type', "message/http"}], Response});
103 {error, Code, Text} ->
104 error(Req, Code, Text)
105 end;
106 Other ->
107 error(Req, 415, "Invalid Content-type header", Other)
108 end
109 end;
110
111 handle_reverse_http(Req, 'POST', AP, [], UrlQueryFields) ->
112 BodyQueryFields = case Req:recv_body() of
113 undefined -> [];
114 V -> mochiweb_util:parse_qs(V)
115 end,
116 QueryFields = BodyQueryFields ++ UrlQueryFields,
117 case lists:keysearch("name", 1, QueryFields) of
118 {value, {_, HostLabel}} ->
119 Token = case lists:keysearch("token", 1, QueryFields) of
120 {value, {_, T}} -> T;
121 false -> random_id(HostLabel)
122 end,
123 LeaseSecondsStr = case lists:keysearch("lease", 1, QueryFields) of
124 {value, {_, L}} -> L;
125 false -> "0"
126 end,
127 case catch list_to_integer(LeaseSecondsStr) of
128 {'EXIT', _} ->
129 error(Req, 400, "Invalid lease seconds setting", LeaseSecondsStr);
130 LeaseSeconds ->
131 ReqName = random_id({HostLabel, Token}),
132 Headers = [link_header(format_request_url(Req,
133 AP, HostLabel, Token, ReqName),
134 "first"),
135 link_header(format_ext_vhost_url(Req, HostLabel),
136 "related"),
137 {"Location", format_int_vhost_url(Req, AP, HostLabel, Token)}],
138 Host = expand_host_label(Req, HostLabel),
139 case reflect_vhost_manager:configure(Host, Token, LeaseSeconds) of
140 {ok, existing, _Pid} ->
141 Req:respond({204, Headers, []});
142 {ok, new, _Pid} ->
143 Req:respond({201, Headers, []});
144 {error, bad_token} ->
145 error(Req, 403, "Bad token", HostLabel);
146 {error, Reason} ->
147 error(Req, 502, "Could not configure vhost manager", {Host, Reason})
148 end
149 end;
150 false ->
151 error(Req, 400, "Missing label parameter", QueryFields)
152 end;
153
154 handle_reverse_http(Req, 'GET', AP, [HostLabel, Token, ReqName], _QueryFields) ->
155 NextReqName = random_id({HostLabel, Token}),
156 Headers = [link_header(format_request_url(Req, AP, HostLabel, Token, NextReqName), "next")],
157 Host = expand_host_label(Req, HostLabel),
158 case reflect_vhost_manager:poll(
159 Host, Token, ReqName,
160 fun (#poll_response{requesting_client = RC, formatted_request = FR}) ->
161 Req:respond({200,
162 Headers ++ [{'Content-type', "message/http"},
163 {'Requesting-Client', RC}],
164 FR}),
165 ok
166 end) of
167 {error, noproc} ->
168 error(Req, 503, "Virtual host manager crashed", HostLabel);
169 {error, timeout} ->
170 Req:respond({204, Headers, []});
171 {error, bad_token} ->
172 error(Req, 403, "Bad token", HostLabel);
173 {error, Reason} ->
174 error(Req, 500, "Internal contract error", {reflect_vhost_manager, poll, Reason});
175 ok ->
176 ok
177 end;
178
179 handle_reverse_http(Req, 'POST', _AP, [HostLabel, _Token, ReqName], _QueryFields) ->
180 case reflect_vhost_manager:respond(
181 ReqName,
182 fun () ->
183 case Req:recv_body() of
184 undefined -> {error, missing_body};
185 Response -> {ok, Response}
186 end
187 end) of
188 {error, not_found} ->
189 error(Req, 404, "Request key not found", {HostLabel, ReqName});
190 {error, missing_body} ->
191 error(Req, 400, "Missing response", {HostLabel, ReqName});
192 ok ->
193 Req:respond({202, [], []})
194 end;
195
196 handle_reverse_http(Req, 'GET', AP, PathComponents, QueryFields) ->
197 handle_meta(Req, AP, PathComponents, QueryFields);
198
199 handle_reverse_http(Req, Method, AP, PathComponents, QueryFields) ->
200 error(Req, 400, "Bad Reverse-HTTP Request", {Method, AP, PathComponents, QueryFields}).
201
202 -ifdef(ENABLE_META).
203 handle_meta(Req, AP, PathComponents, QueryFields) ->
204 reflect_meta:handle(Req, AP, PathComponents, QueryFields).
205 -else.
206 handle_meta(Req, AP, PathComponents, QueryFields) ->
207 ok.
208 -endif.
209
210 %%--------------------------------------------------------------------
211
212 expand_host_label(Req, HostLabel) ->
213 HostLabel ++ "." ++ Req:get_header_value(host).
214
215 format_ext_vhost_url(Req, HostLabel) ->
216 "http://" ++ expand_host_label(Req, HostLabel) ++ "/".
217
218 format_int_vhost_url(Req, AccessPoint, HostLabel, Token) ->
219 "http://" ++ Req:get_header_value(host) ++
220 AccessPoint ++ "/" ++ HostLabel ++ "/" ++ Token.
221
222 format_request_url(Req, AccessPoint, HostLabel, Token, ReqName) ->
223 "http://" ++ Req:get_header_value(host) ++
224 AccessPoint ++ "/" ++ HostLabel ++ "/" ++ Token ++ "/" ++ ReqName.
225
226 link_header(Url, Rel) ->
227 {'Link', "<" ++ Url ++ ">; rel=\"" ++ Rel ++ "\""}.
228
229 random_id(Term) ->
230 mochihex:to_hex(erlang:md5(term_to_binary({Term, erlang:now()}))).
231
232 error(Req, StatusCode, ExplanatoryBodyText) ->
233 error(Req, StatusCode, ExplanatoryBodyText, []).
234
235 error(Req, StatusCode, ExplanatoryBodyText, ExtraInfo) ->
236 error_logger:warning_report({?MODULE, error,
237 {client, request_host(Req)},
238 {reply, StatusCode, ExplanatoryBodyText},
239 ExtraInfo}),
240 Req:respond({StatusCode, [], integer_to_list(StatusCode) ++ " " ++ ExplanatoryBodyText}).
241
242 find_access_point(_Path, []) ->
243 {error, not_found};
244 find_access_point(Path, [AccessPoint | AccessPoints]) ->
245 case lists:prefix(AccessPoint, Path) of
246 true ->
247 StrippedPath = string:substr(Path, length(AccessPoint) + 1),
248 PathComponents = string:tokens(StrippedPath, "/"),
249 {ok, AccessPoint, PathComponents};
250 false ->
251 find_access_point(Path, AccessPoints)
252 end.
253
254 request_host(Req) ->
255 Sock = Req:get(socket),
256 case inet:peername(Sock) of
257 {ok, {Addr, Port}} ->
258 {ok, inet_parse:ntoa(Addr) ++ ":" ++ integer_to_list(Port)};
259 {error, Reason} ->
260 {error, Reason}
261 end.
262
263 extract_host_and_port(HostAndPort) ->
264 case string:tokens(HostAndPort, ":") of
265 [H, P] -> {ok, H, P};
266 [H] -> {ok, H, "80"};
267 _ -> {error, invalid}
268 end.
269
270 check_response(Bytes) ->
271 case httpc_response:parse([Bytes, nolimit, true]) of
272 {ok, {_HttpVersion, StatusCode, StatusText, Headers, Body}} ->
273 {ok, {[integer_to_list(StatusCode), " ", StatusText],
274 http_response:header_list(Headers),
275 Body}};
276 Other ->
277 {error, Other}
278 end.
279
280 make_io(Atom) when is_atom(Atom) ->
281 atom_to_list(Atom);
282 make_io(Integer) when is_integer(Integer) ->
283 integer_to_list(Integer);
284 make_io(Io) when is_list(Io); is_binary(Io) ->
285 Io.
286
287 format_req(Req, Body) ->
288 F = fun ({K, V}, Acc) -> [make_io(K), <<": ">>, V, <<"\r\n">> | Acc] end,
289 Headers = lists:foldl(F, [<<"\r\n">>], mochiweb_headers:to_list(Req:get(headers))),
290 MethodLine = mochifmt:format("{0} {1} HTTP/{2.0}.{2.1}\r\n",
291 [Req:get(method), Req:get(raw_path), Req:get(version)]),
292 case Body of
293 undefined ->
294 [MethodLine, Headers];
295 _ ->
296 [MethodLine, Headers, Body]
297 end.
Something went wrong with that request. Please try again.