插件的代码示例

本页面提供了一些代码示例,这些示例涵盖了插件的一些常见使用场景。

如需查看更多 Rust、Go 和 C++ 版插件示例,请参阅 Service Extensions 插件 GitHub 代码库

插件功能目前为 Media CDN 的预览版

添加 HTTP 请求和响应标头

以下代码示例展示了如何添加 HTTP 请求标头。

C++

#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 {
    // Always be a friendly proxy.
    addRequestHeader("Message", "hello");
    replaceRequestHeader("Welcome", "warm");
    return FilterHeadersStatus::Continue;
  }
};

static RegisterContextFactory register_StaticContext(
    CONTEXT_FACTORY(MyHttpContext), ROOT_FACTORY(RootContext));

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 (vc *vmContext) NewPluginContext(contextID uint32) types.PluginContext {
	return &pluginContext{}
}

func (pc *pluginContext) NewHttpContext(contextID uint32) types.HttpContext {
	return &httpContext{}
}

func (ctx *httpContext) OnHttpRequestHeaders(numHeaders int, endOfStream bool) types.Action {
	defer func() {
		if err := recover(); err != nil {
			proxywasm.SendHttpResponse(500, [][2]string{}, []byte(fmt.Sprintf("%v", err)), 0)
		}
	}()

	// Add and replace headers.
	if err := proxywasm.AddHttpRequestHeader("Message", "hello"); err != nil {
		panic(err)
	}
	if err := proxywasm.ReplaceHttpRequestHeader("Welcome", "warm"); err != nil {
		panic(err)
	}

	return types.ActionContinue
}

Rust

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 {
        // Always be a friendly proxy.
        self.add_http_request_header("Message", "hello");
        self.set_http_request_header("Welcome", Some("warm"));
        return Action::Continue;
    }
}

以下代码示例展示了如何添加 HTTP 响应标头。

C++

#include "proxy_wasm_intrinsics.h"

class MyHttpContext : public Context {
 public:
  explicit MyHttpContext(uint32_t id, RootContext* root) : Context(id, root) {}

  FilterHeadersStatus onResponseHeaders(uint32_t headers,
                                        bool end_of_stream) override {
    // Conditionally add to a header value.
    auto msg = getResponseHeader("Message");
    if (msg && msg->view() == "foo") {
      addResponseHeader("Message", "bar");
    }
    // Unconditionally remove a header.
    removeResponseHeader("Welcome");
    return FilterHeadersStatus::Continue;
  }
};

static RegisterContextFactory register_StaticContext(
    CONTEXT_FACTORY(MyHttpContext), ROOT_FACTORY(RootContext));

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 (vc *vmContext) NewPluginContext(contextID uint32) types.PluginContext {
	return &pluginContext{}
}

func (pc *pluginContext) NewHttpContext(contextID uint32) types.HttpContext {
	return &httpContext{}
}

func (ctx *httpContext) OnHttpResponseHeaders(numHeaders int, endOfStream bool) types.Action {
	defer func() {
		if err := recover(); err != nil {
			proxywasm.SendHttpResponse(500, [][2]string{}, []byte(fmt.Sprintf("%v", err)), 0)
		}
	}()

	// Conditionally add to a header value.
	msgValue, err := proxywasm.GetHttpResponseHeader("Message")
	if err != nil {
		proxywasm.LogCriticalf("failed to get 'Message' header: %v", err)
	} else if msgValue == "foo" {
		if err := proxywasm.AddHttpResponseHeader("Message", "bar"); err != nil {
			panic(err)
		}
	}

	// Unconditionally remove the "Welcome" header.
	if err := proxywasm.RemoveHttpResponseHeader("Welcome"); err != nil {
		panic(err)
	}

	return types.ActionContinue
}

Rust

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_response_headers(&mut self, _: usize, _: bool) -> Action {
        // Conditionally add to a header value.
        let msg = self.get_http_response_header("Message");
        if msg.unwrap_or_default() == "foo" {
            self.add_http_response_header("Message", "bar");
        }
        // Unconditionally remove a header.
        self.set_http_response_header("Welcome", None);
        return Action::Continue;
    }
}

重写请求网址

以下代码示例展示了如何使用正则表达式重写请求网址。以下代码示例移除了部分路径,但任何 URI 变异(例如路径、查询或 fragment)都是可行的。

这些示例还展示了有关正则表达式的最佳实践,即使用线性时间正则表达式库并在插件初始化时编译表达式。

C++

#include "proxy_wasm_intrinsics.h"
#include "re2/re2.h"

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

  bool onConfigure(size_t) override {
    // Compile the regex expression at plugin setup time.
    path_match.emplace("/foo-([^/]+)/");
    return path_match->ok();
  }

  std::optional<re2::RE2> path_match;
};

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

  FilterHeadersStatus onRequestHeaders(uint32_t headers,
                                       bool end_of_stream) override {
    auto path = getRequestHeader(":path");
    if (path) {
      std::string edit = path->toString();  // mutable copy
      if (re2::RE2::Replace(&edit, *root_->path_match, "/\\1/")) {
        replaceRequestHeader(":path", edit);
      }
    }
    return FilterHeadersStatus::Continue;
  }

 private:
  const MyRootContext* root_;
};

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

Go

package main

import (
	"fmt"
	"regexp"

	"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
	pathMatch *regexp.Regexp
}

type httpContext struct {
	types.DefaultHttpContext
	pluginContext *pluginContext
}

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

func (ctx *pluginContext) OnPluginStart(int) types.OnPluginStartStatus {
	var err error
	// Compile the regex expression at plugin setup time.
	ctx.pathMatch, err = regexp.Compile("/foo-([^/]+)/")
	if err != nil {
		proxywasm.LogErrorf("Error compiling the path regular expression: %v", err)
		return types.OnPluginStartStatusFailed
	}
	return types.OnPluginStartStatusOK
}

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

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)
		}
	}()
	path, err := proxywasm.GetHttpRequestHeader(":path")
	if err != types.ErrorStatusNotFound {
		if err != nil {
			panic(err)
		}
		edit := ctx.pluginContext.pathMatch.ReplaceAllString(path, "/$1/")
		if len(edit) != len(path) {
			err = proxywasm.ReplaceHttpRequestHeader(":path", edit)
			if err != nil {
				panic(err)
			}
		}
	}
	return types.ActionContinue
}

Rust

use proxy_wasm::traits::*;
use proxy_wasm::types::*;
use regex::Regex;
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 { path_match: None })
    });
}}

struct MyRootContext {
    path_match: Option<Rc<Regex>>,
}

impl Context for MyRootContext {}

impl RootContext for MyRootContext {
    fn on_configure(&mut self, _: usize) -> bool {
        self.path_match = Some(Rc::new(Regex::new(r"/foo-([^/]+)/").unwrap()));
        return true;
    }

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

struct MyHttpContext {
    path_match: Rc<Regex>,
}

impl Context for MyHttpContext {}

impl HttpContext for MyHttpContext {
    fn on_http_request_headers(&mut self, _: usize, _: bool) -> Action {
        if let Some(path) = self.get_http_request_header(":path") {
            let edit = self.path_match.replace(&path, "/$1/");
            if path.len() != edit.len() {
                self.set_http_request_header(":path", Some(&edit));
            }
        }
        return Action::Continue;
    }
}

为自定义变量启用日志记录

以下代码示例展示了如何对请求查询字符串参数执行基本检查,并将解析后的信息发送到 Cloud Logging。这些示例还展示了如何解析网址。

C++

#include <boost/url/parse.hpp>
#include <boost/url/url.hpp>

#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 {
    WasmDataPtr path = getRequestHeader(":path");
    if (path) {
      std::string token = "<missing>";
      boost::system::result<boost::urls::url_view> url =
          boost::urls::parse_uri_reference(path->view());
      if (url) {
        auto it = url->params().find("token");
        if (it != url->params().end()) {
          token = (*it).value;
        }
      }
      LOG_INFO("token: " + token);
    }
    return FilterHeadersStatus::Continue;
  }
};

static RegisterContextFactory register_StaticContext(
    CONTEXT_FACTORY(MyHttpContext), ROOT_FACTORY(RootContext));

Go

package main

import (
	"fmt"
	"net/url"

	"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)
		}
	}()
	path, err := proxywasm.GetHttpRequestHeader(":path")
	if err != types.ErrorStatusNotFound {
		if err != nil {
			panic(err)
		}
		u, err := url.Parse(path)
		if err != nil {
			panic(err)
		}
		token := u.Query().Get("token")
		if token == "" {
			token = "<missing>"
		}
		proxywasm.LogInfof("token: %s", token)
	}

	return types.ActionContinue
}

Rust

use log::info;
use proxy_wasm::traits::*;
use proxy_wasm::types::*;
use url::Url;

proxy_wasm::main! {{
    proxy_wasm::set_log_level(LogLevel::Trace);  // log everything, subject to plugin LogConfig
    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 {
        if let Some(path) = self.get_http_request_header(":path") {
            // Create dummy base/host to allow parsing relative paths.
            let base = Url::parse("http://example.com").ok();
            let options = Url::options().base_url(base.as_ref());
            let token: Option<String> = match options.parse(&path) {
                Err(_) => None,
                Ok(url) => url.query_pairs().find_map(|(k, v)| {
                    if k == "token" {
                        Some(v.to_string())
                    } else {
                        None
                    }
                }),
            };
            info!("token: {}", token.unwrap_or("<missing>".to_string()));
        }
        return Action::Continue;
    }
}