Gemini (Gemini v0.2.0)
Gemini is a lightweight application protocol. This server implements a lightweight server implementation using Elixir.
Architecture
Gemini
uses :ranch
as a TLS socket pool.
The data flow is as follows:
:ranch
creates aGemini.ClientConnection
for a connection.Gemini.ClientConnection
reads the request.- If request includes a client certificate,
Gemini.ClientConnection
registers a session withGemini.UserCache
. Gemini.ClientConnection
builds the aGemini.Request
and forwards it toGemini.Router
.- The top-level
Gemini.Router
uses the site map:sites
(in config) to find a module to forward the request to (a "Site-Module"). - That site module takes the
Gemini.Request
and returns aGemini.Response
. Gemini.ClientConnection
sends theGemini.Response
and closes the connection.
Configuration
Site-Map
A site map is a data structure with the following shape:
%{
"hostname" => %{
"path-or-path-prefix" => {name, [args]}
}
}
The value with the most specific path-or-path-prefix
wins. name
is either:
- a site module
name
which will be registered under the same name. - a tuple of
{mod, name}
: A site modulemod
registered under the namename
.
The module is started with args
under the supervision of Gemini.Supervisor
.
The top-level site-map is the config key :sites
.
If you do not need a site-map or you want to run your own routing, set config key :router
to a module which implements Gemini.Router
.
Certificates
:ranch
is configured using the config key :ranch_config
. Use it to configure at least :port
, :certfile
and :keyfile
.
:verify
and :verify_fun
is overwritten by the program.
The certificate provided by the user is stored in the :peer
property of Gemini.Request
as {id, meta, cert}
. cert
is the DER-encoded
certificate, id
is a hash-value of that certificate (can be assumed to be unique and should probably be used internally if you implement some kind of permanent DB)
and meta
is a metadata map (see Metadata below).
Rate-Limiting
Rate-limiting is turned on by default with max. 20 calls/minute and a 60 second penalty.
To configure the default rate-limit module, see Gemini.DefaultRateLimit
.
To turn it off, set config key :rate_limit
to false.
To provide your own Rate-Limit module, set :rate_limit
to the name of the module which implements Gemini.RateLimit
.
Site-Modules
Gemini.Site.File
Serves a single file.
%{"/" => {
{Gemini.Site.File, MyIndex},
["public/index", "text/gemini", :infinity]}}
Gemini.Site.Authenticated
Require user certificate.
%{"/auth" => {
{Gemini.Site.Authenticated, MyAuth},
[sites: %{"/" => {
{Gemini.Site.File, MyAuthedFile},
["public/authed", "text/gemini", :infinity]}}]}}
Gemini.Site.Input
Require user input.
%{"/input" => {
{Gemini.Site.Input, MyInput},
[as_meta: false, sites: %{"/" => {
{Gemini.Site.Spy, MySpy}, []}}]}}
Gemini.Site.Spy
Return all data the server has stored about a given request/user/certificate.
%{"/" => {
{Gemini.Site.Spy, MySpy}, []}}
Gemini.Site.ExInfo
Return information about the running elixir/erlang system.
%{"/" => {
{Gemini.Site.ExInfo, MyInfo}, []}}
This set of modules only provides the most basic functionality. Anything complex regarding user interaction and data storage has to be implemented using custom site modules.
Custom Modules
defmodule MyModule do
use Gemini.Site, check_path: true
def start_link([name, path, args]) do
GenServer.start_link(__MODULE__, [path, args], name: name)
end
def init([path, args]) do
{:ok, {path, args}}
end
def path({path, _args}), do: path
def forward_request(request, state) do
response = make_response(:success, "text/plain", "Hello, World!", [])
{:reply, {:ok, response}, state}
end
end
The request
in Gemini.Site.forward_request/2
is a Gemini.Request
.
If you need a user certificate, use your module in a Gemini.Site.Authenticated
site-map or
reimplement that behaviour from scratch.
If you need user input, use your module in a Gemini.Site.Input
site-map or
reimplement that behaviour from scratch.
Metadata
Sites can associate temporary metadata with a user certificate using Gemini.UserCache
. If a request includes
a user certificate, the metadata map is available in Gemini.Request
under :peer
. See Gemini.Request
for details.
Link to this section Summary
Functions
Return hash value of certificate that is used internally to identify users.
Get best site (or {:error, :notfound}
) for a path and sitemap.
Log request & response to Logger.
Return readable hash value.
Remove trailing slash from path
Link to this section Functions
cert_hash(cert)
Specs
Return hash value of certificate that is used internally to identify users.
get_best_site(sites, path)
Specs
Get best site (or {:error, :notfound}
) for a path and sitemap.
log_response(response, request)
Specs
log_response(Gemini.Response.t(), Gemini.Request.t()) :: :ok
Log request & response to Logger.
readable_hash(data)
Specs
Return readable hash value.
remove_trailing_slash(path)
Specs
Remove trailing slash from path