Skip to content

Commit

Permalink
Adding http client support for ext_proc filter (envoyproxy#35676)
Browse files Browse the repository at this point in the history
Risk Level: low
Testing: n/a
Docs Changes: n/a
Release Notes: inline
Fixes:

Description:
This is to address the 2st step of
envoyproxy#35488, i.e, the ext_proc HTTP
client framework.

---------

Signed-off-by: Yanjun Xiang <yanjunxiang@google.com>
  • Loading branch information
yanjunxiang-google authored Aug 17, 2024
1 parent 157a4b2 commit dbb35fa
Show file tree
Hide file tree
Showing 6 changed files with 245 additions and 0 deletions.
29 changes: 29 additions & 0 deletions source/extensions/filters/http/ext_proc/http_client/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
load(
"//bazel:envoy_build_system.bzl",
"envoy_cc_library",
"envoy_extension_package",
)

licenses(["notice"]) # Apache 2

envoy_extension_package()

envoy_cc_library(
name = "client_base_interface",
hdrs = ["client_base.h"],
tags = ["skip_on_windows"],
deps = [],
)

envoy_cc_library(
name = "http_client_lib",
srcs = ["http_client_impl.cc"],
hdrs = ["http_client_impl.h"],
tags = ["skip_on_windows"],
deps = [
"client_base_interface",
"//source/common/common:enum_to_int",
"//source/common/http:utility_lib",
"//source/extensions/filters/http/ext_proc",
],
)
33 changes: 33 additions & 0 deletions source/extensions/filters/http/ext_proc/http_client/client_base.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#pragma once

#include <memory>

namespace Envoy {
namespace Extensions {
namespace HttpFilters {
namespace ExternalProcessing {

/**
* Async callbacks used during external processing.
*/
class RequestCallbacks {
public:
virtual ~RequestCallbacks() = default;
virtual void onComplete() PURE;
};

/**
* Async client base class used during external processing.
*/
class ClientBase {
public:
virtual ~ClientBase() = default;

virtual void sendRequest() PURE;
virtual void cancel() PURE;
};

} // namespace ExternalProcessing
} // namespace HttpFilters
} // namespace Extensions
} // namespace Envoy
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
#include "source/extensions/filters/http/ext_proc/http_client/http_client_impl.h"

#include "source/common/common/enum_to_int.h"
#include "source/common/http/utility.h"

namespace Envoy {
namespace Extensions {
namespace HttpFilters {
namespace ExternalProcessing {

void ExtProcHttpClient::onSuccess(const Http::AsyncClient::Request&,
Http::ResponseMessagePtr&& response) {
auto status = Envoy::Http::Utility::getResponseStatusOrNullopt(response->headers());
if (status.has_value()) {
uint64_t status_code = status.value();
if (status_code == Envoy::enumToInt(Envoy::Http::Code::OK)) {
ENVOY_LOG(error, "Response status is OK");
} else {
ENVOY_LOG(error, "Response status is not OK, status: {}", status_code);
onError();
}
} else {
// This occurs if the response headers are invalid.
ENVOY_LOG(error, "Failed to get the response because response headers are not valid.");
onError();
}
}

void ExtProcHttpClient::onFailure(const Http::AsyncClient::Request&,
Http::AsyncClient::FailureReason reason) {
ASSERT(reason == Http::AsyncClient::FailureReason::Reset ||
reason == Http::AsyncClient::FailureReason::ExceedResponseBufferLimit);
ENVOY_LOG(error, "Request failed: stream has been reset");
onError();
}

void ExtProcHttpClient::onError() {
// Cancel if the request is active.
cancel();
}

} // namespace ExternalProcessing
} // namespace HttpFilters
} // namespace Extensions
} // namespace Envoy
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
#pragma once

#include <memory>

#include "envoy/http/async_client.h"

#include "source/common/common/logger.h"
#include "source/extensions/filters/http/ext_proc/ext_proc.h"
#include "source/extensions/filters/http/ext_proc/http_client/client_base.h"

namespace Envoy {
namespace Extensions {
namespace HttpFilters {
namespace ExternalProcessing {

class ExtProcHttpClient : public ClientBase,
public Http::AsyncClient::Callbacks,
public Logger::Loggable<Logger::Id::init> {
public:
ExtProcHttpClient(const envoy::extensions::filters::http::ext_proc::v3::ExternalProcessor& config,
Server::Configuration::ServerFactoryContext& context)
: config_(config), context_(context) {}

~ExtProcHttpClient() { cancel(); }

void sendRequest() override {}
void cancel() override {}
void onBeforeFinalizeUpstreamSpan(Tracing::Span&, const Http::ResponseHeaderMap*) override {}

// Http::AsyncClient::Callbacks implemented by this class.
void onSuccess(const Http::AsyncClient::Request& request,
Http::ResponseMessagePtr&& response) override;
void onFailure(const Http::AsyncClient::Request& request,
Http::AsyncClient::FailureReason reason) override;

Server::Configuration::ServerFactoryContext& context() const { return context_; }

private:
void onError();
envoy::extensions::filters::http::ext_proc::v3::ExternalProcessor config_;
Server::Configuration::ServerFactoryContext& context_;
};

} // namespace ExternalProcessing
} // namespace HttpFilters
} // namespace Extensions
} // namespace Envoy
26 changes: 26 additions & 0 deletions test/extensions/filters/http/ext_proc/http_client/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
load(
"//bazel:envoy_build_system.bzl",
"envoy_package",
)
load(
"//test/extensions:extensions_build_system.bzl",
"envoy_extension_cc_test",
)

licenses(["notice"]) # Apache 2

envoy_package()

envoy_extension_cc_test(
name = "http_client_test",
size = "small",
srcs = ["http_client_test.cc"],
extension_names = ["envoy.filters.http.ext_proc"],
tags = ["skip_on_windows"],
deps = [
"//source/common/http:message_lib",
"//source/extensions/filters/http/ext_proc/http_client:http_client_lib",
"//test/mocks/server:factory_context_mocks",
"@envoy_api//envoy/extensions/filters/http/ext_proc/v3:pkg_cc_proto",
],
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
#include "envoy/extensions/filters/http/ext_proc/v3/ext_proc.pb.h"

#include "source/common/http/message_impl.h"
#include "source/extensions/filters/http/ext_proc/http_client/http_client_impl.h"

#include "test/mocks/server/server_factory_context.h"

#include "gmock/gmock.h"
#include "gtest/gtest.h"

namespace Envoy {
namespace Extensions {
namespace HttpFilters {
namespace ExternalProcessing {
namespace {

class ExtProcHttpClientTest : public testing::Test {
public:
~ExtProcHttpClientTest() override = default;

void SetUp() override { client_ = std::make_unique<ExtProcHttpClient>(config_, context_); }

protected:
envoy::extensions::filters::http::ext_proc::v3::ExternalProcessor config_;
testing::NiceMock<Server::Configuration::MockServerFactoryContext> context_;
Upstream::MockClusterManager& cm_{context_.cluster_manager_};
std::unique_ptr<ExtProcHttpClient> client_;
testing::NiceMock<Http::MockAsyncClientRequest> async_request_{
&cm_.thread_local_cluster_.async_client_};
};

TEST_F(ExtProcHttpClientTest, Basic) {
SetUp();
client_->sendRequest();
client_->cancel();
client_->context();
Tracing::MockSpan parent_span;
client_->onBeforeFinalizeUpstreamSpan(parent_span, nullptr);
Http::AsyncClient::FailureReason reason = Envoy::Http::AsyncClient::FailureReason::Reset;
client_->onFailure(async_request_, reason);

Http::ResponseHeaderMapPtr resp_headers_ok(new Http::TestResponseHeaderMapImpl({
{":status", "200"},
}));
Http::ResponseMessagePtr response_ok(new Http::ResponseMessageImpl(std::move(resp_headers_ok)));
client_->onSuccess(async_request_, std::move(response_ok));

Http::ResponseHeaderMapPtr resp_headers(new Http::TestResponseHeaderMapImpl({
{":status", "403"},
}));
Http::ResponseMessagePtr response(new Http::ResponseMessageImpl(std::move(resp_headers)));
client_->onSuccess(async_request_, std::move(response));

Http::ResponseHeaderMapPtr resp_headers_foo(new Http::TestResponseHeaderMapImpl({
{":status", "foo"},
}));
Http::ResponseMessagePtr response_foo(new Http::ResponseMessageImpl(std::move(resp_headers_foo)));
client_->onSuccess(async_request_, std::move(response_foo));
}

} // namespace
} // namespace ExternalProcessing
} // namespace HttpFilters
} // namespace Extensions
} // namespace Envoy

0 comments on commit dbb35fa

Please sign in to comment.