diff options
author | Robert Newson <rnewson@apache.org> | 2021-05-11 18:20:20 -0400 |
---|---|---|
committer | Nick Vatamaniuc <vatamane@gmail.com> | 2021-05-12 18:04:53 -0400 |
commit | 2e3286033a5457c5db7f854a68806d274d1f3822 (patch) | |
tree | e433b88b036471d36721efeb75493b35e9140b6e | |
parent | fe7ac3799484c3e484a94620ebb720f9b548cf4c (diff) | |
download | couchdb-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.erl | 3 | ||||
-rw-r--r-- | src/chttpd/test/eunit/chttpd_handlers_tests.erl | 9 | ||||
-rw-r--r-- | src/couch/src/couch_httpd.erl | 17 |
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). |