プラグインのコードサンプル

このページでは、プラグインの一般的なユースケースに対応するコードサンプルを紹介します。

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;
    }
}

リクエスト URL を書き換える

次のコードサンプルは、正規表現を使用してリクエスト URL を書き換える方法を示しています。次のコードサンプルではパスの一部を削除していますが、パス、クエリ、フラグメントなど、URI の変更はすべて可能です。

これらのサンプルは、正規表現に関するベスト プラクティス(線形時間正規表現ライブラリの使用、プラグインの初期化時の式のコンパイルなど)も示しています。

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 に出力する方法を示しています。これらのサンプルでは、URL を解析する方法も示しています。

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;
    }
}