-- Leo's gemini proxy

-- Connecting to rwv.io:1965...

-- Connected

-- Sending request

-- Meta line: 20 text/gemini; lang=en

Dʒɛmɪni


A gemini server written in Racket.


Features


static files

index.gmi directory files

single user CGI scripts

automatic self-signed certificate

file extension based content types

Server Name Indication (SNI)

response handlers in Racket

wrapper aka middleware in Racket


Requirements


Minimal Racket 7 or up

OpenSSL command (to generate server certificate)

Unix-like OS; (tested on Linux and OpenBSD)


Note: OpenSSL is only required when you won't supply your own server certificate and key which can be passed in through the command-line.


Installation


There's two ways of installing dezhemini:


using raco pkg install (racket 8 or up);

or by cloning the repository


The first option creates a "launcher" which depends on your PATH containing whatever directory raco installs launchers in. This is typically something like ".local/share/racket/8.1/bin" in your home directory, where "8.1" is the version number of your racket installation.


raco pkg install git+https://git.sr.ht/~rwv/dezhemini

Now you can run:


dezhmnsrv -h

to find out what's what.


You can also just clone the repository from:


https://git.sr.ht/~rwv/dezhemini


and run the following command to install the required racket packages:


raco pkg install

From here you can run:


racket dezhmnsrv.rkt -h

for the same help message.


Examples in this document will assume you're using the latter method because they depend on other files in the repository.


Running


Start the server with the following command:


racket dezhmnsrv.rkt localhost:root

Point your gemini client to:


gemini://localhost/


Files will be served from the "root" directory in the current working directory. For all configuration options use the "-h" for more information.


CGI scripts


Simply make files in your document directory executable to create CGI scripts (don't forget the shebang!). The process will inherit all environment variables of the server with the following extra variables:


DOCUMENT_ROOT

GATEWAY_INTERFACE (always set to CGI/0)

GEMINI_URL

PATH_INFO

PATH_TRANSLATED

QUERY_STRING

SCRIPT_NAME

SERVER_NAME

SERVER_PROTOCOL (always set to GEMINI)

SERVER_SOFTWARE (always set to dezhmnsrv/0)


When a client certificate is supplied:


AUTH_TYPE (will be set to "CERTIFICATE")

REMOTE_USER (will be set to the certificate common name)

TLS_CLIENT_HASH


Here's an example:


cgi.sh/foo?bar


For more information about CGI see:


https://tools.ietf.org/html/rfc3875


Note: the CGI specification is written with HTTP in mind, therefore this implementation only supports a subset of the specification.


Server Name Indication


Different server names can be served from the same port on the same instance using TLS SNI. Here's an example:


racket dezhmnsrv.rkt business.com:business pleasure.com:pleasure

In the above example all requests for "business.com" will be served from the "business" directory and all requests for "pleasure.com" from "pleasure". The command-line switches for certificates, keys and roots all allow a server name prefix with a semicolon.


Note: it is not possible to start one instance which listens to a different port per domain. Just run a separate instance instead for such a setup.


Handlers


A list of handlers will be tried to respond to an incoming request. The first response is returned to the client.


The default handlers in order are:


cgi-handler

directory-redirect-handler

file-handler


Use the -A and -P switched to append or prepend handlers. These switches expect a racket loadable file which provides a procedure called "handler" and accepts an alist with the following shape:


((url . "gemini://example.com/path?query")
 (path-info . "/path")
 (query-string . "query")
 (document-root . "some_directory")
 (server-name . "example.com")
 (remote-addr . "1.2.3.4"))

When a client certificate is supplied it includes a "client-certificate" member like:


((digest . "SHA256:1A2B3C..")
 (common-name . "fred"))

The handler returns 3 values for a "success" response (status 20 <= status <= 29) or 2 values for other response or #f when the handler does not know how to handle the request, in which case the next handler is tried.


status code (number 20, 30 etc.)

meta (content-type, redirect URL etc.)

body (a string or input port which will be closed after sending)


See also examples/handlers/hello.rkt for an example handler. Try it with:


racket dezhmnsrv.rkt -A examples/handlers/hello.rkt localhost:root

and point your gemini client to:


gemini://localhost/hello-racket


Note you'll need to figure out where raco put the dezhemini files or run from a cloned git repository.


Wrappers


A wrapper will wrap the global handler to do pre or post processing. Multiple wrappers can be applied. Use cases include: templating, logging and setting environment variables for underlying handlers/scripts.


Use the -W switch to load a racket file which provide a "wrap" procedure. The procedure will take a handler procedure and return a new handler procedure. Here's a very basic example:


(define (wrap handler)
  (lambda (req)
    (displayln "do stuff before handling request")
    (handler req)))

Doing something after handling a request requires receiving the returned values and passing them on. That takes a bit more effort because the global handler can return 2 or 3 values depending on the type of response (status, meta and optionally body).


Here's an example of dealing with response values:


(define (wrap handler)
  (lambda (req)
    (displayln "do stuff before handling request")
    (call-with-values
     (lambda () (handler req))
     (lambda res
       (displayln "do stuff after handling request")
       (apply values res)))))

See also examples/censor.rkt for an example handler and try it with:


racket dezhmnsrv.rkt -W examples/wrappers/censor.rkt localhost:root

Logging


This application uses the standard Racket logger and posts messages to the "dezhmn" topic. Message on this topic of level "info" and higher will be written to standard output. To see more messages use the "-v" command-line flag.


For more information configuring logging see:


https://docs.racket-lang.org/reference/logging.html


Content Types


By default this implementation comes with common mappings from file extensions to content types. You can add your own by supplying a mime.types file at the command-line.


Development


This project is hosted on sourcehut:


https://sr.ht/~rwv/dezhemini/


Code:


https://git.sr.ht/~rwv/dezhemini


Tickets:


https://todo.sr.ht/~rwv/dezhemini


Mailing list:


https://lists.sr.ht/~rwv/dezhemini


Before sending patches run the test suite:


raco test dezhmnsrv_test.rkt

Copying


This software is distributed under the GPLv3 license. For more information see:


COPYING.txt

-- Response ended

-- Page fetched on Sat Apr 27 04:26:17 2024