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 中使用
265KB
5.5K SLoC
注意:这是 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
和一个 RouteContext
。RouteContext
包含共享数据、路由参数、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
}
有关如何配置这些绑定的更多信息,请参阅
- https://developers.cloudflare.com/workers/cli-wrangler/configuration#keys
- https://developers.cloudflare.com/workers/learning/using-durable-objects#configuring-durable-object-bindings
持久化对象
在 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、实用工具包等等。事实上,我们一直在寻找优秀的工程师,并且正在招聘许多开放职位——请查看。
常见问题解答
- 我可以部署使用
tokio
或async_std
运行时的 Worker 吗?
- 目前不可以。您 Worker 项目的所有包都必须编译为
wasm32-unknown-unknown
目标,这在某些方面比 x86 和 ARM64 的目标有限。
worker
包没有 X!为什么?
- 很可能它应该有,但我们还没有时间完全实现它或添加一个包装 FFI 的库。请通过打开问题告诉我们您需要的功能。
- 我的包大小超过了 Workers 1MB 的限制,我该怎么办?
- 我们正在这里寻找解决方案,但在同时,您需要尽可能减少代码依赖的crate数量,或者尽可能从
.wasm
二进制文件中移除内容。以下是一些您可以尝试的额外步骤:https://wasm.rust-lang.net.cn/book/reference/code-size.html#optimizing-builds-for-code-size
贡献
欢迎并感谢您的反馈!请使用问题跟踪器来讨论潜在的实现或提出功能请求。如果您对提交PR感兴趣,我们建议尽早打开一个问题来讨论您想要做出的更改。
项目内容
- worker:面向用户的crate,通过包装器和FFI绑定便利库,在Rust <-> JS/WebAssembly互操作中使用Rust熟悉的抽象。
- worker-sys:与Workers JS运行时兼容的Rust extern "C"定义。
- worker-macros:导出
event
和durable_object
宏,用于将Rust入口点包装在ES模块的fetch
方法中,以及生成代码以创建和交互Durable Objects。 - worker-sandbox:一个用于测试功能和易用性的工作函数Cloudflare Worker。
- worker-build:用于基于
workers-rs
的项目的一个跨平台构建命令。
依赖关系
~11–15MB
~293K SLoC