aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAdrian Roe <[email protected]>2013-06-26 18:04:20 +0100
committerLoïc Hoguin <[email protected]>2013-11-08 21:56:37 +0100
commitc2e946708e479d647a81afc8d8f59992f92c6a95 (patch)
treeacfdd8cae6aa88597a11e085e59647967f1045b5
parent9d2096cd35c05ac43fa34ebe4ac31c9265d263cb (diff)
downloadcowboy-c2e946708e479d647a81afc8d8f59992f92c6a95.tar.gz
cowboy-c2e946708e479d647a81afc8d8f59992f92c6a95.tar.bz2
cowboy-c2e946708e479d647a81afc8d8f59992f92c6a95.zip
Add a workaround to disable chunked transfer-encoding
This is an undocumented workaround to disable chunks when using HTTP/1.1. It can be used when the client advertises itself as HTTP/1.1 despite not understanding the chunked transfer-encoding. Usage can be found looking at the test for it. When activated, Cowboy will still advertise itself as HTTP/1.1, but will send the body the same way it would if it was HTTP/1.0.
-rw-r--r--src/cowboy_req.erl53
-rw-r--r--test/http_SUITE.erl14
-rw-r--r--test/http_SUITE_data/http_streamed.erl20
3 files changed, 67 insertions, 20 deletions
diff --git a/src/cowboy_req.erl b/src/cowboy_req.erl
index 452d390..539d961 100644
--- a/src/cowboy_req.erl
+++ b/src/cowboy_req.erl
@@ -164,7 +164,8 @@
%% Response.
resp_compress = false :: boolean(),
- resp_state = waiting :: locked | waiting | chunks | done,
+ resp_state = waiting :: locked | waiting | waiting_stream
+ | chunks | stream | done,
resp_headers = [] :: cowboy:http_headers(),
resp_body = <<>> :: iodata() | resp_body_fun()
| {non_neg_integer(), resp_body_fun()}
@@ -946,7 +947,8 @@ reply(Status, Headers, Body, Req=#http_req{
socket=Socket, transport=Transport,
version=Version, connection=Connection,
method=Method, resp_compress=Compress,
- resp_state=waiting, resp_headers=RespHeaders}) ->
+ resp_state=RespState, resp_headers=RespHeaders})
+ when RespState =:= waiting; RespState =:= waiting_stream ->
HTTP11Headers = if
Transport =/= cowboy_spdy, Version =:= 'HTTP/1.1' ->
[{<<"connection">>, atom_to_connection(Connection)}];
@@ -982,10 +984,12 @@ reply(Status, Headers, Body, Req=#http_req{
if RespType =/= hook, Method =/= <<"HEAD">> ->
ChunkFun = fun(IoData) -> chunk(IoData, Req2) end,
BodyFun(ChunkFun),
- %% Terminate the chunked body for HTTP/1.1 only.
- case Version of
- 'HTTP/1.0' -> Req2;
- _ -> last_chunk(Req2)
+ %% Send the last chunk if chunked encoding was used.
+ if
+ Version =:= 'HTTP/1.0'; RespState =:= waiting_stream ->
+ Req2;
+ true ->
+ last_chunk(Req2)
end;
true -> Req2
end;
@@ -1086,7 +1090,7 @@ chunk(Data, #http_req{socket=Socket, transport=cowboy_spdy,
resp_state=chunks}) ->
cowboy_spdy:stream_data(Socket, Data);
chunk(Data, #http_req{socket=Socket, transport=Transport,
- resp_state=chunks, version='HTTP/1.0'}) ->
+ resp_state=stream}) ->
Transport:send(Socket, Data);
chunk(Data, #http_req{socket=Socket, transport=Transport,
resp_state=chunks}) ->
@@ -1136,16 +1140,17 @@ ensure_response(#http_req{resp_state=done}, _) ->
ok;
%% No response has been sent but everything apparently went fine.
%% Reply with the status code found in the second argument.
-ensure_response(Req=#http_req{resp_state=waiting}, Status) ->
+ensure_response(Req=#http_req{resp_state=RespState}, Status)
+ when RespState =:= waiting; RespState =:= waiting_stream ->
_ = reply(Status, [], [], Req),
ok;
%% Terminate the chunked body for HTTP/1.1 only.
-ensure_response(#http_req{method= <<"HEAD">>, resp_state=chunks}, _) ->
- ok;
-ensure_response(#http_req{version='HTTP/1.0', resp_state=chunks}, _) ->
+ensure_response(#http_req{method= <<"HEAD">>}, _) ->
ok;
ensure_response(Req=#http_req{resp_state=chunks}, _) ->
_ = last_chunk(Req),
+ ok;
+ensure_response(#http_req{}, _) ->
ok.
%% Private setter/getter API.
@@ -1269,19 +1274,27 @@ chunked_response(Status, Headers, Req=#http_req{
resp_headers=[], resp_body= <<>>}};
chunked_response(Status, Headers, Req=#http_req{
version=Version, connection=Connection,
- resp_state=waiting, resp_headers=RespHeaders}) ->
+ resp_state=RespState, resp_headers=RespHeaders})
+ when RespState =:= waiting; RespState =:= waiting_stream ->
RespConn = response_connection(Headers, Connection),
- HTTP11Headers = case Version of
- 'HTTP/1.1' -> [
- {<<"connection">>, atom_to_connection(Connection)},
- {<<"transfer-encoding">>, <<"chunked">>}];
- _ -> []
+ HTTP11Headers = if
+ Version =:= 'HTTP/1.0' -> [];
+ true ->
+ MaybeTE = if
+ RespState =:= waiting_stream -> [];
+ true -> [{<<"transfer-encoding">>, <<"chunked">>}]
+ end,
+ [{<<"connection">>, atom_to_connection(Connection)}|MaybeTE]
+ end,
+ RespState2 = if
+ Version =:= 'HTTP/1.1', RespState =:= 'waiting' -> chunks;
+ true -> stream
end,
{RespType, Req2} = response(Status, Headers, RespHeaders, [
{<<"date">>, cowboy_clock:rfc1123()},
{<<"server">>, <<"Cowboy">>}
|HTTP11Headers], <<>>, Req),
- {RespType, Req2#http_req{connection=RespConn, resp_state=chunks,
+ {RespType, Req2#http_req{connection=RespConn, resp_state=RespState2,
resp_headers=[], resp_body= <<>>}}.
-spec response(cowboy:http_status(), cowboy:http_headers(),
@@ -1313,7 +1326,7 @@ response(Status, Headers, RespHeaders, DefaultHeaders, Body, Req=#http_req{
cowboy_spdy:reply(Socket, status(Status), FullHeaders, Body),
ReqPid ! {?MODULE, resp_sent},
normal;
- waiting ->
+ RespState when RespState =:= waiting; RespState =:= waiting_stream ->
HTTPVer = atom_to_binary(Version, latin1),
StatusLine = << HTTPVer/binary, " ",
(status(Status))/binary, "\r\n" >>,
@@ -1361,7 +1374,7 @@ response_merge_headers(Headers, RespHeaders, DefaultHeaders) ->
merge_headers(Headers, []) ->
Headers;
merge_headers(Headers, [{<<"set-cookie">>, Value}|Tail]) ->
- merge_headers([{<<"set-cookie">>, Value}|Headers], Tail);
+ merge_headers([{<<"set-cookie">>, Value}|Headers], Tail);
merge_headers(Headers, [{Name, Value}|Tail]) ->
Headers2 = case lists:keymember(Name, 1, Headers) of
true -> Headers;
diff --git a/test/http_SUITE.erl b/test/http_SUITE.erl
index 28849fc..f0196ec 100644
--- a/test/http_SUITE.erl
+++ b/test/http_SUITE.erl
@@ -90,6 +90,7 @@
-export([stream_body_set_resp_close/1]).
-export([stream_body_set_resp_chunked/1]).
-export([stream_body_set_resp_chunked10/1]).
+-export([streamed_response/1]).
-export([te_chunked/1]).
-export([te_chunked_chopped/1]).
-export([te_chunked_delayed/1]).
@@ -167,6 +168,7 @@ groups() ->
stream_body_set_resp_close,
stream_body_set_resp_chunked,
stream_body_set_resp_chunked10,
+ streamed_response,
te_chunked,
te_chunked_chopped,
te_chunked_delayed,
@@ -352,6 +354,7 @@ init_dispatch(Config) ->
cowboy_router:compile([
{"localhost", [
{"/chunked_response", http_chunked, []},
+ {"/streamed_response", http_streamed, []},
{"/init_shutdown", http_init_shutdown, []},
{"/long_polling", http_long_polling, []},
{"/headers/dupe", http_handler,
@@ -1285,6 +1288,17 @@ stream_body_set_resp_chunked10(Config) ->
end,
{error, closed} = Transport:recv(Socket, 0, 1000).
+streamed_response(Config) ->
+ Client = ?config(client, Config),
+ {ok, Client2} = cowboy_client:request(<<"GET">>,
+ build_url("/streamed_response", Config), Client),
+ {ok, 200, Headers, Client3} = cowboy_client:response(Client2),
+ false = lists:keymember(<<"transfer-encoding">>, 1, Headers),
+ {ok, Transport, Socket} = cowboy_client:transport(Client3),
+ {ok, <<"streamed_handler\r\nworks fine!">>}
+ = Transport:recv(Socket, 29, 1000),
+ {error, closed} = cowboy_client:response(Client3).
+
te_chunked(Config) ->
Client = ?config(client, Config),
Body = list_to_binary(io_lib:format("~p", [lists:seq(1, 100)])),
diff --git a/test/http_SUITE_data/http_streamed.erl b/test/http_SUITE_data/http_streamed.erl
new file mode 100644
index 0000000..674cc40
--- /dev/null
+++ b/test/http_SUITE_data/http_streamed.erl
@@ -0,0 +1,20 @@
+%% Feel free to use, reuse and abuse the code in this file.
+
+-module(http_streamed).
+-behaviour(cowboy_http_handler).
+-export([init/3, handle/2, terminate/3]).
+
+init({_Transport, http}, Req, _Opts) ->
+ {ok, Req, undefined}.
+
+handle(Req, State) ->
+ Req2 = cowboy_req:set([{resp_state, waiting_stream}], Req),
+ {ok, Req3} = cowboy_req:chunked_reply(200, Req2),
+ timer:sleep(100),
+ cowboy_req:chunk("streamed_handler\r\n", Req3),
+ timer:sleep(100),
+ cowboy_req:chunk("works fine!", Req3),
+ {ok, Req3, State}.
+
+terminate(_, _, _) ->
+ ok.