Code samples for plugins

This page provides code samples that address some common use cases for plugins.

Add HTTP request and response headers

The following code samples show how to add HTTP request and response headers.

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

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

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

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

Rewrite the request URL

The following code samples show how to rewrite the request URL by using regular expressions. The following code samples remove part of the path, but any URI mutation, such as path, query, fragment, and so on, is feasible.

These samples also show best practices around regular expressions, namely using linear-time regex libraries and compiling the expressions at plugin initialization time.

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

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

Enable logging for custom variables

The following code samples show how to perform a basic check on the request query string parameters and emit parsed information to Cloud Logging. These samples also show how to parse URLs.

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

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