Permalink
Browse files

Handle non-chunked responses without content-length in yaws_revproxy

In that case, the revproxy must read until the backend closes.
Add a test for this new functionality
  • Loading branch information...
1 parent 32bdfb0 commit 9a95602dbad3094634c6bbae42a83e6b46a8df95 @capflam capflam committed with vinoski Jun 11, 2012
Showing with 71 additions and 6 deletions.
  1. +41 −4 src/yaws_revproxy.erl
  2. +1 −1 test/conf/revproxy.conf
  3. +1 −1 test/t4/Makefile
  4. +13 −0 test/t4/app_test.erl
  5. +15 −0 test/t4/nolengthtest.erl
View
@@ -220,7 +220,9 @@ out(#arg{state=RPState}=Arg) when RPState#revproxy.state == recvheaders ->
[]),
RPState1#revproxy{state=recvchunk};
_ ->
- RPState1#revproxy{state=terminate}
+ RPState1#revproxy{cliconn_status="close",
+ srvconn_status="close",
+ state=recvcontent}
end;
_ ->
RPState1#revproxy{state=recvcontent}
@@ -242,10 +244,13 @@ out(#arg{state=RPState}=Arg) when RPState#revproxy.state == recvheaders ->
%% The response content is not chunked.
out(#arg{state=RPState}=Arg) when RPState#revproxy.state == recvcontent ->
- Len = list_to_integer((RPState#revproxy.headers)#headers.content_length),
+ Len = case (RPState#revproxy.headers)#headers.content_length of
+ undefined -> undefined;
+ CLen -> list_to_integer(CLen)
+ end,
SC=get(sc),
if
- Len =< SC#sconf.partial_post_size ->
+ is_integer(Len) andalso Len =< SC#sconf.partial_post_size ->
case read(RPState, Len) of
{ok, Data} ->
?Debug("Response content received from the backend server : "
@@ -262,14 +267,23 @@ out(#arg{state=RPState}=Arg) when RPState#revproxy.state == recvcontent ->
end,
outXXX(500, Arg)
end;
- true ->
+
+ is_integer(Len) ->
+ %% Here partial_post_size is always an integer
BlockSize = SC#sconf.partial_post_size,
BlockCount = Len div BlockSize,
LastBlock = Len rem BlockSize,
SrvData = {block, BlockCount, BlockSize, LastBlock},
RPState1 = RPState#revproxy{state = terminate,
is_chunked = true,
srvdata = SrvData},
+ out(Arg#arg{state=RPState1});
+
+ true ->
+ SrvData = {block, undefined, undefined, undefined},
+ RPState1 = RPState#revproxy{state = terminate,
+ is_chunked = true,
+ srvdata = SrvData},
out(Arg#arg{state=RPState1})
end;
@@ -407,6 +421,24 @@ recv_next_chunk(YawsPid, #arg{state=RPState}=Arg) ->
%%==========================================================================
%% This function reads blocks from the server and streams them to the client.
+recv_blocks(YawsPid, #arg{state=RPState}=Arg, undefined, undefined, undefined) ->
+ case read(RPState) of
+ {ok, <<>>} ->
+ %% no data, wait 100 msec to avoid time-consuming loop and retry
+ timer:sleep(100),
+ recv_blocks(YawsPid, Arg, undefined, undefined, undefined);
+ {ok, Data} ->
+ ?Debug("Response content received from the backend server : "
+ "~p bytes~n", [size(Data)]),
+ ok = yaws_api:stream_process_deliver(Arg#arg.clisock, Data),
+ recv_blocks(YawsPid, Arg, undefined, undefined, undefined);
+ {error, closed} ->
+ yaws_api:stream_process_end(closed, YawsPid);
+ {error, _Reason} ->
+ ?Debug("TCP error: ~p~n", [_Reason]),
+ yaws_api:stream_process_end(closed, YawsPid),
+ close(RPState)
+ end;
recv_blocks(YawsPid, #arg{state=RPState}=Arg, 0, _, 0) ->
yaws_api:stream_process_end(Arg#arg.clisock, YawsPid),
case RPState#revproxy.srvconn_status of
@@ -536,6 +568,11 @@ send(#revproxy{srvsock=Sock, type=ssl}, Data) ->
send(#revproxy{srvsock=Sock, type=nossl}, Data) ->
gen_tcp:send(Sock, Data).
+
+read(#revproxy{srvsock=Sock, type=Type}) ->
+ yaws:setopts(Sock, [{packet, raw}, binary], Type),
+ yaws:do_recv(Sock, 0, Type).
+
read(RPState, Len) ->
yaws:setopts(RPState#revproxy.srvsock, [{packet, raw}, binary],
RPState#revproxy.type),
View
@@ -118,7 +118,7 @@ use_fdsrv = false
listen_backlog = 512
deflate = true
partial_post_size = 2048000
- appmods = posttest streamtest
+ appmods = posttest streamtest nolengthtest
docroot = %YTOP%/test/t4/www1
</server>
View
@@ -3,7 +3,7 @@ include ../support/include.mk
.PHONY: all test debug clean
#
-all: conf setup app_test.beam rewritetest.beam posttest.beam streamtest.beam
+all: conf setup app_test.beam rewritetest.beam posttest.beam streamtest.beam nolengthtest.beam
@echo "all ok"
View
@@ -28,6 +28,7 @@ start() ->
keepalive_revproxy_test(),
rewrite_revproxy_test(),
large_content_revproxy_test(),
+ no_content_length_revproxy_test(),
fwdproxy_test(),
ok
catch
@@ -207,6 +208,18 @@ large_content_revproxy_test() ->
?line Bin = Body,
ok.
+no_content_length_revproxy_test() ->
+ io:format("no_content_length_revproxy_test\n", []),
+ Uri = "http://localhost:8001/revproxy1/nolengthtest",
+ Res = lists:duplicate(512, $A),
+
+ ?line {ok, "200", Hdrs, Body} = ibrowse:send_req(Uri, [], get),
+ ?line undefined = proplists:get_value("Content-Length", Hdrs),
+ ?line undefined = proplists:get_value("Transfer-Encoding", Hdrs),
+ ?line "close" = proplists:get_value("Connection", Hdrs),
+ ?line Res = Body,
+ ok.
+
fwdproxy_test() ->
io:format("fwdproxy_test\n", []),
Uri1 = "http://localhost:8001/rewrite/hello.txt",
View
@@ -0,0 +1,15 @@
+-module(nolengthtest).
+-export([out/1]).
+
+-include("../../include/yaws.hrl").
+-include("../../include/yaws_api.hrl").
+
+out(_Arg) ->
+ yaws_api:stream_chunk_deliver(self(), lists:duplicate(256, $A)),
+ yaws_api:stream_chunk_end(self()),
+ [
+ {header, {transfer_encoding, erase}},
+ {header, {content_length, erase}},
+ {header, {connection, "close"}},
+ {streamcontent, "text/plain", lists:duplicate(256, $A)}
+ ].

0 comments on commit 9a95602

Please sign in to comment.