diff --git a/config/constants.go b/config/constants.go index 1020a17a3..17196cb3b 100644 --- a/config/constants.go +++ b/config/constants.go @@ -14,13 +14,13 @@ const ( ) var ( - errKeysMustBeStrings = errors.New("cannot convert nested map: all keys must be strings") - errZeroWeight = errors.New("zero load balancing weight not permitted") - errEndpointWeightsSpec = errors.New("either no weights should be provided, or all endpoints must have non-zero weight specified") - errHostnameMustBeSpecified = errors.New("endpoint hostname must be specified") - errSchemeMustBeSpecified = errors.New("url scheme must be provided") - errEmptyUrls = errors.New("url list is empty") - errEitherToOrRedirectRequired = errors.New("policy should have either `to` or `redirect` defined") + errKeysMustBeStrings = errors.New("cannot convert nested map: all keys must be strings") + errZeroWeight = errors.New("zero load balancing weight not permitted") + errEndpointWeightsSpec = errors.New("either no weights should be provided, or all endpoints must have non-zero weight specified") + errHostnameMustBeSpecified = errors.New("endpoint hostname must be specified") + errSchemeMustBeSpecified = errors.New("url scheme must be provided") + errEmptyUrls = errors.New("url list is empty") + errEitherToOrRedirectOrResponseRequired = errors.New("policy should have either `to` or `redirect` or `response` defined") ) var protoPartial = protojson.UnmarshalOptions{AllowPartial: true, DiscardUnknown: true} diff --git a/config/envoyconfig/routes.go b/config/envoyconfig/routes.go index c6c6ce10e..0d412d525 100644 --- a/config/envoyconfig/routes.go +++ b/config/envoyconfig/routes.go @@ -282,6 +282,9 @@ func (b *Builder) buildRouteForPolicyAndMatch( return nil, err } route.Action = &envoy_config_route_v3.Route_Redirect{Redirect: action} + } else if policy.Response != nil { + action := b.buildPolicyRouteDirectResponseAction(policy.Response) + route.Action = &envoy_config_route_v3.Route_DirectResponse{DirectResponse: action} } else { action, err := b.buildPolicyRouteRouteAction(cfg.Options, policy) if err != nil { @@ -343,6 +346,17 @@ func (b *Builder) buildRouteForPolicyAndMatch( return route, nil } +func (b *Builder) buildPolicyRouteDirectResponseAction(r *config.DirectResponse) *envoy_config_route_v3.DirectResponseAction { + return &envoy_config_route_v3.DirectResponseAction{ + Status: uint32(r.Status), + Body: &envoy_config_core_v3.DataSource{ + Specifier: &envoy_config_core_v3.DataSource_InlineString{ + InlineString: r.Body, + }, + }, + } +} + func (b *Builder) buildPolicyRouteRedirectAction(r *config.PolicyRedirect) (*envoy_config_route_v3.RedirectAction, error) { action := &envoy_config_route_v3.RedirectAction{} switch { diff --git a/config/policy.go b/config/policy.go index d6d2c4ec5..5bf0599d6 100644 --- a/config/policy.go +++ b/config/policy.go @@ -28,8 +28,9 @@ import ( type Policy struct { ID string `mapstructure:"-" yaml:"-" json:"-"` - From string `mapstructure:"from" yaml:"from"` - To WeightedURLs `mapstructure:"to" yaml:"to"` + From string `mapstructure:"from" yaml:"from"` + To WeightedURLs `mapstructure:"to" yaml:"to"` + Response *DirectResponse `mapstructure:"response" yaml:"response,omitempty" json:"response,omitempty"` // LbWeights are optional load balancing weights applied to endpoints specified in To // this field exists for compatibility with mapstructure @@ -213,6 +214,12 @@ type PolicyRedirect struct { StripQuery *bool `mapstructure:"strip_query" yaml:"strip_query,omitempty" json:"strip_query,omitempty"` } +// A DirectResponse is the response to an HTTP request. +type DirectResponse struct { + Status int `mapstructure:"status" yaml:"status,omitempty" json:"status,omitempty"` + Body string `mapstructure:"body" yaml:"body,omitempty" json:"body,omitempty"` +} + // NewPolicyFromProto creates a new Policy from a protobuf policy config route. func NewPolicyFromProto(pb *configpb.Route) (*Policy, error) { var timeout *time.Duration @@ -446,8 +453,8 @@ func (p *Policy) Validate() error { source.String()) } - if len(p.To) == 0 && p.Redirect == nil { - return errEitherToOrRedirectRequired + if len(p.To) == 0 && p.Redirect == nil && p.Response == nil { + return errEitherToOrRedirectOrResponseRequired } for _, u := range p.To { @@ -567,8 +574,11 @@ func (p *Policy) RouteID() (uint64, error) { id.To = dst } else if p.Redirect != nil { id.Redirect = p.Redirect + } else if p.Response != nil { + id.DirectResponseStatus = p.Response.Status + id.DirectResponseBody = p.Response.Body } else { - return 0, errEitherToOrRedirectRequired + return 0, errEitherToOrRedirectOrResponseRequired } return hashutil.Hash(id) @@ -679,12 +689,14 @@ func (p *Policy) GetPassIdentityHeaders(options *Options) bool { } type routeID struct { - From string - To []string - Prefix string - Path string - Regex string - Redirect *PolicyRedirect + From string + To []string + Prefix string + Path string + Regex string + Redirect *PolicyRedirect + DirectResponseStatus int + DirectResponseBody string } /* diff --git a/pkg/grpc/config/config.proto b/pkg/grpc/config/config.proto index 817950834..5d5240419 100644 --- a/pkg/grpc/config/config.proto +++ b/pkg/grpc/config/config.proto @@ -33,12 +33,18 @@ message RouteRedirect { optional bool strip_query = 8; } -// Next ID: 62. +message RouteDirectResponse { + uint32 status = 1; + string body = 2; +} + +// Next ID: 63. message Route { string name = 1; string from = 2; repeated string to = 3; + RouteDirectResponse response = 62; // https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/endpoint/v3/endpoint_components.proto#envoy-v3-api-msg-config-endpoint-v3-lbendpoint // optional load balancing weights assigned to upstream servers defined in TO