summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRobert Newson <rnewson@apache.org>2021-05-11 18:20:20 -0400
committerNick Vatamaniuc <vatamane@gmail.com>2021-05-12 18:04:53 -0400
commit2e3286033a5457c5db7f854a68806d274d1f3822 (patch)
treee433b88b036471d36721efeb75493b35e9140b6e
parentfe7ac3799484c3e484a94620ebb720f9b548cf4c (diff)
downloadcouchdb-prevent-multiple-responses-rebased.tar.gz
Ensure no more than one response per requestprevent-multiple-responses-rebased
Due to a retry loop in erlfb:transactional couchdb might try to send multiple http responses for a single request which is clearly an error. This PR ensures the second attempt is prevented, closing the TCP socket instead.
-rw-r--r--src/chttpd/src/chttpd.erl3
-rw-r--r--src/chttpd/test/eunit/chttpd_handlers_tests.erl9
-rw-r--r--src/couch/src/couch_httpd.erl17
3 files changed, 29 insertions, 0 deletions
diff --git a/src/chttpd/src/chttpd.erl b/src/chttpd/src/chttpd.erl
index 53fdf989a..ba26b2b68 100644
--- a/src/chttpd/src/chttpd.erl
+++ b/src/chttpd/src/chttpd.erl
@@ -160,6 +160,7 @@ stop() ->
mochiweb_http:stop(?MODULE).
handle_request(MochiReq0) ->
+ couch_httpd:response_not_started(),
MochiReq = couch_httpd_vhost:dispatch_host(MochiReq0),
handle_request_int(MochiReq).
@@ -1330,8 +1331,10 @@ handle_response(Req0, Code0, Headers0, Args0, Type) ->
respond_(Req1, Code1, Headers1, Args1, Type).
respond_(#httpd{mochi_req = MochiReq}, Code, Headers, _Args, start_response) ->
+ couch_httpd:abort_if_response_already_started(),
MochiReq:start_response({Code, Headers});
respond_(#httpd{mochi_req = MochiReq}, Code, Headers, Args, Type) ->
+ couch_httpd:abort_if_response_already_started(),
MochiReq:Type({Code, Headers, Args}).
get_user(#httpd{user_ctx = #user_ctx{name = null}}) ->
diff --git a/src/chttpd/test/eunit/chttpd_handlers_tests.erl b/src/chttpd/test/eunit/chttpd_handlers_tests.erl
index 649d82e86..489ff6761 100644
--- a/src/chttpd/test/eunit/chttpd_handlers_tests.erl
+++ b/src/chttpd/test/eunit/chttpd_handlers_tests.erl
@@ -63,6 +63,15 @@ should_escape_dbname_on_replicate(Url) ->
end).
+prevent_multiple_http_response_test() ->
+ ?assertEqual(undefined, couch_httpd:abort_if_response_already_started()),
+ ?assertEqual(true, couch_httpd:response_not_started()),
+ ?assertEqual(undefined, couch_httpd:abort_if_response_already_started()),
+ Err = multiple_responses_attempted,
+ ?assertThrow({http_abort, {mochiweb_response, _}, Err},
+ couch_httpd:abort_if_response_already_started()).
+
+
json_value(JSON, Keys) ->
couch_util:get_nested_json_value(JSON, Keys).
diff --git a/src/couch/src/couch_httpd.erl b/src/couch/src/couch_httpd.erl
index fd83c258a..b90d6dae8 100644
--- a/src/couch/src/couch_httpd.erl
+++ b/src/couch/src/couch_httpd.erl
@@ -36,6 +36,7 @@
-export([validate_bind_address/1]).
-export([check_max_request_length/1]).
-export([maybe_decompress/2]).
+-export([response_not_started/0, abort_if_response_already_started/0]).
-define(HANDLER_NAME_IN_MODULE_POS, 6).
-define(MAX_DRAIN_BYTES, 1048576).
@@ -871,6 +872,7 @@ respond_(#httpd{mochi_req = MochiReq} = Req, Code, Headers, Args, Type) ->
end.
http_respond_(#httpd{mochi_req = MochiReq}, Code, Headers, _Args, start_response) ->
+ abort_if_response_already_started(),
MochiReq:start_response({Code, Headers});
http_respond_(#httpd{mochi_req = MochiReq}, 413, Headers, Args, Type) ->
% Special handling for the 413 response. Make sure the socket is closed as
@@ -885,9 +887,24 @@ http_respond_(#httpd{mochi_req = MochiReq}, 413, Headers, Args, Type) ->
mochiweb_socket:recv(Socket, ?MAX_DRAIN_BYTES, ?MAX_DRAIN_TIME_MSEC),
Result;
http_respond_(#httpd{mochi_req = MochiReq}, Code, Headers, Args, Type) ->
+ abort_if_response_already_started(),
MochiReq:Type({Code, Headers, Args}).
+response_not_started() ->
+ erase(http_response_started).
+
+
+abort_if_response_already_started() ->
+ case get(http_response_started) of
+ undefined ->
+ put(http_response_started, true);
+ true ->
+ ErrResp = mochiweb:new_response({nil, 500, []}),
+ throw({http_abort, ErrResp, multiple_responses_attempted})
+ end.
+
+
%%%%%%%% module tests below %%%%%%%%
-ifdef(TEST).