🦊 Fluffer
Fluffer is an experimental crate that aims to make writing Gemini apps fun and easy.
Other helpful gemini projects:
- trotter: A crate for gemini clients.
- schuifkoppel: A reverse-proxy for gemini.
🗼 Design
Similar to Axum, Fluffer routes are generic functions that
can return anything that implements the [GemBytes] trait.
There are some helpful implementations out of the box, so
please consult [GemBytes] and [Fluff] while you
experiment.
Also, this crate has a lot of examples for you to check out. Including a dice roller app.
Here is a basic example of a Fluffer app.
use ;
async
💎 GemBytes
The [GemBytes] trait returns a Gemini byte response, which
is formatted like this:
<STATUS><SPACE><META>\r\n<CONTENT>
Note: you must include the <SPACE> character, even if
<META> is blank.
To implement [GemBytes] on a type is to decide which
Gemini response is appropriate for it.
For example: it is sensible to represent some mime-ambiguous data as a successful Gemtext response so it can be read in a client.
use ;
📜 Client
Gemini uses client certificates to facilitate identities in geminispace.
[Client] provides methods that correspond to common
identity practices in Gemini.
| Function | Descripion |
|---|---|
[Client::certificate] |
Get the client cert in the PEM format. |
[Client::fingerprint] |
Get the client cert's fingerprint (SHA-256). |
[Client::name] |
Get the client cert's subject_name field. Useful for providing temporary usernames, or just saying hello. |
[Client::verify] |
Verify that this client's cert is one you're expecting. |
🥴 Input, queries, and parameters
When a gemini client is prompted for input, that input consumes the entire query line.
As such, you should not attach query information to a route that also prompts the user.
Keep this in mind when considering the following options.
Input
To get a user's input to a route, call [Client::input].
This returns the whole query line percent-decoded.
default
.route
.run
.await
.unwrap
Queries
For routes where you aren't also accounting for a user's input, queries are suitable for tracking UI state across requests.
For example, you can add warning or error messages to a
gemtext document by redirecting to a path with special query
names. (E.g. /home?err=bad%20thingg%20happened),
The Fluff variant [Fluff::RedirectQueries] helps you to do
this by redirecting to a route with a vector of key-value
queries.
You can persist multiple queries by including them in a gemtext link to other pages (or the same page).
Use the method [Client::query] to look for query values
which correspond to a key.
Parameters
Parameters are derived from patterns you define on a route's path.
To use a parameter, define it in your route's path string,
and call [Client::parameter] in the route method.
default
.route
.run
.await
.unwrap
Fluffer uses [matchit]. If you're unfamiliar, here's a
couple of examples:
"/owo/:a/:b"defines parametersaandb, e.g:/owo/thisisa/thisisb"/page=:n/filter=:fdefines the parametern, andffollowing a prefix, e.g:/page=20/filter=date.
Things to keep in mind:
- Some gemini clients cache pages based on their route. So, you may not want to use parameters for routes that update frequently.
- Every parameter must be included in your url for the route to be found.
- Be careful where you define your parameters. It's possible to consume requests intended for a different route.
- It's more flexible to represent complex expressions as a single parameter, which you parse manually inside the route function.
🌕 Titan
Titan is a sister-protocol to gemini. It allows clients to send data to the server (à la http post). (read more)
This allows titan clients to upload images, videos, and large amounts of text.
To use titan, you must selectively decide which routes will
accept titan data (and how much). You do this by calling
[App::titan] instead of [App::route].
Once you've done that, the titan property on [Client]
will yield a titan resource if the request was
made with titan.
Here's an example of all that in action.
use ;
async
async
🏃 App State
Currently, Fluffer allows you to add one piece of state that
gets attached as a generic to [Client].
This means you'll need to reflect the app's state in every
reference of [Client], so I recommend using a type alias.
use App;
use ;
// Type alias for Client<State> **highly recommended**
type Client = Client;
async
async
✨ Features
| Name | Description | Default |
|---|---|---|
interactive |
Enables interactive prompt for generating key/cert at runtime. | Yes |
anyhow |
Enables the [GemBytes] impl for [anyhow] (not recommended outside of debugging) |
No |
reqwest |
Enables the [GemBytes] impl for [reqwest::Result] and [reqwest::Response] |
No |
📚 Helpful Resources
📋 Todo
- Async for route functions
- Switch to openssl
- Add peer certificate to client
- Spawn threads
- App data
- Titan support
- Add more options to certificate generation
- Tests..?