Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 31 additions & 1 deletion api/envoy/api/v2/route/route_components.proto
Original file line number Diff line number Diff line change
Expand Up @@ -1136,7 +1136,7 @@ message HedgePolicy {
bool hedge_on_per_try_timeout = 3;
}

// [#next-free-field: 9]
// [#next-free-field: 10]
message RedirectAction {
enum RedirectResponseCode {
// Moved Permanently HTTP Status Code - 301.
Expand Down Expand Up @@ -1205,6 +1205,36 @@ message RedirectAction {
// :ref:`RouteAction's prefix_rewrite <envoy_api_field_route.RouteAction.prefix_rewrite>`.
string prefix_rewrite = 5
[(validate.rules).string = {well_known_regex: HTTP_HEADER_VALUE strict: false}];

// Indicates that during forwarding, portions of the path that match the
// pattern should be rewritten, even allowing the substitution of capture
// groups from the pattern into the new path as specified by the rewrite
// substitution string. This is useful to allow application paths to be
// rewritten in a way that is aware of segments with variable content like
// identifiers. The router filter will place the original path as it was
// before the rewrite into the :ref:`x-envoy-original-path
// <config_http_filters_router_x-envoy-original-path>` header.
//
// Only one of :ref:`prefix_rewrite <envoy_api_field_route.RouteAction.prefix_rewrite>`
// or *regex_rewrite* may be specified.
//
// Examples using Google's `RE2 <https://github.com/google/re2>`_ engine:
//
// * The path pattern ``^/service/([^/]+)(/.*)$`` paired with a substitution
// string of ``\2/instance/\1`` would transform ``/service/foo/v1/api``
// into ``/v1/api/instance/foo``.
//
// * The pattern ``one`` paired with a substitution string of ``two`` would
// transform ``/xxx/one/yyy/one/zzz`` into ``/xxx/two/yyy/two/zzz``.
//
// * The pattern ``^(.*?)one(.*)$`` paired with a substitution string of
// ``\1two\2`` would replace only the first occurrence of ``one``,
// transforming path ``/xxx/one/yyy/one/zzz`` into ``/xxx/two/yyy/one/zzz``.
//
// * The pattern ``(?i)/xxx/`` paired with a substitution string of ``/yyy/``
// would do a case-insensitive match and transform path ``/aaa/XxX/bbb`` to
// ``/aaa/yyy/bbb``.
type.matcher.RegexMatchAndSubstitute regex_rewrite = 9;
}

// The HTTP status code to use in the redirect response. The default response
Expand Down
32 changes: 31 additions & 1 deletion api/envoy/config/route/v3/route_components.proto
Original file line number Diff line number Diff line change
Expand Up @@ -1169,7 +1169,7 @@ message HedgePolicy {
bool hedge_on_per_try_timeout = 3;
}

// [#next-free-field: 9]
// [#next-free-field: 10]
message RedirectAction {
option (udpa.annotations.versioning).previous_message_type = "envoy.api.v2.route.RedirectAction";

Expand Down Expand Up @@ -1240,6 +1240,36 @@ message RedirectAction {
// :ref:`RouteAction's prefix_rewrite <envoy_api_field_config.route.v3.RouteAction.prefix_rewrite>`.
string prefix_rewrite = 5
[(validate.rules).string = {well_known_regex: HTTP_HEADER_VALUE strict: false}];

// Indicates that during forwarding, portions of the path that match the
// pattern should be rewritten, even allowing the substitution of capture
// groups from the pattern into the new path as specified by the rewrite
// substitution string. This is useful to allow application paths to be
// rewritten in a way that is aware of segments with variable content like
// identifiers. The router filter will place the original path as it was
// before the rewrite into the :ref:`x-envoy-original-path
// <config_http_filters_router_x-envoy-original-path>` header.
//
// Only one of :ref:`prefix_rewrite <envoy_api_field_config.route.v3.RouteAction.prefix_rewrite>`
// or *regex_rewrite* may be specified.
//
// Examples using Google's `RE2 <https://github.com/google/re2>`_ engine:
//
// * The path pattern ``^/service/([^/]+)(/.*)$`` paired with a substitution
// string of ``\2/instance/\1`` would transform ``/service/foo/v1/api``
// into ``/v1/api/instance/foo``.
//
// * The pattern ``one`` paired with a substitution string of ``two`` would
// transform ``/xxx/one/yyy/one/zzz`` into ``/xxx/two/yyy/two/zzz``.
//
// * The pattern ``^(.*?)one(.*)$`` paired with a substitution string of
// ``\1two\2`` would replace only the first occurrence of ``one``,
// transforming path ``/xxx/one/yyy/one/zzz`` into ``/xxx/two/yyy/one/zzz``.
//
// * The pattern ``(?i)/xxx/`` paired with a substitution string of ``/yyy/``
// would do a case-insensitive match and transform path ``/aaa/XxX/bbb`` to
// ``/aaa/yyy/bbb``.
type.matcher.v3.RegexMatchAndSubstitute regex_rewrite = 9;
}

// The HTTP status code to use in the redirect response. The default response
Expand Down
32 changes: 31 additions & 1 deletion api/envoy/config/route/v4alpha/route_components.proto

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

32 changes: 31 additions & 1 deletion generated_api_shadow/envoy/api/v2/route/route_components.proto

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

38 changes: 36 additions & 2 deletions source/common/router/config_impl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -433,6 +433,15 @@ RouteEntryImplBase::RouteEntryImplBase(const VirtualHostImpl& vhost,
regex_rewrite_substitution_ = rewrite_spec.substitution();
}

if (route.redirect().has_regex_rewrite()) {
if (!prefix_rewrite_redirect_.empty()) {
throw EnvoyException("Cannot specify both prefix_rewrite and regex_rewrite for redirects");
}
auto rewrite_spec = route.redirect().regex_rewrite();
regex_rewrite_redirect_ = Regex::Utility::parseRegex(rewrite_spec.pattern());
regex_rewrite_redirect_substitution_ = rewrite_spec.substitution();
}

if (enable_preserve_query_in_path_redirects_ && path_redirect_has_query_ && strip_query_) {
ENVOY_LOG(warn,
"`strip_query` is set to true, but `path_redirect` contains query string and it will "
Expand Down Expand Up @@ -530,7 +539,8 @@ void RouteEntryImplBase::finalizeRequestHeaders(Http::RequestHeaderMap& headers,
}

// Handle path rewrite
if (!getPathRewrite().empty() || regex_rewrite_ != nullptr) {
absl::optional<std::string> container;
if (!getPathRewrite(headers, container).empty() || regex_rewrite_ != nullptr) {
rewritePathHeader(headers, insert_envoy_original_path);
}
}
Expand Down Expand Up @@ -565,6 +575,29 @@ RouteEntryImplBase::loadRuntimeData(const envoy::config::route::v3::RouteMatch&
return runtime;
}

const std::string& RouteEntryImplBase::getPathRewrite(const Http::RequestHeaderMap& headers, absl::optional<std::string> &container) const {
// Just use the prefix rewrite if this isn't a redirect.
if (!isRedirect()) {
return prefix_rewrite_;
}

// Return the regex rewrite substitution for redirects, if set.
if (regex_rewrite_redirect_ != nullptr) {
std::string path(headers.getPathValue());

// Replace the entire path, but preserve the query parameters
auto just_path(Http::PathUtil::removeQueryAndFragment(path));

// Store the result in the output container, and return a reference to the underlying string.
container = std::move(path.replace(0, just_path.size(),
regex_rewrite_redirect_->replaceAll(just_path, regex_rewrite_redirect_substitution_)));
return container.value();
}

// Otherwise, return the prefix rewrite used for redirects.
return prefix_rewrite_redirect_;
}

// finalizePathHeaders does the "standard" path rewriting, meaning that it
// handles the "prefix_rewrite" and "regex_rewrite" route actions, only one of
// which can be specified. The "matched_path" argument applies only to the
Expand All @@ -575,7 +608,8 @@ RouteEntryImplBase::loadRuntimeData(const envoy::config::route::v3::RouteMatch&
void RouteEntryImplBase::finalizePathHeader(Http::RequestHeaderMap& headers,
absl::string_view matched_path,
bool insert_envoy_original_path) const {
const auto& rewrite = getPathRewrite();
absl::optional<std::string> container;
const auto& rewrite = getPathRewrite(headers, container);
if (rewrite.empty() && regex_rewrite_ == nullptr) {
// There are no rewrites configured. Just return.
return;
Expand Down
12 changes: 7 additions & 5 deletions source/common/router/config_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -450,7 +450,7 @@ class RouteEntryImplBase : public RouteEntry,
if (!isDirectResponse()) {
return false;
}
return !host_redirect_.empty() || !path_redirect_.empty() || !prefix_rewrite_redirect_.empty();
return !host_redirect_.empty() || !path_redirect_.empty() || !prefix_rewrite_redirect_.empty() || regex_rewrite_redirect_ != nullptr;
}

bool matchRoute(const Http::RequestHeaderMap& headers, const StreamInfo::StreamInfo& stream_info,
Expand Down Expand Up @@ -536,19 +536,21 @@ class RouteEntryImplBase : public RouteEntry,
const bool case_sensitive_;
const std::string prefix_rewrite_;
Regex::CompiledMatcherPtr regex_rewrite_;
Regex::CompiledMatcherPtr regex_rewrite_redirect_;
std::string regex_rewrite_substitution_;
std::string regex_rewrite_redirect_substitution_;
const std::string host_rewrite_;
bool include_vh_rate_limits_;
absl::optional<ConnectConfig> connect_config_;

RouteConstSharedPtr clusterEntry(const Http::HeaderMap& headers, uint64_t random_value) const;

/**
* returns the correct path rewrite string for this route.
* returns the correct path rewrite string for this route. the provided container may be used
* to store memory that backs the returned path, and so the lifetime of the container must
* outlife any use of the returned path.
*/
const std::string& getPathRewrite() const {
return (isRedirect()) ? prefix_rewrite_redirect_ : prefix_rewrite_;
}
const std::string& getPathRewrite(const Http::RequestHeaderMap& headers, absl::optional<std::string> &container) const;

void finalizePathHeader(Http::RequestHeaderMap& headers, absl::string_view matched_path,
bool insert_envoy_original_path) const;
Expand Down