Skip to content

Commit

Permalink
Add cowboy_http_req:port/1.
Browse files Browse the repository at this point in the history
Returns the port given in the Host header if present,
otherwise the default port of 443 for HTTPS and 80 for HTTP
is returned.
  • Loading branch information
Loïc Hoguin committed May 4, 2011
1 parent cc663df commit 6c1f73c
Show file tree
Hide file tree
Showing 6 changed files with 84 additions and 43 deletions.
1 change: 1 addition & 0 deletions include/http.hrl
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
peer = undefined :: undefined | {Address::ip_address(), Port::ip_port()},
host = undefined :: undefined | cowboy_dispatcher:path_tokens(),
raw_host = undefined :: undefined | string(),
port = undefined :: undefined | ip_port(),
path = undefined :: undefined | '*' | cowboy_dispatcher:path_tokens(),
raw_path = undefined :: undefined | string(),
qs_vals = undefined :: undefined | list({Name::string(), Value::string() | true}),
Expand Down
52 changes: 37 additions & 15 deletions src/cowboy_dispatcher.erl
Original file line number Diff line number Diff line change
Expand Up @@ -24,17 +24,20 @@

-export_type([bindings/0, path_tokens/0, dispatch_rules/0]).

-include_lib("kernel/include/inet.hrl").
-include_lib("eunit/include/eunit.hrl").

%% API.

-spec split_host(Host::string()) -> Tokens::path_tokens().
-spec split_host(Host::string())
-> {Tokens::path_tokens(), Host::string(), Port::undefined | ip_port()}.
split_host(Host) ->
Host2 = case string:chr(Host, $:) of
0 -> Host;
N -> lists:sublist(Host, N - 1)
end,
string:tokens(Host2, ".").
case string:chr(Host, $:) of
0 -> {string:tokens(Host, "."), Host, undefined};
N ->
{Host2, [$:|Port]} = lists:split(N - 1, Host),
{string:tokens(Host2, "."), Host2, list_to_integer(Port)}
end.

-spec split_path(Path::string())
-> {Tokens::path_tokens(), Path::string(), Qs::string()}.
Expand Down Expand Up @@ -119,19 +122,38 @@ list_match([], [], Binds) ->
split_host_test_() ->
%% {Host, Result}
Tests = [
{"", []},
{".........", []},
{"*", ["*"]},
{"cowboy.dev-extend.eu", ["cowboy", "dev-extend", "eu"]},
{"dev-extend..eu", ["dev-extend", "eu"]},
{"dev-extend.eu", ["dev-extend", "eu"]},
{"dev-extend.eu:8080", ["dev-extend", "eu"]},
{"", {[], "", undefined}},
{".........", {[], ".........", undefined}},
{"*", {["*"], "*", undefined}},
{"cowboy.dev-extend.eu", {["cowboy", "dev-extend", "eu"],
"cowboy.dev-extend.eu", undefined}},
{"dev-extend..eu",
{["dev-extend", "eu"], "dev-extend..eu", undefined}},
{"dev-extend.eu", {["dev-extend", "eu"], "dev-extend.eu", undefined}},
{"dev-extend.eu:8080", {["dev-extend", "eu"], "dev-extend.eu", 8080}},
{"a.b.c.d.e.f.g.h.i.j.k.l.m.n.o.p.q.r.s.t.u.v.w.x.y.z",
["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m",
"n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"]}
{["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m",
"n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"],
"a.b.c.d.e.f.g.h.i.j.k.l.m.n.o.p.q.r.s.t.u.v.w.x.y.z", undefined}}
],
[{H, fun() -> R = split_host(H) end} || {H, R} <- Tests].

split_host_fail_test_() ->
Tests = [
"dev-extend.eu:owns",
"dev-extend.eu: owns",
"dev-extend.eu:42fun",
"dev-extend.eu: 42fun",
"dev-extend.eu:42 fun",
"dev-extend.eu:fun 42",
"dev-extend.eu: 42",
":owns",
":42 fun"
],
[{H, fun() -> case catch split_host(H) of
{'EXIT', _Reason} -> ok
end end} || H <- Tests].

split_path_test_() ->
%% {Path, Result, QueryString}
Tests = [
Expand Down
48 changes: 31 additions & 17 deletions src/cowboy_http_protocol.erl
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,6 @@ request({http_error, _Any}, State) ->
error_terminate(400, State).

-spec wait_header(Req::#http_req{}, State::#state{}) -> ok.
%% @todo We don't want to wait T at each header...
%% We want to wait T total until we reach the body.
wait_header(Req, State=#state{socket=Socket,
transport=Transport, timeout=T}) ->
case Transport:recv(Socket, 0, T) of
Expand All @@ -101,22 +99,19 @@ wait_header(Req, State=#state{socket=Socket,

-spec header({http_header, I::integer(), Field::http_header(), R::term(),
Value::string()} | http_eoh, Req::#http_req{}, State::#state{}) -> ok.
header({http_header, _I, 'Host', _R, RawHost}, Req=#http_req{path=Path,
host=undefined}, State=#state{dispatch=Dispatch}) ->
header({http_header, _I, 'Host', _R, RawHost}, Req=#http_req{
transport=Transport, host=undefined}, State) ->
RawHost2 = string_to_lower(RawHost),
Host = cowboy_dispatcher:split_host(RawHost2),
%% @todo We probably want to filter the Host and Path here to allow
%% things like url rewriting.
case cowboy_dispatcher:match(Host, Path, Dispatch) of
{ok, Handler, Opts, Binds} ->
wait_header(Req#http_req{
host=Host, raw_host=RawHost2, bindings=Binds,
headers=[{'Host', RawHost2}|Req#http_req.headers]},
State#state{handler={Handler, Opts}});
{error, notfound, host} ->
error_terminate(400, State);
{error, notfound, path} ->
error_terminate(404, State)
case catch cowboy_dispatcher:split_host(RawHost2) of
{Host, RawHost3, undefined} ->
Port = default_port(Transport:name()),
dispatch(Req#http_req{host=Host, raw_host=RawHost3, port=Port,
headers=[{'Host', RawHost3}|Req#http_req.headers]}, State);
{Host, RawHost3, Port} ->
dispatch(Req#http_req{host=Host, raw_host=RawHost3, port=Port,
headers=[{'Host', RawHost3}|Req#http_req.headers]}, State);
{'EXIT', _Reason} ->
error_terminate(400, State)
end;
%% Ignore Host headers if we already have it.
header({http_header, _I, 'Host', _R, _V}, Req, State) ->
Expand All @@ -137,6 +132,21 @@ header(http_eoh, Req, State) ->
header({http_error, _String}, _Req, State) ->
error_terminate(500, State).

-spec dispatch(Req::#http_req{}, State::#state{}) -> ok.
dispatch(Req=#http_req{host=Host, path=Path},
State=#state{dispatch=Dispatch}) ->
%% @todo We probably want to filter the Host and Path here to allow
%% things like url rewriting.
case cowboy_dispatcher:match(Host, Path, Dispatch) of
{ok, Handler, Opts, Binds} ->
wait_header(Req#http_req{bindings=Binds},
State#state{handler={Handler, Opts}});
{error, notfound, host} ->
error_terminate(400, State);
{error, notfound, path} ->
error_terminate(404, State)
end.

-spec handler_init(Req::#http_req{}, State::#state{}) -> ok.
handler_init(Req, State=#state{
transport=Transport, handler={Handler, Opts}}) ->
Expand Down Expand Up @@ -227,6 +237,10 @@ connection_to_atom(Connection) ->
_Any -> keepalive
end.

-spec default_port(TransportName::atom()) -> 80 | 443.
default_port(ssl) -> 443;
default_port(_) -> 80.

%% More efficient implementation of string:to_lower.
%% We are excluding a few characters on purpose.
-spec string_to_lower(string()) -> string().
Expand Down
6 changes: 5 additions & 1 deletion src/cowboy_http_req.erl
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@

-export([
method/1, version/1, peer/1,
host/1, raw_host/1,
host/1, raw_host/1, port/1,
path/1, raw_path/1,
qs_val/2, qs_val/3, qs_vals/1, raw_qs/1,
binding/2, binding/3, bindings/1,
Expand Down Expand Up @@ -63,6 +63,10 @@ host(Req) ->
raw_host(Req) ->
{Req#http_req.raw_host, Req}.

-spec port(Req::#http_req{}) -> {Port::ip_port(), Req::#http_req{}}.
port(Req) ->
{Req#http_req.port, Req}.

-spec path(Req::#http_req{})
-> {Path::cowboy_dispatcher:path_tokens(), Req::#http_req{}}.
path(Req) ->
Expand Down
18 changes: 9 additions & 9 deletions src/cowboy_http_websocket.erl
Original file line number Diff line number Diff line change
Expand Up @@ -81,9 +81,9 @@ upgrade_error(Req=#http_req{socket=Socket, transport=Transport}) ->
-spec websocket_handshake(State::#state{}, Req::#http_req{},
HandlerState::term()) -> ok.
websocket_handshake(State=#state{origin=Origin, challenge=Challenge},
Req=#http_req{transport=Transport, raw_host=Host, raw_path=Path},
HandlerState) ->
Location = websocket_location(Transport:name(), Host, Path),
Req=#http_req{transport=Transport, raw_host=Host, port=Port,
raw_path=Path}, HandlerState) ->
Location = websocket_location(Transport:name(), Host, Port, Path),
{ok, Req2} = cowboy_http_req:reply(
"101 WebSocket Protocol Handshake",
[{"Connection", "Upgrade"},
Expand All @@ -94,12 +94,12 @@ websocket_handshake(State=#state{origin=Origin, challenge=Challenge},
handler_loop(State#state{messages=Transport:messages()},
Req2, HandlerState, <<>>).

-spec websocket_location(TransName::atom(), Host::string(), Path::string())
-> string().
websocket_location(ssl, Host, Path) ->
"wss://" ++ Host ++ Path;
websocket_location(_Any, Host, Path) ->
"ws://" ++ Host ++ Path.
-spec websocket_location(TransName::atom(), Host::string(),
Port::ip_port(), Path::string()) -> string().
websocket_location(ssl, Host, Port, Path) ->
"wss://" ++ Host ++ ":" ++ integer_to_list(Port) ++ Path;
websocket_location(_Any, Host, Port, Path) ->
"ws://" ++ Host ++ ":" ++ integer_to_list(Port) ++ Path.

-spec handler_loop(State::#state{}, Req::#http_req{},
HandlerState::term(), SoFar::binary()) -> ok.
Expand Down
2 changes: 1 addition & 1 deletion test/http_SUITE.erl
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,7 @@ websocket(Config) ->
[Headers, Body] = websocket_headers(erlang:decode_packet(httph, Rest, []), []),
{'Connection', "Upgrade"} = lists:keyfind('Connection', 1, Headers),
{'Upgrade', "WebSocket"} = lists:keyfind('Upgrade', 1, Headers),
{"sec-websocket-location", "ws://localhost/websocket"}
{"sec-websocket-location", "ws://localhost:80/websocket"}
= lists:keyfind("sec-websocket-location", 1, Headers),
{"sec-websocket-origin", "http://localhost"}
= lists:keyfind("sec-websocket-origin", 1, Headers),
Expand Down

0 comments on commit 6c1f73c

Please sign in to comment.