gopher_server¶
gopher_server is a library for building Gopher protocol servers.
For maximum flexibility it takes a layered approach, separating the code for I/O, Gopher protocol semantics and generating a response.
To get started, take a look at the examples below, or dive right into the API documentation.
A simple Gopher server¶
Here’s an example of a server which serves static files:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | from asyncio import get_event_loop
from gopher_server.application import Application
from gopher_server.handlers import DirectoryHandler
from gopher_server.listeners import tcp_listener
handler = DirectoryHandler("data/")
application = Application(handler)
if __name__ == "__main__":
loop = get_event_loop()
loop.create_task(tcp_listener(application, "localhost", "0.0.0.0", 7000))
loop.run_forever()
|
The Application
is initialised
on line 9. Its sole argument is a handler (any object implementing the
IHandler
interface). In this case we
use a DirectoryHandler
,
which serves static files from a given directory.
The application and handler layers are self-contained and do no I/O. Instead,
the job of listening for connections is done by a listener function. On line
14 we use the tcp_listener
,
which listens for connections using an unencrypted TCP socket (as specified by
the Gopher standard).
If all you want to do is serve static files, you can do so by invoking the gopher_server package directly. This will serve files from the current directory:
python -m gopher_server
It also accepts an argument for a specific directory:
python -m gopher_server /path/to/data
Less simple Gopher servers¶
Alternative listeners¶
Whilst Gopher-over-TCP was a reasonable choice in 1993, the modern internet should be encrypted by default. As such this library provides a couple of non-standard listeners which have encryption:
tcp_tls_listener
serves Gopher-over-TLS. Though non-standard, there are many clients which support using TLS for encryption.quic_listener
serves Gopher-over-QUIC. As the QUIC protocol is still new, client support for this is currently almost non-existent.
For hybrid servers it’s easy to run multiple listeners with a single application:
if __name__ == "__main__":
loop = get_event_loop()
loop.create_task(tcp_listener(application, "localhost", "0.0.0.0", 7000))
loop.create_task(tcp_tls_listener(
application, "localhost", "0.0.0.0", 7001,
"server.crt", "key.pem",
))
loop.create_task(quic_listener(
application, "localhost", "0.0.0.0", 7000,
"server.crt", "key.pem",
))
loop.run_forever()
Alternative handlers¶
The DirectoryHandler
is fine
for static gophersites, but if you want to do something more complex (eg.
loading content from a database) then more advanced options are available.
The first option is to use the PatternHandler
. It works similarly to the router in
web frameworks such as Flask and Django: view functions are registered against
a regular expression pattern, and the relevant function is called when an
incoming request has a matching selector.
handler = PatternHandler()
@handler.register("")
def home(request):
return Menu([
InfoMenuItem("hello world example menu"),
MenuItem("0", "foo", "hello/foo", request.hostname, request.port),
MenuItem("0", "bar", "hello/bar", request.hostname, request.port),
MenuItem("0", "baz", "hello/baz", request.hostname, request.port),
])
@handler.register("hello/(?P<name>.+)")
def hello(request, name):
return "hello %s" % name
In the above example, an empty selector calls the home function, and selectors
starting with “hello/” call the hello function with subsequent text in the
name keyword argument. The home function also uses the Menu
class to build a gophermap.
If neither of the built-in handlers are good enough for you, your second option
is to create your own handler by implementing the IHandler
interface.