Update echo example

This commit is contained in:
Michael Lazar 2020-05-18 00:00:06 -04:00
parent d4ceb4d085
commit 2d7856f27c
6 changed files with 55 additions and 33 deletions

32
examples/echo.py Normal file
View File

@ -0,0 +1,32 @@
"""
A bare-bones server that with echo back the request to the client.
This example demonstrates the simplest proof-of-concept of how you can write
your own application from scratch instead of sub-classing from the provided
JetforceApplication. The server/application interface is almost identical to
WSGI defined in PEP-3333 [1].
Unless you're feeling adventurous, you probably want to stick to the
JetforceApplication instead of going this low-level.
[1] https://www.python.org/dev/peps/pep-3333/#id20
"""
import jetforce
def app(environ, send_status):
"""
Arguments:
environ: A dictionary containing information about the request
send_status: A callback function that takes two parameters: The
response status (int) and the response meta text (str).
Returns: A generator containing the response body.
"""
send_status(10, "text/gemini")
yield f"Received path: {environ['GEMINI_URL']}"
if __name__ == "__main__":
server = jetforce.GeminiServer(app)
server.run()

View File

@ -1,27 +0,0 @@
"""
A simple Gemini server that echos back the request to the client.
"""
import asyncio
import jetforce
def echo(environ, send_status):
url = environ["GEMINI_URL"]
send_status(jetforce.Status.SUCCESS, "text/gemini")
yield f"Received path: {url}".encode()
if __name__ == "__main__":
args = jetforce.command_line_parser().parse_args()
ssl_context = jetforce.make_ssl_context(
args.hostname, args.certfile, args.keyfile, args.cafile, args.capath
)
server = jetforce.GeminiServer(
host=args.host,
port=args.port,
ssl_context=ssl_context,
hostname=args.hostname,
app=echo,
)
asyncio.run(server.run())

View File

@ -59,16 +59,32 @@ class GeminiProtocol(LineOnlyReceiver):
def lineReceived(self, line):
"""
This method is invoked by LineOnlyReceiver for every incoming line.
Because Gemini requests are only ever a single line long, this will
only be called once and we can use it to handle the lifetime of the
connection without managing any state.
"""
self.request = line
return ensureDeferred(self._handle_request_noblock())
async def _handle_request_noblock(self):
"""
Handle the gemini request and write the raw response to the socket.
This method is implemented using an async coroutine, which has been
supported by twisted since python 3.5 by wrapping the method in
ensureDeferred(). Twisted + coroutines is a bitch to figure out, but
once it clicks it really does turn out to be an elegant solution.
Any time that we call into the application code, we wrap the call with
deferToThread() which will execute the code in a separate thread using
twisted's thread pool. deferToThread() will return a future object
that we can then `await` to get the result when the thread finishes.
This is important because we don't want application code to block the
twisted event loop from serving other requests at the same time.
In the future, I would like to add the capability for applications to
implement proper coroutines that can call `await` on directly without
needing to wrap them in threads. Conceptually, this shouldn't be too
difficult, but it will require implementing an alternate version of
the JetforceApplication that's async-compatible.
"""
try:
self.parse_header()
except Exception:

View File

@ -124,7 +124,8 @@ class GeminiCertificateOptions(CertificateOptions):
keyfile: typing.Optional[str] = None,
cafile: typing.Optional[str] = None,
capath: typing.Optional[str] = None,
):
) -> None:
self.certfile = certfile
self.keyfile = keyfile
self.cafile = cafile
@ -136,7 +137,7 @@ class GeminiCertificateOptions(CertificateOptions):
fixBrokenPeers=True,
)
def _makeContext(self):
def _makeContext(self) -> OpenSSL.SSL.Context:
"""
Most of this code is copied directly from the parent class method.
"""