| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You 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. |
| */ |
| |
| package routes |
| |
| import ( |
| "net/http" |
| "time" |
| ) |
| |
| import ( |
| envoy_config_core_v3 "github.com/envoyproxy/go-control-plane/envoy/config/core/v3" |
| envoy_config_route_v3 "github.com/envoyproxy/go-control-plane/envoy/config/route/v3" |
| envoy_type_matcher_v3 "github.com/envoyproxy/go-control-plane/envoy/type/matcher/v3" |
| |
| "github.com/pkg/errors" |
| |
| "google.golang.org/protobuf/types/known/anypb" |
| ) |
| |
| import ( |
| util_proto "github.com/apache/dubbo-kubernetes/pkg/util/proto" |
| envoy_virtual_hosts "github.com/apache/dubbo-kubernetes/pkg/xds/envoy/virtualhosts" |
| ) |
| |
| // RouteMatchExactPath updates the route to match the exact path. This |
| // replaces any previous path match specification. |
| func RouteMatchExactPath(path string) RouteConfigurer { |
| if path == "" { |
| return RouteConfigureFunc(nil) |
| } |
| |
| return RouteMustConfigureFunc(func(r *envoy_config_route_v3.Route) { |
| r.Match.PathSpecifier = &envoy_config_route_v3.RouteMatch_Path{ |
| Path: path, |
| } |
| }) |
| } |
| |
| // RouteMatchPrefixPath updates the route to match the given path |
| // prefix. This is a byte-wise prefix, so it just checks that the request |
| // path begins with the given string. This replaces any previous path match |
| // specification. |
| func RouteMatchPrefixPath(prefix string) RouteConfigurer { |
| if prefix == "" { |
| return RouteConfigureFunc(nil) |
| } |
| |
| return RouteMustConfigureFunc(func(r *envoy_config_route_v3.Route) { |
| r.Match.PathSpecifier = &envoy_config_route_v3.RouteMatch_Prefix{ |
| Prefix: prefix, |
| } |
| }) |
| } |
| |
| // RouteMatchRegexPath updates the route to match the path using the |
| // given regex. This replaces any previous path match specification. |
| func RouteMatchRegexPath(regex string) RouteConfigurer { |
| if regex == "" { |
| return RouteConfigureFunc(nil) |
| } |
| |
| return RouteMustConfigureFunc(func(r *envoy_config_route_v3.Route) { |
| r.Match.PathSpecifier = &envoy_config_route_v3.RouteMatch_SafeRegex{ |
| SafeRegex: &envoy_type_matcher_v3.RegexMatcher{Regex: regex}, |
| } |
| }) |
| } |
| |
| // RouteMatchExactHeader appends an exact match for the value of the named HTTP request header. |
| func RouteMatchExactHeader(name string, value string) RouteConfigurer { |
| if name == "" || value == "" { |
| return RouteConfigureFunc(nil) |
| } |
| |
| return RouteMustConfigureFunc(func(r *envoy_config_route_v3.Route) { |
| matcher := envoy_type_matcher_v3.StringMatcher{ |
| MatchPattern: &envoy_type_matcher_v3.StringMatcher_Exact{ |
| Exact: value, |
| }, |
| } |
| r.Match.Headers = append(r.Match.Headers, |
| &envoy_config_route_v3.HeaderMatcher{ |
| Name: name, |
| HeaderMatchSpecifier: &envoy_config_route_v3.HeaderMatcher_StringMatch{ |
| StringMatch: &matcher, |
| }, |
| }, |
| ) |
| }) |
| } |
| |
| // RouteMatchRegexHeader appends a regex match for the value of the named HTTP request header. |
| func RouteMatchRegexHeader(name string, regex string) RouteConfigurer { |
| if name == "" || regex == "" { |
| return RouteConfigureFunc(nil) |
| } |
| |
| return RouteMustConfigureFunc(func(r *envoy_config_route_v3.Route) { |
| r.Match.Headers = append(r.Match.Headers, |
| &envoy_config_route_v3.HeaderMatcher{ |
| Name: name, |
| HeaderMatchSpecifier: &envoy_config_route_v3.HeaderMatcher_StringMatch{ |
| StringMatch: &envoy_type_matcher_v3.StringMatcher{ |
| MatchPattern: &envoy_type_matcher_v3.StringMatcher_SafeRegex{ |
| SafeRegex: &envoy_type_matcher_v3.RegexMatcher{ |
| Regex: regex, |
| }, |
| }, |
| }, |
| }, |
| }, |
| ) |
| }) |
| } |
| |
| // RouteMatchPresentHeader appends a present match for the names HTTP request header (presentMatch makes absent) |
| func RouteMatchPresentHeader(name string, presentMatch bool) RouteConfigurer { |
| if name == "" { |
| return RouteConfigureFunc(nil) |
| } |
| |
| return RouteMustConfigureFunc(func(r *envoy_config_route_v3.Route) { |
| r.Match.Headers = append(r.Match.Headers, |
| &envoy_config_route_v3.HeaderMatcher{ |
| Name: name, |
| HeaderMatchSpecifier: &envoy_config_route_v3.HeaderMatcher_PresentMatch{ |
| PresentMatch: presentMatch, |
| }, |
| }, |
| ) |
| }) |
| } |
| |
| // RouteMatchPrefixHeader appends a prefix match for the names HTTP request header |
| func RouteMatchPrefixHeader(name string, match string) RouteConfigurer { |
| if name == "" { |
| return RouteConfigureFunc(nil) |
| } |
| |
| return RouteMustConfigureFunc(func(r *envoy_config_route_v3.Route) { |
| r.Match.Headers = append(r.Match.Headers, |
| &envoy_config_route_v3.HeaderMatcher{ |
| Name: name, |
| HeaderMatchSpecifier: &envoy_config_route_v3.HeaderMatcher_PrefixMatch{ |
| PrefixMatch: match, |
| }, |
| }, |
| ) |
| }) |
| } |
| |
| // RouteMatchExactQuery appends an exact match for the value of the named query parameter. |
| func RouteMatchExactQuery(name string, value string) RouteConfigurer { |
| if name == "" || value == "" { |
| return RouteConfigureFunc(nil) |
| } |
| |
| return RouteMustConfigureFunc(func(r *envoy_config_route_v3.Route) { |
| matcher := envoy_type_matcher_v3.StringMatcher{ |
| MatchPattern: &envoy_type_matcher_v3.StringMatcher_Exact{ |
| Exact: value, |
| }, |
| } |
| |
| r.Match.QueryParameters = append(r.Match.QueryParameters, |
| &envoy_config_route_v3.QueryParameterMatcher{ |
| Name: name, |
| QueryParameterMatchSpecifier: &envoy_config_route_v3.QueryParameterMatcher_StringMatch{ |
| StringMatch: &matcher, |
| }, |
| }, |
| ) |
| }) |
| } |
| |
| // RouteMatchRegexQuery appends a regex match for the value of the named query parameter. |
| func RouteMatchRegexQuery(name string, regex string) RouteConfigurer { |
| if name == "" || regex == "" { |
| return RouteConfigureFunc(nil) |
| } |
| |
| return RouteMustConfigureFunc(func(r *envoy_config_route_v3.Route) { |
| matcher := envoy_type_matcher_v3.StringMatcher{ |
| MatchPattern: &envoy_type_matcher_v3.StringMatcher_SafeRegex{ |
| SafeRegex: &envoy_type_matcher_v3.RegexMatcher{Regex: regex}, |
| }, |
| } |
| |
| r.Match.QueryParameters = append(r.Match.QueryParameters, |
| &envoy_config_route_v3.QueryParameterMatcher{ |
| Name: name, |
| QueryParameterMatchSpecifier: &envoy_config_route_v3.QueryParameterMatcher_StringMatch{ |
| StringMatch: &matcher, |
| }, |
| }, |
| ) |
| }) |
| } |
| |
| func RouteAppendHeader(name string, value string) *envoy_config_core_v3.HeaderValueOption { |
| return &envoy_config_core_v3.HeaderValueOption{ |
| AppendAction: envoy_config_core_v3.HeaderValueOption_APPEND_IF_EXISTS_OR_ADD, |
| Header: &envoy_config_core_v3.HeaderValue{ |
| Key: http.CanonicalHeaderKey(name), |
| Value: value, |
| }, |
| } |
| } |
| |
| func RouteReplaceHeader(name string, value string) *envoy_config_core_v3.HeaderValueOption { |
| return &envoy_config_core_v3.HeaderValueOption{ |
| AppendAction: envoy_config_core_v3.HeaderValueOption_OVERWRITE_IF_EXISTS_OR_ADD, |
| Header: &envoy_config_core_v3.HeaderValue{ |
| Key: http.CanonicalHeaderKey(name), |
| Value: value, |
| }, |
| } |
| } |
| |
| // RouteAddRequestHeader alters the given request header value. |
| func RouteAddRequestHeader(option *envoy_config_core_v3.HeaderValueOption) RouteConfigurer { |
| if option == nil { |
| return RouteConfigureFunc(nil) |
| } |
| |
| return RouteMustConfigureFunc(func(r *envoy_config_route_v3.Route) { |
| r.RequestHeadersToAdd = append(r.RequestHeadersToAdd, option) |
| }) |
| } |
| |
| // RouteAddResponseHeader alters the given response header value. |
| func RouteAddResponseHeader(option *envoy_config_core_v3.HeaderValueOption) RouteConfigurer { |
| if option == nil { |
| return RouteConfigureFunc(nil) |
| } |
| |
| return RouteMustConfigureFunc(func(r *envoy_config_route_v3.Route) { |
| r.ResponseHeadersToAdd = append(r.ResponseHeadersToAdd, option) |
| }) |
| } |
| |
| // RouteReplaceHostHeader replaces the Host header on the forwarded |
| // request. It is an error to rewrite the header if the route is not |
| // forwarding. The route action must be configured beforehand. |
| func RouteReplaceHostHeader(host string) RouteConfigurer { |
| if host == "" { |
| return RouteConfigureFunc(nil) |
| } |
| |
| return RouteConfigureFunc(func(r *envoy_config_route_v3.Route) error { |
| if r.GetAction() == nil { |
| return errors.New("cannot configure the Host header before the route action") |
| } |
| |
| if action := r.GetRoute(); action != nil { |
| action.HostRewriteSpecifier = &envoy_config_route_v3.RouteAction_HostRewriteLiteral{ |
| HostRewriteLiteral: host, |
| } |
| } |
| |
| return nil |
| }) |
| } |
| |
| func RouteSetRewriteHostToBackendHostname(value bool) RouteConfigurer { |
| if !value { |
| return RouteConfigureFunc(nil) |
| } |
| |
| return RouteConfigureFunc(func(r *envoy_config_route_v3.Route) error { |
| if r.GetAction() == nil { |
| return errors.New("cannot set the 'auto_host_rewrite' before the route action") |
| } |
| |
| if action := r.GetRoute(); action != nil { |
| action.HostRewriteSpecifier = &envoy_config_route_v3.RouteAction_AutoHostRewrite{ |
| AutoHostRewrite: util_proto.Bool(value), |
| } |
| } |
| |
| return nil |
| }) |
| } |
| |
| // RouteDeleteRequestHeader deletes the given header from the HTTP request. |
| func RouteDeleteRequestHeader(name string) RouteConfigurer { |
| if name == "" { |
| return RouteConfigureFunc(nil) |
| } |
| |
| return RouteMustConfigureFunc(func(r *envoy_config_route_v3.Route) { |
| r.RequestHeadersToRemove = append(r.RequestHeadersToRemove, name) |
| }) |
| } |
| |
| // RouteDeleteResponseHeader deletes the given header from the HTTP response. |
| func RouteDeleteResponseHeader(name string) RouteConfigurer { |
| if name == "" { |
| return RouteConfigureFunc(nil) |
| } |
| |
| return RouteMustConfigureFunc(func(r *envoy_config_route_v3.Route) { |
| r.ResponseHeadersToRemove = append(r.ResponseHeadersToRemove, name) |
| }) |
| } |
| |
| // RoutePerFilterConfig sets an optional per-filter configuration message |
| // for this route. filterName is the name of the filter that should receive |
| // the configuration that is specified in filterConfig |
| func RoutePerFilterConfig(filterName string, filterConfig *anypb.Any) RouteConfigurer { |
| return RouteConfigureFunc(func(r *envoy_config_route_v3.Route) error { |
| if r.GetTypedPerFilterConfig() == nil { |
| r.TypedPerFilterConfig = map[string]*anypb.Any{} |
| } |
| |
| m := r.GetTypedPerFilterConfig() |
| |
| if _, ok := m[filterName]; ok { |
| return errors.Errorf("duplicate %q per-filter config for %s", |
| filterConfig.GetTypeUrl(), filterName) |
| } |
| |
| m[filterName] = filterConfig |
| return nil |
| }) |
| } |
| |
| // RouteActionRequestTimeout sets the total timeout for an upstream request. |
| func RouteActionRequestTimeout(timeout time.Duration) RouteConfigurer { |
| if timeout == 0 { |
| return RouteConfigureFunc(nil) |
| } |
| |
| return RouteConfigureFunc(func(r *envoy_config_route_v3.Route) error { |
| if p := r.GetRoute(); p != nil { |
| p.Timeout = util_proto.Duration(timeout) |
| } |
| |
| return nil |
| }) |
| } |
| |
| func RouteActionIdleTimeout(timeout time.Duration) RouteConfigurer { |
| return RouteConfigureFunc(func(r *envoy_config_route_v3.Route) error { |
| if p := r.GetRoute(); p != nil { |
| p.IdleTimeout = util_proto.Duration(timeout) |
| } |
| |
| return nil |
| }) |
| } |
| |
| // RouteActionDirectResponse sets the direct response for a route |
| func RouteActionDirectResponse(status uint32, respStr string) RouteConfigurer { |
| return RouteConfigureFunc(func(r *envoy_config_route_v3.Route) error { |
| r.Action = &envoy_config_route_v3.Route_DirectResponse{ |
| DirectResponse: &envoy_config_route_v3.DirectResponseAction{ |
| Status: status, |
| Body: &envoy_config_core_v3.DataSource{ |
| Specifier: &envoy_config_core_v3.DataSource_InlineString{ |
| InlineString: respStr, |
| }, |
| }, |
| }, |
| } |
| return nil |
| }) |
| } |
| |
| // VirtualHostRoute creates an option to add the route builder to a |
| // virtual host. On execution, the builder will build the route and append |
| // it to the virtual host. Since Envoy evaluates route matches in order, |
| // route builders should be configured on virtual hosts in the intended |
| // match order. |
| func VirtualHostRoute(route *RouteBuilder) envoy_virtual_hosts.VirtualHostBuilderOpt { |
| return envoy_virtual_hosts.AddVirtualHostConfigurer( |
| envoy_virtual_hosts.VirtualHostConfigureFunc(func(vh *envoy_config_route_v3.VirtualHost) error { |
| resource, err := route.Build() |
| if err != nil { |
| return err |
| } |
| |
| routeProto, ok := resource.(*envoy_config_route_v3.Route) |
| if !ok { |
| return errors.Errorf("attempt to attach %T as type %q", |
| resource, "envoy_config_route_v3.Route") |
| } |
| |
| vh.Routes = append(vh.Routes, routeProto) |
| return nil |
| }), |
| ) |
| } |