Introducing Gophersnake
=======================
*Written: 2020-04-22 00:44:00 UTC*
I finally took the time to read about the Gopher protocol and it turns out that
I love it. It was even easier than I thought it would be. I made a *super*
basic gopher server which can be found on my [rawtext gitea][1]. Though, I can
put the entire contents in here and it's really not that bad. Because at the
moment it's only 63 lines. See?
---
#!/usr/bin/env python3
import asyncio
import os.path
import stat
from pathlib import Path
GOPHER_ROOT = os.environ.get('GOPHER_ROOT', '/var/gopher')
def get_file(filename):
# Relative paths are bad. normpath strips interior `/../` out
filename = Path(GOPHER_ROOT, os.path.normpath(filename.rstrip()).lstrip('/')).resolve()
gophermap = filename / 'gophermap'
#print(filename)
#print(gophermap)
result = None
if filename.exists() and filename.is_file():
result = filename
elif gophermap.exists():
result = gophermap
#print(result)
# If there was a result, but it's not world readable, unset the
# result.
#print(stat.filemode(result.stat().st_mode))
if result and stat.filemode(result.stat().st_mode)[-3] != 'r':
result = None
return result
async def handle_message(reader, writer):
data = await reader.read(1024)
request = data.decode()
addr = writer.get_extra_info('peername')
if not request.strip():
resource = get_file('')
else:
resource = get_file(request)
print('returning', resource)
if resource is None:
writer.write(b'Error: File or directory not found!')
else:
writer.write(resource.read_bytes())
await writer.drain()
writer.close()
loop = asyncio.get_event_loop()
coro = asyncio.start_server(handle_message, '0.0.0.0', int(os.environ.get('GOPHER_PORT', 70)), loop=loop)
server = loop.run_until_complete(coro)
print('serving forever')
try:
loop.run_forever()
except KeyboardInterrupt:
print('\r^C caught. goodbye!')
pass
server.close()
loop.run_until_complete(server.wait_closed())
loop.close()
---
*How cool is that??* Like... I knew gopher was simple, but I didn't know just
how simple. Yeah, that's not going to autogen your directory listing, but
I could probably add that in 10 lines or so. For my purposes there I really
didn't want to do that, though, since it's just going to be a super basic
server for my own personal gopher hole.
All gopher really is is asking a server for what lives at a path. That's it.
There's not even a domain involved. Which is kind of sad because you can't host
multiple different domains on one server, but... that's kind of OK.
Anyway, this is a server that exists and if it is helpful for you at all,
great! If not... well, I hope you learned anything?
[1]:
https://git.rawtext.club/wangofett/gophersnake