Crates.io | fluffer |
lib.rs | fluffer |
version | 4.0.1 |
source | src |
created_at | 2023-10-24 02:38:57.763745 |
updated_at | 2024-07-04 21:03:18.48082 |
description | π¦ Fluffer is a fun and experimental gemini server framework. |
homepage | https://codeberg.org/catboomer/fluffer |
repository | https://codeberg.org/catboomer/fluffer |
max_upload_size | |
id | 1012036 |
size | 127,272 |
Fluffer is a fun and experimental gemini server framework.
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;
}
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()
}
}
Gemini uses certificates to identify clients. The [Client
] struct implements
common functionality.
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()
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 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"
defines A
and B
. (/owo/this_is_A/this_is_B
)"/page=:N/filter=:F
defines N
and F
. (/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.
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 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()
}
Name | Description | Default |
---|---|---|
interactive |
Enable prompt for generating key/cert at runtime. | Yes |
anyhow |
Enable [GemBytes ] for anyhow (not recommended outside of debugging) |
No |
reqwest |
Enable [GemBytes ] for reqwest::Result and reqwest::Response |
No |