Extism Go SDK
repository·main·Indexed 41 days ago
https://github.com/extism/go-sdkA SDK for Go developers to integrate the Extism runtime into host applications, enabling the secure and efficient execution of WebAssembly (Wasm) plugins. It provides tools for initializing plugins via manifests, calling exported functions, implementing custom host functions, and managing plugin state and memory.
What's inside Extism Go SDK
- Plug-ins can be stateful or stateless. A stateful plug-in can maintain state between calls using internal variables. This state persists for the lifetime of the plug-in instance and is cleared when the plug-in is freed or a new one is initialized. Note that each individual plug-in instance maintains its own unique state.
Implement Host Functions to extend Plug-in capabilities
mainHost functions allow you to grant new capabilities to plug-ins (like access to a database or a key-value store) by passing Go functions into the plug-in. Use
extism.NewHostFunctionWithStackto define these functions. Inside the host function, you use the*extism.CurrentPluginparameter (often namedp) to read arguments from the stack or write results back to it using methods likeReadString,ReadBytes, andWriteBytes.// Example: A host function that reads a string from the stack 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}, // Input types []ValueType{ValueTypePTR}, // Output types ) // Pass the host function to NewPlugin plugin, err := extism.NewPlugin(ctx, manifest, config, []extism.HostFunction{kvRead})Enable filesystem access for Wasm plugins
mainTo allow a Wasm plugin to read or write files on the host system, you must:
- Set
EnableWasi: truein theextism.PluginConfig. - Map host directories to plugin-internal paths using the
AllowedPathsfield in theextism.Manifest. The map key is the 'plugin' path and the value is the 'host' path.
manifest := extism.Manifest{ AllowedPaths: map[string]string{ // Maps the host directory "/home/user/data" to the plugin's "/mnt" "/mnt": "/home/user/data", }, Wasm: []extism.Wasm{ extism.WasmFile{Path: "fs_plugin.wasm"}, }, } config := extism.PluginConfig{ EnableWasi: true, } plugin, err := extism.NewPlugin(ctx, manifest, config, []extism.HostFunction{})- Set
Pre-compile plugins for concurrent usage
mainIf you need to create many instances of the same plug-in (e.g., in a high-concurrency environment), use
extism.NewCompiledPluginto compile the module once. You can then efficiently create multiple independent instances from that single compiled object usingcompiledPlugin.Instance(ctx, extism.PluginInstanceConfig{}). Each instance will maintain its own separate state.// 1. Compile once compiledPlugin, err := extism.NewCompiledPlugin(ctx, manifest, config, []extism.HostFunction{}) // 2. Create instances in goroutines for i := 0; i < 3; i++ { go func() { plugin, err := compiledPlugin.Instance(ctx, extism.PluginInstanceConfig{}) if err != nil { return } defer plugin.Close(ctx) // ... call plugin }() }Build example plugins using TinyGo
mainThe example plugins in this repository are written in Go and require TinyGo to compile them into WebAssembly (WASM) targets. To build a specific plugin (e.g., the
configplugin), navigate to its directory and use thetinygo buildcommand with the-target wasiflag.cd plugins/config tinygo build -target wasi -o ../wasm/config.wasm main.goOptimize performance with Compilation Cache
mainTo speed up subsequent initializations of Wasm modules, you can enable a compilation cache using the underlying Wazero runtime. This involves creating a
wazero.NewCompilationCache()and passing it into theRuntimeConfigwithin yourextism.PluginConfig.ctx := context.Background() cache := wazero.NewCompilationCache() // Ensure the cache is not closed until all dependent plugins are finished defer cache.Close(ctx) config := extism.PluginConfig{ EnableWasi: true, ModuleConfig: wazero.NewModuleConfig(), RuntimeConfig: wazero.NewRuntimeConfig().WithCompilationCache(cache), } _, err := extism.NewPlugin(ctx, manifest, config, []extism.HostFunction{})Install the Extism Go SDK
mainTo integrate the Extism runtime into your Go host applications, install the SDK using
go get.go get github.com/extism/go-sdkInitialize an Extism Plug-in
mainA plug-in is a code module stored in a
.wasmfile. You can load plug-ins from a local file on disk or from a URL. Useextism.NewPluginto initialize a plug-in by providing acontext.Context, anextism.Manifest(which defines the Wasm source), anextism.PluginConfig, and a slice ofextism.HostFunctions.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) } }Define Wasm sources in a Manifest
mainThe
Manifestsupports three ways to provide WebAssembly data via theWasminterface:- WasmData: In-memory byte arrays.
- WasmFile: Loading from a local file path.
- WasmUrl: Fetching from a remote URL with optional HTTP methods and headers.
Each can optionally include a
Hashfor verification and aNamefor identification.// Example of different Wasm sources wasmSources := []extism.Wasm{ extism.WasmData{Data: []byte{0x00, 0x01}, Name: "in-memory"}, extism.WasmFile{Path: "./plugin.wasm", Name: "local-file"}, extism.WasmUrl{Url: "https://example.com/plugin.wasm", Method: "GET", Name: "remote"}, }How HostFunctionStackCallback works
mainA
HostFunctionStackCallbackis a Go function that acts as a host-side implementation of a function called by a WebAssembly plugin.Signature:
func(ctx context.Context, p *CurrentPlugin, stack []uint64)Behavior:
- Parameters: Read input parameters from the
stackslice usingDecodeXXXfunctions. The index in the stack corresponds to the parameter order. - Plugin Access: Use the
p *CurrentPluginargument to access the plugin's memory (viap.Memory()), log messages (p.Log()), or manage memory (p.Alloc(),p.Free()). - Results: Write return values back into the
stackslice starting at index zero. - Lifecycle: The
stackmust not be used after the function returns.
Example Pattern:
// Read two I32 parameters and return one I32 result func(ctx context.Context, p *CurrentPlugin, stack []uint64) { val1 := DecodeI32(stack[0]) val2 := DecodeI32(stack[1]) stack[0] = EncodeI32(val1 + val2) }- Parameters: Read input parameters from the
Configure a plugin using a Manifest
mainA
Manifestdefines the configuration for an Extism plugin, including which WebAssembly modules to load, memory limits, environment variables, and security constraints like allowed hosts and file paths. You can define Wasm modules using in-memory data, local files, or remote URLs.manifest := extism.Manifest{ Wasm: []extism.Wasm{ extism.WasmFile{Path: "plugin.wasm"}, }, Config: map[string]string{ "key": "value", }, AllowedHosts: []string{"example.com"}, AllowedPaths: map[string]string{"/tmp": "rw"}, }Configure filesystem access via AllowedPaths
mainYou can restrict or provide filesystem access to a plugin using the
AllowedPathsfield in theManifest. Paths prefixed withro:are mounted as read-only directories.When instantiating a plugin, these paths are applied to the module's filesystem configuration. Note that if
AllowedPathsis used, the filesystem configuration provided inPluginConfig.ModuleConfigis ignored.