pkg/xds/envoy/routes/route_configurers.go (304 lines of code) (raw):

/* * 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 }), ) }