Better error handling for closed connections

This commit is contained in:
Michael Lazar 2020-06-20 01:11:59 -04:00
parent b49cf9d2b6
commit c288c72ad2
2 changed files with 34 additions and 2 deletions

View File

@ -1,5 +1,17 @@
# Jetforce Changelog # 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) ### v0.4.0 (2020-06-09)
#### Features #### Features

View File

@ -7,6 +7,8 @@ import urllib.parse
from twisted.internet.address import IPv4Address, IPv6Address from twisted.internet.address import IPv4Address, IPv6Address
from twisted.internet.defer import Deferred, ensureDeferred 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.internet.task import deferLater
from twisted.protocols.basic import LineOnlyReceiver from twisted.protocols.basic import LineOnlyReceiver
@ -50,6 +52,7 @@ class GeminiProtocol(LineOnlyReceiver):
def __init__(self, server: GeminiServer, app: JetforceApplication): def __init__(self, server: GeminiServer, app: JetforceApplication):
self.server = server self.server = server
self.app = app self.app = app
self._currently_deferred: typing.Optional[Deferred] = None
def connectionMade(self): def connectionMade(self):
""" """
@ -60,6 +63,14 @@ class GeminiProtocol(LineOnlyReceiver):
self.response_buffer = "" self.response_buffer = ""
self.client_addr = self.transport.getPeer() 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): def lineReceived(self, line):
""" """
This method is invoked by LineOnlyReceiver for every incoming line. This method is invoked by LineOnlyReceiver for every incoming line.
@ -102,20 +113,22 @@ class GeminiProtocol(LineOnlyReceiver):
environ = self.build_environ() environ = self.build_environ()
response_generator = self.app(environ, self.write_status) response_generator = self.app(environ, self.write_status)
if isinstance(response_generator, Deferred): if isinstance(response_generator, Deferred):
response_generator = await response_generator response_generator = await self.track_deferred(response_generator)
else: else:
# Yield control of the event loop # Yield control of the event loop
await deferLater(self.server.reactor, 0) await deferLater(self.server.reactor, 0)
for data in response_generator: for data in response_generator:
if isinstance(data, Deferred): if isinstance(data, Deferred):
data = await data data = await self.track_deferred(data)
self.write_body(data) self.write_body(data)
else: else:
self.write_body(data) self.write_body(data)
# Yield control of the event loop # Yield control of the event loop
await deferLater(self.server.reactor, 0) await deferLater(self.server.reactor, 0)
except ConnectionClosed:
pass
except Exception: except Exception:
self.server.log_message(traceback.format_exc()) self.server.log_message(traceback.format_exc())
self.write_status(Status.CGI_ERROR, "An unexpected error occurred") self.write_status(Status.CGI_ERROR, "An unexpected error occurred")
@ -124,6 +137,13 @@ class GeminiProtocol(LineOnlyReceiver):
self.log_request() self.log_request()
self.transport.loseConnection() 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]: def build_environ(self) -> typing.Dict[str, typing.Any]:
""" """
Construct a dictionary that will be passed to the application handler. Construct a dictionary that will be passed to the application handler.