diff --git a/CHANGELOG.md b/CHANGELOG.md index 3ba0ee7..465718b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -65,16 +65,18 @@ variable. #### Other Changes - A client certificate can now have an empty ``commonName`` field. -- For the ``JetforceApplication``, named capture groups in a route's regex - pattern will now be passed as keyword arguments to the wrapped function. See +- ``JetforceApplication.route()`` - named capture groups in regex patterns will + now be passed as keyword arguments to the wrapped function. See examples/pagination.py for an example of how to use this feature. -- A ``CompositeApplication`` class is now included to support virtual hosting - by composing multiple applications behind the same jetforce server. See +- ``CompositeApplication`` - A class is now included to support composing + composing multiple applications behind the same jetforce server. See examples/vhost.py for an example of how to use this feature. -- CGI variables - ``SCRIPT_NAME`` and ``PATH_INfO`` have been changed to match +- CGI variables - ``SCRIPT_NAME`` and ``PATH_INFO`` have been changed to match their intended usage as defined in RFC 3875. - CGI variables - ``TLS_CIPHER`` and ``TLS_VERSION`` have been added and contain information about the established TLS connection. +- Applications can now optionally return ``Deferred`` objects instead of bytes, + in order to support full-blown asynchronous coroutines. ### v0.2.3 (2020-05-24) diff --git a/examples/guestbook.py b/examples/guestbook.py index 76b3516..09bdc62 100644 --- a/examples/guestbook.py +++ b/examples/guestbook.py @@ -5,18 +5,13 @@ This is an example of how to return a 10 INPUT request to the client and retrieve their response by parsing the URL query string. This example stores the guestbook inside of a persistent sqlite database. -Because each request will run inside of a separate thread, we must create a new -connection object inside of the request handler instead of re-using a global -database connection. This thread-safety can be disabled in sqlite3 by using the -check_same_thread=False argument, but then it's up to you to ensure that only -connection request is writing to the database at any given time. """ import sqlite3 from datetime import datetime from jetforce import GeminiServer, JetforceApplication, Response, Status -DB = "/tmp/guestbook.sqlite" +db = sqlite3.connect("/tmp/guestbook.sqlite", detect_types=sqlite3.PARSE_DECLTYPES) SCHEMA = """ CREATE TABLE IF NOT EXISTS guestbook ( @@ -25,8 +20,7 @@ CREATE TABLE IF NOT EXISTS guestbook ( message TEXT ) """ -with sqlite3.connect(DB) as c: - c.execute(SCHEMA) +db.execute(SCHEMA) app = JetforceApplication() @@ -36,16 +30,14 @@ app = JetforceApplication() def index(request): lines = ["Guestbook", "=>/submit Sign the Guestbook"] - with sqlite3.connect(DB, detect_types=sqlite3.PARSE_DECLTYPES) as c: - for row in c.execute("SELECT * FROM guestbook ORDER BY created_at"): - ip_address, created_at, message = row - line = f"{created_at:%Y-%m-%d} - [{ip_address}] {message}" - lines.append("") - lines.append(line) + for row in db.execute("SELECT * FROM guestbook ORDER BY created_at"): + ip_address, created_at, message = row + line = f"{created_at:%Y-%m-%d} - [{ip_address}] {message}" + lines.append("") + lines.append(line) lines.extend(["", "...", ""]) body = "\n".join(lines) - return Response(Status.SUCCESS, "text/gemini", body) @@ -55,9 +47,8 @@ def submit(request): message = request.query[:256] created = datetime.now() ip_address = request.environ["REMOTE_HOST"] - with sqlite3.connect(DB) as c: - values = (ip_address, created, message) - c.execute("INSERT INTO guestbook VALUES (?, ?, ?)", values) + values = (ip_address, created, message) + db.execute("INSERT INTO guestbook VALUES (?, ?, ?)", values) return Response(Status.REDIRECT_TEMPORARY, "") else: return Response(Status.INPUT, "Enter your message (max 256 characters)") diff --git a/jetforce/protocol.py b/jetforce/protocol.py index 2a67e08..a7bacad 100644 --- a/jetforce/protocol.py +++ b/jetforce/protocol.py @@ -106,13 +106,12 @@ class GeminiProtocol(LineOnlyReceiver): # Yield control of the event loop await deferLater(self.server.reactor, 0) while True: - try: - data = await maybeDeferred(response_generator.__next__) - self.write_body(data) - # Yield control of the event loop - await deferLater(self.server.reactor, 0) - except StopIteration: + data = await maybeDeferred(response_generator.__next__) + if data is None: break + self.write_body(data) + # Yield control of the event loop + await deferLater(self.server.reactor, 0) except Exception: self.server.log_message(traceback.format_exc()) self.write_status(Status.CGI_ERROR, "An unexpected error occurred")