Expand description
§🦊 Fluffer
Fluffer is a fun and experimental gemini server framework.
§📔 Overview
Routes are generic functions that return anything implementing the GemBytes
trait.
There are some helpful implementations out of the box. Please consult
GemBytes and Fluff while you experiment. Also check out the examples.
use fluffer::{App, Fluff};
#[tokio::main]
async fn main() {
App::default()
.route("/", |_| async {
"# Welcome\n=> /u32 Should show a number\n=> /pic 🦊 Here's a cool picture!"
})
.route("/u32", |_| async { 777 })
.route("/pic", |_| async { Fluff::File("picture.png".to_string()) })
.run()
.await;
}§💎 GemBytes
The GemBytes trait has one method for returning a gemini byte response:
<STATUS><SPACE><META>\r\n<CONTENT>Remember you must include the <SPACE> character—even if <META> is blank.
To implement GemBytes on a type is to decide the response appropriate for
it.
For example: you may represent a mime-ambiguous type as formatted gemtext.
use fluffer::{GemBytes, async_trait};
struct Profile {
name: String,
bio: String,
}
#[async_trait]
impl GemBytes for Profile {
async fn gem_bytes(&self) -> Vec<u8> {
format!("20 text/gemini\r\n# {},\n\n## Bio\n\n{}", self.name, self.bio).into_bytes()
}
}§🙃 Identity
Gemini uses certificates to identify clients. The Client struct implements
common functionality.
§🔗 Input, queries, and parameters
§Input
Calling Client::input returns the request’s query line percent-decoded.
App::default()
.route("/" |c| async {
c.input().unwrap_or("no input 😥".to_string())
})
.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 by redirecting to a route
with a vector of key-value queries.
Use Client::query to inspect query values.
§Parameters
Parameters are derived from patterns you define in a route’s path.
Define a parameter in your route string, and access it by calling
Client::parameter.
App::default()
.route("/page=:number" |c| async {
format!("{}", c.parameter("number").unwrap_or("0"))
})
.run()
.await
.unwrap()If you’re unfamiliar with matchit, here are a few examples:
"/owo/:A/:B"definesAandB. (/owo/this_is_A/this_is_B)"/page=:N/filter=:FdefinesNandF. (/page=20/filter=date)
Keep in mind: some clients cache pages based on their url. You may want to avoid using parameters in routes that update frequently.
§🏃 State
Fluffer allows you to choose one data object to attach as a generic to
Client.
use fluffer::App;
use std::sync::{Arc, Mutex};
// Alias for Client<State>
type Client = fluffer::Client<Arc<Mutex<State>>>;
#[derive(Default)]
struct State {
visitors: u32,
}
async fn index(c: Client) -> String {
let mut state = c.state.lock().unwrap();
state.visitors += 1;
format!("Visitors: {}", state.visitors)
}
#[tokio::main]
async fn main() {
let state = Arc::new(Mutex::new(State::default()));
App::default()
.state(state) // <- Must be called first.
.route("/", index)
.run()
.await
.unwrap()
}§🌕 Titan
Titan is a sister protocol for uploading files.
You can enable titan on a route by calling App::titan instead of
App::route.
On a titan-enabled route, the titan property in Client may yield a resource.
use fluffer::{App, Client};
async fn index(c: Client) -> String {
if let Some(titan) = c.titan {
return format!(
"Size: {}\nMime: {}\nContent: {}\nToken: {}",
titan.size,
titan.mime,
std::str::from_utf8(&titan.content).unwrap_or("[not utf8]"),
titan.token.unwrap_or(String::from("[no token]")),
);
}
format!(
"Hello, I'm expecting a text/plain gemini request.\n=> titan://{} Click me",
c.url.domain().unwrap_or("")
)
}
#[tokio::main]
async fn main() {
App::default()
.titan("/", index, 20_000_000) // < limits content size to 20mb
.run()
.await
.unwrap()
}§✨ Features
Structs§
Enums§
- AppErr
- Fluff
- 🐰 A general-purpose implementation of GemBytes.
- Status
- Re-export from
trotter. Enum for representing gemini status codes.
Traits§
Attribute Macros§
- async_
trait - Procedural macro used when implementing
GemBytes.