higress-wasm-go-plugin

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Higress WASM Go Plugin Development

Higress WASM Go插件开发

Develop Higress gateway WASM plugins using Go language with the
wasm-go
SDK.
使用
wasm-go
SDK,通过Go语言开发Higress网关WASM插件。

Quick Start

快速开始

Project Setup

项目搭建

bash
undefined
bash
undefined

Create project directory

Create project directory

mkdir my-plugin && cd my-plugin
mkdir my-plugin && cd my-plugin

Initialize Go module

Initialize Go module

go mod init my-plugin
go mod init my-plugin

Set proxy (China)

Set proxy (China)

Download dependencies

Download dependencies

go get github.com/higress-group/proxy-wasm-go-sdk@go-1.24 go get github.com/higress-group/wasm-go@main go get github.com/tidwall/gjson
undefined
go get github.com/higress-group/proxy-wasm-go-sdk@go-1.24 go get github.com/higress-group/wasm-go@main go get github.com/tidwall/gjson
undefined

Minimal Plugin Template

极简插件模板

go
package main

import (
    "github.com/higress-group/wasm-go/pkg/wrapper"
    "github.com/higress-group/proxy-wasm-go-sdk/proxywasm"
    "github.com/higress-group/proxy-wasm-go-sdk/proxywasm/types"
    "github.com/tidwall/gjson"
)

func main() {}

func init() {
    wrapper.SetCtx(
        "my-plugin",
        wrapper.ParseConfig(parseConfig),
        wrapper.ProcessRequestHeaders(onHttpRequestHeaders),
    )
}

type MyConfig struct {
    Enabled bool
}

func parseConfig(json gjson.Result, config *MyConfig) error {
    config.Enabled = json.Get("enabled").Bool()
    return nil
}

func onHttpRequestHeaders(ctx wrapper.HttpContext, config MyConfig) types.Action {
    if config.Enabled {
        proxywasm.AddHttpRequestHeader("x-my-header", "hello")
    }
    return types.HeaderContinue
}
go
package main

import (
    "github.com/higress-group/wasm-go/pkg/wrapper"
    "github.com/higress-group/proxy-wasm-go-sdk/proxywasm"
    "github.com/higress-group/proxy-wasm-go-sdk/proxywasm/types"
    "github.com/tidwall/gjson"
)

func main() {}

func init() {
    wrapper.SetCtx(
        "my-plugin",
        wrapper.ParseConfig(parseConfig),
        wrapper.ProcessRequestHeaders(onHttpRequestHeaders),
    )
}

type MyConfig struct {
    Enabled bool
}

func parseConfig(json gjson.Result, config *MyConfig) error {
    config.Enabled = json.Get("enabled").Bool()
    return nil
}

func onHttpRequestHeaders(ctx wrapper.HttpContext, config MyConfig) types.Action {
    if config.Enabled {
        proxywasm.AddHttpRequestHeader("x-my-header", "hello")
    }
    return types.HeaderContinue
}

Compile

编译

bash
go mod tidy
GOOS=wasip1 GOARCH=wasm go build -buildmode=c-shared -o main.wasm ./
bash
go mod tidy
GOOS=wasip1 GOARCH=wasm go build -buildmode=c-shared -o main.wasm ./

Core Concepts

核心概念

Plugin Lifecycle

插件生命周期

  1. init() - Register plugin with
    wrapper.SetCtx()
  2. parseConfig - Parse YAML config (auto-converted to JSON)
  3. HTTP processing phases - Handle requests/responses
  1. init() - 通过
    wrapper.SetCtx()
    注册插件
  2. parseConfig - 解析YAML配置(自动转换为JSON)
  3. HTTP处理阶段 - 处理请求/响应

HTTP Processing Phases

HTTP处理阶段

PhaseTriggerHandler
Request HeadersGateway receives client request headers
ProcessRequestHeaders
Request BodyGateway receives client request body
ProcessRequestBody
Response HeadersGateway receives backend response headers
ProcessResponseHeaders
Response BodyGateway receives backend response body
ProcessResponseBody
Stream DoneHTTP stream completes
ProcessStreamDone
阶段触发时机处理器
请求头网关接收到客户端请求头时
ProcessRequestHeaders
请求体网关接收到客户端请求体时
ProcessRequestBody
响应头网关接收到后端响应头时
ProcessResponseHeaders
响应体网关接收到后端响应体时
ProcessResponseBody
流完成HTTP流结束时
ProcessStreamDone

Action Return Values

动作返回值

ActionBehavior
types.HeaderContinue
Continue to next filter
types.HeaderStopIteration
Stop header processing, wait for body
types.HeaderStopAllIterationAndWatermark
Stop all processing, buffer data, call
proxywasm.ResumeHttpRequest/Response()
to resume
动作行为
types.HeaderContinue
继续执行下一个过滤器
types.HeaderStopIteration
停止头处理,等待体数据
types.HeaderStopAllIterationAndWatermark
停止所有处理,缓存数据,调用
proxywasm.ResumeHttpRequest/Response()
恢复处理

API Reference

API参考

HttpContext Methods

HttpContext方法

go
// Request info (cached, safe to call in any phase)
ctx.Scheme()   // :scheme
ctx.Host()     // :authority
ctx.Path()     // :path
ctx.Method()   // :method

// Body handling
ctx.HasRequestBody()        // Check if request has body
ctx.HasResponseBody()       // Check if response has body
ctx.DontReadRequestBody()   // Skip reading request body
ctx.DontReadResponseBody()  // Skip reading response body
ctx.BufferRequestBody()     // Buffer instead of stream
ctx.BufferResponseBody()    // Buffer instead of stream

// Content detection
ctx.IsWebsocket()           // Check WebSocket upgrade
ctx.IsBinaryRequestBody()   // Check binary content
ctx.IsBinaryResponseBody()  // Check binary content

// Context storage
ctx.SetContext(key, value)
ctx.GetContext(key)
ctx.GetStringContext(key, defaultValue)
ctx.GetBoolContext(key, defaultValue)

// Custom logging
ctx.SetUserAttribute(key, value)
ctx.WriteUserAttributeToLog()
go
// Request info (cached, safe to call in any phase)
ctx.Scheme()   // :scheme
ctx.Host()     // :authority
ctx.Path()     // :path
ctx.Method()   // :method

// Body handling
ctx.HasRequestBody()        // Check if request has body
ctx.HasResponseBody()       // Check if response has body
ctx.DontReadRequestBody()   // Skip reading request body
ctx.DontReadResponseBody()  // Skip reading response body
ctx.BufferRequestBody()     // Buffer instead of stream
ctx.BufferResponseBody()    // Buffer instead of stream

// Content detection
ctx.IsWebsocket()           // Check WebSocket upgrade
ctx.IsBinaryRequestBody()   // Check binary content
ctx.IsBinaryResponseBody()  // Check binary content

// Context storage
ctx.SetContext(key, value)
ctx.GetContext(key)
ctx.GetStringContext(key, defaultValue)
ctx.GetBoolContext(key, defaultValue)

// Custom logging
ctx.SetUserAttribute(key, value)
ctx.WriteUserAttributeToLog()

Header/Body Operations (proxywasm)

头/体操作(proxywasm)

go
// Request headers
proxywasm.GetHttpRequestHeader(name)
proxywasm.AddHttpRequestHeader(name, value)
proxywasm.ReplaceHttpRequestHeader(name, value)
proxywasm.RemoveHttpRequestHeader(name)
proxywasm.GetHttpRequestHeaders()
proxywasm.ReplaceHttpRequestHeaders(headers)

// Response headers
proxywasm.GetHttpResponseHeader(name)
proxywasm.AddHttpResponseHeader(name, value)
proxywasm.ReplaceHttpResponseHeader(name, value)
proxywasm.RemoveHttpResponseHeader(name)
proxywasm.GetHttpResponseHeaders()
proxywasm.ReplaceHttpResponseHeaders(headers)

// Request body (only in body phase)
proxywasm.GetHttpRequestBody(start, size)
proxywasm.ReplaceHttpRequestBody(body)
proxywasm.AppendHttpRequestBody(data)
proxywasm.PrependHttpRequestBody(data)

// Response body (only in body phase)
proxywasm.GetHttpResponseBody(start, size)
proxywasm.ReplaceHttpResponseBody(body)
proxywasm.AppendHttpResponseBody(data)
proxywasm.PrependHttpResponseBody(data)

// Direct response
proxywasm.SendHttpResponse(statusCode, headers, body, grpcStatus)

// Flow control
proxywasm.ResumeHttpRequest()   // Resume paused request
proxywasm.ResumeHttpResponse()  // Resume paused response
go
// Request headers
proxywasm.GetHttpRequestHeader(name)
proxywasm.AddHttpRequestHeader(name, value)
proxywasm.ReplaceHttpRequestHeader(name, value)
proxywasm.RemoveHttpRequestHeader(name)
proxywasm.GetHttpRequestHeaders()
proxywasm.ReplaceHttpRequestHeaders(headers)

// Response headers
proxywasm.GetHttpResponseHeader(name)
proxywasm.AddHttpResponseHeader(name, value)
proxywasm.ReplaceHttpResponseHeader(name, value)
proxywasm.RemoveHttpResponseHeader(name)
proxywasm.GetHttpResponseHeaders()
proxywasm.ReplaceHttpResponseHeaders(headers)

// Request body (only in body phase)
proxywasm.GetHttpRequestBody(start, size)
proxywasm.ReplaceHttpRequestBody(body)
proxywasm.AppendHttpRequestBody(data)
proxywasm.PrependHttpRequestBody(data)

// Response body (only in body phase)
proxywasm.GetHttpResponseBody(start, size)
proxywasm.ReplaceHttpResponseBody(body)
proxywasm.AppendHttpResponseBody(data)
proxywasm.PrependHttpResponseBody(data)

// Direct response
proxywasm.SendHttpResponse(statusCode, headers, body, grpcStatus)

// Flow control
proxywasm.ResumeHttpRequest()   // Resume paused request
proxywasm.ResumeHttpResponse()  // Resume paused response

Common Patterns

常见模式

External HTTP Call

外部HTTP调用

See references/http-client.md for complete HTTP client patterns.
go
func parseConfig(json gjson.Result, config *MyConfig) error {
    serviceName := json.Get("serviceName").String()
    servicePort := json.Get("servicePort").Int()
    config.client = wrapper.NewClusterClient(wrapper.FQDNCluster{
        FQDN: serviceName,
        Port: servicePort,
    })
    return nil
}

func onHttpRequestHeaders(ctx wrapper.HttpContext, config MyConfig) types.Action {
    err := config.client.Get("/api/check", nil, func(statusCode int, headers http.Header, body []byte) {
        if statusCode != 200 {
            proxywasm.SendHttpResponse(403, nil, []byte("Forbidden"), -1)
            return
        }
        proxywasm.ResumeHttpRequest()
    }, 3000) // timeout ms
    
    if err != nil {
        return types.HeaderContinue // fallback on error
    }
    return types.HeaderStopAllIterationAndWatermark
}
完整的HTTP客户端模式请参考references/http-client.md
go
func parseConfig(json gjson.Result, config *MyConfig) error {
    serviceName := json.Get("serviceName").String()
    servicePort := json.Get("servicePort").Int()
    config.client = wrapper.NewClusterClient(wrapper.FQDNCluster{
        FQDN: serviceName,
        Port: servicePort,
    })
    return nil
}

func onHttpRequestHeaders(ctx wrapper.HttpContext, config MyConfig) types.Action {
    err := config.client.Get("/api/check", nil, func(statusCode int, headers http.Header, body []byte) {
        if statusCode != 200 {
            proxywasm.SendHttpResponse(403, nil, []byte("Forbidden"), -1)
            return
        }
        proxywasm.ResumeHttpRequest()
    }, 3000) // timeout ms
    
    if err != nil {
        return types.HeaderContinue // fallback on error
    }
    return types.HeaderStopAllIterationAndWatermark
}

Redis Integration

Redis集成

See references/redis-client.md for complete Redis patterns.
go
func parseConfig(json gjson.Result, config *MyConfig) error {
    config.redis = wrapper.NewRedisClusterClient(wrapper.FQDNCluster{
        FQDN: json.Get("redisService").String(),
        Port: json.Get("redisPort").Int(),
    })
    return config.redis.Init(
        json.Get("username").String(),
        json.Get("password").String(),
        json.Get("timeout").Int(),
    )
}
完整的Redis模式请参考references/redis-client.md
go
func parseConfig(json gjson.Result, config *MyConfig) error {
    config.redis = wrapper.NewRedisClusterClient(wrapper.FQDNCluster{
        FQDN: json.Get("redisService").String(),
        Port: json.Get("redisPort").Int(),
    })
    return config.redis.Init(
        json.Get("username").String(),
        json.Get("password").String(),
        json.Get("timeout").Int(),
    )
}

Multi-level Config

多级配置

插件配置支持在控制台不同级别设置:全局、域名级、路由级。控制面会自动处理配置的优先级和匹配逻辑,插件代码中通过
parseConfig
解析到的就是当前请求匹配到的配置。
插件配置支持在控制台不同级别设置:全局、域名级、路由级。控制面会自动处理配置的优先级和匹配逻辑,插件代码中通过
parseConfig
解析到的就是当前请求匹配到的配置。

Local Testing

本地测试

See references/local-testing.md for Docker Compose setup.
Docker Compose搭建方式请参考references/local-testing.md

Advanced Topics

进阶主题

See references/advanced-patterns.md for:
  • Streaming body processing
  • Route call pattern
  • Tick functions (periodic tasks)
  • Leader election
  • Memory management
  • Custom logging
请参考references/advanced-patterns.md了解:
  • 流式体处理
  • 路由调用模式
  • Tick函数(周期性任务)
  • 主节点选举
  • 内存管理
  • 自定义日志

Best Practices

最佳实践

  1. Never call Resume after SendHttpResponse - Response auto-resumes
  2. Check HasRequestBody() before returning HeaderStopIteration - Avoids blocking
  3. Use cached ctx methods -
    ctx.Path()
    works in any phase,
    GetHttpRequestHeader(":path")
    only in header phase
  4. Handle external call failures gracefully - Return
    HeaderContinue
    on error to avoid blocking
  5. Set appropriate timeouts - Default HTTP call timeout is 500ms
  1. 在SendHttpResponse后切勿调用Resume - 响应会自动恢复
  2. 返回HeaderStopIteration前检查HasRequestBody() - 避免阻塞
  3. 使用缓存的ctx方法 -
    ctx.Path()
    可在任意阶段使用,
    GetHttpRequestHeader(":path")
    仅能在头阶段使用
  4. 优雅处理外部调用失败 - 出错时返回
    HeaderContinue
    避免阻塞
  5. 设置合适的超时时间 - 默认HTTP调用超时为500ms