全部产品
Search
文档中心

API Gateway:Mengembangkan plugin gerbang menggunakan Go

更新时间:Nov 10, 2025

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.

Penting

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).

Catatan

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 Files atau Program Files (x86).

  • Setelah instalasi selesai, tekan pintasan keyboard Win+R untuk membuka jendela Run. Masukkan cmd dan klik OK untuk membuka command prompt. Jalankan perintah go 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.gz
    • Konfigurasikan variabel lingkungan.

      export PATH=$PATH:/usr/local/go/bin
    • Jalankan perintah go version. Jika perintah tersebut mengembalikan versi saat ini, instalasi berhasil.

Menulis plugin

Inisialisasi direktori proyek

  1. Buat direktori proyek, misalnya wasm-demo-go.

  2. Di dalam direktori tersebut, jalankan perintah berikut untuk menginisialisasi proyek Go.

    go mod init wasm-demo-go
  3. Jika Anda berada di Tiongkok Daratan, Anda mungkin perlu mengatur proxy untuk mengunduh dependensi.

    go env -w GOPROXY=https://proxy.golang.com.cn,direct
  4. Unduh 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.

Catatan
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

wrapper.ProcessRequestHeadersBy

Fase pemrosesan badan permintaan HTTP

Saat gerbang menerima data badan permintaan dari klien

wrapper.ProcessRequestBodyBy

Fase pemrosesan header respons HTTP

Saat gerbang menerima data header respons dari layanan backend

wrapper.ProcessResponseHeadersBy

Fase pemrosesan badan respons HTTP

Saat gerbang menerima data badan respons dari layanan backend

wrapper.ProcessResponseBodyBy

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

-

Penting

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

HeaderContinue

Menunjukkan bahwa filter saat ini telah selesai memproses dan permintaan dapat diteruskan ke filter berikutnya. types.ActionContinue sesuai dengan status ini.

HeaderStopIteration

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().

HeaderContinueAndEndStream

Menunjukkan bahwa header dapat diteruskan ke filter berikutnya, tetapi filter berikutnya menerima end_stream = false untuk menandai bahwa permintaan belum selesai. Hal ini memungkinkan filter saat ini menambahkan lebih banyak data badan.

HeaderStopAllIterationAndBuffer

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 proxywasm.ResumeHttpRequest(), proxywasm.ResumeHttpResponse(), atau proxywasm.SendHttpResponseWithDetail() untuk melanjutkan pemrosesan selanjutnya.

HeaderStopAllIterationAndWatermark

Sama seperti HeaderStopAllIterationAndBuffer, kecuali ketika buffer melebihi batas, hal ini memicu throttling, yang menjeda pembacaan data dari koneksi. types.ActionPause dalam ABI 0.2.1 sesuai dengan status ini.

Catatan

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

  1. Buka direktori yang Anda buat saat menulis plugin, misalnya direktori `wasm-demo`. Pastikan file `main.wasm` telah dikompilasi dan dihasilkan di direktori ini.

  2. 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: {}
  3. 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: 80
  4. Jalankan perintah berikut untuk memulai Docker Compose.

    docker compose up

Verifikasi fitur

Verifikasi fitur WASM

  1. 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"
    }
  2. 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

  1. Ubah `envoy.yaml` dan atur mockEnable menjadi true.

      configuration:
        "@type": "type.googleapis.com/google.protobuf.StringValue"
        value: |
          {
            "mockEnable": true
          }
  2. 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
}