5 个版本

0.0.15 2023 年 1 月 15 日
0.0.14 2023 年 1 月 6 日
0.0.13 2022 年 12 月 31 日
0.0.12 2022 年 12 月 31 日

#896网页编程

每月 24 次下载
sentry-cloudflare 中使用

Apache-2.0

265KB
5.5K SLoC

workers-rs crates.io docs.rs

注意:这是 workers-rs 的分支。

进行中 的 Cloudflare Workers 环境的 Rust 绑定,用 Rust 编写你的整个 Worker!

阅读 注释和常见问题解答

示例用法

use worker::*;

#[event(fetch)]
pub async fn main(req: Request, env: Env, _ctx: worker::Context) -> Result<Response> {
    console_log!(
        "{} {}, located at: {:?}, within: {}",
        req.method().to_string(),
        req.path(),
        req.cf().coordinates().unwrap_or_default(),
        req.cf().region().unwrap_or("unknown region".into())
    );

    if !matches!(req.method(), Method::Post) {
        return Response::error("Method Not Allowed", 405);
    }

    if let Some(file) = req.form_data().await?.get("file") {
        return match file {
            FormEntry::File(buf) => {
                Response::ok(&format!("size = {}", buf.bytes().await?.len()))
            }
            _ => Response::error("`file` part of POST form must be a file", 400),
        };
    }

    Response::error("Bad Request", 400)
}

或者使用 Router

参数化路由,并从处理器内部访问参数值。每个处理器函数接受一个 Request 和一个 RouteContextRouteContext 包含共享数据、路由参数、Env 绑定等。

use worker::*;

#[event(fetch)]
pub async fn main(req: Request, env: Env, _ctx: worker::Context) -> Result<Response> {

    // Create an instance of the Router, which can use parameters (/user/:name) or wildcard values
    // (/file/*pathname). Alternatively, use `Router::with_data(D)` and pass in arbitrary data for
    // routes to access and share using the `ctx.data()` method.
    let router = Router::new();

    // useful for JSON APIs
    #[derive(Deserialize, Serialize)]
    struct Account {
        id: u64,
        // ...
    }
    router
        .get_async("/account/:id", |_req, ctx| async move {
            if let Some(id) = ctx.param("id") {
                let accounts = ctx.kv("ACCOUNTS")?;
                return match accounts.get(id).json::<Account>().await? {
                    Some(account) => Response::from_json(&account),
                    None => Response::error("Not found", 404),
                };
            }

            Response::error("Bad Request", 400)
        })
        // handle files and fields from multipart/form-data requests
        .post_async("/upload", |mut req, _ctx| async move {
            let form = req.form_data().await?;
            if let Some(entry) = form.get("file") {
                match entry {
                    FormEntry::File(file) => {
                        let bytes = file.bytes().await?;
                    }
                    FormEntry::Field(_) => return Response::error("Bad Request", 400),
                }
                // ...

                if let Some(permissions) = form.get("permissions") {
                    // permissions == "a,b,c,d"
                }
                // or call `form.get_all("permissions")` if using multiple entries per field
            }

            Response::error("Bad Request", 400)
        })
        // read/write binary data
        .post_async("/echo-bytes", |mut req, _ctx| async move {
            let data = req.bytes().await?;
            if data.len() < 1024 {
                return Response::error("Bad Request", 400);
            }

            Response::from_bytes(data)
        })
        .run(req, env).await
}

入门

该项目使用 wrangler 版本 2.x 来运行和发布您的 Worker。

手动获取 Rust Worker 项目的 模板,或者运行以下命令

npm init cloudflare project_name worker-rust
cd project_name

您应该看到一个带有 src/lib.rs 的新项目布局。从这里开始!使用任何本地或远程的 Crates 和模块(只要它们编译到 wasm32-unknown-unknown 目标)。

一旦您准备好运行您的项目

首先检查 wrangler 版本是 2.x

npx wrangler --version

然后,运行您的 Worker

npx wrangler dev

最后,上线

# configure your routes, zones & more in your worker's `wrangler.toml` file
npx wrangler publish

如果您想在您的机器上安装 wrangler,请参阅 wrangler 仓库 中的说明。

持久化对象、KV、密钥和变量绑定

所有“绑定”到您的脚本(持久化对象 & KV 命名空间、密钥和变量)都可以从提供给入口点(此示例中的 main)和路由处理器回调(在 ctx 参数中)的 env 参数中访问,如果您使用的是 worker 包中的 Router

use worker::*;

#[event(fetch, respond_with_errors)]
pub async fn main(req: Request, env: Env, _ctx: worker::Context) -> Result<Response> {
    utils::set_panic_hook();

    let router = Router::new();

    router
        .on_async("/durable", |_req, ctx| async move {
            let namespace = ctx.durable_object("CHATROOM")?;
            let stub = namespace.id_from_name("A")?.get_stub()?;
            stub.fetch_with_str("/messages").await
        })
        .get("/secret", |_req, ctx| {
            Response::ok(ctx.secret("CF_API_TOKEN")?.to_string())
        })
        .get("/var", |_req, ctx| {
            Response::ok(ctx.var("BUILD_NUMBER")?.to_string())
        })
        .post_async("/kv", |_req, ctx| async move {
            let kv = ctx.kv("SOME_NAMESPACE")?;

            kv.put("key", "value")?.execute().await?;

            Response::empty()
        })
        .run(req, env).await
}

有关如何配置这些绑定的更多信息,请参阅

持久化对象

在 Rust 中定义持久对象

要使用 worker 包定义持久对象,您需要在自己的结构体上实现 DurableObject 特性。此外,必须将 #[durable_object] 属性宏应用到您的结构体定义以及对应的特性 impl 块中。

use worker::*;

#[durable_object]
pub struct Chatroom {
    users: Vec<User>,
    messages: Vec<Message>,
    state: State,
    env: Env, // access `Env` across requests, use inside `fetch`
}

#[durable_object]
impl DurableObject for Chatroom {
    fn new(state: State, env: Env) -> Self {
        Self {
            users: vec![],
            messages: vec![],
            state: state,
            env,
        }
    }

    async fn fetch(&mut self, _req: Request) -> Result<Response> {
        // do some work when a worker makes a request to this DO
        Response::ok(&format!("{} active users.", self.users.len()))
    }
}

当您发布工作脚本时,需要将其“迁移”以便它能够识别这种新的持久对象,并在您的 wrangler.toml 文件中包含一个绑定。

  • 在您的 wrangler.toml 文件中包含持久对象绑定类型
# ...

[durable_objects]
bindings = [
  { name = "CHATROOM", class_name = "Chatroom" } # the `class_name` uses the Rust struct identifier name
]

[[migrations]]
tag = "v1" # Should be unique for each entry
new_classes = ["Chatroom"] # Array of new classes
  • 有关在更改时迁移持久对象的信息,请参阅此处文档:[https://developers.cloudflare.com/workers/learning/using-durable-objects#durable-object-migrations-in-wranglertoml](https://developers.cloudflare.com/workers/learning/using-durable-objects#durable-object-migrations-in-wranglertoml)

队列

启用队列

由于队列目前处于测试阶段,您需要启用 queue 功能标志。

通过将其添加到您的 Cargo.toml 文件中的工作依赖项来启用它

worker = {version = "...", features = ["queue"]}

示例工作脚本消耗和产生消息

use worker::*;
use serde::{Deserialize, Serialize};
#[derive(Serialize, Debug, Clone, Deserialize)]
pub struct MyType {
    foo: String,
    bar: u32,
}

// Consume messages from a queue
#[event(queue)]
pub async fn main(message_batch: MessageBatch<MyType>, env: Env, _ctx: Context) -> Result<()> {
    // Get a queue with the binding 'my_queue'
    let my_queue = env.queue("my_queue")?;

    // Deserialize the message batch
    let messages = message_batch.messages()?;

    // Loop through the messages
    for message in messages {
        // Log the message and meta data
        console_log!(
            "Got message {:?}, with id {} and timestamp: {}",
            message.body,
            message.id,
            message.timestamp.to_string()
        );

        // Send the message body to the other queue
        my_queue.send(&message.body).await?;
    }

    // Retry all messages
    message_batch.retry_all();
    Ok(())
}

注意和常见问题解答

看到这样一个框架能实现多少功能,非常令人兴奋,它为开发者在 Workers 平台上构建时提供了更多选项。然而,还有很多工作要做。预期会有一些粗糙的边缘、一些未实现的 API,以及可能的一些错误。在这里值得指出的是,一些可能在您的 Rust 代码中工作的东西在这里可能不起作用——毕竟,最终都是 WebAssembly,如果您的代码或第三方库没有针对 wasm32-unknown-unknown 进行优化,它们就不能在 Workers 上使用。此外,您必须将线程化的异步运行时留在家里;这意味着没有 Tokio 或 async_std 支持。但是,当您使用 worker 包时,async/await 语法仍然可用且支持。

我们完全打算支持这个包,并继续构建其缺失的功能,但您的帮助和反馈是必不可少的。我们不喜欢在真空中构建,并且我们非常幸运地拥有像您这样的优秀客户,他们可以帮助我们打造更好的产品。

所以,试试吧,留下一些反馈,并星标仓库,以鼓励我们投入更多的时间和资源来支持这类项目。

如果您对此感兴趣并想帮忙,我们将非常乐意让外部贡献者开始。我们知道有许多改进可以做出,比如与流行的 Rust HTTP 生态系统类型兼容(如果您想进行转换,我们有 Headers 的示例),实现额外的 Web API、实用工具包等等。事实上,我们一直在寻找优秀的工程师,并且正在招聘许多开放职位——请查看

常见问题解答

  1. 我可以部署使用 tokioasync_std 运行时的 Worker 吗?
  • 目前不可以。您 Worker 项目的所有包都必须编译为 wasm32-unknown-unknown 目标,这在某些方面比 x86 和 ARM64 的目标有限。
  1. worker 包没有 X!为什么?
  • 很可能它应该有,但我们还没有时间完全实现它或添加一个包装 FFI 的库。请通过打开问题告诉我们您需要的功能。
  1. 我的包大小超过了 Workers 1MB 的限制,我该怎么办?

贡献

欢迎并感谢您的反馈!请使用问题跟踪器来讨论潜在的实现或提出功能请求。如果您对提交PR感兴趣,我们建议尽早打开一个问题来讨论您想要做出的更改。

项目内容

  • worker:面向用户的crate,通过包装器和FFI绑定便利库,在Rust <-> JS/WebAssembly互操作中使用Rust熟悉的抽象。
  • worker-sys:与Workers JS运行时兼容的Rust extern "C"定义。
  • worker-macros:导出eventdurable_object宏,用于将Rust入口点包装在ES模块的fetch方法中,以及生成代码以创建和交互Durable Objects。
  • worker-sandbox:一个用于测试功能和易用性的工作函数Cloudflare Worker。
  • worker-build:用于基于workers-rs的项目的一个跨平台构建命令。

依赖关系

~11–15MB
~293K SLoC