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.

Indices and tables