33 个版本 (10 个稳定版)

1.5.0 2024 年 7 月 23 日
1.3.0 2024 年 5 月 22 日
1.2.0 2024 年 3 月 12 日
1.0.0-rc72023 年 12 月 16 日
0.1.0 2022 年 11 月 30 日

#13 in WebAssembly

Download history 1602/week @ 2024-05-03 1546/week @ 2024-05-10 1141/week @ 2024-05-17 1313/week @ 2024-05-24 1432/week @ 2024-05-31 1648/week @ 2024-06-07 1652/week @ 2024-06-14 1713/week @ 2024-06-21 1884/week @ 2024-06-28 2232/week @ 2024-07-05 1741/week @ 2024-07-12 1803/week @ 2024-07-19 1676/week @ 2024-07-26 1316/week @ 2024-08-02 1149/week @ 2024-08-09 1445/week @ 2024-08-16

每月下载量 5,959
用于 22 个 crates(14 个直接使用)

BSD-3-Clause

205KB
5K SLoC

Extism 运行时和 rust-sdk

此仓库包含 Extism 运行时和 rust-sdk 的代码。它可以嵌入任何 Rust 应用程序中以调用 Extism 插件。

注意:如果您不确定 Extism 是什么或 SDK 是什么,请访问我们的主页: https://extism.org

安装

Cargo

要使用 extism crate,您可以在 Cargo 文件中添加它

[dependencies]
extism = "1.4.1"

环境变量

有几个环境变量可用于调试目的

  • EXTISM_ENABLE_WASI_OUTPUT=1:显示 WASI 标准输出/标准错误
  • EXTISM_MEMDUMP=extism.mem:将 Extism 线性内存转储到文件
  • EXTISM_COREDUMP=extism.core:在 WebAssembly 函数陷阱时将 coredump 写入文件
  • EXTISM_DEBUG=1:生成调试信息
  • EXTISM_PROFILE=perf|jitdump|vtune:启用 Wasmtime 性能分析
  • EXTISM_CACHE_CONFIG=path/to/config.toml:启用 Wasmtime 缓存,有关配置的详细信息,请参阅 文档。将此设置为空字符串将禁用缓存。

注意:仅当插件出现错误时,才会写入调试和 coredump 信息。

入门指南

本指南应引导您了解 Extism 和 extism crate 的一些概念。

创建插件

在 Extism 中,主要的概念是插件。你可以把插件想象成一个存储在 .wasm 文件中的代码模块。

由于你可能没有现成的 Extism 插件进行测试,让我们从网络上加载一个演示插件。

use extism::*;

fn main() {
  let url = Wasm::url(
    "https://github.com/extism/plugins/releases/latest/download/count_vowels.wasm"
  );
  let manifest = Manifest::new([url]);
  let mut plugin = Plugin::new(&manifest, [], true).unwrap();
  let res = plugin.call::<&str, &str>("count_vowels", "Hello, world!").unwrap();
  println!("{}", res);
}

注意:请参阅 Manifest 文档,因为它具有丰富的架构和许多选项。

调用插件的导出函数

这个插件是用 Rust 编写的,它只做一件事,即计算字符串中的元音字母数量。因此,它暴露了一个 "导出" 函数:count_vowels。我们可以使用 Extism::Plugin::call 来调用导出函数。

let res = plugin.call::<&str, &str>("count_vowels", "Hello, world!").unwrap();
println!("{}", res);
# => {"count": 3, "total": 3, "vowels": "aeiouAEIOU"}

所有导出都有一个简单的接口:输入字节和输出字节。这个插件恰好接收一个字符串,并返回一个包含结果报告的 JSON 编码字符串。

call 函数使用 extism-convert 来确定哪些输入/输出类型可以使用。如果我们想为 count_vowels 结果使用具体类型,我们可以定义一个结构体

#[derive(Debug, serde::Deserialize)]
struct VowelCount {
  count: usize,
  total: usize,
  vowels: String,
}

然后我们可以使用 Json 将 JSON 结果解码到 VowelCount

let Json(res) = plugin.call::<&str, Json<VowelCount>>("count_vowels", "Hello, world!").unwrap();
println!("{:?}", res);
# => VowelCount {count: 3, total: 3, vowels: "aeiouAEIOU"}

插件状态

插件可以是有状态的或无状态的。通过使用变量,插件可以在调用之间保持状态。我们的计数元音插件通过在结果中的 "total" 键中记住它所计算过的元音总数来保持状态。你可以通过多次调用导出函数来看到这一点。

let res = plugin.call::<&str, &str>("count_vowels", "Hello, world!").unwrap();
println!("{}", res);
# => {"count": 3, "total": 6, "vowels": "aeiouAEIOU"}

let res = plugin.call::<&str, &str>("count_vowels", "Hello, world!").unwrap();
println!("{}", res);
# => {"count": 3, "total": 9, "vowels": "aeiouAEIOU"}

这些变量将一直持续到这个插件被释放或你初始化一个新的插件。

配置

插件可以可选地接收一个配置对象。这是一种静态配置插件的方式。我们的 count-vowels 插件接受一个可选配置,用于更改哪些字符被认为是元音。示例

let manifest = Manifest::new([url]);
let mut plugin = Plugin::new(&manifest, [], true);
let res = plugin.call::<&str, &str>("count_vowels", "Yellow, world!").unwrap();
println!("{}", res);
# => {"count": 3, "total": 3, "vowels": "aeiouAEIOU"}
let mut plugin = Plugin::new(&manifest, [], true).with_config_key("vowels", "aeiouyAEIOUY");
let res = plugin.call::<&str, &str>("count_vowels", "Yellow, world!").unwrap();
println!("{}", res);
# => {"count": 4, "total": 4, "vowels": "aeiouyAEIOUY"}

主机函数

让我们扩展一下 count-vowels 的示例:不是将 total 存储在临时的插件变量中,而是将其存储在持久的关键字值存储中!

Wasm 不能单独使用我们的 KV 存储。这正是 主机函数 的用武之地。

主机函数 允许我们从应用程序中授予插件新的能力。它们是你编写的可以传递并从插件内部任何语言调用的 Rust 函数。

让我们像往常一样加载清单,但加载这个 count_vowels_kvstore 插件

let url = Wasm::url(
  "https://github.com/extism/plugins/releases/latest/download/count_vowels_kvstore.wasm"
);
let manifest = Manifest::new([url]);

注意:此源代码位于 此处,用 Rust 编写,但可以用我们的 PDK 中的任何一种语言编写。

与我们的上一个插件不同,这个插件期望你提供满足其导入接口的 KV 存储的主机函数。

我们想向插件公开两个函数,kv_write(key: String, value: Bytes),它将字节值写入键,以及 kv_read(key: String) -> Bytes,它读取给定 key 的字节。

use extism::*;

// pretend this is redis or something :)
type KVStore = std::collections::BTreeMap<String, Vec<u8>>;

// When a first argument separated with a semicolon is provided to `host_fn` it is used as the
// variable name and type for the `UserData` parameter
host_fn!(kv_read(user_data: KVStore; key: String) -> u32 {
    let kv = user_data.get()?;
    let kv = kv.lock().unwrap();
    let value = kv
        .get(&key)
        .map(|x| u32::from_le_bytes(x.clone().try_into().unwrap()))
        .unwrap_or_else(|| 0u32);
    Ok(value)
});

host_fn!(kv_write(user_data: KVStore; key: String, value: u32) {
    let kv = user_data.get()?;
    let mut kv = kv.lock().unwrap();
    kv.insert(key, value.to_le_bytes().to_vec());
    Ok(())
});

fn main() {
    let kv_store = UserData::new(KVStore::default());

    let url = Wasm::url(
        "https://github.com/extism/plugins/releases/latest/download/count_vowels_kvstore.wasm",
    );
    let manifest = Manifest::new([url]);
    let mut plugin = PluginBuilder::new(manifest)
        .with_wasi(true)
        .with_function(
            "kv_read",
            [PTR],
            [PTR],
            kv_store.clone(),
            kv_read,
        )
        .with_function(
            "kv_write",
            [PTR, PTR],
            [],
            kv_store.clone(),
            kv_write,
        )
        .build()
        .unwrap();

    for _ in 0..5 {
        let res = plugin
            .call::<&str, &str>("count_vowels", "Hello, world!")
            .unwrap();
        println!("{}", res);
    }
}

注意:要编写主机函数,您应该熟悉 CurrentPluginUserData 类型的方法。

现在我们可以调用事件

let res = plugin.call::<&str, &str>("count_vowels", "Hello, world!").unwrap();
println!("{}", res);
# => Read from key=count-vowels"
# => Writing value=3 from key=count-vowels"
# => {"count": 3, "total": 3, "vowels": "aeiouAEIOU"}

let res = plugin.call::<&str, &str>("count_vowels", "Hello, world!").unwrap();
println!("{}", res);
# => Read from key=count-vowels"
# => Writing value=6 from key=count-vowels"
# => {"count": 3, "total": 6, "vowels": "aeiouAEIOU"}

依赖项

~23–35MB
~592K SLoC