Ensure no more than one response per request
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.
diff --git a/src/chttpd/src/chttpd.erl b/src/chttpd/src/chttpd.erl
index dc9a30f..064a648 100644
--- a/src/chttpd/src/chttpd.erl
+++ b/src/chttpd/src/chttpd.erl
@@ -154,6 +154,7 @@
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 @@
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 8f7fedd..0f66b42 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 @@
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 @@
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 @@
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 @@
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).