-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Support for streamed response from chunked_reply when writing to non-compliant clients #548
Changes from 1 commit
0a0549b
e7e24d2
d0e6e38
8811750
ec4fc56
9efafb0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -951,7 +951,10 @@ 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_streaming -> | ||
|
||
HTTP11Headers = if | ||
Transport =/= cowboy_spdy, Version =:= 'HTTP/1.1' -> | ||
[{<<"connection">>, atom_to_connection(Connection)}]; | ||
|
@@ -1090,8 +1093,10 @@ chunk(_Data, #http_req{method= <<"HEAD">>}) -> | |
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'}) -> | ||
chunk(Data, #http_req{socket=Socket, transport=Transport, | ||
version=Version, resp_state=RespState}) | ||
when Version=:='HTTP/1.0'; | ||
RespState=:=streamed -> | ||
Transport:send(Socket, Data); | ||
chunk(Data, #http_req{socket=Socket, transport=Transport, | ||
resp_state=chunks}) -> | ||
|
@@ -1128,17 +1133,19 @@ 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; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't understand why you changed this. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I made this change because once the method is HEAD it doesn't matter what the resp_state is - we just need to return ok. Alternately we could add multiple cases (resp_state=chunks, resp_state=streamed…) but that is more brittle and (for me at least) shows the intent less well. I've changed (removed!) the documentation and am fighting with tab etc (very different defaults on our editors!) Should have a new push in the next hour or so Dr Adrian Roe On Saturday, 29 June 2013 at 18:46, Loïc Hoguin wrote:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No I mean HTTP/1.0. You make it return the last chunk, except HTTP/1.0 doesn't do chunks so it must not be sent. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Because you can only get a resp_state of chunks if you are HTTP1.1 - if it's HTTP1.0 I set the resp_state to streamed By the way, by git skills are limited and I forgot to branch my fork when making the changes so suspect I'll need to re-merge from a fresh pull :( Apologies if you end up with another pull request! Thanks AdrianDr Adrian Roe On Friday, 12 July 2013 at 21:02, Loïc Hoguin wrote:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. OK I'll have to look again then to make sure I'm not missing anything. Don't worry about the branch yet. |
||
RespState =:= waiting_streaming -> | ||
_ = 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. | ||
ok; | ||
ensure_response(#http_req{}, _) -> | ||
ok. | ||
|
||
%% Private setter/getter API. | ||
|
||
|
@@ -1261,19 +1268,25 @@ 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_streaming -> | ||
RespConn = response_connection(Headers, Connection), | ||
HTTP11Headers = case Version of | ||
'HTTP/1.1' -> [ | ||
{<<"connection">>, atom_to_connection(Connection)}, | ||
{<<"transfer-encoding">>, <<"chunked">>}]; | ||
_ -> [] | ||
{HTTP11Headers, NewRespState} = case {Version, RespState} of | ||
{'HTTP/1.1', waiting} -> {[ | ||
{<<"connection">>, atom_to_connection(Connection)}, | ||
{<<"transfer-encoding">>, <<"chunked">>}], | ||
chunks}; | ||
{'HTTP/1.1', waiting_streaming} -> {[ | ||
{<<"connection">>, atom_to_connection(Connection)}], | ||
streamed}; | ||
_ -> {[], streamed} | ||
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=NewRespState, | ||
resp_headers=[], resp_body= <<>>}}. | ||
|
||
-spec response(cowboy:http_status(), cowboy:http_headers(), | ||
|
@@ -1296,6 +1309,7 @@ response(Status, Headers, RespHeaders, DefaultHeaders, Body, Req=#http_req{ | |
Req#http_req{resp_headers=[], resp_body= <<>>, | ||
onresponse=already_called}) | ||
end, | ||
|
||
ReplyType = case Req2#http_req.resp_state of | ||
waiting when Transport =:= cowboy_spdy, Body =:= stream -> | ||
cowboy_spdy:stream_reply(Socket, status(Status), FullHeaders), | ||
|
@@ -1305,7 +1319,8 @@ response(Status, Headers, RespHeaders, DefaultHeaders, Body, Req=#http_req{ | |
cowboy_spdy:reply(Socket, status(Status), FullHeaders, Body), | ||
ReqPid ! {?MODULE, resp_sent}, | ||
normal; | ||
waiting -> | ||
WaitingState when WaitingState =:= waiting; | ||
WaitingState =:= waiting_streaming -> | ||
HTTPVer = atom_to_binary(Version, latin1), | ||
StatusLine = << HTTPVer/binary, " ", | ||
(status(Status))/binary, "\r\n" >>, | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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_streaming}], 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. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No we don't want it documented. :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Reasonably sure :) waiting_stream is set in the handler using cowboy_req:set- see test/http_SUITE_data/http_streamed.erl for an example (copied below) - it's called in the streamed_response test in the http_SUITE
%% 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.
Dr Adrian Roe
Director
On Saturday, 29 June 2013 at 18:45, Loïc Hoguin wrote: