💾 Archived View for mozz.us › journal › 2020-05-21.gmi captured on 2022-04-29 at 11:29:53. Gemini links have been rewritten to link to archived content
⬅️ Previous capture (2020-09-24)
-=-=-=-=-=-=-
Published 2020-05-21
When I left off, I was still searching for a python server framework with support for PyOpenSSL. Since then, I've done some more research and made a lot of progress. I've been intending to document it here, but I've also been busy writing code. And when faced with a choice between writing new code and writing prose... I'll take the code every time.
The next idea that I had was to rip out all of the async bits and replace them with socketserver.
https://docs.python.org/3.8/library/socketserver.html
Socketserver is a very basic server framework that's included in the python standard library. It's not pretty by any means. It uses class-based inheritance in the way that people who write articles titled "OOP considered harmful" like to complain about. But I've used it before and it gets the job done all right.
I got decently far down this path. I was at the point where the server was all hooked up and handling gemini requests. I was using the TheadingMixin to spin up a new thread for every request. I got to keep my no third-party dependency mantra, and life was good! All that was left to do was hook up the PyOpenSSL connection..
I will say this: When folks tell you that PyOpenSSL is significantly harder to use than the standard library, take their word for it.
Here's how to read from an SSL wrapped socket using the python standard library:
wrapped_socket.recv()
And here's the same code using a PyOpenSSL connection:
def recv() try: data = connection.recv(*args, **kwargs) except OpenSSL.SSL.SysCallError as e: if suppress_ragged_eofs and e.args == (-1, "Unexpected EOF"): return b"" else: raise SocketError(str(e)) except OpenSSL.SSL.ZeroReturnError: if self.connection.get_shutdown() == OpenSSL.SSL.RECEIVED_SHUTDOWN: return b"" else: raise except OpenSSL.SSL.WantReadError: if not wait_for_read(socket, socket.gettimeout()): raise timeout("The read operation timed out") else: return recv(*args, **kwargs) else: return data
I mean.. I'm sure I could eventually figure it out. But I'm not THAT comfortable with TLS yet. And the documentation is sparse. I'm too afraid that I would screw something up that causes 1 out of 100 handshakes to hang or something like that.
Long story short, I have officially settled on twisted
Twisted is an event-driven networking engine written in Python. It's:
Twisted - The Report Of Our Death
I had never really considered twisted as a potential option before. I guess because it's so old, I just assumed it had been superseded by newer libraries. There are definitely some places where it shows its age, like the random camelCased variable names. But the library is still receiving regular updates. Their next release is going to finally drop Python 2 compatibility. They even support writing coroutines using the async/await keywords since Python 3.5!
As a result of taking on twisted, I had to drop the zero dependencies flag that I was holding onto. It's a shame because IMO it does make it harder to install and run the server. But in a way, it was nice to embrace it. I went and broke up the jetforce server from a single file into a better organized package. This in turn made me feel liberated to actually add new code and features that I was holding off from because of fear of adding bloat.
I have been spending a lot of time polishing up the application framework that jetforce exposes. Refining and tweaking, re-refining and re-tweaking. I'm mostly satisfied with it now. There's still some documentation to write and a lot of testing to do. I pushed it up to a separate branch:
https://github.com/michael-lazar/jetforce/tree/v0.3.0
This whole process has made me reflect on what my goals for jetforce are. I think there are two things that I would like to accomplish:
There was a discussion on the mailing list asking which servers support virtual hosting. Jetforce does, but only in the "framework" sense and not in the "works out of the box" sense. I'm ok with that balance, I think. I don't want it to slowly evolve into a configuration-driven workhorse like Apache. There's certainly a place for servers like that, but it's someone else's place.