准备插件代码

您为 Service Extensions 插件创建的自定义代码必须先打包并上传到 Artifact Registry,然后其他服务才能访问该代码。本页面介绍了如何创建插件代码、打包代码,以及将代码上传到 Artifact Registry 代码库。

此功能处于 Media CDN 的预览版阶段。

如需了解 Service Extensions,请参阅 Service Extensions 概览

在开始之前,请查看有关编写插件代码的最佳实践

如需查看更多示例,请参阅插件的代码示例

准备工作

  1. 登录您的 Google Cloud 账号。如果您是 Google Cloud新手,请 创建一个账号来评估我们的产品在实际场景中的表现。新客户还可获享 $300 赠金,用于运行、测试和部署工作负载。
  2. In the Google Cloud console, on the project selector page, select or create a Google Cloud project.

    Roles required to select or create a project

    • Select a project: Selecting a project doesn't require a specific IAM role—you can select any project that you've been granted a role on.
    • Create a project: To create a project, you need the Project Creator role (roles/resourcemanager.projectCreator), which contains the resourcemanager.projects.create permission. Learn how to grant roles.

    Go to project selector

  3. Verify that billing is enabled for your Google Cloud project.

  4. Enable the Network Services, Network Actions, Artifact Registry, Cloud Build, Cloud Logging, and Cloud Monitoring APIs.

    Roles required to enable APIs

    To enable APIs, you need the Service Usage Admin IAM role (roles/serviceusage.serviceUsageAdmin), which contains the serviceusage.services.enable permission. Learn how to grant roles.

    Enable the APIs

  5. 安装 Google Cloud CLI。

  6. 如果您使用的是外部身份提供方 (IdP),则必须先使用联合身份登录 gcloud CLI

  7. 如需初始化 gcloud CLI,请运行以下命令:

    gcloud init
  8. In the Google Cloud console, on the project selector page, select or create a Google Cloud project.

    Roles required to select or create a project

    • Select a project: Selecting a project doesn't require a specific IAM role—you can select any project that you've been granted a role on.
    • Create a project: To create a project, you need the Project Creator role (roles/resourcemanager.projectCreator), which contains the resourcemanager.projects.create permission. Learn how to grant roles.

    Go to project selector

  9. Verify that billing is enabled for your Google Cloud project.

  10. Enable the Network Services, Network Actions, Artifact Registry, Cloud Build, Cloud Logging, and Cloud Monitoring APIs.

    Roles required to enable APIs

    To enable APIs, you need the Service Usage Admin IAM role (roles/serviceusage.serviceUsageAdmin), which contains the serviceusage.services.enable permission. Learn how to grant roles.

    Enable the APIs

  11. 安装 Google Cloud CLI。

  12. 如果您使用的是外部身份提供方 (IdP),则必须先使用联合身份登录 gcloud CLI

  13. 如需初始化 gcloud CLI,请运行以下命令:

    gcloud init

设置工具链

C++

借助 Proxy-Wasm C++ SDK,开发者可以使用 C++ 为 Service Extensions 实现 WebAssembly (Wasm) 插件。该 SDK 使用 C++ WebAssembly 工具链 Emscripten 以及其他库,例如 protobuf 和(可选)Abseil。

由于构建以 C++ 编写的插件取决于这些工具和库的特定版本,因此我们建议使用 Proxy-Wasm C++ SDK 提供的 Docker 映像。本页上的 C++ 说明使用 Docker 方法。如需在不使用 Docker 的情况下构建 C++ 插件,请参阅 Proxy-Wasm C++ SDK 文档

  1. 安装 Docker(如果尚未安装)。Cloud Shell( Google Cloud 交互式 shell 环境)中包含 Docker。

  2. 下载 Proxy-Wasm C++ SDK 的副本。最简单的方法是克隆 Git 代码库:

    git clone https://github.com/proxy-wasm/proxy-wasm-cpp-sdk.git
    
  3. 根据 SDK 提供的 Dockerfile 构建 Proxy-Wasm C++ SDK Docker 映像:

    cd proxy-wasm-cpp-sdk
    docker build -t wasmsdk:v3 -f Dockerfile-sdk .
    

    当该命令完成构建 SDK 库和依赖项后,生成的 Docker 映像会与指定的标记(在本例中为 wasmsdk:v3)相关联。

Go

Proxy-Wasm Go SDK 提供了一个功能齐全的 Go SDK。Go 语言可提供出色的性能,并对纯 Go 编写的第三方库提供丰富的支持。

安装 Go 工具链 v1.24.0

Rust

Service Extensions 的自定义功能通过使用 WebAssembly 和 Proxy-Wasm 提供。WebAssembly 支持多种编程语言。Google 推荐使用 Rust,因为它可提供出色的 WebAssembly 支持,而 Proxy-Wasm 可提供功能全面的 Rust SDK。Rust 还具有良好的性能和强大的类型安全性。

  1. 安装 Rust 工具链

    在安装过程结束时,按照打印到控制台的任何说明完成配置过程。

  2. 向 Rust 工具链添加 Wasm 支持:

    rustup target add wasm32-wasip1
    

创建插件软件包

C++

  1. 创建一个与 proxy-wasm-cpp-sdk 分开的新目录:

    mkdir myproject
    
  2. 在该目录中,创建一个包含以下内容的 Makefile:

    # Express any dependencies
    PROTOBUF=     # full / lite / none
    WASM_DEPS=    # absl_base re2 ...
    
    # Include the SDK Makefile
    PROXY_WASM_CPP_SDK=/sdk
    include ${PROXY_WASM_CPP_SDK}/Makefile
    
  3. 在同一目录中为插件添加 C++ 源文件。C++ 源文件的名称必须与 Makefile 所针对的 Wasm 文件一致,但 .wasm 后缀替换为 .cc。在此示例中,源文件必须命名为 myproject.cc

  4. 将插件代码添加到源文件中。

    以下示例源代码是一个重写请求主机并发出响应标头的插件:

    #include "proxy_wasm_intrinsics.h"
    
    class MyHttpContext : public Context {
     public:
      explicit MyHttpContext(uint32_t id, RootContext* root) : Context(id, root) {}
    
      FilterHeadersStatus onRequestHeaders(uint32_t headers,
                                           bool end_of_stream) override {
        LOG_INFO("onRequestHeaders: hello from wasm");
    
        // Route Extension example: host rewrite
        if (replaceRequestHeader(":authority", "service-extensions.com") != WasmResult::Ok) {
          LOG_ERROR("Failed to replace :authority header");
        }
        if (replaceRequestHeader(":path", "/") != WasmResult::Ok) {
          LOG_ERROR("Failed to replace :path header");
        }
        return FilterHeadersStatus::Continue;
      }
    
      FilterHeadersStatus onResponseHeaders(uint32_t headers,
                                            bool end_of_stream) override {
        LOG_INFO("onResponseHeaders: hello from wasm");
    
        // Traffic Extension example: add response header
        if (addResponseHeader("hello", "service-extensions") != WasmResult::Ok) {
          LOG_ERROR("Failed to add response header");
        }
        return FilterHeadersStatus::Continue;
      }
    };
    
    static RegisterContextFactory register_StaticContext(
        CONTEXT_FACTORY(MyHttpContext), ROOT_FACTORY(RootContext));

    onRequestHeaders 方法是 Service Extensions 调用的回调

Go

  1. 为插件创建新目录:

    mkdir go-plugin
    
  2. 在该目录中,使用 Go 工具创建一个 go.mod 文件:

    go mod init go-plugin
    
  3. 在同一目录中,创建一个名为 main.go 的源文件,并将插件代码添加到该文件中。

    以下示例源代码是一个重写请求主机并发出响应标头的插件:

    package main
    
    import (
    	"fmt"
    
    	"github.com/proxy-wasm/proxy-wasm-go-sdk/proxywasm"
    	"github.com/proxy-wasm/proxy-wasm-go-sdk/proxywasm/types"
    )
    
    func main() {}
    func init() {
    	proxywasm.SetVMContext(&vmContext{})
    }
    
    type vmContext struct {
    	types.DefaultVMContext
    }
    
    type pluginContext struct {
    	types.DefaultPluginContext
    }
    
    type httpContext struct {
    	types.DefaultHttpContext
    }
    
    func (*vmContext) NewPluginContext(contextID uint32) types.PluginContext {
    	return &pluginContext{}
    }
    
    func (*pluginContext) NewHttpContext(uint32) types.HttpContext {
    	return &httpContext{}
    }
    
    func (ctx *httpContext) OnHttpRequestHeaders(numHeaders int, endOfStream bool) types.Action {
    	defer func() {
    		err := recover()
    		if err != nil {
    			proxywasm.SendHttpResponse(500, [][2]string{}, []byte(fmt.Sprintf("%v", err)), 0)
    		}
    	}()
    	proxywasm.LogInfof("onRequestHeaders: hello from wasm")
    
    	// Route Extension example: host rewrite
    	err := proxywasm.ReplaceHttpRequestHeader(":authority", "service-extensions.com")
    	if err != nil {
    		panic(err)
    	}
    	err = proxywasm.ReplaceHttpRequestHeader(":path", "/")
    	if err != nil {
    		panic(err)
    	}
    	return types.ActionContinue
    }
    
    func (ctx *httpContext) OnHttpResponseHeaders(numHeaders int, endOfStream bool) types.Action {
    	defer func() {
    		err := recover()
    		if err != nil {
    			proxywasm.SendHttpResponse(500, [][2]string{}, []byte(fmt.Sprintf("%v", err)), 0)
    		}
    	}()
    	proxywasm.LogInfof("onResponseHeaders: hello from wasm")
    
    	// Traffic Extension example: add response header
    	err := proxywasm.AddHttpResponseHeader("hello", "service-extensions")
    	if err != nil {
    		panic(err)
    	}
    	return types.ActionContinue
    }
    

    OnHttpRequestHeaders 方法是 Service Extensions 调用的回调

  4. 如需下载并固定库依赖项,请运行 go mod tidy

    go mod tidy
    

Rust

  1. 使用 Rust 的软件包管理系统 Cargo 中的 cargo new 命令创建 Rust 软件包目录:

    cargo new --lib my-wasm-plugin
    

    该命令会创建一个目录,其中包含一个 cargo.toml 文件(您可以更新该文件来描述如何构建 Rust 软件包)和一个 src 目录(您可以在其中存储插件代码)。

  2. 更新 cargo.toml 文件以指定构建软件包所需的参数:

    [package]
    name = "my-wasm-plugin"
    version = "0.1.0"
    edition = "2021"
    
  3. 如需将 Proxy-Wasm Rust SDK 和日志记录支持注册为依赖项,请添加 dependencies 部分。例如:

    [dependencies]
    proxy-wasm = "0.2"
    log = "0.4"
    
  4. 如需构建插件所需的动态库,请添加 lib 部分。例如:

    [lib]
    crate-type = ["cdylib"]
    
  5. 如需减小已编译插件的大小,请添加 profile.release 部分。例如:

    [profile.release]
    lto = true
    opt-level = 3
    codegen-units = 1
    panic = "abort"
    strip = "debuginfo"
    
  6. 将插件代码添加到 src 目录中的 lib.rs 文件。

    以下示例源代码是一个重写请求主机并发出响应标头的插件:

    use log::info;
    use proxy_wasm::traits::*;
    use proxy_wasm::types::*;
    
    proxy_wasm::main! { {
        proxy_wasm::set_log_level(LogLevel::Trace);
        proxy_wasm::set_http_context(|_, _| -> Box<dyn HttpContext> { Box::new(MyHttpContext) });
    } }
    
    struct MyHttpContext;
    
    impl Context for MyHttpContext {}
    
    impl HttpContext for MyHttpContext {
        fn on_http_request_headers(&mut self, _: usize, _: bool) -> Action {
            info!("onRequestHeaders: hello from wasm");
    
            // Route extension example: host rewrite
            self.set_http_request_header(":authority", Some("service-extensions.com"));
            self.set_http_request_header(":path", Some("/"));
            return Action::Continue;
        }
    
        fn on_http_response_headers(&mut self, _: usize, _: bool) -> Action {
            info!("onResponseHeaders: hello from wasm");
    
            // Traffic extension example: add response header
            self.add_http_response_header("hello", "service-extensions");
            return Action::Continue;
        }
    }

    on_http_request_headers 方法是 Service Extensions 调用的回调

编译插件

C++

如需编译插件,请从 Makefile 和 C++ 插件源文件所在的目录中运行以下命令:

docker run -v $PWD:/work -w /work wasmsdk:v3 /build_wasm.sh myproject.wasm

此命令将当前目录映射到 Docker 映像中的 work 目录,然后运行 Docker 映像提供的 build_wasm.sh 脚本来构建插件代码。编译操作成功完成后,当前目录中会创建一个 myproject.wasm 文件,其中包含编译后的 Wasm 字节码。

首次使用 Docker 映像编译插件代码时,Emscripten 会生成标准库。为了将这些内容缓存在 Docker 映像中,以便每次都不需要重新生成,请在首次成功编译后提交包含标准库的映像:

docker commit `docker ps -l | grep wasmsdk:v3 | awk '{print $1}'` wasmsdk:v3

如需详细了解如何构建 C++ 插件,请参阅 Proxy-Wasm C++ SDK 文档

Go

如需编译插件代码,请运行 go build 命令:

env GOOS=wasip1 GOARCH=wasm go build -buildmode=c-shared -o main.wasm main.go

成功编译后,会在当前目录中创建一个 main.wasm 文件。

Rust

如需编译插件代码,请运行 cargo build 命令:

cargo build --release --target wasm32-wasip1

构建成功完成后,系统会显示一条 Finished release [optimized] target(s) 消息。如果您熟悉 Envoy,不妨在 Envoy 中加载插件并验证其行为。

将已编译的插件代码上传到 Artifact Registry

将编译后的插件代码上传到 Artifact Registry 代码库,以便 Google Cloud 服务可以访问该代码。

如果您将 Service Extensions 与全球负载均衡器搭配使用,请使用多区域 us 位置中的代码库。如果您使用的是区域级负载均衡器,请使用位于同一区域或同一大洲的多区域位置的代码库。

将插件附加到全球负载平衡器时,Service Extensions 会将 Wasm 模块从 Artifact Registry 代码库复制到其位于世界各地的自有存储空间。因此,代码库的位置不会影响发送到负载均衡器的请求的延迟时间。对于区域级负载均衡器,插件会复制到与负载均衡器位于同一区域的位置。

Service Extensions 插件可以上传到 Artifact Registry 通用代码库或 Docker 代码库。

为了更直接地管理独立二进制文件,我们建议您使用通用代码库来存储 Wasm 文件。此选项目前为预览版

通用代码库

  1. 创建一个本地 package/ 目录,并将可发布的插件模块复制到该目录中。插件制品必须命名为 plugin.wasm。以下示例命令会复制由 Rust 构建的插件制品:

    mkdir -p package && cp -f target/wasm32-wasip1/release/my_wasm_plugin.wasm package/plugin.wasm
    
  2. 创建 Artifact Registry 代码库,并将 --repository-format 设置为 generic

  3. 验证您是否拥有访问代码库所需的权限

  4. 如需将您的 Wasm 模块作为通用制品上传到代码库,请使用 gcloud artifacts generic upload 命令

    gcloud artifacts generic upload \
        --project=PROJECT_ID \
        --location=LOCATION \
        --repository=REPOSITORY \
        --source=package/plugin.wasm \
        --package=PACKAGE \
        --version=VERSION
    

    替换以下内容:

    • PROJECT_ID:您的 Google Cloud 项目 ID
    • LOCATION:代码库的区域或多区域位置
    • REPOSITORY:您要将 Wasm 模块上传到的代码库的名称
    • PACKAGE:文件的软件包名称
    • VERSION:Wasm 模块的版本

    上传完成后,请记下带有 name 标签的通用制品名称,例如以下示例中的 add_header_plugin:v4。您可以在创建插件或插件版本时指定此名称。

    Uploading file: plugin.wasm...done.
    '@type': type.googleapis.com/google.devtools.artifactregistry.v1.GenericArtifact
    createTime: '2025-06-16T11:02:25.080248Z'
    name: projects/my-project/locations/us/repositories/my-generic-repo/genericArtifacts/add_header_plugin:v4
    updateTime: '2025-06-16T11:02:25.080248Z'
    version: v4
    
  5. 如需确认制品已成功上传到 Artifact Registry,请运行 gcloud artifacts files list 命令,并验证该列表是否包含名为 PACKAGE:VERSION:plugin.wasm 的文件。

     gcloud artifacts files list \
         --project=PROJECT_ID \
         --location=LOCATION \
         --repository=REPOSITORY
    

Docker 代码库

  1. 创建 Artifact Registry 代码库,并将 --repository-format 设置为 docker

  2. 验证您是否拥有访问代码库所需的权限

  3. 创建一个本地 package/ 目录,并将可发布的插件制品复制到该目录中。以下示例复制了由 Rust 构建的插件制品:

    mkdir -p package && cp -f target/wasm32-wasip1/release/my_wasm_plugin.wasm package/plugin.wasm
    
  4. 创建一个包含以下内容的 package/Dockerfile 文件:

    FROM scratch
    COPY plugin.wasm plugin.wasm
    
  5. 使用 Cloud Build 或 Docker 打包插件代码。

    Cloud Build

    1. 创建一个包含以下内容的 package/cloudbuild.yaml build config 文件:

      steps:
        - name: 'gcr.io/cloud-builders/docker'
          args: [ 'build', '--no-cache', '--platform', 'wasm',
                '-t', 'LOCATION-docker.pkg.dev/PROJECT_ID/REPOSITORY/IMAGE:IMAGE_TAG', '.' ]
      images: [ 'LOCATION-docker.pkg.dev/PROJECT_ID/REPOSITORY/IMAGE:IMAGE_TAG' ]
      

      您存储插件的路径必须采用以下格式:

      LOCATION-docker.pkg.dev/PROJECT_ID/REPOSITORY/IMAGE:IMAGE_TAG
      

      替换以下内容:

      • LOCATION:代码库的区域或多区域位置
      • PROJECT_ID:您的 Google Cloud 控制台项目 ID
      • REPOSITORY:您打算存储映像的代码库的名称
      • IMAGE:仓库中容器映像的名称,例如 us-docker.pkg.dev/my-project/my-repo/my-wasm-plugin
      • IMAGE_TAG:要分配给容器的映像标记,例如 production
    2. 触发操作以构建插件容器并将其上传到 Artifact Registry:

      gcloud builds submit --config package/cloudbuild.yaml package/
      

    Docker

    1. 安装 Docker(如果尚未安装)。Cloud Shell( Google Cloud 交互式 shell 环境)中包含 Docker。

    2. 配置 Docker 以向 Artifact Registry 进行身份验证。 例如:

      gcloud auth login
      gcloud auth configure-docker LOCATION-docker.pkg.dev
      gcloud auth print-access-token | docker login -u oauth2accesstoken --password-stdin https://LOCATION-docker.pkg.dev
      
    3. 构建容器映像:

      docker build --no-cache --platform wasm -t my-wasm-plugin package/
      
    4. 如需使用代码库映像名称标记本地映像,请使用映像标记

      docker tag my-wasm-plugin LOCATION-docker.pkg.dev/PROJECT_ID/REPOSITORY/IMAGE:IMAGE_TAG
      

      替换以下内容:

      • LOCATION:制品库的区域或多区域位置
      • PROJECT_ID:您的 Google Cloud 控制台项目 ID
      • REPOSITORY:您打算存储映像的代码库的名称
      • IMAGE:代码库中容器映像的名称,例如 us-docker.pkg.dev/my-project/my-repo/my-wasm-plugin
      • IMAGE_TAG:要分配给容器的映像标记,例如 production
    5. 将带有标记的容器映像上传到 Artifact Registry。

      docker push LOCATION-docker.pkg.dev/PROJECT_ID/REPOSITORY/IMAGE:IMAGE_TAG
      
  6. 如需确认映像已成功上传到 Artifact Registry,请运行 gcloud artifacts docker images list 命令

    gcloud artifacts docker images list LOCATION-docker.pkg.dev/PROJECT_ID/REPOSITORY/IMAGE \
        --include-tags
    

准备并上传配置文件

插件可以选择性地接收配置数据,这些数据可能会影响插件在运行时的行为。配置数据可以是文本或二进制数据,并且采用插件接受的任何格式。如果配置数据的大小较大,您可能需要将配置文件上传到 Artifact Registry。

编写插件代码以读取配置数据

C++

配置数据会传递给根上下文对象的 onConfigure 方法,该对象在插件启动时实例化一次,并在托管插件的 Wasm 运行时的整个生命周期内保持活跃状态。根上下文对象是 RootContext 类或 RootContext 的子类的实例。

插件代码可以通过 RegisterContextFactory 值控制用于根上下文的类。例如,以下插件代码将 MyRootContextMyHttpContext 注册为用于根上下文实例和流上下文实例的类。然后,它会从插件配置数据中读取一个密文值,流上下文实例可以通过根上下文对象访问该密文值。

#include <string>
#include <string_view>

#include "proxy_wasm_intrinsics.h"

class MyRootContext : public RootContext {
 public:
  explicit MyRootContext(uint32_t id, std::string_view root_id)
      : RootContext(id, root_id) {}

  bool onConfigure(size_t config_len) override {
    auto buffer = getBufferBytes(WasmBufferType::PluginConfiguration, 0, config_len);
    if (!buffer) {
      LOG_ERROR("Failed to retrieve plugin configuration");
      return false;
    }
    secret_ = buffer->toString();
    return true;
  }

  const std::string& secret() const { return secret_; }

 private:
  std::string secret_;
};

class MyHttpContext : public Context {
 public:
  explicit MyHttpContext(uint32_t id, RootContext* root)
      : Context(id, root),
        secret_(static_cast<MyRootContext*>(root)->secret()) {}

  FilterHeadersStatus onRequestHeaders(uint32_t headers,
                                       bool end_of_stream) override {
    // Use secret here...
    LOG_INFO("secret: " + secret_);
    return FilterHeadersStatus::Continue;
  }

 private:
  const std::string& secret_;
};

static RegisterContextFactory register_MyHttpContext(
    CONTEXT_FACTORY(MyHttpContext), ROOT_FACTORY(MyRootContext));

Go

在运行 OnPluginStart 时读取配置数据,这是一种方法接收器,运行时会在插件启动时在 PluginContext 结构体上执行一次。在托管插件的 Wasm 运行时生命周期内,配置数据不会发生变化。PluginContext 可用于执行初始化逻辑,并在多个请求之间保持状态。

插件代码可以通过在 VMContext 上实现 NewPluginContext 方法来控制用于 PluginContext 的上下文,该方法会实例化 PluginContextPluginContext 随后会实例化 HttpContext 上下文。

例如,以下插件代码注册了 VMContext,后者会实例化 PluginContext(负责读取配置数据并根据需要实例化 HttpContext 上下文的上下文)。PluginContext 从插件配置数据中读取一个 secret 值,将其存储在 PluginContext 上下文中,并在 NewHttpContext 中将其传递给 HttpContext

package main

import (
	"fmt"

	"github.com/proxy-wasm/proxy-wasm-go-sdk/proxywasm"
	"github.com/proxy-wasm/proxy-wasm-go-sdk/proxywasm/types"
)

func main() {}
func init() {
	proxywasm.SetVMContext(&vmContext{})
}

type vmContext struct {
	types.DefaultVMContext
}

type pluginContext struct {
	types.DefaultPluginContext
	secret string
}

type httpContext struct {
	types.DefaultHttpContext
	pluginContext *pluginContext
}

func (*vmContext) NewPluginContext(contextID uint32) types.PluginContext {
	return &pluginContext{}
}

func (ctx *pluginContext) OnPluginStart(int) types.OnPluginStartStatus {
	config, err := proxywasm.GetPluginConfiguration()
	if err != nil {
		proxywasm.LogErrorf("Error reading the configuration: %v", err)
		return types.OnPluginStartStatusFailed
	}
	if config == nil {
		proxywasm.LogError("Configuration is nil")
		return types.OnPluginStartStatusFailed
	}
	ctx.secret = string(config)
	return types.OnPluginStartStatusOK
}

func (ctx *pluginContext) NewHttpContext(uint32) types.HttpContext {
	return &httpContext{pluginContext: ctx}
}

func (ctx *pluginContext) Secret() string {
	return ctx.secret
}

func (ctx *httpContext) OnHttpRequestHeaders(numHeaders int, endOfStream bool) types.Action {
	defer func() {
		err := recover()
		if err != nil {
			proxywasm.SendHttpResponse(500, [][2]string{}, []byte(fmt.Sprintf("%v", err)), 0)
		}
	}()
	// Use secret here...
	proxywasm.LogInfof("secret: %v", ctx.pluginContext.Secret())
	return types.ActionContinue
}

Rust

配置数据是从 RootContext trait 读取的,该 trait 在插件启动时实例化一次,并在托管插件的 Wasm 运行时的整个生命周期内保持有效。RootContext 特征对于跨多个请求执行操作或维护状态非常有用。

插件代码可以通过 set_root_context 方法控制用于根上下文的类,而根上下文会实例化流上下文。例如,以下插件代码注册了 MyRootContext,该插件会根据需要实例化 MyHttpContext。根上下文从插件配置数据中读取密钥值,并将其传递给流上下文。

use log::info;
use proxy_wasm::traits::*;
use proxy_wasm::types::*;
use std::rc::Rc;

proxy_wasm::main! { {
    proxy_wasm::set_log_level(LogLevel::Trace);
    proxy_wasm::set_root_context(|_| -> Box<dyn RootContext> {
        Box::new(MyRootContext { secret: Rc::new("missing".to_string()) })
    });
} }

struct MyRootContext {
    secret: Rc<String>,
}

impl Context for MyRootContext {}

impl RootContext for MyRootContext {
    fn on_configure(&mut self, _: usize) -> bool {
        if let Some(config_bytes) = self.get_plugin_configuration() {
            self.secret = Rc::new(String::from_utf8(config_bytes).unwrap())
        }
        true
    }

    fn create_http_context(&self, _: u32) -> Option<Box<dyn HttpContext>> {
        Some(Box::new(MyHttpContext {
            secret: self.secret.clone(), // shallow copy, ref count only
        }))
    }

    fn get_type(&self) -> Option<ContextType> {
        Some(ContextType::HttpContext)
    }
}

struct MyHttpContext {
    secret: Rc<String>,
}

impl Context for MyHttpContext {}

impl HttpContext for MyHttpContext {
    fn on_http_request_headers(&mut self, _: usize, _: bool) -> Action {
        // Use secret here...
        info!("secret: {}", self.secret);
        Action::Continue
    }
}

上传配置文件

如果要在 on_configure 中交付给插件的数据大小超过 900 KiB,请按照将已编译的插件代码上传到 Artifact Registry 中描述的方法将其上传到 Artifact Registry。在这种情况下,请将配置文件另存为 plugin.config(而非 plugin.wasm)。

下一步是创建插件

创建插件时,您需要提供已上传的 Wasm 模块或映像的 URI。

创建新版本的插件代码

如需创建新版本的插件代码,请修改插件文件。然后,如前几部分所述,编译插件代码,重新打包,并将其上传到 Artifact Registry。

回调

您编译为 Wasm 的代码可以定义任意方法或函数,但其中一些具有特殊意义。这些方法在您选择的语言的 Proxy-Wasm SDK 中定义,并映射到 Proxy-Wasm 应用二进制接口 (ABI) 规范。Service Extensions 会调用这些回调来响应用户请求或插件生命周期事件。

下表列出了这些回调,并按其通常的调用顺序进行了排序:

回调名称和说明 C++ 方法名称 Go 方法名称 Rust 方法名称
START_PLUGIN:在插件启动时调用。 RootContext::onStart VMContext.OnVmStart RootContext::on_vm_start
CONFIGURE_PLUGIN:在插件启动后调用,以向插件提供配置数据。 RootContext::onConfigure PluginContext.OnPluginStart RootContext::on_configure
CREATE_CONTEXT:在新流上下文创建时调用。每个数据流都对应于一个客户端 HTTP 请求。 Context::onCreate PluginContext.NewHttpContext RootContext::create_http_context
HTTP_REQUEST_HEADERS:调用以处理 HTTP 请求标头。 Context::onRequestHeaders HttpContext.OnHttpRequestHeaders HttpContext::on_http_request_headers
HTTP_REQUEST_BODY:重复调用以处理 HTTP 请求正文块。 Context::onRequestBody HttpContext.OnHttpRequestBody HttpContext::on_http_request_body
HTTP_RESPONSE_HEADERS:调用以处理 HTTP 响应标头。 Context::onResponseHeaders HttpContext.OnHttpResponseHeaders HttpContext::on_http_response_headers
HTTP_RESPONSE_BODY:重复调用以处理 HTTP 响应正文块。 Context::onResponseBody HttpContext.OnHttpResponseBody HttpContext::on_http_response_body
DONE:在插件处理完成时调用。 Context::onDone HttpContext.OnHttpStreamDone Context::on_done
DELETE:当与客户端 HTTP 请求对应的流上下文对象被删除时调用。 Context::onDelete (无回调) (无回调)

后续步骤