Skip to content

Commit

Permalink
feat: add sample for HTTP redirect.
Browse files Browse the repository at this point in the history
Signed-off-by: Will Hayworth <[email protected]>
  • Loading branch information
wsh committed Oct 8, 2023
1 parent 16441bd commit 67559ca
Show file tree
Hide file tree
Showing 5 changed files with 195 additions and 0 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ plugin. Extend them to fit your particular use case.
* [Rewrite the path using regex](samples/regex_rewrite): Remove a piece of the
URI using regex replacement. Demonstrate a standard way to use regular
expressions, compiling them at plugin initialization.
* [Send an HTTP redirect](samples/http_redirect): Send an HTTP 301 Moved
Permanently response for requests with specific paths.

# Feature set / ABI

Expand Down
41 changes: 41 additions & 0 deletions samples/http_redirect/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
load("//:plugins.bzl", "proxy_wasm_plugin_cpp", "proxy_wasm_plugin_rust")

licenses(["notice"]) # Apache 2

proxy_wasm_plugin_rust(
name = "plugin_rust.wasm",
srcs = ["plugin.rs"],
deps = [
"//cargo:url",
"@proxy_wasm_rust_sdk//:proxy_wasm",
"@proxy_wasm_rust_sdk//bazel/cargo:log",
],
)

proxy_wasm_plugin_cpp(
name = "plugin_cpp.wasm",
srcs = ["plugin.cc"],
deps = [
"//:boost_exception",
"@boost//:url",
"@proxy_wasm_cpp_sdk//contrib:contrib_lib",
],
)

cc_test(
name = "plugin_test",
srcs = ["test.cc"],
data = [
":plugin_cpp.wasm",
":plugin_rust.wasm",
],
deps = [
"//test:framework",
"@com_google_absl//absl/status",
"@com_google_absl//absl/status:statusor",
"@com_google_googletest//:gtest_main",
"@proxy_wasm_cpp_host//:base_lib",
"@proxy_wasm_cpp_host//:v8_lib",
"@proxy_wasm_cpp_host//test:utility_lib",
],
)
43 changes: 43 additions & 0 deletions samples/http_redirect/plugin.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// Copyright 2023 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#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) {
return FilterHeadersStatus::Continue;
}
boost::system::result<boost::urls::url_view> url =
boost::urls::parse_uri_reference(path->view());
if (!url) {
return FilterHeadersStatus::Continue;
}
if (url->path() == "/index.php") {
sendLocalResponse(301, "", "", {{"Location", "http://www.example.com/"}});
}
return FilterHeadersStatus::Continue;
}
};

static RegisterContextFactory register_StaticContext(
CONTEXT_FACTORY(MyHttpContext), ROOT_FACTORY(RootContext));
50 changes: 50 additions & 0 deletions samples/http_redirect/plugin.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// Copyright 2023 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

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

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 {
let Some(path) = self.get_http_request_header(":path") else {
// This standard Envoy pseudo-header should always be present.
return Action::Continue
};
// Url requires a base for parsing relative URLs.
let base = Url::parse("http://www.example.com").ok();
let options = Url::options().base_url(base.as_ref());
let Ok(url) = options.parse(&path) else {
// Don't try to redirect on a malformed URL.
return Action::Continue
};
let redirect = match url.path() {
"/index.php" => base,
_ => None
};
if let Some(redirect_url) = redirect {
self.send_http_response(301, vec![("Location", redirect_url.as_str())], None);
}
Action::Continue
}
}
59 changes: 59 additions & 0 deletions samples/http_redirect/test.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// Copyright 2023 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "include/proxy-wasm/exports.h"
#include "include/proxy-wasm/wasm.h"
#include "test/framework.h"

using ::testing::ElementsAre;
using ::testing::Pair;

namespace service_extensions_samples {

INSTANTIATE_TEST_SUITE_P(
EnginesAndPlugins, HttpTest,
::testing::Combine(
::testing::ValuesIn(proxy_wasm::getWasmEngines()),
::testing::Values("samples/http_redirect/plugin_cpp.wasm",
"samples/http_redirect/plugin_rust.wasm")));

TEST_P(HttpTest, RunPlugin) {
// Create VM + load plugin.
ASSERT_TRUE(CreatePlugin(engine(), path()).ok());

// Create stream context.
auto http_context = TestHttpContext(handle_);

// Expect no-op if no path is specified. (Never expected.)
auto res = http_context.SendRequestHeaders({});
EXPECT_EQ(res.http_code, 0);

// Send request with non-matching path. We have to supply the ":path"
// pseudo-header even though Envoy will do it automatically in production:
// https://github.com/GoogleCloudPlatform/service-extensions-samples/issues/10
auto res1 = http_context.SendRequestHeaders({{":path", "/"}});
EXPECT_EQ(res1.http_code, 0);

// Send request with matching path, expect redirect.
auto res2 = http_context.SendRequestHeaders({{":path", "/index.php"}});
EXPECT_EQ(res2.http_code, 301);
EXPECT_THAT(res2.headers,
ElementsAre(Pair("Location", "http://www.example.com/")));

EXPECT_FALSE(handle_->wasm()->isFailed());
}

} // namespace service_extensions_samples

0 comments on commit 67559ca

Please sign in to comment.