diff --git a/CHANGELOG.md b/CHANGELOG.md index 646492d..ef85427 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,17 @@ # Jetforce Changelog +### Unreleased + +#### Static Fileserver + +- Error stack traces are no longer shown when the client prematurely closes + the connection. + +#### Internal Framework + +- If a gemini response returns a twisted.Deferred object, the errback will + now be invoked when the TCP connection is closed. + ### v0.4.0 (2020-06-09) #### Features diff --git a/jetforce/protocol.py b/jetforce/protocol.py index c0cfd47..dfc53c0 100644 --- a/jetforce/protocol.py +++ b/jetforce/protocol.py @@ -7,6 +7,8 @@ import urllib.parse from twisted.internet.address import IPv4Address, IPv6Address from twisted.internet.defer import Deferred, ensureDeferred +from twisted.internet.error import ConnectionClosed +from twisted.internet.protocol import connectionDone from twisted.internet.task import deferLater from twisted.protocols.basic import LineOnlyReceiver @@ -50,6 +52,7 @@ class GeminiProtocol(LineOnlyReceiver): def __init__(self, server: GeminiServer, app: JetforceApplication): self.server = server self.app = app + self._currently_deferred: typing.Optional[Deferred] = None def connectionMade(self): """ @@ -60,6 +63,14 @@ class GeminiProtocol(LineOnlyReceiver): self.response_buffer = "" self.client_addr = self.transport.getPeer() + def connectionLost(self, reason=connectionDone): + """ + This is invoked by twisted after the connection has been closed. + """ + if self._currently_deferred: + self._currently_deferred.errback(reason) + self._currently_deferred = None + def lineReceived(self, line): """ This method is invoked by LineOnlyReceiver for every incoming line. @@ -102,20 +113,22 @@ class GeminiProtocol(LineOnlyReceiver): environ = self.build_environ() response_generator = self.app(environ, self.write_status) if isinstance(response_generator, Deferred): - response_generator = await response_generator + response_generator = await self.track_deferred(response_generator) else: # Yield control of the event loop await deferLater(self.server.reactor, 0) for data in response_generator: if isinstance(data, Deferred): - data = await data + data = await self.track_deferred(data) self.write_body(data) else: self.write_body(data) # Yield control of the event loop await deferLater(self.server.reactor, 0) + except ConnectionClosed: + pass except Exception: self.server.log_message(traceback.format_exc()) self.write_status(Status.CGI_ERROR, "An unexpected error occurred") @@ -124,6 +137,13 @@ class GeminiProtocol(LineOnlyReceiver): self.log_request() self.transport.loseConnection() + async def track_deferred(self, deferred: Deferred): + self._currently_deferred = deferred + try: + return await deferred + finally: + self._currently_deferred = None + def build_environ(self) -> typing.Dict[str, typing.Any]: """ Construct a dictionary that will be passed to the application handler.