-
Notifications
You must be signed in to change notification settings - Fork 5.5k
router: add support for direct (non-proxied) http responses. #2335
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 2 commits
942d251
eec6775
3f04d58
dfc0b8d
ea91a05
77a170f
49b2732
e309720
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -43,6 +43,20 @@ class RedirectEntry { | |
| virtual Http::Code redirectResponseCode() const PURE; | ||
| }; | ||
|
|
||
| /** | ||
| * A routing primitive that specifies a direct (non-proxied) HTTP response . | ||
| */ | ||
| class DirectResponseEntry { | ||
| public: | ||
| virtual ~DirectResponseEntry() {} | ||
|
|
||
| /** | ||
| * Returns the HTTP status code to return. | ||
| * @return Http::Code the response Code. | ||
| */ | ||
| virtual Http::Code responseCode() const PURE; | ||
| }; | ||
|
|
||
| /** | ||
| * CorsPolicy for Route and VirtualHost. | ||
| */ | ||
|
|
@@ -416,7 +430,7 @@ class Decorator { | |
| typedef std::unique_ptr<const Decorator> DecoratorConstPtr; | ||
|
|
||
| /** | ||
| * An interface that holds a RedirectEntry or a RouteEntry for a request. | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd prefer this replace RedirectEntry rather than be a slightly duplicate thing.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks for the suggestion; combining RedirectEntry and DirectResponseEntry sounds good to me. The combined code will have to deal with some incompatibilities between the two -- e.g., under the current data plane API, redirects aren't allowed to send the I'll merge the implementations post an update to this PR later today.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. cool. hopefully if we do some sanity checks with an IsRedirectReturnCode() in the right places we'll guard against bad configs/code
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't see any reason redirects cannot also populate response_headers_to_add. That seems pretty useful to me. Thoughts?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Adding response_headers_to_add to redirects would change the behavior of existing configs. If we were designing the config schema from scratch, I’d advocate for sending the headers with redirects. But at this point it would impact backward compatibility. Unless we added a “send_headers_with_redirects” flag that defaults to false, I suppose :(
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, fair enough. I kind of think we could "just do it" and document it in this case as a change. But that's me. Fine to block/TODO for now if you think that would be best. |
||
| * An interface that holds a RedirectEntry, DirectResponseEntry, or RouteEntry for a request. | ||
| */ | ||
| class Route { | ||
| public: | ||
|
|
@@ -427,6 +441,11 @@ class Route { | |
| */ | ||
| virtual const RedirectEntry* redirectEntry() const PURE; | ||
|
|
||
| /** | ||
| * @return the direct response entry or nullptr if there is no direct response for the request. | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @return DirectResponseEntry* |
||
| */ | ||
| virtual const DirectResponseEntry* directResponseEntry() const PURE; | ||
|
|
||
| /** | ||
| * @return the route entry or nullptr if there is no matching route for the request. | ||
| */ | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -236,15 +236,22 @@ void RdsJson::translateRoute(const Json::Object& json_route, envoy::api::v2::Rou | |
| throw EnvoyException("Redirect route entries must not have WebSockets set"); | ||
| } | ||
| } | ||
| bool has_direct_response = false; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. All the v1 code can be dropped, right? |
||
| if (json_route.hasObject("status") || json_route.hasObject("body")) { | ||
| has_direct_response = true; | ||
| auto* direct_response = route.mutable_direct_response(); | ||
| direct_response->set_status(json_route.getInteger("status")); | ||
| } | ||
| const bool has_cluster = json_route.hasObject("cluster") || | ||
| json_route.hasObject("cluster_header") || | ||
| json_route.hasObject("weighted_clusters"); | ||
|
|
||
| if (has_cluster && has_redirect) { | ||
| throw EnvoyException("routes must be either redirects or cluster targets"); | ||
| } else if (!has_cluster && !has_redirect) { | ||
| throw EnvoyException( | ||
| "routes must have redirect or one of cluster/cluster_header/weighted_clusters"); | ||
| if ((has_cluster && (has_redirect || has_direct_response)) || | ||
| (has_redirect && has_direct_response)) { | ||
| throw EnvoyException("routes must be either redirects, direct responses, or cluster targets"); | ||
| } else if (!has_cluster && !has_redirect && !has_direct_response) { | ||
| throw EnvoyException("routes must have redirect, direct response, or one of " | ||
| "cluster/cluster_header/weighted_clusters"); | ||
| } else if (has_cluster) { | ||
| auto* action = route.mutable_route(); | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -672,6 +672,7 @@ const std::string Json::Schema::ROUTE_ENTRY_CONFIGURATION_SCHEMA(R"EOF( | |
| "weighted_clusters": {"$ref" : "#/definitions/weighted_clusters"}, | ||
| "host_redirect" : {"type" : "string"}, | ||
| "path_redirect" : {"type" : "string"}, | ||
| "status": {"type": "integer"}, | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. del? |
||
| "prefix_rewrite" : {"type" : "string"}, | ||
| "host_rewrite" : {"type" : "string"}, | ||
| "auto_host_rewrite" : {"type" : "boolean"}, | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -238,7 +238,8 @@ RouteEntryImplBase::RouteEntryImplBase(const VirtualHostImpl& vhost, | |
| route.route().response_headers_to_remove())), | ||
| opaque_config_(parseOpaqueConfig(route)), decorator_(parseDecorator(route)), | ||
| redirect_response_code_( | ||
| ConfigUtility::parseRedirectResponseCode(route.redirect().response_code())) { | ||
| ConfigUtility::parseRedirectResponseCode(route.redirect().response_code())), | ||
| direct_response_code_(static_cast<Http::Code>(route.direct_response().status())) { | ||
| if (route.route().has_metadata_match()) { | ||
| const auto filter_it = route.route().metadata_match().filter_metadata().find( | ||
| Envoy::Config::MetadataFilters::get().ENVOY_LB); | ||
|
|
@@ -426,17 +427,29 @@ DecoratorConstPtr RouteEntryImplBase::parseDecorator(const envoy::api::v2::Route | |
| } | ||
|
|
||
| const RedirectEntry* RouteEntryImplBase::redirectEntry() const { | ||
| // A route for a request can exclusively be a route entry or a redirect entry. | ||
| // A route for a request can exclusively be a route entry, a direct response entry, | ||
| // or a redirect entry. | ||
| if (isRedirect()) { | ||
| return this; | ||
| } else { | ||
| return nullptr; | ||
| } | ||
| } | ||
|
|
||
| const DirectResponseEntry* RouteEntryImplBase::directResponseEntry() const { | ||
| // A route for a request can exclusively be a route entry, a direct response entry, | ||
| // or a redirect entry. | ||
| if (isDirectResponse()) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, I think this is confusing, since a redirect entry is a direct response too |
||
| return this; | ||
| } else { | ||
| return nullptr; | ||
| } | ||
| } | ||
|
|
||
| const RouteEntry* RouteEntryImplBase::routeEntry() const { | ||
| // A route for a request can exclusively be a route entry or a redirect entry. | ||
| if (isRedirect()) { | ||
| // A route for a request can exclusively be a route entry, a direct response entry, | ||
| // or a redirect entry. | ||
| if (isRedirect() || isDirectResponse()) { | ||
| return nullptr; | ||
| } else { | ||
| return this; | ||
|
|
@@ -448,7 +461,7 @@ RouteConstSharedPtr RouteEntryImplBase::clusterEntry(const Http::HeaderMap& head | |
| // Gets the route object chosen from the list of weighted clusters | ||
| // (if there is one) or returns self. | ||
| if (weighted_clusters_.empty()) { | ||
| if (!cluster_name_.empty() || isRedirect()) { | ||
| if (!cluster_name_.empty() || isRedirect() || isDirectResponse()) { | ||
| return shared_from_this(); | ||
| } else { | ||
| ASSERT(!cluster_header_name_.get().empty()); | ||
|
|
@@ -489,7 +502,7 @@ RouteConstSharedPtr RouteEntryImplBase::clusterEntry(const Http::HeaderMap& head | |
| } | ||
|
|
||
| void RouteEntryImplBase::validateClusters(Upstream::ClusterManager& cm) const { | ||
| if (isRedirect()) { | ||
| if (isRedirect() || isDirectResponse()) { | ||
| return; | ||
| } | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -60,6 +60,7 @@ class SslRedirectRoute : public Route { | |
| public: | ||
| // Router::Route | ||
| const RedirectEntry* redirectEntry() const override { return &SSL_REDIRECTOR; } | ||
| const DirectResponseEntry* directResponseEntry() const override { return nullptr; } | ||
| const RouteEntry* routeEntry() const override { return nullptr; } | ||
| const Decorator* decorator() const override { return nullptr; } | ||
|
|
||
|
|
@@ -287,13 +288,15 @@ class DecoratorImpl : public Decorator { | |
| class RouteEntryImplBase : public RouteEntry, | ||
| public Matchable, | ||
| public RedirectEntry, | ||
| public DirectResponseEntry, | ||
| public Route, | ||
| public std::enable_shared_from_this<RouteEntryImplBase> { | ||
| public: | ||
| RouteEntryImplBase(const VirtualHostImpl& vhost, const envoy::api::v2::Route& route, | ||
| Runtime::Loader& loader); | ||
|
|
||
| bool isRedirect() const { return !host_redirect_.empty() || !path_redirect_.empty(); } | ||
| bool isDirectResponse() const { return static_cast<int>(direct_response_code_) != 0; } | ||
|
|
||
| bool matchRoute(const Http::HeaderMap& headers, uint64_t random_value) const; | ||
| void validateClusters(Upstream::ClusterManager& cm) const; | ||
|
|
@@ -333,8 +336,12 @@ class RouteEntryImplBase : public RouteEntry, | |
| std::string newPath(const Http::HeaderMap& headers) const override; | ||
| Http::Code redirectResponseCode() const override { return redirect_response_code_; } | ||
|
|
||
| // Router::DirectResponseEntry | ||
| Http::Code responseCode() const override { return direct_response_code_; } | ||
|
|
||
| // Router::Route | ||
| const RedirectEntry* redirectEntry() const override; | ||
| const DirectResponseEntry* directResponseEntry() const override; | ||
| const RouteEntry* routeEntry() const override; | ||
| const Decorator* decorator() const override { return decorator_.get(); } | ||
|
|
||
|
|
@@ -403,6 +410,7 @@ class RouteEntryImplBase : public RouteEntry, | |
|
|
||
| // Router::Route | ||
| const RedirectEntry* redirectEntry() const override { return nullptr; } | ||
| const DirectResponseEntry* directResponseEntry() const override { return nullptr; } | ||
| const RouteEntry* routeEntry() const override { return this; } | ||
| const Decorator* decorator() const override { return nullptr; } | ||
|
|
||
|
|
@@ -488,6 +496,7 @@ class RouteEntryImplBase : public RouteEntry, | |
|
|
||
| const DecoratorConstPtr decorator_; | ||
| const Http::Code redirect_response_code_; | ||
| const Http::Code direct_response_code_; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: I might consider making this |
||
| }; | ||
|
|
||
| /** | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -217,6 +217,21 @@ Http::FilterHeadersStatus Filter::decodeHeaders(Http::HeaderMap& headers, bool e | |
| return Http::FilterHeadersStatus::StopIteration; | ||
| } | ||
|
|
||
| // Determine if there is a direct response for the request. | ||
| if (route_->directResponseEntry()) { | ||
| Http::Utility::sendLocalReply( | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Take a look at callers of sendLocalReply - I think you can simplify the arguments a bit (and if I'm incorrect please educate me! :-P)
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh, I see, this method is in the same class that provides a friendly wrapper around sendLocalReply. I'll switch to that. |
||
| [this](Http::HeaderMapPtr&& headers, bool end_stream) -> void { | ||
| // TODO(brian-pane) add response_headers_to_add to the headers here | ||
| callbacks_->encodeHeaders(std::move(headers), end_stream); | ||
| }, | ||
| [this](Buffer::Instance& data, bool end_stream) -> void { | ||
| callbacks_->encodeData(data, end_stream); | ||
| }, | ||
| stream_destroyed_, route_->directResponseEntry()->responseCode(), ""); | ||
| // TODO(brian-pane) support sending a response body. | ||
| return Http::FilterHeadersStatus::StopIteration; | ||
| } | ||
|
|
||
| // A route entry matches for the request. | ||
| route_entry_ = route_->routeEntry(); | ||
| Upstream::ThreadLocalCluster* cluster = config_.cm_.get(route_entry_->clusterName()); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -114,6 +114,10 @@ ServerContextConfigImpl::ServerContextConfigImpl(const envoy::api::v2::Downstrea | |
| validateAndAppendKey(ret, datasource.inline_bytes()); | ||
| break; | ||
| } | ||
| case envoy::api::v2::DataSource::kInlineString: { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we remove from this PR?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I’ll move this to a separate PR |
||
| validateAndAppendKey(ret, datasource.inline_string()); | ||
| break; | ||
| } | ||
| default: | ||
| throw EnvoyException(fmt::format("Unexpected DataSource::specifier_case(): {}", | ||
| datasource.specifier_case())); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,6 +5,8 @@ | |
| #include <string> | ||
| #include <vector> | ||
|
|
||
| #include "envoy/http/codes.h" | ||
|
|
||
| #include "common/network/address_impl.h" | ||
|
|
||
| #include "api/base.pb.h" | ||
|
|
@@ -67,6 +69,10 @@ class ConfigHelper { | |
| bool validate_clusters, envoy::api::v2::RouteAction::ClusterNotFoundResponseCode code, | ||
| envoy::api::v2::VirtualHost::TlsRequirementType type = envoy::api::v2::VirtualHost::NONE); | ||
|
|
||
| // Add an additional route with a direct (non-proxied) response. | ||
| void addDirectResponse(const std::string& domains, const std::string& prefix, | ||
| Http::Code responseCode, const std::string& body); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do you think this will be used widely? If not I'd just suggest doing this as with void addConfigModifier(HttpModifierFunction function); in testRouterDirectResponse |
||
|
|
||
| // Add an HTTP filter prior to existing filters. | ||
| void addFilter(const std::string& filter_yaml); | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
response.