mirror of
https://github.com/alibaba/higress.git
synced 2026-06-01 08:37:26 +08:00
252 lines
7.3 KiB
Markdown
252 lines
7.3 KiB
Markdown
---
|
|
name: higress-wasm-go-plugin
|
|
description: Develop Higress WASM plugins using Go 1.24+. Use when creating, modifying, or debugging Higress gateway plugins for HTTP request/response processing, external service calls, Redis integration, or custom gateway logic.
|
|
---
|
|
|
|
# Higress WASM Go Plugin Development
|
|
|
|
Develop Higress gateway WASM plugins using Go language with the `wasm-go` SDK.
|
|
|
|
## Quick Start
|
|
|
|
### Project Setup
|
|
|
|
```bash
|
|
# Create project directory
|
|
mkdir my-plugin && cd my-plugin
|
|
|
|
# Initialize Go module
|
|
go mod init my-plugin
|
|
|
|
# Set proxy (China)
|
|
go env -w GOPROXY=https://proxy.golang.com.cn,direct
|
|
|
|
# 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
|
|
```
|
|
|
|
### 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
|
|
}
|
|
```
|
|
|
|
### Compile
|
|
|
|
```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
|
|
|
|
### HTTP Processing Phases
|
|
|
|
| Phase | Trigger | Handler |
|
|
|-------|---------|---------|
|
|
| Request Headers | Gateway receives client request headers | `ProcessRequestHeaders` |
|
|
| Request Body | Gateway receives client request body | `ProcessRequestBody` |
|
|
| Response Headers | Gateway receives backend response headers | `ProcessResponseHeaders` |
|
|
| Response Body | Gateway receives backend response body | `ProcessResponseBody` |
|
|
| Stream Done | HTTP stream completes | `ProcessStreamDone` |
|
|
|
|
### Action Return Values
|
|
|
|
| Action | Behavior |
|
|
|--------|----------|
|
|
| `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 |
|
|
|
|
## API Reference
|
|
|
|
### HttpContext Methods
|
|
|
|
```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)
|
|
|
|
```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
|
|
|
|
See [references/http-client.md](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
|
|
}
|
|
```
|
|
|
|
### Redis Integration
|
|
|
|
See [references/redis-client.md](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(),
|
|
)
|
|
}
|
|
```
|
|
|
|
### Multi-level Config
|
|
|
|
插件配置支持在控制台不同级别设置:全局、域名级、路由级。控制面会自动处理配置的优先级和匹配逻辑,插件代码中通过 `parseConfig` 解析到的就是当前请求匹配到的配置。
|
|
|
|
## Local Testing
|
|
|
|
See [references/local-testing.md](references/local-testing.md) for Docker Compose setup.
|
|
|
|
## Advanced Topics
|
|
|
|
See [references/advanced-patterns.md](references/advanced-patterns.md) for:
|
|
- Streaming body processing
|
|
- Route call pattern
|
|
- Tick functions (periodic tasks)
|
|
- Leader election
|
|
- Memory management
|
|
- Custom logging
|
|
|
|
## 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
|