Use a producer to stream back responses

The problem with dumping all of the json response into the Request object at
once is that doing so starts the timeout for the next request to be received:
so if it takes longer than 60s to stream back the response to the client, the
client never gets it.

The correct solution is to use a Producer; then the timeout is only started
once all of the content is sent over the TCP connection.
This commit is contained in:
Richard van der Hoff 2018-08-14 16:13:17 +01:00
parent 4601129c44
commit afcd655ab6

View file

@ -27,6 +27,7 @@ from twisted.internet import defer
from twisted.python import failure from twisted.python import failure
from twisted.web import resource, server from twisted.web import resource, server
from twisted.web.server import NOT_DONE_YET from twisted.web.server import NOT_DONE_YET
from twisted.web.static import NoRangeStaticProducer
from twisted.web.util import redirectTo from twisted.web.util import redirectTo
import synapse.events import synapse.events
@ -42,6 +43,11 @@ from synapse.util.caches import intern_dict
from synapse.util.logcontext import LoggingContext, PreserveLoggingContext from synapse.util.logcontext import LoggingContext, PreserveLoggingContext
from synapse.util.metrics import Measure from synapse.util.metrics import Measure
if PY3:
from io import BytesIO
else:
from cStringIO import StringIO as BytesIO
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
HTML_ERROR_TEMPLATE = """<!DOCTYPE html> HTML_ERROR_TEMPLATE = """<!DOCTYPE html>
@ -413,8 +419,7 @@ def respond_with_json(request, code, json_object, send_cors=False,
return return
if pretty_print: if pretty_print:
json_bytes = (encode_pretty_printed_json(json_object) + "\n" json_bytes = encode_pretty_printed_json(json_object) + b"\n"
).encode("utf-8")
else: else:
if canonical_json or synapse.events.USE_FROZEN_DICTS: if canonical_json or synapse.events.USE_FROZEN_DICTS:
# canonicaljson already encodes to bytes # canonicaljson already encodes to bytes
@ -450,8 +455,12 @@ def respond_with_json_bytes(request, code, json_bytes, send_cors=False,
if send_cors: if send_cors:
set_cors_headers(request) set_cors_headers(request)
request.write(json_bytes) # todo: we can almost certainly avoid this copy and encode the json straight into
finish_request(request) # the bytesIO, but it would involve faffing around with string->bytes wrappers.
bytes_io = BytesIO(json_bytes)
producer = NoRangeStaticProducer(request, bytes_io)
producer.start()
return NOT_DONE_YET return NOT_DONE_YET