ゲートウェイプラグインを開発すると、API ゲートウェイのコア機能が拡張され、より複雑で特定のビジネスニーズに対応できるようになります。このトピックでは、Go を使用してゲートウェイプラグインを開発する方法について説明し、ローカルでの開発とデバッグのガイダンスを提供します。
Higress は、TinyGo 0.29 + Go 1.20 のコンパイルソリューションから、Go 1.24 でサポートされるネイティブ WebAssembly (Wasm) コンパイルに移行しました。Go 1.24 は現在、Wasm ファイルのコンパイルをネイティブでサポートしています。
以前に TinyGo を使用してプラグインをコンパイルしていた場合は、Go 1.24 のコンパイルモードに移行するために調整を行う必要があります。これらの調整には、go.mod ファイル内の依存関係の変更や、プラグインの初期化ロジックを main 関数から init 関数に移動することが含まれます。具体的な例については、このトピックの後半の内容をご参照ください。
元々 TinyGo で実装されていたプラグインについては、次の互換性の調整にご注意ください:
1. ヘッダー処理フェーズ中に外部サービスを呼び出し、type.ActionPause を返す場合は、戻り値の型を types.HeaderStopAllIterationAndWatermark に変更する必要があります。実装例については、このトピックの後半にある外部サービスを呼び出すプラグインの例をご参照ください。
2. TinyGo が標準の regexp ライブラリを完全にサポートしていないために go-re2 ライブラリを使用していた場合は、公式の Go regexp パッケージに置き換える必要があります。
前提条件
Go をインストールします。
Go
公式インストールガイド (バージョン 1.24 以降が必要です)。
Go 1.24 でコンパイルされたプラグインには、クラウドネイティブ API ゲートウェイのバージョン 2.1.5 以降が必要です。以前のゲートウェイバージョンについては、「Go を使用して Wasm プラグインを開発する」をご参照ください。
Windows
インストーラーファイルをダウンロードします。
ダウンロードしたインストーラーファイルを開いてインストールを開始します。デフォルトでは、Go は
Program FilesまたはProgram Files (x86)ディレクトリにインストールされます。インストールが完了したら、Win+R キーボードショートカットを押して [ファイル名を指定して実行] ウィンドウを開きます。
cmdと入力し、[OK] をクリックしてコマンドプロンプトを開きます。go versionコマンドを実行します。コマンドが現在のバージョンを返した場合、インストールは成功です。
macOS
インストーラーファイルをダウンロードします。
ダウンロードしたインストーラーファイルをダブルクリックしてインストールを開始します。デフォルトでは、Go は
/usr/local/goディレクトリにインストールされます。ターミナルを開き、
go versionコマンドを実行します。コマンドが現在のバージョンを返した場合、インストールは成功です。
Linux
インストーラーファイルをダウンロードします。
次のコマンドを実行して Go をインストールします。
Go をインストールします。
rm -rf /usr/local/go && tar -C /usr/local -xzf go1.24.4.linux-amd64.tar.gz環境変数を設定します。
export PATH=$PATH:/usr/local/go/bingo versionコマンドを実行します。コマンドが現在のバージョンを返した場合、インストールは成功です。
プラグインの作成
プロジェクトディレクトリの初期化
プロジェクトディレクトリ (例:
wasm-demo-go) を作成します。ディレクトリで、次のコマンドを実行して Go プロジェクトを初期化します。
go mod init wasm-demo-go中国本土にいる場合は、依存関係をダウンロードするためにプロキシを設定する必要がある場合があります。
go env -w GOPROXY=https://proxy.golang.com.cn,directプラグインをビルドするための依存関係をダウンロードします。
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
main.go ファイルの作成
次の例では、プラグイン構成で mockEnable: true が設定されている場合、hello world 応答が直接返されます。プラグインが構成されていない場合、または mockEnable: false が設定されている場合は、元のリクエストに hello: world ヘッダーが追加されます。その他の例については、この Topic の「その他の例」セクションをご参照ください。
ゲートウェイコンソールのプラグイン設定は YAML 形式です。プラグインに送信される前に自動的に JSON 形式に変換されます。したがって、この例の `parseConfig` 関数は、JSON から直接設定を解析できます。
package main
import (
"github.com/higress-group/wasm-go/pkg/wrapper"
logs "github.com/higress-group/wasm-go/pkg/log"
"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.ParseConfigBy(parseConfig),
// リクエストヘッダーを処理するためのユーザー定義関数を設定します
wrapper.ProcessRequestHeadersBy(onHttpRequestHeaders),
)
}
// カスタムプラグイン設定
type MyConfig struct {
mockEnable bool
}
// コンソールからの YAML 設定は自動的に JSON に変換されます。json パラメーターから直接設定を解析できます。
func parseConfig(json gjson.Result, config *MyConfig, log logs.Log) error {
// 設定を解析し、config で更新します。
config.mockEnable = json.Get("mockEnable").Bool()
return nil
}
func onHttpRequestHeaders(ctx wrapper.HttpContext, config MyConfig, log logs.Log) types.Action {
proxywasm.AddHttpRequestHeader("hello", "world")
if config.mockEnable {
proxywasm.SendHttpResponse(200, nil, []byte("hello world"), -1)
}
return types.HeaderContinue
}HTTP 処理マウントポイント
上記の例では、wrapper.ProcessRequestHeadersBy メソッドを使用して、ユーザー定義関数 onHttpRequestHeaders を HTTP リクエストヘッダー処理フェーズ に適用します。他のフェーズにユーザー定義関数を設定するために、次のメソッドを使用することもできます。
HTTP 処理フェーズ | トリガー条件 | マウントメソッド |
HTTP リクエストヘッダー処理フェーズ | ゲートウェイがクライアントからリクエストヘッダーデータを受信したとき |
|
HTTP リクエストボディ処理フェーズ | ゲートウェイがクライアントからリクエストボディデータを受信したとき |
|
HTTP レスポンスヘッダー処理フェーズ | ゲートウェイがバックエンドサービスからレスポンスヘッダーデータを受信したとき |
|
HTTP レスポンスボディ処理フェーズ | ゲートウェイがバックエンドサービスからレスポンスボディデータを受信したとき |
|
ユーティリティメソッド
上記の例の proxywasm.AddHttpRequestHeader および proxywasm.SendHttpResponse メソッドは、プラグイン SDK によって提供される 2 つのユーティリティメソッドです。主なユーティリティメソッドを次の表に示します。
カテゴリ | メソッド名 | 目的 | 有効な HTTP 処理フェーズ |
リクエストヘッダー処理 | GetHttpRequestHeaders | クライアントからすべてのリクエストヘッダーを取得します | HTTP リクエストヘッダー処理フェーズ |
ReplaceHttpRequestHeaders | クライアントからのすべてのリクエストヘッダーを置き換えます | HTTP リクエストヘッダー処理フェーズ | |
GetHttpRequestHeader | クライアントから特定のリクエストヘッダーを取得します | HTTP リクエストヘッダー処理フェーズ | |
RemoveHttpRequestHeader | クライアントから特定のリクエストヘッダーを削除します | HTTP リクエストヘッダー処理フェーズ | |
ReplaceHttpRequestHeader | クライアントから特定のリクエストヘッダーを置き換えます | HTTP リクエストヘッダー処理フェーズ | |
AddHttpRequestHeader | 新しいクライアントリクエストヘッダーを追加します | HTTP リクエストヘッダー処理フェーズ | |
リクエストボディ処理 | GetHttpRequestBody | クライアントリクエストボディを取得します | HTTP リクエストボディ処理フェーズ |
AppendHttpRequestBody | クライアントリクエストボディの末尾に指定されたバイト文字列を追加します | HTTP リクエストボディ処理フェーズ | |
PrependHttpRequestBody | クライアントリクエストボディの先頭に指定されたバイト文字列を追加します | HTTP リクエストボディ処理フェーズ | |
ReplaceHttpRequestBody | クライアントリクエストボディを置き換えます | HTTP リクエストボディ処理フェーズ | |
レスポンスヘッダー処理 | GetHttpResponseHeaders | バックエンドからすべてのレスポンスヘッダーを取得します | HTTP レスポンスヘッダー処理フェーズ |
ReplaceHttpResponseHeaders | バックエンドからのすべてのレスポンスヘッダーを置き換えます | HTTP レスポンスヘッダー処理フェーズ | |
GetHttpResponseHeader | バックエンドから特定のレスポンスヘッダーを取得します | HTTP レスポンスヘッダー処理フェーズ | |
RemoveHttpResponseHeader | バックエンドから特定のレスポンスヘッダーを削除します | HTTP レスポンスヘッダー処理フェーズ | |
ReplaceHttpResponseHeader | バックエンドから特定のレスポンスヘッダーを置き換えます | HTTP レスポンスヘッダー処理フェーズ | |
AddHttpResponseHeader | 新しいバックエンドレスポンスヘッダーを追加します | HTTP レスポンスヘッダー処理フェーズ | |
レスポンスボディ処理 | GetHttpResponseBody | クライアントリクエストボディを取得できます。 | HTTP レスポンスボディ処理フェーズ |
AppendHttpResponseBody | バックエンドレスポンスボディの末尾に指定されたバイト文字列を追加します | HTTP レスポンスボディ処理フェーズ | |
PrependHttpResponseBody | バックエンドレスポンスボディの先頭に指定されたバイト文字列を追加します | HTTP レスポンスボディ処理フェーズ | |
ReplaceHttpResponseBody | バックエンドレスポンスボディを置き換えます | HTTP レスポンスボディ処理フェーズ | |
HTTP 呼び出し | DispatchHttpCall | HTTP リクエストを送信します | - |
GetHttpCallResponseHeaders | `DispatchHttpCall` リクエストのレスポンスヘッダーを取得します | - | |
GetHttpCallResponseBody | `DispatchHttpCall` リクエストのレスポンスボディを取得します | - | |
GetHttpCallResponseTrailers | `DispatchHttpCall` リクエストのレスポンストレーラーを取得します | - | |
直接応答 | SendHttpResponse | 特定の HTTP 応答を直接返します | - |
フローの再開 | ResumeHttpRequest | 以前に一時停止したリクエスト処理フローを再開します | - |
ResumeHttpResponse | 以前に一時停止したレスポンス処理フローを再開します | - |
`ResumeHttpRequest` または `ResumeHttpResponse` は、リクエストまたはレスポンスが一時停止状態でないときに呼び出さないでください。`SendHttpResponse` が呼び出された後、一時停止されたリクエストまたはレスポンスは自動的に再開されることに注意してください。`ResumeHttpRequest` または `ResumeHttpResponse` を再度呼び出すと、未定義の動作になります。
WASM ファイルのコンパイルと生成
wasm ファイルをローカルでコンパイルする
カスタムで初期化されたディレクトリを使用する場合は、次のコマンドを実行して Wasm ファイルをコンパイルします。
go mod tidy
GOOS=wasip1 GOARCH=wasm go build -buildmode=c-shared -o main.wasm ./コンパイルが成功すると、`main.wasm` という名前の新しいファイルが生成されます。このファイルは、このトピックの後半にあるローカルデバッグの例で使用されます。クラウドネイティブゲートウェイマーケットプレイスでカスタムプラグイン機能を使用するには、このファイルをアップロードする必要があります。
ヘッダーの状態管理
ヘッダー | 説明 |
| 現在のフィルターの処理が完了し、リクエストを次のフィルターに渡すことができることを示します。 |
| ヘッダーをまだ次のフィルターに渡すことができないことを示します。ただし、接続からのデータ読み取りは停止されず、ボディデータの処理は引き続きトリガーされます。これにより、ボディデータ処理フェーズ中に HTTP リクエストヘッダーを更新できます。ボディデータを次のフィルターに渡す必要がある場合、ヘッダーも一緒に渡されます。 説明 この状態が返される場合、本文が必要です。本文がない場合、リクエストまたはレスポンスは無期限にブロックされます。 リクエストボディが存在するかどうかを確認するには、HasRequestBody() を使用します。 |
| ヘッダーを次のフィルターに渡すことができることを示しますが、次のフィルターはリクエストがまだ完了していないことを示すために |
| すべての反復を停止します。これは、ヘッダーを次のフィルターに渡すことができず、現在のフィルターがボディデータを受信できないことを示します。現在および後続のフィルターのヘッダー、データ、およびトレーラーはバッファリングされます。バッファーサイズが制限を超えると、リクエストフェーズ中に 413 ステータスコードが返され、レスポンスフェーズ中に 500 ステータスコードが返されます。後続の処理を再開するには、 |
| これは |
`types.HeaderStopIteration` と `HeaderStopAllIterationAndWatermark` を含むシナリオについては、公式の Higress ai-transformer プラグインと ai-quota プラグインをご参照ください。
WasmPlugin カスタムリソース定義 (CRD) またはコンソール UI を使用して Higress でこのプラグインを設定するには、Wasm ファイルを OCI または Docker イメージにパッケージ化する必要があります。詳細については、「カスタムプラグイン」をご参照ください。
ローカルデバッグ
前提条件
Docker をインストールする必要があります。
Docker Compose を使用した起動と検証
プラグインを作成したときに作成したディレクトリ (例: `wasm-demo` ディレクトリ) に移動します。このディレクトリに `main.wasm` ファイルがコンパイルされて生成されていることを確認します。
ディレクトリに、`docker-compose.yaml` という名前のファイルを次の内容で作成します:
version: '3.7' services: envoy: image: higress-registry.cn-hangzhou.cr.aliyuncs.com/higress/gateway:v2.1.5 entrypoint: /usr/local/bin/envoy # ここでは wasm のデバッグレベルのロギングが有効になっていることに注意してください。本番環境のデプロイメントでは、デフォルトのレベルは info です。 command: -c /etc/envoy/envoy.yaml --component-log-level wasm:debug depends_on: - httpbin networks: - wasmtest ports: - "10000:10000" volumes: - ./envoy.yaml:/etc/envoy/envoy.yaml - ./main.wasm:/etc/envoy/main.wasm httpbin: image: kennethreitz/httpbin:latest networks: - wasmtest ports: - "12345:80" networks: wasmtest: {}同じディレクトリに、`envoy.yaml` という名前のファイルを次の内容で作成します:
admin: address: socket_address: protocol: TCP address: 0.0.0.0 port_value: 9901 static_resources: listeners: - name: listener_0 address: socket_address: protocol: TCP address: 0.0.0.0 port_value: 10000 filter_chains: - filters: - name: envoy.filters.network.http_connection_manager typed_config: "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager scheme_header_transformation: scheme_to_overwrite: https stat_prefix: ingress_http route_config: name: local_route virtual_hosts: - name: local_service domains: ["*"] routes: - match: prefix: "/" route: cluster: httpbin http_filters: - name: wasmdemo typed_config: "@type": type.googleapis.com/udpa.type.v1.TypedStruct type_url: type.googleapis.com/envoy.extensions.filters.http.wasm.v3.Wasm value: config: name: wasmdemo vm_config: runtime: envoy.wasm.runtime.v8 code: local: filename: /etc/envoy/main.wasm configuration: "@type": "type.googleapis.com/google.protobuf.StringValue" value: | { "mockEnable": false } - name: envoy.filters.http.router typed_config: "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router clusters: - name: httpbin connect_timeout: 30s type: LOGICAL_DNS # v6 ネットワークでテストするには、次の行をコメントアウトします dns_lookup_family: V4_ONLY lb_policy: ROUND_ROBIN load_assignment: cluster_name: httpbin endpoints: - lb_endpoints: - endpoint: address: socket_address: address: httpbin port_value: 80次のコマンドを実行して Docker Compose を起動します。
docker compose up
機能の検証
WASM 機能の検証
curl を使用して httpbin に直接アクセスします。リクエストがゲートウェイを通過しない場合のリクエストヘッダーは、次の例のように表示されます。
curl http://127.0.0.1:12345/get { "args": {}, "headers": { "Accept": "*/*", "Host": "127.0.0.1:12345", "User-Agent": "curl/7.79.1" }, "origin": "172.18.0.1", "url": "http://127.0.0.1:12345/get" }curl を使用してゲートウェイ経由で httpbin にアクセスします。ゲートウェイによって処理された後のリクエストヘッダーは、次の例のように表示されます。
curl http://127.0.0.1:10000/get { "args": {}, "headers": { "Accept": "*/*", "Hello": "world", "Host": "127.0.0.1:10000", "Original-Host": "127.0.0.1:10000", "Req-Start-Time": "1681269273896", "User-Agent": "curl/7.79.1", "X-Envoy-Expected-Rq-Timeout-Ms": "15000" }, "origin": "172.18.0.3", "url": "https://127.0.0.1:10000/get" }
プラグイン機能がアクティブになり、`hello: world` リクエストヘッダーが追加されました。
プラグイン設定変更の検証
`envoy.yaml` を変更し、
mockEnableをtrueに設定します。configuration: "@type": "type.googleapis.com/google.protobuf.StringValue" value: | { "mockEnable": true }curl を使用してゲートウェイ経由で httpbin にアクセスします。ゲートウェイによって処理された後のレスポンスは、次の例のように表示されます。
curl http://127.0.0.1:10000/get hello world
これは、プラグイン設定の変更が有効であることを示します。モックレスポンスが有効になり、`hello world` が直接返されます。
その他の例
設定のないプラグイン
プラグインに設定が不要な場合は、空の構造体を定義するだけです。
package main
import (
"github.com/higress-group/wasm-go/pkg/wrapper"
logs "github.com/higress-group/wasm-go/pkg/log"
"github.com/higress-group/proxy-wasm-go-sdk/proxywasm"
"github.com/higress-group/proxy-wasm-go-sdk/proxywasm/types"
)
func main() {}
func init() {
wrapper.SetCtx(
"hello-world",
wrapper.ProcessRequestHeadersBy(onHttpRequestHeaders),
)
}
type MyConfig struct {}
func onHttpRequestHeaders(ctx wrapper.HttpContext, config MyConfig, log logs.Log) types.Action {
proxywasm.SendHttpResponse(200, nil, []byte("hello world"), -1)
return types.HeaderContinue
}プラグインから外部サービスをリクエストする
現在、HTTP 呼び出しのみがサポートされています。Nacos サービス、Kubernetes サービス、およびゲートウェイコンソールで設定されている固定 IP アドレスまたは DNS ソースからのサービスにアクセスできます。なお、net/http ライブラリの HTTP クライアントは直接使用できません。次の例に示すカプセル化された HTTP クライアントを使用する必要があります。
次の例では、設定解析フェーズでサービスタイプが解析され、対応する HTTP クライアントが生成されます。リクエストヘッダー処理フェーズでは、設定されたリクエストパスに基づいて対応するサービスが呼び出されます。その後、レスポンスヘッダーが解析され、元のリクエストヘッダーに設定されます。
package main
import (
"errors"
"net/http"
"strings"
"github.com/higress-group/wasm-go/pkg/wrapper"
logs "github.com/higress-group/wasm-go/pkg/log"
"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(
"http-call",
wrapper.ParseConfigBy(parseConfig),
wrapper.ProcessRequestHeadersBy(onHttpRequestHeaders),
)
}
type MyConfig struct {
// HTTP 呼び出しを開始するために使用されるクライアント
client wrapper.HttpClient
// リクエスト URL
requestPath string
// このキーを使用して、呼び出されたサービスのレスポンスヘッダーから対応するフィールドを取得し、元のリクエストヘッダーに設定します。キーはこの設定項目です。
tokenHeader string
}
func parseConfig(json gjson.Result, config *MyConfig, log logs.Log) error {
config.tokenHeader = json.Get("tokenHeader").String()
if config.tokenHeader == "" {
return errors.New("missing tokenHeader in config")
}
config.requestPath = json.Get("requestPath").String()
if config.requestPath == "" {
return errors.New("missing requestPath in config")
}
// サービスタイプを含む完全な FQDN (例: my-svc.dns、my-svc.static、service-provider.DEFAULT-GROUP.public.nacos、または httpbin.my-ns.svc.cluster.local)
serviceName := json.Get("serviceName").String()
servicePort := json.Get("servicePort").Int()
if servicePort == 0 {
if strings.HasSuffix(serviceName, ".static") {
// 静的 IP サービスの論理ポートは 80 です
servicePort = 80
}
}
config.client = wrapper.NewClusterClient(wrapper.FQDNCluster{
FQDN: serviceName,
Port: servicePort,
})
}
func onHttpRequestHeaders(ctx wrapper.HttpContext, config MyConfig, log logs.Log) types.Action {
// クライアントの Get メソッドを使用して HTTP GET 呼び出しを開始します。ここではタイムアウトパラメーターは省略されており、デフォルトのタイムアウトは 500 ms です。
err := config.client.Get(config.requestPath, nil,
// コールバック関数。レスポンスが非同期で返されたときに実行されます
func(statusCode int, responseHeaders http.Header, responseBody []byte) {
// リクエストが 200 ステータスコードを返しませんでした。これを処理します。
if statusCode != http.StatusOK {
log.Errorf("http call failed, status: %d", statusCode)
proxywasm.SendHttpResponse(http.StatusInternalServerError, nil,
[]byte("http call failed"), -1)
return
}
// HTTP ステータスコードとレスポンスボディを出力します
log.Infof("get status: %d, response body: %s", statusCode, responseBody)
// レスポンスヘッダーからトークンフィールドを解析し、元のリクエストヘッダーに設定します
token := responseHeaders.Get(config.tokenHeader)
if token != "" {
proxywasm.AddHttpRequestHeader(config.tokenHeader, token)
}
// 元のリクエストフローを再開して処理を続行し、バックエンドサービスに転送できるようにします
proxywasm.ResumeHttpRequest()
})
if err != nil {
// 外部サービスへの呼び出しが失敗したため、リクエストを続行し、イベントをログに記録します。
log.Errorf("Error occured while calling http, it seems cannot find the service cluster.")
return types.ActionContinue
} else {
// 非同期コールバックが完了するのを待ちます。HeaderStopAllIterationAndWatermark ステータスを返します。これは ResumeHttpRequest で再開できます。
return types.HeaderStopAllIterationAndWatermark
}
}プラグインから Redis を呼び出す
次のサンプルコードを使用して、Redis レート制限プラグインを実装します。
package main
import (
"strconv"
"time"
"github.com/higress-group/proxy-wasm-go-sdk/proxywasm"
"github.com/higress-group/proxy-wasm-go-sdk/proxywasm/types"
"github.com/tidwall/gjson"
"github.com/tidwall/resp"
"github.com/higress-group/wasm-go/pkg/wrapper"
logs "github.com/higress-group/wasm-go/pkg/log"
)
func main() {}
func init() {
wrapper.SetCtx(
"redis-demo",
wrapper.ParseConfigBy(parseConfig),
wrapper.ProcessRequestHeadersBy(onHttpRequestHeaders),
wrapper.ProcessResponseHeadersBy(onHttpResponseHeaders),
)
}
type RedisCallConfig struct {
client wrapper.RedisClient
qpm int
}
func parseConfig(json gjson.Result, config *RedisCallConfig, log logs.Log) error {
// サービスタイプを含む完全な FQDN (例: my-redis.dns または redis.my-ns.svc.cluster.local)
serviceName := json.Get("serviceName").String()
servicePort := json.Get("servicePort").Int()
if servicePort == 0 {
if strings.HasSuffix(serviceName, ".static") {
// 静的 IP サービスの論理ポートは 80 です
servicePort = 80
} else {
servicePort = 6379
}
}
username := json.Get("username").String()
password := json.Get("password").String()
// 単位: ms
timeout := json.Get("timeout").Int()
if timeout == 0 {
timeout = 1000
}
qpm := json.Get("qpm").Int()
config.qpm = int(qpm)
config.client = wrapper.NewRedisClusterClient(wrapper.FQDNCluster{
FQDN: serviceName,
Port: servicePort,
})
return config.client.Init(username, password, timeout)
}
func onHttpRequestHeaders(ctx wrapper.HttpContext, config RedisCallConfig, log logs.Log) types.Action {
now := time.Now()
minuteAligned := now.Truncate(time.Minute)
timeStamp := strconv.FormatInt(minuteAligned.Unix(), 10)
// Redis API が err != nil を返す場合、通常はゲートウェイが Redis バックエンドサービスを見つけられないことが原因です。Redis バックエンドサービスが誤って削除されていないか確認してください。
err := config.client.Incr(timeStamp, func(response resp.Value) {
if response.Error() != nil {
log.Errorf("call redis error: %v", response.Error())
proxywasm.ResumeHttpRequest()
} else {
ctx.SetContext("timeStamp", timeStamp)
ctx.SetContext("callTimeLeft", strconv.Itoa(config.qpm-response.Integer()))
if response.Integer() == 1 {
err := config.client.Expire(timeStamp, 60, func(response resp.Value) {
if response.Error() != nil {
log.Errorf("call redis error: %v", response.Error())
}
proxywasm.ResumeHttpRequest()
})
if err != nil {
log.Errorf("Error occured while calling redis, it seems cannot find the redis cluster.")
proxywasm.ResumeHttpRequest()
}
} else {
if response.Integer() > config.qpm {
proxywasm.SendHttpResponse(429, [][2]string{{"timeStamp", timeStamp}, {"callTimeLeft", "0"}}, []byte("Too many requests\n"), -1)
} else {
proxywasm.ResumeHttpRequest()
}
}
}
})
if err != nil {
// Redis への呼び出しが失敗したため、リクエストを続行し、イベントをログに記録します。
log.Errorf("Error occured while calling redis, it seems cannot find the redis cluster.")
return types.HeaderContinue
} else {
// リクエストを保留し、Redis の呼び出しが完了するのを待ちます。
return types.HeaderStopAllIterationAndWatermark
}
}
func onHttpResponseHeaders(ctx wrapper.HttpContext, config RedisCallConfig, log logs.Log) types.Action {
if ctx.GetContext("timeStamp") != nil {
proxywasm.AddHttpResponseHeader("timeStamp", ctx.GetContext("timeStamp").(string))
}
if ctx.GetContext("callTimeLeft") != nil {
proxywasm.AddHttpResponseHeader("callTimeLeft", ctx.GetContext("callTimeLeft").(string))
}
return types.HeaderContinue
}