Browse Source

Add cowboy_http_req:port/1.

Returns the port given in the Host header if present,
otherwise the default port of 443 for HTTPS and 80 for HTTP
is returned.
Loïc Hoguin 14 years ago
parent
commit
6c1f73c53c

+ 1 - 0
include/http.hrl

@@ -50,6 +50,7 @@
 	peer       = undefined :: undefined | {Address::ip_address(), Port::ip_port()},
 	peer       = undefined :: undefined | {Address::ip_address(), Port::ip_port()},
 	host       = undefined :: undefined | cowboy_dispatcher:path_tokens(),
 	host       = undefined :: undefined | cowboy_dispatcher:path_tokens(),
 	raw_host   = undefined :: undefined | string(),
 	raw_host   = undefined :: undefined | string(),
+	port       = undefined :: undefined | ip_port(),
 	path       = undefined :: undefined | '*' | cowboy_dispatcher:path_tokens(),
 	path       = undefined :: undefined | '*' | cowboy_dispatcher:path_tokens(),
 	raw_path   = undefined :: undefined | string(),
 	raw_path   = undefined :: undefined | string(),
 	qs_vals    = undefined :: undefined | list({Name::string(), Value::string() | true}),
 	qs_vals    = undefined :: undefined | list({Name::string(), Value::string() | true}),

+ 37 - 15
src/cowboy_dispatcher.erl

@@ -24,17 +24,20 @@
 
 
 -export_type([bindings/0, path_tokens/0, dispatch_rules/0]).
 -export_type([bindings/0, path_tokens/0, dispatch_rules/0]).
 
 
+-include_lib("kernel/include/inet.hrl").
 -include_lib("eunit/include/eunit.hrl").
 -include_lib("eunit/include/eunit.hrl").
 
 
 %% API.
 %% 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) ->
 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())
 -spec split_path(Path::string())
 	-> {Tokens::path_tokens(), Path::string(), Qs::string()}.
 	-> {Tokens::path_tokens(), Path::string(), Qs::string()}.
@@ -119,19 +122,38 @@ list_match([], [], Binds) ->
 split_host_test_() ->
 split_host_test_() ->
 	%% {Host, Result}
 	%% {Host, Result}
 	Tests = [
 	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"],
+			 "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].
 	[{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_() ->
 split_path_test_() ->
 	%% {Path, Result, QueryString}
 	%% {Path, Result, QueryString}
 	Tests = [
 	Tests = [

+ 31 - 17
src/cowboy_http_protocol.erl

@@ -89,8 +89,6 @@ request({http_error, _Any}, State) ->
 	error_terminate(400, State).
 	error_terminate(400, State).
 
 
 -spec wait_header(Req::#http_req{}, State::#state{}) -> ok.
 -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,
 wait_header(Req, State=#state{socket=Socket,
 		transport=Transport, timeout=T}) ->
 		transport=Transport, timeout=T}) ->
 	case Transport:recv(Socket, 0, T) of
 	case Transport:recv(Socket, 0, T) of
@@ -101,22 +99,19 @@ wait_header(Req, State=#state{socket=Socket,
 
 
 -spec header({http_header, I::integer(), Field::http_header(), R::term(),
 -spec header({http_header, I::integer(), Field::http_header(), R::term(),
 	Value::string()} | http_eoh, Req::#http_req{}, State::#state{}) -> ok.
 	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),
 	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;
 	end;
 %% Ignore Host headers if we already have it.
 %% Ignore Host headers if we already have it.
 header({http_header, _I, 'Host', _R, _V}, Req, State) ->
 header({http_header, _I, 'Host', _R, _V}, Req, State) ->
@@ -137,6 +132,21 @@ header(http_eoh, Req, State) ->
 header({http_error, _String}, _Req, State) ->
 header({http_error, _String}, _Req, State) ->
 	error_terminate(500, 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.
 -spec handler_init(Req::#http_req{}, State::#state{}) -> ok.
 handler_init(Req, State=#state{
 handler_init(Req, State=#state{
 		transport=Transport, handler={Handler, Opts}}) ->
 		transport=Transport, handler={Handler, Opts}}) ->
@@ -227,6 +237,10 @@ connection_to_atom(Connection) ->
 		_Any -> keepalive
 		_Any -> keepalive
 	end.
 	end.
 
 
+-spec default_port(TransportName::atom()) -> 80 | 443.
+default_port(ssl) -> 443;
+default_port(_) -> 80.
+
 %% More efficient implementation of string:to_lower.
 %% More efficient implementation of string:to_lower.
 %% We are excluding a few characters on purpose.
 %% We are excluding a few characters on purpose.
 -spec string_to_lower(string()) -> string().
 -spec string_to_lower(string()) -> string().

+ 5 - 1
src/cowboy_http_req.erl

@@ -17,7 +17,7 @@
 
 
 -export([
 -export([
 	method/1, version/1, peer/1,
 	method/1, version/1, peer/1,
-	host/1, raw_host/1,
+	host/1, raw_host/1, port/1,
 	path/1, raw_path/1,
 	path/1, raw_path/1,
 	qs_val/2, qs_val/3, qs_vals/1, raw_qs/1,
 	qs_val/2, qs_val/3, qs_vals/1, raw_qs/1,
 	binding/2, binding/3, bindings/1,
 	binding/2, binding/3, bindings/1,
@@ -63,6 +63,10 @@ host(Req) ->
 raw_host(Req) ->
 raw_host(Req) ->
 	{Req#http_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{})
 -spec path(Req::#http_req{})
 	-> {Path::cowboy_dispatcher:path_tokens(), Req::#http_req{}}.
 	-> {Path::cowboy_dispatcher:path_tokens(), Req::#http_req{}}.
 path(Req) ->
 path(Req) ->

+ 9 - 9
src/cowboy_http_websocket.erl

@@ -81,9 +81,9 @@ upgrade_error(Req=#http_req{socket=Socket, transport=Transport}) ->
 -spec websocket_handshake(State::#state{}, Req::#http_req{},
 -spec websocket_handshake(State::#state{}, Req::#http_req{},
 	HandlerState::term()) -> ok.
 	HandlerState::term()) -> ok.
 websocket_handshake(State=#state{origin=Origin, challenge=Challenge},
 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(
 	{ok, Req2} = cowboy_http_req:reply(
 		"101 WebSocket Protocol Handshake",
 		"101 WebSocket Protocol Handshake",
 		[{"Connection", "Upgrade"},
 		[{"Connection", "Upgrade"},
@@ -94,12 +94,12 @@ websocket_handshake(State=#state{origin=Origin, challenge=Challenge},
 	handler_loop(State#state{messages=Transport:messages()},
 	handler_loop(State#state{messages=Transport:messages()},
 		Req2, HandlerState, <<>>).
 		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{},
 -spec handler_loop(State::#state{}, Req::#http_req{},
 	HandlerState::term(), SoFar::binary()) -> ok.
 	HandlerState::term(), SoFar::binary()) -> ok.

+ 1 - 1
test/http_SUITE.erl

@@ -199,7 +199,7 @@ websocket(Config) ->
 	[Headers, Body] = websocket_headers(erlang:decode_packet(httph, Rest, []), []),
 	[Headers, Body] = websocket_headers(erlang:decode_packet(httph, Rest, []), []),
 	{'Connection', "Upgrade"} = lists:keyfind('Connection', 1, Headers),
 	{'Connection', "Upgrade"} = lists:keyfind('Connection', 1, Headers),
 	{'Upgrade', "WebSocket"} = lists:keyfind('Upgrade', 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),
 		= lists:keyfind("sec-websocket-location", 1, Headers),
 	{"sec-websocket-origin", "http://localhost"}
 	{"sec-websocket-origin", "http://localhost"}
 		= lists:keyfind("sec-websocket-origin", 1, Headers),
 		= lists:keyfind("sec-websocket-origin", 1, Headers),