aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLoïc Hoguin <[email protected]>2015-03-29 20:29:31 +0200
committerLoïc Hoguin <[email protected]>2015-03-29 20:29:31 +0200
commit6068736c62bae081c338349feca311cfd5443ded (patch)
tree96d55ccafb748f5010114bc7a4a7bd15d984ea2e
parentc216566ddc60670334bdcf54617e6daedfdd1928 (diff)
downloadgun-6068736c62bae081c338349feca311cfd5443ded.tar.gz
gun-6068736c62bae081c338349feca311cfd5443ded.tar.bz2
gun-6068736c62bae081c338349feca311cfd5443ded.zip
Improve request code
The content-type and content-length alone indicate whether a body is present for request/4. The host header can now be overriden. A number of headers like transfer-encoding are automatically deleted from the list of headers we receive. SPDY also deletes connection, keep-alive and proxy-connection. SPDY now sends the port in the :host header.
-rw-r--r--src/gun_http.erl44
-rw-r--r--src/gun_spdy.erl30
2 files changed, 51 insertions, 23 deletions
diff --git a/src/gun_http.erl b/src/gun_http.erl
index d29ca2d..620f8ae 100644
--- a/src/gun_http.erl
+++ b/src/gun_http.erl
@@ -135,7 +135,7 @@ handle_head(Data, State=#http_state{owner=Owner, version=ClientVersion,
{101, {websocket, _, WsKey, WsExtensions, WsProtocols, WsOpts}} ->
ws_handshake(Rest2, State, Headers, WsKey, WsExtensions, WsProtocols, WsOpts);
_ ->
- In = io_from_headers(Version, Headers),
+ In = response_io_from_headers(Version, Headers),
IsFin = case In of head -> fin; _ -> nofin end,
case IsAlive of
false ->
@@ -153,7 +153,7 @@ handle_head(Data, State=#http_state{owner=Owner, version=ClientVersion,
Conn =:= close -> close;
Version =:= 'HTTP/1.0' -> close;
ClientVersion =:= 'HTTP/1.0' -> close;
- true -> conn_from_headers(Headers)
+ true -> conn_from_headers(Version, Headers)
end,
%% We always reset in_state even if not chunked.
if
@@ -200,18 +200,14 @@ keepalive(State) ->
request(State=#http_state{socket=Socket, transport=Transport, version=Version,
out=head}, StreamRef, Method, Host, Port, Path, Headers) ->
- Headers2 = case Version of
- 'HTTP/1.0' -> lists:keydelete(<<"transfer-encoding">>, 1, Headers);
- 'HTTP/1.1' -> Headers
- end,
+ Headers2 = lists:keydelete(<<"transfer-encoding">>, 1, Headers),
Headers3 = case lists:keymember(<<"host">>, 1, Headers) of
false -> [{<<"host">>, [Host, $:, integer_to_binary(Port)]}|Headers2];
true -> Headers2
end,
%% We use Headers2 because this is the smallest list.
- Conn = conn_from_headers(Headers2),
- %% @todo This should probably also check for content-type like SPDY.
- Out = io_from_headers(Version, Headers2),
+ Conn = conn_from_headers(Version, Headers2),
+ Out = request_io_from_headers(Headers2),
Transport:send(Socket, cow_http:request(Method, Path, Version, Headers3)),
new_stream(State#http_state{connection=Conn, out=Out}, StreamRef).
@@ -224,10 +220,10 @@ request(State=#http_state{socket=Socket, transport=Transport, version=Version,
true -> Headers2
end,
%% We use Headers2 because this is the smallest list.
- Conn = conn_from_headers(Headers2),
+ Conn = conn_from_headers(Version, Headers2),
Transport:send(Socket, [
cow_http:request(Method, Path, Version, [
- {<<"content-length">>, integer_to_list(iolist_size(Body))}
+ {<<"content-length">>, integer_to_binary(iolist_size(Body))}
|Headers3]),
Body]),
new_stream(State#http_state{connection=Conn}, StreamRef).
@@ -296,8 +292,10 @@ error_stream_not_found(State=#http_state{owner=Owner}) ->
%% Headers information retrieval.
-conn_from_headers(Headers) ->
+conn_from_headers(Version, Headers) ->
case lists:keyfind(<<"connection">>, 1, Headers) of
+ false when Version =:= 'HTTP/1.0' ->
+ close;
false ->
keepalive;
{_, ConnHd} ->
@@ -308,7 +306,20 @@ conn_from_headers(Headers) ->
end
end.
-io_from_headers(Version, Headers) ->
+request_io_from_headers(Headers) ->
+ case lists:keyfind(<<"content-length">>, 1, Headers) of
+ {_, <<"0">>} ->
+ head;
+ {_, Length} ->
+ {body, cow_http_hd:parse_content_length(Length)};
+ _ ->
+ case lists:keymember(<<"content-type">>, 1, Headers) of
+ true -> body_chunked;
+ false -> head
+ end
+ end.
+
+response_io_from_headers(Version, Headers) ->
case lists:keyfind(<<"content-length">>, 1, Headers) of
{_, <<"0">>} ->
head;
@@ -369,9 +380,12 @@ ws_upgrade(State=#http_state{socket=Socket, transport=Transport, out=head},
{<<"sec-websocket-key">>, Key}
|ExtHeaders
],
+ IsSecure = Transport:secure(),
Headers3 = case lists:keymember(<<"host">>, 1, Headers) of
- false -> [{<<"host">>, [Host, $:, integer_to_binary(Port)]}|Headers2];
- true -> Headers2
+ true -> Headers2;
+ false when Port =:= 80, not IsSecure -> [{<<"host">>, Host}|Headers2];
+ false when Port =:= 443, IsSecure -> [{<<"host">>, Host}|Headers2];
+ false -> [{<<"host">>, [Host, $:, integer_to_binary(Port)]}|Headers2]
end,
Transport:send(Socket, cow_http:request(<<"GET">>, Path, 'HTTP/1.1', Headers3)),
new_stream(State#http_state{connection=keepalive, out=head},
diff --git a/src/gun_spdy.erl b/src/gun_spdy.erl
index fc2fb1b..7651584 100644
--- a/src/gun_spdy.erl
+++ b/src/gun_spdy.erl
@@ -189,30 +189,44 @@ keepalive(State=#spdy_state{socket=Socket, transport=Transport,
Transport:send(Socket, cow_spdy:ping(PingID)),
State#spdy_state{ping_id=PingID + 2}.
-%% @todo Allow overriding the host when doing requests.
+%% @todo Always https scheme?
request(State=#spdy_state{socket=Socket, transport=Transport, zdef=Zdef,
- stream_id=StreamID}, StreamRef, Method, Host, _Port, Path, Headers) ->
- Out = false =/= lists:keyfind(<<"content-type">>, 1, Headers),
+ stream_id=StreamID}, StreamRef, Method, Host, Port, Path, Headers) ->
+ {Host2, Headers2} = prepare_request(Headers, Host, Port),
+ Out = (false =/= lists:keyfind(<<"content-type">>, 1, Headers2))
+ orelse (false =/= lists:keyfind(<<"content-length">>, 1, Headers2)),
Transport:send(Socket, cow_spdy:syn_stream(Zdef,
StreamID, 0, not Out, false, 0,
- Method, <<"https">>, Host, Path, <<"HTTP/1.1">>, Headers)),
+ Method, <<"https">>, Host2, Path, <<"HTTP/1.1">>, Headers2)),
new_stream(StreamID, StreamRef, true, Out, <<"HTTP/1.1">>,
State#spdy_state{stream_id=StreamID + 2}).
%% @todo Handle Body > 16MB. (split it out into many frames)
+%% @todo Always https scheme?
request(State=#spdy_state{socket=Socket, transport=Transport, zdef=Zdef,
- stream_id=StreamID}, StreamRef, Method, Host, _Port, Path, Headers, Body) ->
- Headers2 = lists:keystore(<<"content-length">>, 1, Headers,
- {<<"content-length">>, integer_to_list(iolist_size(Body))}),
+ stream_id=StreamID}, StreamRef, Method, Host, Port, Path, Headers, Body) ->
+ {Host2, Headers2} = prepare_request(Headers, Host, Port),
+ Headers3 = lists:keystore(<<"content-length">>, 1, Headers2,
+ {<<"content-length">>, integer_to_binary(iolist_size(Body))}),
Transport:send(Socket, [
cow_spdy:syn_stream(Zdef,
StreamID, 0, false, false, 0,
- Method, <<"https">>, Host, Path, <<"HTTP/1.1">>, Headers2),
+ Method, <<"https">>, Host2, Path, <<"HTTP/1.1">>, Headers3),
cow_spdy:data(StreamID, true, Body)
]),
new_stream(StreamID, StreamRef, true, false, <<"HTTP/1.1">>,
State#spdy_state{stream_id=StreamID + 2}).
+prepare_request(Headers, Host, Port) ->
+ Headers2 = lists:keydelete(<<"keep-alive">>, 1,
+ lists:keydelete(<<"proxy-connection">>, 1,
+ lists:keydelete(<<"transfer-encoding">>, 1,
+ lists:keydelete(<<"connection">>, 1, Headers)))),
+ case lists:keytake(<<"host">>, 1, Headers2) of
+ false -> {[Host, $:, integer_to_binary(Port)], Headers2};
+ {value, {_, Host1}, Headers3} -> {Host1, Headers3}
+ end.
+
data(State=#spdy_state{socket=Socket, transport=Transport},
StreamRef, IsFin, Data) ->
case get_stream_by_ref(StreamRef, State) of