It was thus said that the Great Gary Johnson once stated:
> Yes, these are CGI scripts in practice. I'm just trying not to shell out
> to subprocesses in the interest of server responsiveness. I'm currently
> just thinking through how to provide as simple and intuitive an
> interface as possible for the script writer. Probably it will amount to
> some kind of handler function API that takes a request map and returns a
> response map in the classic tradition of all ring-based Clojure web
> apps.
>
> Scripts are currently run within their own auto-generated temporary
> namespaces, which prevents them from accidentally polluting the server's
> namespaces with def* forms. Any I/O to stdout is captured in a string
> and currently returned as the body of a "20 text/gemini; charset=utf-8"
> response. This is fine as a starting point, but I think I want to create
> something that feels more functional and less state-oriented pretty
> soon.
>
GLV-1.12556 [1] uses handlers to generate content. The builtin handlers
include ones to serve up a file (yup--just one file), a filesystem (handles
serving up data from a directory) and user directories [2]. The API I use
is simple. The optional functions are:
okay,err = init(conf)
This function does any intialization for the handler. It is given the
configuration parameters from the configuration file and returns 'true' if
success, or 'false' and an error message if it fails. Typically, if I have
this function, it verifies the configuration passed in to make sure it will
handle requests. This configuratio block can also be used for state
information for the handler if need be.
okay,err = fini(conf)
This function is called when the server is being shut down, and is
intended to do any cleanup (that isn't not already handled by the garbage
collector) or saving any persistent data [3].
The mandatory function is:
status,mime,data = handler(conf,auth,location,match)
This handles a request. It's given the configuration block, the client
certificate (if any [4]), the parsed URL and the 'match' array, which I
don't know how to explain other than an example, in this case, the
filesystem handler (from my development system configuration file):
{
path = "^(/cgi%-bin/)(.*)",
module = "GLV-1.handlers.filesystem",
directory = "/home/spc/projects/gemini/non-checkin/cgi-bin",
cgi = true,
}
The path component is a Lua pattern (a type of regex) to determine which
handler to call for a given request, and it has two parts, the first (which
is captured in ()) determines the "base" path of the request, and the second
is the part the handler is reponsible for. It's these two captures that are
passed in the 'match' section.
This function returns a status code, meta information (named mime here)
and a body (which for errors, has to be "") as a string. Handlers do not
have direct access to the socket. And a given handler can be instantiated
multiple times, each with its own configuration block.
As handlers in GLV-1.12556 are also Lua modules, that means they get their
own "global" namespace. It works and I've had no issue with this structure.
It also means it's easier for me to write a handler than it is a CGI (or
SCGI) script, although it does mean I have to add a configuration block and
restart the server when adding a new handler.
-spc
[1]
https://github.com/spc476/GLV-1.12556
[2] I run more custom handlers than I do builtin ones.
[3] My "quote of the day" handler will save which quote was last served
in order to pick up from where it left off.
[4] Authorization has already been done by the time the request is
handled so the handler doesn't need to do it, but some handlers
(like the filesystem handler, which deals with CGI/SCGi) can do some
additional processing.