__________________
DYNAMIC MARMOTTE
Nicolas Herry
__________________
2022/10/31
1 Dynamic marmotte
==================
This is the second article on the Gopher server I'm currently
working on, marmotte. After I introduced the project in [the
first article], I have worked some more on the project and
present here some of the new features.
[the first article] <
gopher://gopher.beastieboy.net/0/marmotte.txt>
1.1 Articles in this series
~~~~~~~~~~~~~~~~~~~~~~~~~~~
[Marmotte]
[Marmotte] <
gopher://gopher.beastieboy.net/0/marmotte.txt>
1.2 Dynamic responses
~~~~~~~~~~~~~~~~~~~~~
These last few days' efforts were all about adding dynamic
rendering to marmotte. The original [RFC1436] describes Gopher in
its abstract in these words:
The Internet Gopher protocol is designed for
distributed document search and retrieval. This
document describes the protocol, lists some of the
implementations currently available, and has an
overview of how to implement new client and server
applications. This document is adapted from the
basic Internet Gopher protocol document first issued
by the Microcomputer Center at the University of
Minnesota in 1991.
Apart from the part on searching, this description doesn't
mention any dynamic aspect of Gopher, and the rest of the
document doesn't address this either, apart from one little
sentence in the rather mundanely named section 3.6, /Building
ordinary internet Gopher servers/:
The retrieval string sent to the server might be a
path to a file or directory. It might be the name of
a script, an application or even a query that
generates the document or directory returned.
That's all. Dynamic content? Oh, let's just not cover it and only
briefly hint at the possibility... This is very typical of
Gopher, how it's been designed, and how it's been documented. To
me, this makes Gopher very much a protocol for hackers: it leaves
many things to the creativity of the coders. I intend to get back
to the topic of hackability in software in a later essay; for
now, let's focus on marmotte, and more specifically, on the three
ways marmotte can now produce dynamic responses.
[RFC1436] <
https://www.rfc-editor.org/rfc/rfc1436>
1.2.1 Rendering pipeline
------------------------
At the heart of marmotte are two pipelines: one for processing
requests, and one for preparing responses to these requests. Both
pipelines are implemented in the same manner: a collection of
transformations applied to a piece of data describing a request
or a response. Each transformation is made up of a predicate and
a function. To understand how this works, let's follow a simple
exchange between a client and a server.
A client sends a request to the server, in the form of a selector
(a string). Upon receving the request, the server simply goes
through the collection of request transformations, asking each,
via its associated predicate, if it should run, and if so, calls
the function with the request. The modified request is then
passed to the next transformation in the pipeline, and so on
until all candidate transformations have got a chance to run. The
final version of the request is then passed to the response
pipeline the same way: each transformation lets its predicate
decide whether it should run, in which case its function is run
against the request and the current version of the response, and
both are passed down the rest of the collection. The final
response is what is returned to the client.
This generic approach allows to satisfy basic cases, where a
request contains a path to a file, for example, and doesn't need
any change, and where a response is handled by a single
transformation, which simply loads the data from the
corresponding file and stores it in the response. But the same
architecture caters to more complex scenarios. One is that of
selector rewriting: it is similar to URL rewriting, where a path
in a selector is changed to map onto a real, physical directory,
and is an easy way of implementing virtual directories. Another
is transforming the data itself: adding headers, footers,
modifying the data, compressing it... All this can be implemented
via instances of marmotte's RequestTransformers and
ResponseTransformers.
The current version of marmotte implements dynamic gophermaps
that way: when handling a directory where no gophermap file is
present, the ResponseTransformer simply generates one. Headers
and Footers are also available and multiple instances can be
defined: one can devise a Footer for all gophermaps, a Header for
textfiles in a specific directory, another Header for files
maching specific names, and these can be combined or remain
exclusive. This is made possible thanks to having the functions
being separate from the predicates: a given transformation
function can be combined with any predicate and packaged in a
ResponseTransformer any number of times. In the examples above,
we would have three ResponseTransformers, each having the same
functions (Header or Footer) and different predicates. Some would
only match on gophermaps, some on files with a specific name, or
in a specific path...
Over time, I will keep adding to these transformation functions
and predicates to implement gateways to other protocols, file
format conversion, file compression, responses that would change
depending on the time of the day, and so on.
1.2.2 Executables
-----------------
marmotte now also has a new ResponseTransformer, the
ExecutableTransformer. As the name suggests, it allows marmotte
to call external programs and return the data generated by these
programs. The programs in question can be anything that marmotte
can call: shell scripts, Perl scripts, binaries... The main
contract between marmotte and a program is in two parts: the
latter will receive a root path where to look for and generate
files and directories, and the former will get in return a single
string, which can be either a directory or a file. The response
crafted by this transformation is then picked up by the rest of
the pipeline and either presented as a gophermap (generated or
loaded from a gophermap file the program would have placed in the
directory), or loaded from a file on the disk.
Below is a simple example:
,----
| #!/bin/sh
|
| # Generate the filename
| FILENAME=`cat /dev/random | env LC_CTYPE=C tr -cd "[:alnum:]" | head -c 32`
| FILEPATH=/tmp/$FILENAME
|
| # Generate a file with some text contents
| if [ ! -d $1/tmp ]; then
| mkdir $1/tmp
| fi
|
| echo "This is an automatically generated file, named $FILENAME." > $1/$FILEPATH
|
| # Return the filepath to marmotte
| echo $FILEPATH
|
`----
This shell script generates a file with a random name (tapping
into the power of `/dev/random'), places it in the subdirectory
`/tmp' of the gopherhole and returns the path to it (relative to
the server's root). Nothing fancy, but anything can happen
between the first line and the last line...
Having the ability to run executables opens many, many doors:
this is one of the main reasons the web exploded in the mid-90s,
and is akin to the `cgi-bin' that were popular then (and still
are everywhere today, in the more modern implementation of
`fast-cgi'). A file can be altered before being served, or
generated from scratch, and entire directory hierarchies can even
be generated in one go and served to the client.
One thing that I will need to implement in the coming weeks is
sandboxing, to restrict the programs to only safe portions of the
filesystem, instead on relying on the executables to politely
place everything under the server root given in argument.
1.2.3 Templates
---------------
The last addition in terms of dynamic rendering is
templates. This is another feature that propelled the early Web
forward in the 90s, and opens at least as many doors as the
ability to call external executables. marmotte being written in
Go, I've made the simple choice here to leverage the power of the
template engine that comes with this language.
The current implementation is still in its very early stages, and
is only used for dynamic error reporting (more on that in a later
article). These error documents are designed as Go templates that
accept from the server a structure of data containing arbitrary
informations.
Here's an example below:
,----
| You have the wrong permissions to access the resources behind the selector {{ .Selector }}. If you believe you should have access to these resources, please contact the administrator.
`----
Here, the selector originally sent by the client will be
displayed, coming from the data structure provided by the
server. This might seem fairly basic, but this opens all kinds of
possibilities, as this structure can contain any type of data,
from any provenance, and even data contributed to by other
RequestTranformers or ResponseTransformers, and be used in any
kind of document, not only error messages.
In the coming weeks, I will probably introduce these templates to
the existing Headers and Footers facilities, to make these even
more interesting, and simply generalise and promote the use of
templates as a very simple way to prepare dynamic content. Again,
ResponseTransformers will do the work here, simply running
against any file with a `.gt' extension, for Gopher Template.
1.3 In the next episode...
~~~~~~~~~~~~~~~~~~~~~~~~~~
I have left some features out for today and will cover these in
future articles: virtual gopherspaces (aka, /tildes/, as in
~bboy) and errors and errorcodes. Once again, stay tuned!