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:
:ranchcreates aGemini.ClientConnectionfor a connection.Gemini.ClientConnectionreads the request.- If request includes a client certificate,
Gemini.ClientConnectionregisters a session withGemini.UserCache. Gemini.ClientConnectionbuilds the aGemini.Requestand forwards it toGemini.Router.- The top-level
Gemini.Routeruses the site map:sites(in config) to find a module to forward the request to (a "Site-Module"). - That site module takes the
Gemini.Requestand returns aGemini.Response. Gemini.ClientConnectionsends theGemini.Responseand 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
namewhich will be registered under the same name. - a tuple of
{mod, name}: A site modulemodregistered 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
endThe 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