18 releases (4 major breaking)
2023.10.27 |
|
---|---|
4.0.1 | Jul 4, 2024 |
4.0.0 | Feb 29, 2024 |
3.0.0 | Feb 28, 2024 |
0.9.1 | Nov 23, 2023 |
#795 in Network programming
Used in 2 crates
42KB
806 lines
π¦ 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"
definesA
andB
. (/owo/this_is_A/this_is_B
)"/page=:N/filter=:F
definesN
andF
. (/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
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 |
Dependencies
~9β24MB
~365K SLoC