#gemini #routes #server-framework #traits #experimental #fun #gem-bytes

fluffer

🦊 Fluffer是一个有趣且实验性的Gemini服务器框架

18个版本 (4个主要破坏)

2023.10.27 2023年10月27日
4.0.1 2024年7月4日
4.0.0 2024年2月29日
3.0.0 2024年2月28日
0.9.1 2023年11月23日

#663 in 网络编程


用于dovetail

GPL-3.0-only

42KB
806

🦊 Fluffer

Fluffer是一个有趣且实验性的Gemini服务器框架。

📔 概述

路由是返回任何实现GemBytes特质的泛型函数。

有一些有用的内置实现。请在实验时查阅GemBytesFluff。同时,查看示例

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

GemBytes特质有一个返回Gemini字节响应的方法

<STATUS><SPACE><META>\r\n<CONTENT>

请记住,你必须包含空格字符<SPACE>——即使<META>为空。

要在类型上实现GemBytes就是决定适当的响应。

例如:您可以使用格式化的gemtext表示MIME模糊的类型。

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使用证书来识别客户端。Client结构体实现了常见功能。

🔗 输入、查询和参数

输入

调用Client::input返回请求的查询行百分比值解码。

App::default()
    .route("/" |c| async {
        c.input().unwrap_or("no input 😥".to_string())
    })
    .run()
    .await
    .unwrap()

查询

对于不处理用户输入的路由,查询适合跟踪请求之间的UI状态。

例如,您可以通过重定向到具有特殊查询名称的路径将警告或错误消息添加到gemtext文档中。(例如 /home?err=bad%20thingg%20happened),

Fluff变体Fluff::RedirectQueries通过将请求重定向到包含键值查询的路由来提供帮助。

使用Client::query来检查查询值。

参数

参数来自您在路由路径中定义的模式。

在路由字符串中定义一个参数,并通过调用Client::parameter来访问它。

App::default()
    .route("/page=:number" |c| async {
        format!("{}", c.parameter("number").unwrap_or("0"))
    })
    .run()
    .await
    .unwrap()

如果您不熟悉matchit,这里有一些示例

  • "/owo/:A/:B"定义了AB。(/owo/this_is_A/this_is_B)
  • "/page=:N/filter=:F定义了NF。(/page=20/filter=date)

请注意:一些客户端会根据它们的URL缓存页面。您可能希望在更新频繁的路由中避免使用参数。

🏃 状态

Fluffer允许您选择一个数据对象作为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是用于上传文件的协议。

您可以通过调用App::titan而不是App::route来在路由上启用Titan。

在启用了Titan的路由上,Client中的titan属性可能会产生一个资源。

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()
}

✨ 功能

名称 描述 默认
interactive 启用在运行时生成密钥/证书的提示。
anyhow anyhow启用GemBytes(不建议在调试之外使用)
reqwest reqwest::Resultreqwest::Response启用GemBytes

依赖项

~9–24MB
~381K SLoC