Tüm Ürünler
Search
Belge Merkezi

API Gateway:Develop gateway plugins in Go

Son Güncelleme:Oct 04, 2026

Karmaşık iş gereksinimleri için API ağ geçidi işlevselliğini genişletmek üzere Go dilinde özel ağ geçidi eklentileri geliştirin ve yerel olarak hata ayıklayın.

Önemli

Higress, TinyGo 0.29 + Go 1.20 sürümünden Go 1.24'teki yerel Wasm derlemesine geçiş yapmıştır. Go 1.24, .wasm dosyalarını derlemeyi yerel olarak destekler.

TinyGo'dan Go 1.24'e geçiş yapmak için eklenti başlatma mantığını main işlevinden init işlevine taşıyın ve go.mod bağımlılıklarınızı güncelleyin. Aşağıda bir örnek verilmiştir.

TinyGo tabanlı eklentilerin geçişi için uyumluluk ayarlamaları:

1. Üst bilgi işleme sırasında harici bir hizmet çağırıyorsanız ve type.ActionPause döndürüyorsanız, bunun yerine types.HeaderStopAllIterationAndWatermark kullanın. Aşağıdaki "Eklentiden harici bir hizmet çağırma" örneği bunu göstermektedir.

2. go-re2 kullandıysanız (TinyGo'nun eksik regexp desteği nedeniyle gerekliydi), bunun yerine standart Go regexp paketini kullanın.

Ön koşullar

Geliştirme makinenize Go 1.24 veya sonraki bir sürümü yükleyin.

Golang

Resmi kurulum kılavuzu üzerinden Go 1.24 veya sonraki bir sürümü yükleyin.

Not

Go 1.24 eklentileri Cloud-native API Gateway 2.1.5 veya sonraki bir sürümünü gerektirir. Daha eski ağ geçidi sürümleri için Go dilini kullanarak WASM eklentileri geliştirme bölümüne bakın.

Windows

  • Kurulum dosyasını indirin.

  • Yükleyiciyi çalıştırın. Go varsayılan olarak Program Files veya Program Files (x86) dizinine yüklenir.

  • Win+R tuşlarına basın, cmd girin ve komut istemini açmak için OK öğesine tıklayın. Kurulumu doğrulamak için go version komutunu çalıştırın.

macOS

  • Kurulum paketini indirin.

  • Pakete çift tıklayarak kurulumu yapın. Go varsayılan olarak /usr/local/go dizinine yüklenir.

  • Bir terminal açın ve kurulumu doğrulamak için go version komutunu çalıştırın.

Linux

  • Kurulum paketini indirin.

  • Go'yu yüklemek için şu komutları çalıştırın:

    • Go'yu yükleyin.

      rm -rf /usr/local/go && tar -C /usr/local -xzf go1.24.4.linux-amd64.tar.gz
    • Ortam değişkenini yapılandırın.

      export PATH=$PATH:/usr/local/go/bin
    • Kurulumu doğrulamak için go version komutunu çalıştırın.

Eklentiyi yazma

Projeyi başlatma

  1. Bir proje dizini oluşturun ve bir Go modülü başlatın:

       mkdir wasm-demo-go && cd wasm-demo-go
       go mod init wasm-demo-go
  2. Eklenti SDK bağımlılıklarını indirin:

       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

    Çin anakarasındaysanız önce bir proxy ayarlayın:

       go env -w GOPROXY=https://proxy.golang.com.cn,direct

main.go dosyasını yazma

Aşağıdaki örnek, gelen her isteğe bir hello: world istek üst bilgisi ekler. Eklenti yapılandırmasında mockEnable değeri true olduğunda, eklenti arka uca iletmek yerine doğrudan hello world yanıtını döndürür.

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(
    // Plugin name
    "my-plugin",
    // Custom configuration parser
     wrapper.ParseConfigBy(parseConfig),
    // Hook into the request header processing phase
    wrapper.ProcessRequestHeadersBy(onHttpRequestHeaders),
  )
}

// Custom plugin configuration
type MyConfig struct {
  mockEnable bool
}

// Parse the JSON configuration into the config struct.
// The gateway console converts YAML to JSON automatically.
func parseConfig(json gjson.Result, config *MyConfig, log logs.Log) error {
  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
}

Temel noktalar:

  • main() işlevi boş olmalıdır. Tüm başlatma mantığını init() içine yerleştirin.

  • wrapper.SetCtx eklenti adını, yapılandırma ayrıştırıcısını ve işleme kancalarını kaydeder.

  • İsteği sonraki filtreye aktarmak için types.HeaderContinue (types.ActionContinue ile eşdeğer) döndürün.

SDK referansı

Yardımcı yöntemler

Eklenti SDK'sı, istek ve yanıt manipülasyonu için aşağıdaki proxywasm yöntemlerini sağlar:

İstek üst bilgisi işleme (istek üst bilgisi aşamasında etkilidir)


Yöntem


Amaç


GetHttpRequestHeaders


Tüm istek üst bilgilerini alın


ReplaceHttpRequestHeaders


Tüm istek üst bilgilerini değiştirin


GetHttpRequestHeader


Belirli bir istek üst bilgisini alın


RemoveHttpRequestHeader


Belirli bir istek üst bilgisini kaldırın


ReplaceHttpRequestHeader


Belirli bir istek üst bilgisini değiştirin


AddHttpRequestHeader


Bir istek üst bilgisi ekleyin

İstek gövdesi işleme (istek gövdesi aşamasında etkilidir)


Yöntem


Amaç


GetHttpRequestBody


İstek gövdesini alın


AppendHttpRequestBody


İstek gövdesinin sonuna veri ekleyin


PrependHttpRequestBody


İstek gövdesinin başına veri ekleyin


ReplaceHttpRequestBody


Tüm istek gövdesini değiştirin

Yanıt üst bilgisi işleme (yanıt üst bilgisi aşamasında etkilidir)


Yöntem


Amaç


GetHttpResponseHeaders


Arka uçtan tüm yanıt üst bilgilerini alın


ReplaceHttpResponseHeaders


Tüm yanıt üst bilgilerini değiştirin


GetHttpResponseHeader


Belirli bir yanıt üst bilgisini alın


RemoveHttpResponseHeader


Belirli bir yanıt üst bilgisini kaldırın


ReplaceHttpResponseHeader


Belirli bir yanıt üst bilgisini değiştirin


AddHttpResponseHeader


Bir yanıt üst bilgisi ekleyin

Yanıt gövdesi işleme (yanıt gövdesi aşamasında etkilidir)


Yöntem


Amaç


GetHttpResponseBody


Yanıt gövdesini alın


AppendHttpResponseBody


Yanıt gövdesinin sonuna veri ekleyin


PrependHttpResponseBody


Yanıt gövdesinin başına veri ekleyin


ReplaceHttpResponseBody


Tüm yanıt gövdesini değiştirin

HTTP çağrıları ve akış kontrolü


Yöntem


Amaç


DispatchHttpCall


Harici bir hizmete HTTP isteği gönderin


GetHttpCallResponseHeaders


DispatchHttpCall isteğinden yanıt üst bilgilerini alın


GetHttpCallResponseBody


DispatchHttpCall isteğinden yanıt gövdesini alın


GetHttpCallResponseTrailers


DispatchHttpCall isteğinden yanıt trailer'larını alın


SendHttpResponse


İstemciye doğrudan bir HTTP yanıtı döndürün


ResumeHttpRequest


Durdurulmuş istek işleme akışını devam ettirin


ResumeHttpResponse


Durdurulmuş yanıt işleme akışını devam ettirin

Önemli

İşleme durdurulmadığında ResumeHttpRequest veya ResumeHttpResponse çağrısı yapmayın. SendHttpResponse çağrıldıktan sonra, durdurulmuş herhangi bir istek veya yanıt otomatik olarak devam eder. Tekrar ResumeHttpRequest veya ResumeHttpResponse çağrısı yapmak tanımsız davranışa neden olur.

Üst bilgi durum kodları

Her işleme kancası, istek akışını kontrol eden bir üst bilgi durum kodu döndürür. Eklentinizin verileri durdurması, tamponlaması veya akış olarak göndermesi gerekip gerekmediğine bağlı olarak uygun durumu seçin:


Durum


Davranış


HeaderContinue


Mevcut filtre tamamlandı. İsteği sonraki filtreye iletin. types.ActionContinue ile eşdeğerdir.


HeaderStopIteration


Üst bilgiyi tutun ancak gövde verilerini okumaya devam edin. Bunu, gövde işleme aşamasında istek üst bilgilerini değiştirmek için kullanın. Bir gövde gereklidir -- gövde yoksa istek süresiz olarak engellenir. HasRequestBody() ile kontrol edin.


HeaderContinueAndEndStream


Üst bilgiyi end_stream = false ile sonraki filtreye iletin; bu, mevcut filtrenin daha fazla gövde verisi eklemesine olanak tanır.


HeaderStopAllIterationAndBuffer


Tüm yinelemeyi durdurun ve üst bilgileri, gövdeyi ve trailer'ları tamponlayın. Tampon limiti aşarsa, ağ geçidi istek aşamasında 413 veya yanıt aşamasında 500 döndürür. proxywasm.ResumeHttpRequest(), proxywasm.ResumeHttpResponse() veya proxywasm.SendHttpResponseWithDetail() ile devam ettirin.


HeaderStopAllIterationAndWatermark


HeaderStopAllIterationAndBuffer ile aynıdır, ancak bir hata döndürmek yerine tampon limiti aşıldığında bağlantı düzeyinde kısıtlamayı tetikler. ABI 0.2.1'deki types.ActionPause ile eşdeğerdir.

Not

HeaderStopIteration ve HeaderStopAllIterationAndWatermark kullanımının gerçek dünya örnekleri için Higress ai-transformer eklentisine ve ai-quota eklentisine bakın.

Wasm dosyasını derleme

Eklentiyi bir Wasm ikili dosyasına derleyin:

go mod tidy
GOOS=wasip1 GOARCH=wasm go build -buildmode=c-shared -o main.wasm ./

Bu işlem bir main.wasm dosyası üretir. Bu dosyayı yerel hata ayıklama için kullanın veya özel bir eklenti dağıtmak üzere cloud-native ağ geçidi pazar yeri aracılığıyla yükleyin.

WasmPlugin CustomResourceDefinition (CRD) veya konsol kullanıcı arayüzü kullanarak Higress üzerinden dağıtmak için Wasm dosyasını bir OCI veya Docker görüntüsüne paketleyin. Ayrıntılar için Özel eklentiler bölümüne bakın.

Yerel hata ayıklama

Ön koşullar

Docker yükleyin.

Test ortamını hazırlama

Proje dizininizde main.wasm dosyasının bulunduğundan emin olun, ardından aşağıdaki iki dosyayı oluşturun.

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
    # Enable debug-level logging for Wasm. Use info level in production.
    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
    # Comment out the following line to test on v6 networks
    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

Ortamı başlatın:

docker compose up

Doğrulama

Eklenen üst bilgiyi test edin. Ağ geçidi üzerinden (10000 bağlantı noktası) bir istek gönderin ve Hello: world üst bilgisinin göründüğünü onaylayın:

curl http://127.0.0.1:10000/get

Beklenen yanıt:

{
  "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 üst bilgisi, eklentinin aktif olduğunu onaylar.

Karşılaştırma amacıyla, httpbin'e doğrudan yapılan bir istek (12345 bağlantı noktası) bu üst bilgiyi içermez:

curl http://127.0.0.1:12345/get

Bir yapılandırma değişikliğini test edin. envoy.yaml dosyasını düzenleyin ve mockEnable değerini true olarak ayarlayın:

configuration:
    "@type": "type.googleapis.com/google.protobuf.StringValue"
    value: |
      {
        "mockEnable": true
      }

Ortamı yeniden başlatın ve aynı isteği gönderin:

curl http://127.0.0.1:10000/get

Beklenen yanıt:

hello world

Mock yanıtı, yapılandırma değişikliklerinin doğru şekilde etkili olduğunu onaylar.

Daha fazla örnek

Yapılandırma gerektirmeyen eklenti

Yapılandırma gerektirmeyen bir eklenti için boş bir yapılandırma yapısı tanımlayın ve ParseConfigBy öğesini atlayın:

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
}

Harici bir HTTP hizmetini çağırma

Eklentiler, Nacos hizmetlerine, Kubernetes hizmetlerine ve ağ geçidi konsolunda yapılandırılan sabit adresli veya DNS hizmetlerine HTTP çağrıları destekler. Standart net/http kitaplığı Wasm çalışma zamanında kullanılamaz. Bunun yerine SDK'nın kapsüllenmiş HTTP istemcisini kullanın.

Aşağıdaki örnek, başlatma sırasında hizmet yapılandırmasını ayrıştırır, ardından istek işleme sırasında hizmeti çağırır. Yanıt üst bilgilerinden bir belirteç çıkarır ve bunu orijinal isteğe enjekte eder.

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 for external calls
  client      wrapper.HttpClient
  // Path to request on the external service
  requestPath string
  // Response header key to extract and inject into the original request
  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")
  }
  // Full FQDN with service type suffix.
  // Examples: 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") {
      // Default port for static IP services
      servicePort = 80
    }
  }
  config.client = wrapper.NewClusterClient(wrapper.FQDNCluster{
    FQDN: serviceName,
    Port: servicePort,
        })
}

func onHttpRequestHeaders(ctx wrapper.HttpContext, config MyConfig, log logs.Log) types.Action {
  // Send an async HTTP GET request. Default timeout is 500 ms.
  err := config.client.Get(config.requestPath, nil,
           // Callback runs when the response arrives
           func(statusCode int, responseHeaders http.Header, responseBody []byte) {
             if statusCode != http.StatusOK {
               log.Errorf("http call failed, status: %d", statusCode)
               proxywasm.SendHttpResponse(http.StatusInternalServerError, nil,
                 []byte("http call failed"), -1)
               return
             }
             log.Infof("get status: %d, response body: %s", statusCode, responseBody)
             // Extract the token from the response and add it to the original request
             token := responseHeaders.Get(config.tokenHeader)
             if token != "" {
               proxywasm.AddHttpRequestHeader(config.tokenHeader, token)
             }
             // Resume the paused request so it can be forwarded to the backend
             proxywasm.ResumeHttpRequest()
    })

  if err != nil {
    // If the service call fails, let the request proceed and log the error
    log.Errorf("Error occured while calling http, it seems cannot find the service cluster.")
    return types.ActionContinue
  } else {
    // Pause the request until the async callback completes
    return types.HeaderStopAllIterationAndWatermark
  }
}

Eklentiden Redis çağırma

Aşağıdaki örnek, Redis tarafından desteklenen bir hız sınırlama eklentisi uygular. Dakika başına istek sayısını (QPM) izler ve limit aşıldığında HTTP 429 döndürür.

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 {
  // Full FQDN with service type suffix.
  // Examples: 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") {
      servicePort = 80
    } else {
      servicePort = 6379
    }
  }
  username := json.Get("username").String()
  password := json.Get("password").String()
  // Timeout in milliseconds
  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)
  // If err != nil, the gateway likely cannot reach the Redis backend.
  // Verify that the Redis service has not been deleted.
  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 {
    // If the Redis call fails, let the request proceed and log the error
    log.Errorf("Error occured while calling redis, it seems cannot find the redis cluster.")
    return types.HeaderContinue
  } else {
    // Pause the request until the Redis callback completes
    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
}