golang跨语言WebAssembly插件系统开发框架Extism Go SDK的使用
Golang跨语言WebAssembly插件系统开发框架Extism Go SDK的使用
安装
通过go get
安装:
go get github.com/extism/go-sdk
快速入门
创建插件
Extism的核心概念是插件(Plug-in)。插件可以看作存储在.wasm
文件中的代码模块。
以下是一个加载演示插件的示例:
package main
import (
"context"
"fmt"
"github.com/extism/go-sdk"
"os"
)
func main() {
manifest := extism.Manifest{
Wasm: []extism.Wasm{
extism.WasmUrl{
Url: "https://github.com/extism/plugins/releases/latest/download/count_vowels.wasm",
},
},
}
ctx := context.Background()
config := extism.PluginConfig{}
plugin, err := extism.NewPlugin(ctx, manifest, config, []extism.HostFunction{})
if err != nil {
fmt.Printf("Failed to initialize plugin: %v\n", err)
os.Exit(1)
}
}
调用插件函数
这个插件是用Rust编写的,功能是计算字符串中的元音字母数量。它导出了一个函数count_vowels
:
func main() {
// ...
data := []byte("Hello, World!")
exit, out, err := plugin.Call("count_vowels", data)
if err != nil {
fmt.Println(err)
os.Exit(int(exit))
}
response := string(out)
fmt.Println(response)
// => {"count": 3, "total": 3, "vowels": "aeiouAEIOU"}
}
运行后会输出JSON格式的元音统计结果:
$ go run main.go
# => {"count":3,"total":3,"vowels":"aeiouAEIOU"}
插件状态
插件可以是状态保持的(stateful)或无状态的(stateless)。我们的元音计数插件会记住它曾经统计过的元音总数:
func main () {
// ...
exit, out, err := plugin.Call("count_vowels", []byte("Hello, World!"))
if err != nil {
fmt.Println(err)
os.Exit(int(exit))
}
fmt.Println(string(out))
// => {"count": 3, "total": 6, "vowels": "aeiouAEIOU"}
exit, out, err = plugin.Call("count_vowels", []byte("Hello, World!"))
if err != nil {
fmt.Println(err)
os.Exit(int(exit))
}
fmt.Println(string(out))
// => {"count": 3, "total": 9, "vowels": "aeiouAEIOU"}
}
配置
插件可以接受配置对象。我们的元音计数插件可以配置哪些字符被视为元音:
func main() {
manifest := extism.Manifest{
Wasm: []extism.Wasm{
extism.WasmUrl{
Url: "https://github.com/extism/plugins/releases/latest/download/count_vowels.wasm",
},
},
Config: map[string]string{
"vowels": "aeiouyAEIOUY",
},
}
ctx := context.Background()
config := extism.PluginConfig{}
plugin, err := extism.NewPlugin(ctx, manifest, config, []extism.HostFunction{})
if err != nil {
fmt.Printf("Failed to initialize plugin: %v\n", err)
os.Exit(1)
}
exit, out, err := plugin.Call("count_vowels", []byte("Yellow, World!"))
if err != nil {
fmt.Println(err)
os.Exit(int(exit))
}
fmt.Println(string(out))
// => {"count": 4, "total": 4, "vowels": "aeiouAEIOUY"}
}
主机函数(Host Functions)
我们可以通过主机函数给插件提供新的能力。下面是一个KV存储的示例:
// 模拟KV存储
kvStore := make(map[string][]byte)
// 定义读取KV的函数
kvRead := extism.NewHostFunctionWithStack(
"kv_read",
func(ctx context.Context, p *extism.CurrentPlugin, stack []uint64) {
key, err := p.ReadString(stack[0])
if err != nil {
panic(err)
}
value, success := kvStore[key]
if !success {
value = []byte{0, 0, 0, 0}
}
stack[0], err = p.WriteBytes(value)
},
[]ValueType{ValueTypePTR},
[]ValueType{ValueTypePTR},
)
// 定义写入KV的函数
kvWrite := extism.NewHostFunctionWithStack(
"kv_write",
func(ctx context.Context, p *extism.CurrentPlugin, stack []uint64) {
key, err := p.ReadString(stack[0])
if err != nil {
panic(err)
}
value, err := p.ReadBytes(stack[1])
if err != nil {
panic(err)
}
kvStore[key] = value
},
[]ValueType{ValueTypePTR, ValueTypePTR},
[]ValueType{},
)
// 创建插件时传入主机函数
plugin, err := extism.NewPlugin(ctx, manifest, config, []extism.HostFunction{kvRead, kvWrite});
启用编译缓存
可以启用编译缓存来加速后续的插件初始化:
ctx := context.Background()
cache := wazero.NewCompilationCache()
defer cache.Close(ctx)
config := PluginConfig{
EnableWasi: true,
ModuleConfig: wazero.NewModuleConfig(),
RuntimeConfig: wazero.NewRuntimeConfig().WithCompilationCache(cache),
}
_, err := NewPlugin(ctx, manifest, config, []HostFunction{})
预编译插件
如果需要创建多个插件实例,可以先编译插件:
// 第一步:编译插件
compiledPlugin, err := extism.NewCompiledPlugin(ctx, manifest, config, []extism.HostFunction{})
if err != nil {
panic(err)
}
// 第二步:从编译好的插件创建实例
var wg sync.WaitGroup
for i := 0; i < 3; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
plugin, err := compiledPlugin.Instance(ctx, extism.PluginInstanceConfig{})
if err != nil {
fmt.Printf("Failed to initialize plugin: %v\n", err)
return
}
defer plugin.Close(ctx)
data := []byte(fmt.Sprintf("Hello, World from goroutine %d!", id))
_, out, err := plugin.Call("count_vowels", data)
if err != nil {
fmt.Println(err)
return
}
fmt.Printf("Goroutine %d result: %s\n", id, string(out))
}(i)
}
wg.Wait()
启用文件系统访问
WASM插件可以读写外部文件。我们需要在extism.Manifest
中添加AllowedPaths
映射:
package main
import (
"context"
"fmt"
"os"
extism "github.com/extism/go-sdk"
)
func main() {
manifest := extism.Manifest{
AllowedPaths: map[string]string{
// 将主机目录data映射到WASM运行时内的/mnt目录
"data": "/mnt",
},
Wasm: []extism.Wasm{
extism.WasmFile{
Path: "fs_plugin.wasm",
},
},
}
ctx := context.Background()
config := extism.PluginConfig{
EnableWasi: true,
}
plugin, err := extism.NewPlugin(ctx, manifest, config, []extism.HostFunction{})
if err != nil {
fmt.Printf("Failed to initialize plugin: %v\n", err)
os.Exit(1)
}
data := []byte("Hello world, this is written from within our wasm plugin.")
exit, _, err := plugin.Call("write_file", data)
if err != nil {
fmt.Println(err)
os.Exit(int(exit))
}
}
构建示例插件
示例插件也是用Go编写的,我们使用TinyGo来编译它们:
cd plugins/config
tinygo build -target wasi -o ../wasm/config.wasm main.go
以上是Extism Go SDK的基本使用方法,更多高级功能可以参考官方文档。
更多关于golang跨语言WebAssembly插件系统开发框架Extism Go SDK的使用的实战教程也可以访问 https://www.itying.com/category-94-b0.html
更多关于golang跨语言WebAssembly插件系统开发框架Extism Go SDK的使用的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
Extism Go SDK 使用指南
Extism 是一个跨语言的 WebAssembly 插件系统,允许你在应用程序中安全地运行用户提供的代码。Extism Go SDK 提供了与 Extism 交互的 Go 语言接口。
安装
首先安装 Extism Go SDK:
go get github.com/extism/go-sdk
基本使用
1. 加载并运行 WebAssembly 模块
package main
import (
"fmt"
"github.com/extism/go-sdk"
)
func main() {
// 从文件加载 wasm 模块
wasm, err := extism.NewModuleFromFile("example.wasm")
if err != nil {
panic(err)
}
// 创建插件配置
config := extism.PluginConfig{
ModuleConfig: extism.ModuleConfig{
Wasm: []extism.Wasm{wasm},
},
}
// 创建插件实例
plugin, err := extism.NewPlugin(config, extism.HostFunctions{}, false)
if err != nil {
panic(err)
}
// 调用插件中的函数
input := []byte("Hello, Extism!")
output, err := plugin.Call("greet", input)
if err != nil {
panic(err)
}
fmt.Println("Output:", string(output))
}
2. 定义宿主函数
宿主函数允许 WebAssembly 模块调用 Go 代码:
package main
import (
"fmt"
"github.com/extism/go-sdk"
)
// 定义宿主函数
func helloWorld(plugin *extism.Plugin, params []extism.Val, returns []extism.Val) extism.Error {
name, err := plugin.InputString(params[0])
if err != nil {
return extism.Error{Code: 1, Message: err.Error()}
}
greeting := fmt.Sprintf("Hello, %s from Go!", name)
err = returns[0].SetString(greeting, plugin)
if err != nil {
return extism.Error{Code: 1, Message: err.Error()}
}
return extism.Error{}
}
func main() {
// 创建宿主函数映射
hostFunctions := extism.HostFunctions{
"hello_world": helloWorld,
}
// 加载 wasm 模块
wasm, err := extism.NewModuleFromFile("example.wasm")
if err != nil {
panic(err)
}
// 创建插件配置
config := extism.PluginConfig{
ModuleConfig: extism.ModuleConfig{
Wasm: []extism.Wasm{wasm},
},
}
// 创建插件实例
plugin, err := extism.NewPlugin(config, hostFunctions, false)
if err != nil {
panic(err)
}
// 调用插件函数
output, err := plugin.Call("call_host", []byte("Extism"))
if err != nil {
panic(err)
}
fmt.Println("Output:", string(output))
}
高级功能
1. 内存管理
// 分配内存
offset, err := plugin.Malloc(len(input))
if err != nil {
panic(err)
}
// 写入内存
err = plugin.MemWrite(offset, input)
if err != nil {
panic(err)
}
// 读取内存
output, err := plugin.MemRead(offset, length)
if err != nil {
panic(err)
}
2. 使用 WASI
config := extism.PluginConfig{
ModuleConfig: extism.ModuleConfig{
Wasm: []extism.Wasm{wasm},
},
WasiConfig: &extism.WasiConfig{
Args: []string{"arg1", "arg2"},
Env: map[string]string{
"KEY": "VALUE",
},
// 其他 WASI 配置...
},
}
3. 多模块支持
config := extism.PluginConfig{
ModuleConfig: extism.ModuleConfig{
Wasm: []extism.Wasm{
extism.NewModuleFromFile("module1.wasm"),
extism.NewModuleFromFile("module2.wasm"),
},
},
}
性能优化建议
- 复用插件实例:创建插件实例有一定开销,尽可能复用
- 批量调用:减少 Go 和 WASM 之间的上下文切换
- 合理设置内存限制:避免不必要的内存分配
- 预编译 WASM:使用 AOT 编译优化性能
错误处理
Extism Go SDK 提供了详细的错误信息:
output, err := plugin.Call("function", input)
if err != nil {
if extismErr, ok := err.(extism.Error); ok {
fmt.Printf("Error code: %d, message: %s\n", extismErr.Code, extismErr.Message)
} else {
fmt.Println("Non-Extism error:", err)
}
return
}
实际应用场景
- 用户自定义逻辑:允许用户上传业务逻辑作为插件
- 沙盒环境:安全执行不受信任的代码
- 多语言集成:在 Go 应用中运行其他语言编写的逻辑
- 动态扩展:无需重新编译主程序即可添加新功能
Extism Go SDK 提供了一种安全、高效的方式来集成 WebAssembly 插件到 Go 应用程序中,特别适合需要灵活性和安全性的场景。