diff options
author | Robert Newson <rnewson@apache.org> | 2020-11-11 17:19:02 +0000 |
---|---|---|
committer | Robert Newson <rnewson@apache.org> | 2020-11-12 12:37:52 +0000 |
commit | 51d522ceb8dc029680d7d2305ef2da3c263d42a0 (patch) | |
tree | 0799e933d94f699e6df6550ba1350d37a91bb871 | |
parent | 1a138804f692d0e578a7d4a19f9f13099a59a1cd (diff) | |
download | couchdb-prevent-multiple-responses.tar.gz |
Ensure no more than one response per requestprevent-multiple-responses
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/couch/src/couch_httpd.erl | 18 |
2 files changed, 21 insertions, 0 deletions
diff --git a/src/chttpd/src/chttpd.erl b/src/chttpd/src/chttpd.erl index dc9a30fd7..064a648fb 100644 --- a/src/chttpd/src/chttpd.erl +++ b/src/chttpd/src/chttpd.erl @@ -154,6 +154,7 @@ stop() -> handle_request(MochiReq0) -> erlang:put(?REWRITE_COUNT, 0), + couch_httpd:response_not_started(), MochiReq = couch_httpd_vhost:dispatch_host(MochiReq0), handle_request_int(MochiReq). @@ -1282,8 +1283,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/couch/src/couch_httpd.erl b/src/couch/src/couch_httpd.erl index 8f7fedd5e..0f66b426e 100644 --- a/src/couch/src/couch_httpd.erl +++ b/src/couch/src/couch_httpd.erl @@ -39,6 +39,7 @@ -export([check_max_request_length/1]). -export([handle_request/1]). -export([set_auth_handlers/0]). +-export([response_not_started/0, abort_if_response_already_started/0]). -define(HANDLER_NAME_IN_MODULE_POS, 6). -define(MAX_DRAIN_BYTES, 1048576). @@ -229,6 +230,7 @@ handle_request(MochiReq, DefaultFun, UrlHandlers, DbUrlHandlers, DesignUrlHandlers) -> %% reset rewrite count for new request erlang:put(?REWRITE_COUNT, 0), + response_not_started(), MochiReq1 = couch_httpd_vhost:dispatch_host(MochiReq), @@ -1204,6 +1206,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 @@ -1218,6 +1221,7 @@ 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}). peer(MochiReq) -> @@ -1228,6 +1232,20 @@ peer(MochiReq) -> MochiReq:get(peer) end. + +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 -> + throw({http_abort, mochiweb_response:new(nil, 500, []), multiple_responses_attempted}) + end. + + %%%%%%%% module tests below %%%%%%%% -ifdef(TEST). |