Anda dapat mengembangkan plugin gerbang untuk memperluas fitur inti AI Gateway dan memenuhi kebutuhan bisnis yang kompleks. Topik ini menjelaskan cara mengembangkan plugin gerbang menggunakan Go serta menyediakan panduan untuk pengembangan dan debugging lokal.
Higress telah bermigrasi dari solusi kompilasi TinyGo 0.29 dan Go 1.20 ke kompilasi WASM native yang didukung oleh Go 1.24. Go 1.24 secara native mendukung kompilasi berkas WASM.
Jika sebelumnya Anda menggunakan TinyGo untuk mengompilasi plugin dan ingin bermigrasi ke mode kompilasi Go 1.24, Anda harus memindahkan logika inisialisasi plugin dari fungsi main ke fungsi init. Anda juga perlu menyesuaikan dependensi dalam berkas go.mod. Untuk contoh spesifik, lihat bagian selanjutnya dalam topik ini.
Untuk plugin yang awalnya diimplementasikan dengan TinyGo, perhatikan poin adaptasi berikut:
1. Jika Anda memanggil layanan eksternal selama fase pemrosesan header dan mengembalikan types.ActionPause, Anda harus mengubahnya menjadi mengembalikan types.HeaderStopAllIterationAndWatermark. Untuk implementasi referensi, lihat contoh pemanggilan layanan eksternal dari plugin di bagian selanjutnya dalam topik ini.
2. Jika Anda menggunakan pustaka go-re2 karena dukungan TinyGo terhadap pustaka regexp standar tidak lengkap, Anda harus menggantinya dengan paket regexp resmi Go.
Prasyarat
Instal Go.
Golang
Panduan resmi (versi 1.24 atau lebih baru).
Plugin yang dikompilasi dengan Go 1.24 memerlukan AI Gateway v2.1.5 atau lebih baru. Untuk versi gerbang yang lebih lama, lihat Mengembangkan plugin WASM menggunakan Go.
Windows
Unduh berkas instalasi.
Buka berkas instalasi yang telah diunduh untuk menginstal Go. Secara default, Go diinstal di direktori
Program FilesatauProgram Files (x86).Setelah instalasi selesai, tekan pintasan keyboard Win+R untuk membuka jendela Run. Masukkan
cmddan klik OK untuk membuka command prompt. Jalankan perintahgo version. Jika perintah tersebut mengembalikan nomor versi yang diinstal, instalasi berhasil.
macOS
Unduh berkas instalasi.
Klik ganda berkas instalasi yang telah diunduh untuk menginstal Go. Secara default, Go diinstal di direktori
/usr/local/go.Buka Terminal dan jalankan perintah
go version. Jika perintah tersebut mengembalikan nomor versi yang diinstal, instalasi berhasil.
Linux
Unduh berkas instalasi.
Jalankan perintah berikut untuk menginstal Go.
Instal Go.
rm -rf /usr/local/go && tar -C /usr/local -xzf go1.24.4.linux-amd64.tar.gzKonfigurasikan variabel lingkungan.
export PATH=$PATH:/usr/local/go/binJalankan perintah
go version. Jika perintah tersebut mengembalikan nomor versi yang diinstal, instalasi berhasil.
Menulis plugin
Inisialisasi direktori proyek
Buat direktori proyek, misalnya
wasm-demo-go.Di dalam direktori tersebut, jalankan perintah berikut untuk menginisialisasi proyek Go.
go mod init wasm-demo-goJika Anda berada di Tiongkok daratan, Anda mungkin perlu mengatur proxy untuk mengunduh dependensi.
go env -w GOPROXY=https://proxy.golang.com.cn,directUnduh dependensi untuk membangun plugin.
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
Menulis berkas main.go
Contoh berikut menunjukkan cara langsung mengembalikan respons hello world ketika mockEnable: true ditetapkan dalam konfigurasi plugin. Jika plugin tidak dikonfigurasi atau jika mockEnable: false ditetapkan, contoh tersebut menambahkan header hello: world ke permintaan asli. Untuk contoh lainnya, lihat bagian Contoh lain dalam topik ini.
Konfigurasi plugin di konsol gerbang berformat YAML. Konfigurasi tersebut secara otomatis dikonversi ke format JSON sebelum dikirim ke plugin. Oleh karena itu, fungsi parseConfig dalam contoh dapat mengurai konfigurasi langsung dari 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(
// Nama plugin
"my-plugin",
// Tetapkan fungsi user-defined untuk mengurai konfigurasi plugin
wrapper.ParseConfigBy(parseConfig),
// Tetapkan fungsi user-defined untuk memproses header permintaan
wrapper.ProcessRequestHeadersBy(onHttpRequestHeaders),
)
}
// Konfigurasi plugin kustom
type MyConfig struct {
mockEnable bool
}
// Konfigurasi YAML dari konsol secara otomatis dikonversi ke JSON. Anda dapat mengurai konfigurasi langsung dari parameter json.
func parseConfig(json gjson.Result, config *MyConfig, log logs.Log) error {
// Uraikan konfigurasi dan perbarui dalam 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
}Titik pemasangan pemrosesan HTTP
Dalam contoh di atas, Anda menggunakan metode wrapper.ProcessRequestHeadersBy untuk menerapkan fungsi user-defined onHttpRequestHeaders pada fase pemrosesan header permintaan HTTP. Anda juga dapat menggunakan metode berikut untuk menetapkan fungsi user-defined untuk fase lainnya.
Fase pemrosesan HTTP | Kondisi pemicu | Metode Pengaitan |
Fase pemrosesan header permintaan HTTP | Saat gerbang menerima data header permintaan dari klien |
|
Fase pemrosesan badan permintaan HTTP | Saat gerbang menerima data badan permintaan dari klien |
|
Fase pemrosesan header respons HTTP | Saat gerbang menerima data header respons dari layanan backend |
|
Fase pemrosesan badan respons HTTP | Saat gerbang menerima data badan respons dari layanan backend |
|
Metode utilitas
Metode proxywasm.AddHttpRequestHeader dan proxywasm.SendHttpResponse dalam contoh di atas adalah dua metode utilitas yang disediakan oleh SDK plugin. Metode utilitas utama tercantum dalam tabel berikut.
Kategori | Nama metode | Tujuan | Efektif Fase Pemrosesan HTTP |
Pemrosesan header permintaan | GetHttpRequestHeaders | Mendapatkan semua header permintaan dari klien | Fase pemrosesan header permintaan HTTP |
ReplaceHttpRequestHeaders | Mengganti semua header permintaan dari klien | Fase pemrosesan header permintaan HTTP | |
GetHttpRequestHeader | Mendapatkan header permintaan tertentu dari klien | Fase pemrosesan header permintaan HTTP | |
RemoveHttpRequestHeader | Menghapus header permintaan tertentu dari klien | Fase pemrosesan header permintaan HTTP | |
ReplaceHttpRequestHeader | Mengganti header permintaan tertentu dari klien | Fase pemrosesan header permintaan HTTP | |
AddHttpRequestHeader | Menambahkan header permintaan klien baru | Fase pemrosesan header permintaan HTTP | |
Pemrosesan badan permintaan | GetHttpRequestBody | Mendapatkan badan permintaan klien | Fase pemrosesan badan permintaan HTTP |
AppendHttpRequestBody | Menambahkan string byte tertentu ke akhir badan permintaan klien | Fase pemrosesan badan permintaan HTTP | |
PrependHttpRequestBody | Menambahkan string byte tertentu ke awal badan permintaan klien | Fase pemrosesan badan permintaan HTTP | |
ReplaceHttpRequestBody | Mengganti badan permintaan klien | Fase pemrosesan badan permintaan HTTP | |
Pemrosesan header respons | GetHttpResponseHeaders | Mendapatkan semua header respons dari backend | Fase pemrosesan header respons HTTP |
ReplaceHttpResponseHeaders | Mengganti semua header respons dari backend | Fase pemrosesan header respons HTTP | |
GetHttpResponseHeader | Mendapatkan header respons tertentu dari backend | Fase pemrosesan header respons HTTP | |
RemoveHttpResponseHeader | Menghapus header respons tertentu dari backend | Fase pemrosesan header respons HTTP | |
ReplaceHttpResponseHeader | Mengganti header respons tertentu dari backend | Fase pemrosesan header respons HTTP | |
AddHttpResponseHeader | Menambahkan header respons backend baru | Fase pemrosesan header respons HTTP | |
Pemrosesan badan respons | GetHttpResponseBody | Anda dapat memperoleh badan permintaan klien. | Fase pemrosesan badan respons HTTP |
AppendHttpResponseBody | Menambahkan string byte tertentu ke akhir badan respons backend | Fase pemrosesan badan respons HTTP | |
PrependHttpResponseBody | Menambahkan string byte tertentu ke awal badan respons backend | Fase pemrosesan badan respons HTTP | |
ReplaceHttpResponseBody | Mengganti badan respons backend | Fase pemrosesan badan respons HTTP | |
Panggilan HTTP | DispatchHttpCall | Mengirim permintaan HTTP | - |
GetHttpCallResponseHeaders | Mendapatkan header respons dari permintaan `DispatchHttpCall` | - | |
GetHttpCallResponseBody | Mendapatkan badan respons dari permintaan `DispatchHttpCall` | - | |
GetHttpCallResponseTrailers | Mendapatkan trailer respons dari permintaan `DispatchHttpCall` | - | |
Respons langsung | SendHttpResponse | Langsung mengembalikan respons HTTP tertentu | - |
Pelanjutan aliran | ResumeHttpRequest | Melanjutkan aliran pemrosesan permintaan yang sebelumnya dijeda | - |
ResumeHttpResponse | Melanjutkan aliran pemrosesan respons yang sebelumnya dijeda | - |
Jangan memanggil ResumeHttpRequest atau ResumeHttpResponse ketika permintaan atau respons tidak dalam keadaan dijeda. Perhatikan bahwa setelah SendHttpResponse dipanggil, permintaan atau respons yang dijeda akan dilanjutkan secara otomatis. Memanggil ResumeHttpRequest atau ResumeHttpResponse lagi menghasilkan perilaku yang tidak terdefinisi.
Kompilasi dan hasilkan berkas WASM
Kompilasi berkas wasm secara lokal
Jika Anda menggunakan direktori inisialisasi kustom, jalankan perintah berikut untuk mengompilasi berkas Wasm.
go mod tidy
GOOS=wasip1 GOARCH=wasm go build -buildmode=c-shared -o main.wasm ./Setelah kompilasi berhasil, sebuah berkas baru bernama main.wasm dihasilkan. Berkas ini digunakan dalam contoh debugging lokal di bagian selanjutnya dalam topik ini. Untuk menggunakan fitur plugin kustom di pasar gateway cloud-native, Anda harus mengunggah berkas ini.
Manajemen status header
Header | Deskripsi |
| Menunjukkan bahwa filter saat ini telah selesai memproses dan permintaan dapat diteruskan ke filter berikutnya. |
| Menunjukkan bahwa header belum dapat diteruskan ke filter berikutnya. Namun, pembacaan data dari koneksi tidak dihentikan, dan pemrosesan data badan tetap dipicu. Hal ini memungkinkan Anda memperbarui header permintaan HTTP selama fase pemrosesan data badan. Jika data badan perlu diteruskan ke filter berikutnya, header juga akan diteruskan bersamanya. Catatan Saat status ini dikembalikan, badan diperlukan. Jika tidak ada badan, permintaan atau respons akan diblokir tanpa batas waktu. Untuk memeriksa apakah badan permintaan ada, gunakan HasRequestBody(). |
| Menunjukkan bahwa header dapat diteruskan ke filter berikutnya, tetapi filter berikutnya menerima |
| Menghentikan semua iterasi. Ini menunjukkan bahwa header tidak dapat diteruskan ke filter berikutnya, dan filter saat ini tidak dapat menerima data badan. Header, data, dan trailer untuk filter saat ini dan berikutnya dibuffer. Jika ukuran buffer melebihi batas, kode status 413 dikembalikan selama fase permintaan, dan kode status 500 dikembalikan selama fase respons. Anda juga harus memanggil |
| Sama seperti |
Untuk skenario yang melibatkan types.HeaderStopIteration dan HeaderStopAllIterationAndWatermark, lihat plugin resmi Higress ai-transformer dan ai-quota.
Untuk mengonfigurasi plugin ini di Higress menggunakan CustomResourceDefinition (CRD) WasmPlugin atau antarmuka pengguna konsol, Anda harus mengemas berkas Wasm ke dalam citra OCI atau Docker. Untuk informasi selengkapnya, lihat Plugin kustom.
Debugging lokal
Prasyarat
Anda harus menginstal Docker.
Mulai dan verifikasi menggunakan Docker Compose
Buka direktori yang Anda buat saat menulis plugin, misalnya direktori wasm-demo. Pastikan berkas main.wasm telah dikompilasi dan dihasilkan di direktori ini.
Di dalam direktori tersebut, buat berkas bernama docker-compose.yaml dengan konten berikut:
version: '3.7' services: envoy: image: higress-registry.cn-hangzhou.cr.aliyuncs.com/higress/gateway:v2.1.5 entrypoint: /usr/local/bin/envoy # Perhatikan bahwa logging tingkat debug diaktifkan untuk wasm di sini. Untuk penyebaran produksi, level default adalah 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: {}Di direktori yang sama, buat berkas bernama envoy.yaml dengan konten berikut:
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 # Komentari baris berikut untuk menguji pada jaringan 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: 80Jalankan perintah berikut untuk memulai Docker Compose.
docker compose up
Verifikasi fitur
Verifikasi fitur WASM
Gunakan curl untuk langsung mengakses httpbin. Anda dapat melihat header permintaan ketika permintaan tidak melewati gerbang, seperti pada contoh berikut.
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" }Gunakan curl untuk mengakses httpbin melalui gerbang. Anda dapat melihat header permintaan setelah diproses oleh gerbang, seperti pada contoh berikut.
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" }
Fitur plugin sekarang aktif, dan header permintaan hello: world telah ditambahkan.
Verifikasi modifikasi konfigurasi plugin
Ubah envoy.yaml dan atur
mockEnablemenjaditrue.configuration: "@type": "type.googleapis.com/google.protobuf.StringValue" value: | { "mockEnable": true }Gunakan curl untuk mengakses httpbin melalui gerbang. Anda dapat melihat respons setelah diproses oleh gerbang, seperti pada contoh berikut.
curl http://127.0.0.1:10000/get hello world
Hal ini menunjukkan bahwa modifikasi konfigurasi plugin efektif. Respons mock diaktifkan, dan hello world dikembalikan secara langsung.
Contoh lain
Plugin tanpa konfigurasi
Jika plugin tidak memerlukan konfigurasi, Anda cukup mendefinisikan struct kosong.
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
}Meminta layanan eksternal dari plugin
Saat ini, hanya panggilan HTTP yang didukung. Anda dapat mengakses layanan Nacos, layanan Kubernetes, dan layanan dari alamat IP tetap atau sumber DNS yang dikonfigurasi di konsol gerbang. Perhatikan bahwa Anda tidak dapat langsung menggunakan klien HTTP dari pustaka net/http. Anda harus menggunakan klien HTTP terenkapsulasi seperti pada contoh berikut.
Dalam contoh berikut, tipe layanan diuraikan selama fase penguraian konfigurasi untuk menghasilkan klien HTTP yang sesuai. Selama fase pemrosesan header permintaan, layanan yang sesuai dipanggil berdasarkan path permintaan yang dikonfigurasi. Header respons kemudian diuraikan dan ditetapkan dalam header permintaan asli.
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 {
// Klien yang digunakan untuk memulai panggilan HTTP
client wrapper.HttpClient
// URL permintaan
requestPath string
// Gunakan kunci ini untuk mengambil bidang yang sesuai dari header respons layanan yang dipanggil, lalu menetapkannya dalam header permintaan asli. Kuncinya adalah item konfigurasi ini.
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 lengkap dengan tipe layanan, seperti my-svc.dns, my-svc.static, service-provider.DEFAULT-GROUP.public.nacos, atau httpbin.my-ns.svc.cluster.local
serviceName := json.Get("serviceName").String()
servicePort := json.Get("servicePort").Int()
if servicePort == 0 {
if strings.HasSuffix(serviceName, ".static") {
// Port logis untuk layanan IP statis adalah 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 {
// Gunakan metode Get klien untuk memulai panggilan HTTP GET. Parameter timeout dihilangkan di sini, dan timeout default adalah 500 ms.
err := config.client.Get(config.requestPath, nil,
// Fungsi callback, yang dieksekusi ketika respons dikembalikan secara asinkron
func(statusCode int, responseHeaders http.Header, responseBody []byte) {
// Permintaan tidak mengembalikan kode status 200. Proses hal tersebut.
if statusCode != http.StatusOK {
log.Errorf("http call failed, status: %d", statusCode)
proxywasm.SendHttpResponse(http.StatusInternalServerError, nil,
[]byte("http call failed"), -1)
return
}
// Cetak kode status HTTP dan badan respons
log.Infof("get status: %d, response body: %s", statusCode, responseBody)
// Uraikan bidang token dari header respons dan tetapkan dalam header permintaan asli
token := responseHeaders.Get(config.tokenHeader)
if token != "" {
proxywasm.AddHttpRequestHeader(config.tokenHeader, token)
}
// Lanjutkan aliran permintaan asli untuk melanjutkan pemrosesan agar dapat diteruskan ke layanan backend
proxywasm.ResumeHttpRequest()
})
if err != nil {
// Karena panggilan ke layanan eksternal gagal, izinkan permintaan untuk dilanjutkan dan catat kejadian tersebut.
log.Errorf("Error occured while calling http, it seems cannot find the service cluster.")
return types.ActionContinue
} else {
// Tunggu hingga callback asinkron selesai. Kembalikan status HeaderStopAllIterationAndWatermark, yang dapat dilanjutkan oleh ResumeHttpRequest.
return types.HeaderStopAllIterationAndWatermark
}
}Memanggil Redis dari plugin
Gunakan contoh kode berikut untuk mengimplementasikan plugin pembatas laju 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 lengkap dengan tipe layanan, seperti my-redis.dns atau redis.my-ns.svc.cluster.local
serviceName := json.Get("serviceName").String()
servicePort := json.Get("servicePort").Int()
if servicePort == 0 {
if strings.HasSuffix(serviceName, ".static") {
// Port logis untuk layanan IP statis adalah 80
servicePort = 80
} else {
servicePort = 6379
}
}
username := json.Get("username").String()
password := json.Get("password").String()
// Satuan: 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)
// Jika API Redis mengembalikan err != nil, biasanya karena gerbang tidak dapat menemukan layanan backend Redis. Periksa apakah layanan backend Redis tidak sengaja dihapus.
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 {
// Karena panggilan ke Redis gagal, izinkan permintaan untuk dilanjutkan dan catat kejadian tersebut.
log.Errorf("Error occured while calling redis, it seems cannot find the redis cluster.")
return types.HeaderContinue
} else {
// Tahan permintaan dan tunggu hingga panggilan Redis selesai.
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
}