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
### 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

View File

@ -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.