Better error handling for closed connections
This commit is contained in:
parent
b49cf9d2b6
commit
c288c72ad2
12
CHANGELOG.md
12
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
|
||||
|
|
|
@ -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.
|
||||
|
|
Loading…
Reference in New Issue