diff --git a/synapse/http/server.py b/synapse/http/server.py
index a90e2e1125..b17b190ee5 100644
--- a/synapse/http/server.py
+++ b/synapse/http/server.py
@@ -367,10 +367,29 @@ def respond_with_json_bytes(request, code, json_bytes, send_cors=False,
"Origin, X-Requested-With, Content-Type, Accept")
request.write(json_bytes)
- request.finish()
+ finish_request(request)
return NOT_DONE_YET
+def finish_request(request):
+ """ Finish writing the response to the request.
+
+ Twisted throws a RuntimeException if the connection closed before the
+ response was written but doesn't provide a convenient or reliable way to
+ determine if the connection was closed. So we catch and log the RuntimeException
+
+ You might think that ``request.notifyFinish`` could be used to tell if the
+ request was finished. However the deferred it returns won't fire if the
+ connection was already closed, meaning we'd have to have called the method
+ right at the start of the request. By the time we want to write the response
+ it will already be too late.
+ """
+ try:
+ request.finish()
+ except RuntimeError as e:
+ logger.info("Connection disconnected before response was written: %r", e)
+
+
def _request_user_agent_is_curl(request):
user_agents = request.requestHeaders.getRawHeaders(
"User-Agent", default=[]
diff --git a/synapse/rest/client/v1/login.py b/synapse/rest/client/v1/login.py
index 7199113dac..79101106ac 100644
--- a/synapse/rest/client/v1/login.py
+++ b/synapse/rest/client/v1/login.py
@@ -17,6 +17,8 @@ from twisted.internet import defer
from synapse.api.errors import SynapseError, LoginError, Codes
from synapse.types import UserID
+from synapse.http.server import finish_request
+
from base import ClientV1RestServlet, client_path_patterns
import simplejson as json
@@ -263,7 +265,7 @@ class SAML2RestServlet(ClientV1RestServlet):
'?status=authenticated&access_token=' +
token + '&user_id=' + user_id + '&ava=' +
urllib.quote(json.dumps(saml2_auth.ava)))
- request.finish()
+ finish_request(request)
defer.returnValue(None)
defer.returnValue((200, {"status": "authenticated",
"user_id": user_id, "token": token,
@@ -272,7 +274,7 @@ class SAML2RestServlet(ClientV1RestServlet):
request.redirect(urllib.unquote(
request.args['RelayState'][0]) +
'?status=not_authenticated')
- request.finish()
+ finish_request(request)
defer.returnValue(None)
defer.returnValue((200, {"status": "not_authenticated"}))
@@ -309,7 +311,7 @@ class CasRedirectServlet(ClientV1RestServlet):
"service": "%s?%s" % (hs_redirect_url, client_redirect_url_param)
})
request.redirect("%s?%s" % (self.cas_server_url, service_param))
- request.finish()
+ finish_request(request)
class CasTicketServlet(ClientV1RestServlet):
@@ -362,7 +364,7 @@ class CasTicketServlet(ClientV1RestServlet):
redirect_url = self.add_login_token_to_redirect_url(client_redirect_url,
login_token)
request.redirect(redirect_url)
- request.finish()
+ finish_request(request)
def add_login_token_to_redirect_url(self, url, token):
url_parts = list(urlparse.urlparse(url))
diff --git a/synapse/rest/client/v2_alpha/auth.py b/synapse/rest/client/v2_alpha/auth.py
index ff71c40b43..78181b7b18 100644
--- a/synapse/rest/client/v2_alpha/auth.py
+++ b/synapse/rest/client/v2_alpha/auth.py
@@ -18,6 +18,7 @@ from twisted.internet import defer
from synapse.api.constants import LoginType
from synapse.api.errors import SynapseError
from synapse.api.urls import CLIENT_V2_ALPHA_PREFIX
+from synapse.http.server import finish_request
from synapse.http.servlet import RestServlet
from ._base import client_v2_patterns
@@ -130,7 +131,7 @@ class AuthRestServlet(RestServlet):
request.setHeader(b"Content-Length", b"%d" % (len(html_bytes),))
request.write(html_bytes)
- request.finish()
+ finish_request(request)
defer.returnValue(None)
else:
raise SynapseError(404, "Unknown auth stage type")
@@ -176,7 +177,7 @@ class AuthRestServlet(RestServlet):
request.setHeader(b"Content-Length", b"%d" % (len(html_bytes),))
request.write(html_bytes)
- request.finish()
+ finish_request(request)
defer.returnValue(None)
else:
diff --git a/synapse/rest/media/v0/content_repository.py b/synapse/rest/media/v0/content_repository.py
index dcf3eaee1f..d9fc045fc6 100644
--- a/synapse/rest/media/v0/content_repository.py
+++ b/synapse/rest/media/v0/content_repository.py
@@ -13,7 +13,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-from synapse.http.server import respond_with_json_bytes
+from synapse.http.server import respond_with_json_bytes, finish_request
from synapse.util.stringutils import random_string
from synapse.api.errors import (
@@ -144,7 +144,7 @@ class ContentRepoResource(resource.Resource):
# after the file has been sent, clean up and finish the request
def cbFinished(ignored):
f.close()
- request.finish()
+ finish_request(request)
d.addCallback(cbFinished)
else:
respond_with_json_bytes(
diff --git a/synapse/rest/media/v1/base_resource.py b/synapse/rest/media/v1/base_resource.py
index 58d56ec7a4..58ef91c0b8 100644
--- a/synapse/rest/media/v1/base_resource.py
+++ b/synapse/rest/media/v1/base_resource.py
@@ -16,7 +16,7 @@
from .thumbnailer import Thumbnailer
from synapse.http.matrixfederationclient import MatrixFederationHttpClient
-from synapse.http.server import respond_with_json
+from synapse.http.server import respond_with_json, finish_request
from synapse.util.stringutils import random_string
from synapse.api.errors import (
cs_error, Codes, SynapseError
@@ -238,7 +238,7 @@ class BaseMediaResource(Resource):
with open(file_path, "rb") as f:
yield FileSender().beginFileTransfer(f, request)
- request.finish()
+ finish_request(request)
else:
self._respond_404(request)
|