implement direct response

This commit is contained in:
Caleb Doxsey 2024-02-08 14:46:23 -07:00
parent 84448eb9e0
commit 99b3b73ab4
4 changed files with 51 additions and 19 deletions

View file

@ -20,7 +20,7 @@ var (
errHostnameMustBeSpecified = errors.New("endpoint hostname must be specified") errHostnameMustBeSpecified = errors.New("endpoint hostname must be specified")
errSchemeMustBeSpecified = errors.New("url scheme must be provided") errSchemeMustBeSpecified = errors.New("url scheme must be provided")
errEmptyUrls = errors.New("url list is empty") errEmptyUrls = errors.New("url list is empty")
errEitherToOrRedirectRequired = errors.New("policy should have either `to` or `redirect` defined") errEitherToOrRedirectOrResponseRequired = errors.New("policy should have either `to` or `redirect` or `response` defined")
) )
var protoPartial = protojson.UnmarshalOptions{AllowPartial: true, DiscardUnknown: true} var protoPartial = protojson.UnmarshalOptions{AllowPartial: true, DiscardUnknown: true}

View file

@ -282,6 +282,9 @@ func (b *Builder) buildRouteForPolicyAndMatch(
return nil, err return nil, err
} }
route.Action = &envoy_config_route_v3.Route_Redirect{Redirect: action} 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 { } else {
action, err := b.buildPolicyRouteRouteAction(cfg.Options, policy) action, err := b.buildPolicyRouteRouteAction(cfg.Options, policy)
if err != nil { if err != nil {
@ -343,6 +346,17 @@ func (b *Builder) buildRouteForPolicyAndMatch(
return route, nil 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) { func (b *Builder) buildPolicyRouteRedirectAction(r *config.PolicyRedirect) (*envoy_config_route_v3.RedirectAction, error) {
action := &envoy_config_route_v3.RedirectAction{} action := &envoy_config_route_v3.RedirectAction{}
switch { switch {

View file

@ -30,6 +30,7 @@ type Policy struct {
From string `mapstructure:"from" yaml:"from"` From string `mapstructure:"from" yaml:"from"`
To WeightedURLs `mapstructure:"to" yaml:"to"` 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 // LbWeights are optional load balancing weights applied to endpoints specified in To
// this field exists for compatibility with mapstructure // 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"` 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. // NewPolicyFromProto creates a new Policy from a protobuf policy config route.
func NewPolicyFromProto(pb *configpb.Route) (*Policy, error) { func NewPolicyFromProto(pb *configpb.Route) (*Policy, error) {
var timeout *time.Duration var timeout *time.Duration
@ -446,8 +453,8 @@ func (p *Policy) Validate() error {
source.String()) source.String())
} }
if len(p.To) == 0 && p.Redirect == nil { if len(p.To) == 0 && p.Redirect == nil && p.Response == nil {
return errEitherToOrRedirectRequired return errEitherToOrRedirectOrResponseRequired
} }
for _, u := range p.To { for _, u := range p.To {
@ -567,8 +574,11 @@ func (p *Policy) RouteID() (uint64, error) {
id.To = dst id.To = dst
} else if p.Redirect != nil { } else if p.Redirect != nil {
id.Redirect = p.Redirect id.Redirect = p.Redirect
} else if p.Response != nil {
id.DirectResponseStatus = p.Response.Status
id.DirectResponseBody = p.Response.Body
} else { } else {
return 0, errEitherToOrRedirectRequired return 0, errEitherToOrRedirectOrResponseRequired
} }
return hashutil.Hash(id) return hashutil.Hash(id)
@ -685,6 +695,8 @@ type routeID struct {
Path string Path string
Regex string Regex string
Redirect *PolicyRedirect Redirect *PolicyRedirect
DirectResponseStatus int
DirectResponseBody string
} }
/* /*

View file

@ -33,12 +33,18 @@ message RouteRedirect {
optional bool strip_query = 8; optional bool strip_query = 8;
} }
// Next ID: 62. message RouteDirectResponse {
uint32 status = 1;
string body = 2;
}
// Next ID: 63.
message Route { message Route {
string name = 1; string name = 1;
string from = 2; string from = 2;
repeated string to = 3; 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 // 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 // optional load balancing weights assigned to upstream servers defined in TO