Pengembangan plugin gerbang memperluas fitur inti API Gateway sehingga dapat memenuhi kebutuhan bisnis yang lebih kompleks dan spesifik. 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 + Go 1.20 ke kompilasi WebAssembly (Wasm) native yang didukung oleh Go 1.24. Go 1.24 kini secara native mendukung kompilasi file Wasm.
Jika sebelumnya Anda menggunakan TinyGo untuk mengompilasi plugin, Anda harus menyesuaikan kode agar bermigrasi ke mode kompilasi Go 1.24. Penyesuaian tersebut mencakup modifikasi dependensi dalam file go.mod dan pemindahan logika inisialisasi plugin dari fungsi main ke fungsi init. Untuk contoh spesifik, lihat bagian selanjutnya dalam topik ini.
Untuk plugin yang awalnya diimplementasikan dengan TinyGo, perhatikan penyesuaian kompatibilitas berikut:
1. Jika Anda memanggil layanan eksternal selama fase pemrosesan header dan mengembalikan tipe.ActionPause, ubah tipe pengembalian menjadi types.HeaderStopAllIterationAndWatermark. Untuk implementasi contoh, lihat contoh plugin yang memanggil layanan eksternal di bagian selanjutnya dalam topik ini.
2. Jika Anda menggunakan pustaka go-re2 karena dukungan TinyGo terhadap pustaka regexp standar tidak lengkap, gantilah dengan paket regexp resmi Go.
Prasyarat
Instal Go.
Go
Panduan instalasi resmi (diperlukan versi 1.24 atau lebih baru).
Plugin yang dikompilasi dengan Go 1.24 memerlukan Cloud-native API Gateway versi 2.1.5 atau lebih baru. Untuk versi gerbang yang lebih lama, lihat Mengembangkan plugin Wasm menggunakan Go.
Windows
Unduh file installer.
Buka file installer yang telah diunduh untuk memulai instalasi. 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 versi saat ini, instalasi berhasil.
macOS
Unduh file installer.
Klik ganda file installer yang telah diunduh untuk memulai instalasi. Secara default, Go diinstal di direktori
/usr/local/go.Buka terminal dan jalankan perintah
go version. Jika perintah tersebut mengembalikan versi saat ini, instalasi berhasil.
Linux
Unduh file installer.
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 versi saat ini, 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 file main.go
Contoh berikut menunjukkan cara mengembalikan respons hello world secara langsung 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 pada fase lainnya.
Fase pemrosesan HTTP | Kondisi pemicu | Metode pemasangan |
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 | - |
Resume 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 file WASM
Kompilasi file wasm secara lokal
Jika Anda menggunakan direktori inisialisasi kustom, jalankan perintah berikut untuk mengompilasi file Wasm.
go mod tidy
GOOS=wasip1 GOARCH=wasm go build -buildmode=c-shared -o main.wasm ./Setelah kompilasi berhasil, file baru bernama `main.wasm` dihasilkan. File 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 file 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 file Wasm ke dalam citra OCI atau Docker. Untuk informasi lebih lanjut, 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 file `main.wasm` telah dikompilasi dan dihasilkan di direktori ini.
Di dalam direktori tersebut, buat file 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 level debug diaktifkan untuk wasm di sini. Untuk deployment 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 file 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 mengakses httpbin secara langsung. 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 kini 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 tetapkan 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 ini.
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 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 rate-limiting 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 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
}