Backport of NET-5530 Support response header modifiers on http-route config entry into release/1.16.x (#18725)
* NET-5530 Support response header modifiers on http-route config entry (#18646) * Add response header filters to http-route config entry definitions * Map response header filters from config entry when constructing route destination * Support response header modifiers at the service level as well * Update protobuf definitions * Update existing unit tests * Add response filters to route consolidation logic * Make existing unit tests more robust * Add missing docstring * Add changelog entry * Add response filter modifiers to existing integration test * Add more robust testing for response header modifiers in the discovery chain * Add more robust testing for request header modifiers in the discovery chain * Modify test to verify that service filter modifiers take precedence over rule filter modifiers * Generate deep-copy code --------- Co-authored-by: Nathan Coleman <nathan.coleman@hashicorp.com>
This commit is contained in:
parent
1eb12f79c0
commit
07c75c2b27
|
@ -0,0 +1,3 @@
|
||||||
|
```release-note:feature
|
||||||
|
api-gateway: Add support for response header modifiers on http-route configuration entry
|
||||||
|
```
|
|
@ -27,9 +27,10 @@ type GatewayChainSynthesizer struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type hostnameMatch struct {
|
type hostnameMatch struct {
|
||||||
match structs.HTTPMatch
|
match structs.HTTPMatch
|
||||||
filters structs.HTTPFilters
|
filters structs.HTTPFilters
|
||||||
services []structs.HTTPService
|
responseFilters structs.HTTPResponseFilters
|
||||||
|
services []structs.HTTPService
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewGatewayChainSynthesizer creates a new GatewayChainSynthesizer for the
|
// NewGatewayChainSynthesizer creates a new GatewayChainSynthesizer for the
|
||||||
|
@ -87,9 +88,10 @@ func initHostMatches(hostname string, route *structs.HTTPRouteConfigEntry, curre
|
||||||
// Add all matches for this rule to the list for this hostname
|
// Add all matches for this rule to the list for this hostname
|
||||||
for _, match := range rule.Matches {
|
for _, match := range rule.Matches {
|
||||||
matches = append(matches, hostnameMatch{
|
matches = append(matches, hostnameMatch{
|
||||||
match: match,
|
match: match,
|
||||||
filters: rule.Filters,
|
filters: rule.Filters,
|
||||||
services: rule.Services,
|
responseFilters: rule.ResponseFilters,
|
||||||
|
services: rule.Services,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -226,9 +228,10 @@ func consolidateHTTPRoutes(matchesByHostname map[string][]hostnameMatch, listene
|
||||||
// Add all rules for this hostname
|
// Add all rules for this hostname
|
||||||
for _, rule := range rules {
|
for _, rule := range rules {
|
||||||
route.Rules = append(route.Rules, structs.HTTPRouteRule{
|
route.Rules = append(route.Rules, structs.HTTPRouteRule{
|
||||||
Matches: []structs.HTTPMatch{rule.match},
|
Matches: []structs.HTTPMatch{rule.match},
|
||||||
Filters: rule.filters,
|
Filters: rule.filters,
|
||||||
Services: rule.services,
|
ResponseFilters: rule.responseFilters,
|
||||||
|
Services: rule.services,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -79,7 +79,8 @@ func httpRouteToDiscoveryChain(route structs.HTTPRouteConfigEntry) (*structs.Ser
|
||||||
var defaults []*structs.ServiceConfigEntry
|
var defaults []*structs.ServiceConfigEntry
|
||||||
|
|
||||||
for idx, rule := range route.Rules {
|
for idx, rule := range route.Rules {
|
||||||
modifier := httpRouteFiltersToServiceRouteHeaderModifier(rule.Filters.Headers)
|
requestModifier := httpRouteFiltersToServiceRouteHeaderModifier(rule.Filters.Headers)
|
||||||
|
responseModifier := httpRouteFiltersToServiceRouteHeaderModifier(rule.ResponseFilters.Headers)
|
||||||
prefixRewrite := httpRouteFiltersToDestinationPrefixRewrite(rule.Filters.URLRewrite)
|
prefixRewrite := httpRouteFiltersToDestinationPrefixRewrite(rule.Filters.URLRewrite)
|
||||||
|
|
||||||
var destination structs.ServiceRouteDestination
|
var destination structs.ServiceRouteDestination
|
||||||
|
@ -90,16 +91,29 @@ func httpRouteToDiscoveryChain(route structs.HTTPRouteConfigEntry) (*structs.Ser
|
||||||
if service.Filters.URLRewrite == nil {
|
if service.Filters.URLRewrite == nil {
|
||||||
servicePrefixRewrite = prefixRewrite
|
servicePrefixRewrite = prefixRewrite
|
||||||
}
|
}
|
||||||
serviceModifier := httpRouteFiltersToServiceRouteHeaderModifier(service.Filters.Headers)
|
|
||||||
modifier.Add = mergeMaps(modifier.Add, serviceModifier.Add)
|
// Merge service request header modifier(s) onto route rule modifiers
|
||||||
modifier.Set = mergeMaps(modifier.Set, serviceModifier.Set)
|
// Note: Removals for the same header may exist on the rule + the service and
|
||||||
modifier.Remove = append(modifier.Remove, serviceModifier.Remove...)
|
// will result in idempotent duplicate values in the modifier w/ service coming last
|
||||||
|
serviceRequestModifier := httpRouteFiltersToServiceRouteHeaderModifier(service.Filters.Headers)
|
||||||
|
requestModifier.Add = mergeMaps(requestModifier.Add, serviceRequestModifier.Add)
|
||||||
|
requestModifier.Set = mergeMaps(requestModifier.Set, serviceRequestModifier.Set)
|
||||||
|
requestModifier.Remove = append(requestModifier.Remove, serviceRequestModifier.Remove...)
|
||||||
|
|
||||||
|
// Merge service response header modifier(s) onto route rule modifiers
|
||||||
|
// Note: Removals for the same header may exist on the rule + the service and
|
||||||
|
// will result in idempotent duplicate values in the modifier w/ service coming last
|
||||||
|
serviceResponseModifier := httpRouteFiltersToServiceRouteHeaderModifier(service.ResponseFilters.Headers)
|
||||||
|
responseModifier.Add = mergeMaps(responseModifier.Add, serviceResponseModifier.Add)
|
||||||
|
responseModifier.Set = mergeMaps(responseModifier.Set, serviceResponseModifier.Set)
|
||||||
|
responseModifier.Remove = append(responseModifier.Remove, serviceResponseModifier.Remove...)
|
||||||
|
|
||||||
destination.Service = service.Name
|
destination.Service = service.Name
|
||||||
destination.Namespace = service.NamespaceOrDefault()
|
destination.Namespace = service.NamespaceOrDefault()
|
||||||
destination.Partition = service.PartitionOrDefault()
|
destination.Partition = service.PartitionOrDefault()
|
||||||
destination.PrefixRewrite = servicePrefixRewrite
|
destination.PrefixRewrite = servicePrefixRewrite
|
||||||
destination.RequestHeaders = modifier
|
destination.RequestHeaders = requestModifier
|
||||||
|
destination.ResponseHeaders = responseModifier
|
||||||
|
|
||||||
// since we have already validated the protocol elsewhere, we
|
// since we have already validated the protocol elsewhere, we
|
||||||
// create a new service defaults here to make sure we pass validation
|
// create a new service defaults here to make sure we pass validation
|
||||||
|
@ -115,7 +129,8 @@ func httpRouteToDiscoveryChain(route structs.HTTPRouteConfigEntry) (*structs.Ser
|
||||||
destination.Namespace = route.NamespaceOrDefault()
|
destination.Namespace = route.NamespaceOrDefault()
|
||||||
destination.Partition = route.PartitionOrDefault()
|
destination.Partition = route.PartitionOrDefault()
|
||||||
destination.PrefixRewrite = prefixRewrite
|
destination.PrefixRewrite = prefixRewrite
|
||||||
destination.RequestHeaders = modifier
|
destination.RequestHeaders = requestModifier
|
||||||
|
destination.ResponseHeaders = responseModifier
|
||||||
|
|
||||||
splitter := &structs.ServiceSplitterConfigEntry{
|
splitter := &structs.ServiceSplitterConfigEntry{
|
||||||
Kind: structs.ServiceSplitter,
|
Kind: structs.ServiceSplitter,
|
||||||
|
|
|
@ -518,8 +518,70 @@ func TestGatewayChainSynthesizer_Synthesize(t *testing.T) {
|
||||||
Kind: structs.HTTPRoute,
|
Kind: structs.HTTPRoute,
|
||||||
Name: "http-route",
|
Name: "http-route",
|
||||||
Rules: []structs.HTTPRouteRule{{
|
Rules: []structs.HTTPRouteRule{{
|
||||||
|
Filters: structs.HTTPFilters{
|
||||||
|
Headers: []structs.HTTPHeaderFilter{
|
||||||
|
{
|
||||||
|
Add: map[string]string{"add me to the rule request": "present"},
|
||||||
|
Set: map[string]string{"set me on the rule request": "present"},
|
||||||
|
Remove: []string{"remove me from the rule request"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Add: map[string]string{"add me to the rule and service request": "rule"},
|
||||||
|
Set: map[string]string{"set me on the rule and service request": "rule"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Remove: []string{"remove me from the rule and service request"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ResponseFilters: structs.HTTPResponseFilters{
|
||||||
|
Headers: []structs.HTTPHeaderFilter{{
|
||||||
|
Add: map[string]string{
|
||||||
|
"add me to the rule response": "present",
|
||||||
|
"add me to the rule and service response": "rule",
|
||||||
|
},
|
||||||
|
Set: map[string]string{
|
||||||
|
"set me on the rule response": "present",
|
||||||
|
"set me on the rule and service response": "rule",
|
||||||
|
},
|
||||||
|
Remove: []string{
|
||||||
|
"remove me from the rule response",
|
||||||
|
"remove me from the rule and service response",
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
},
|
||||||
Services: []structs.HTTPService{{
|
Services: []structs.HTTPService{{
|
||||||
Name: "foo",
|
Name: "foo",
|
||||||
|
Filters: structs.HTTPFilters{
|
||||||
|
Headers: []structs.HTTPHeaderFilter{
|
||||||
|
{
|
||||||
|
Add: map[string]string{"add me to the service request": "present"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Set: map[string]string{"set me on the service request": "present"},
|
||||||
|
Remove: []string{"remove me from the service request"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Add: map[string]string{"add me to the rule and service request": "service"},
|
||||||
|
Set: map[string]string{"set me on the rule and service request": "service"},
|
||||||
|
Remove: []string{"remove me from the rule and service request"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ResponseFilters: structs.HTTPResponseFilters{
|
||||||
|
Headers: []structs.HTTPHeaderFilter{
|
||||||
|
{
|
||||||
|
Add: map[string]string{"add me to the service response": "present"},
|
||||||
|
Set: map[string]string{"set me on the service response": "present"},
|
||||||
|
Remove: []string{"remove me from the service response"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Add: map[string]string{"add me to the rule and service response": "service"},
|
||||||
|
Set: map[string]string{"set me on the rule and service response": "service"},
|
||||||
|
Remove: []string{"remove me from the rule and service response"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
}},
|
}},
|
||||||
}},
|
}},
|
||||||
},
|
},
|
||||||
|
@ -557,8 +619,40 @@ func TestGatewayChainSynthesizer_Synthesize(t *testing.T) {
|
||||||
Partition: "default",
|
Partition: "default",
|
||||||
Namespace: "default",
|
Namespace: "default",
|
||||||
RequestHeaders: &structs.HTTPHeaderModifiers{
|
RequestHeaders: &structs.HTTPHeaderModifiers{
|
||||||
Add: make(map[string]string),
|
Add: map[string]string{
|
||||||
Set: make(map[string]string),
|
"add me to the rule request": "present",
|
||||||
|
"add me to the service request": "present",
|
||||||
|
"add me to the rule and service request": "service",
|
||||||
|
},
|
||||||
|
Set: map[string]string{
|
||||||
|
"set me on the rule request": "present",
|
||||||
|
"set me on the service request": "present",
|
||||||
|
"set me on the rule and service request": "service",
|
||||||
|
},
|
||||||
|
Remove: []string{
|
||||||
|
"remove me from the rule request",
|
||||||
|
"remove me from the rule and service request",
|
||||||
|
"remove me from the service request",
|
||||||
|
"remove me from the rule and service request",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ResponseHeaders: &structs.HTTPHeaderModifiers{
|
||||||
|
Add: map[string]string{
|
||||||
|
"add me to the rule response": "present",
|
||||||
|
"add me to the service response": "present",
|
||||||
|
"add me to the rule and service response": "service",
|
||||||
|
},
|
||||||
|
Set: map[string]string{
|
||||||
|
"set me on the rule response": "present",
|
||||||
|
"set me on the service response": "present",
|
||||||
|
"set me on the rule and service response": "service",
|
||||||
|
},
|
||||||
|
Remove: []string{
|
||||||
|
"remove me from the rule response",
|
||||||
|
"remove me from the rule and service response",
|
||||||
|
"remove me from the service response",
|
||||||
|
"remove me from the rule and service response",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -663,6 +757,10 @@ func TestGatewayChainSynthesizer_Synthesize(t *testing.T) {
|
||||||
Add: make(map[string]string),
|
Add: make(map[string]string),
|
||||||
Set: make(map[string]string),
|
Set: make(map[string]string),
|
||||||
},
|
},
|
||||||
|
ResponseHeaders: &structs.HTTPHeaderModifiers{
|
||||||
|
Add: make(map[string]string),
|
||||||
|
Set: make(map[string]string),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
NextNode: "resolver:foo-2.default.default.dc2",
|
NextNode: "resolver:foo-2.default.default.dc2",
|
||||||
|
@ -850,6 +948,10 @@ func TestGatewayChainSynthesizer_ComplexChain(t *testing.T) {
|
||||||
Add: make(map[string]string),
|
Add: make(map[string]string),
|
||||||
Set: make(map[string]string),
|
Set: make(map[string]string),
|
||||||
},
|
},
|
||||||
|
ResponseHeaders: &structs.HTTPHeaderModifiers{
|
||||||
|
Add: make(map[string]string),
|
||||||
|
Set: make(map[string]string),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
NextNode: "splitter:splitter-one.default.default",
|
NextNode: "splitter:splitter-one.default.default",
|
||||||
|
|
|
@ -421,6 +421,12 @@ type HTTPFilters struct {
|
||||||
URLRewrite *URLRewrite
|
URLRewrite *URLRewrite
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HTTPResponseFilters specifies a list of filters used to modify the
|
||||||
|
// response returned by an upstream
|
||||||
|
type HTTPResponseFilters struct {
|
||||||
|
Headers []HTTPHeaderFilter
|
||||||
|
}
|
||||||
|
|
||||||
// HTTPHeaderFilter specifies how HTTP headers should be modified.
|
// HTTPHeaderFilter specifies how HTTP headers should be modified.
|
||||||
type HTTPHeaderFilter struct {
|
type HTTPHeaderFilter struct {
|
||||||
Add map[string]string
|
Add map[string]string
|
||||||
|
@ -438,6 +444,9 @@ type HTTPRouteRule struct {
|
||||||
// Filters is a list of HTTP-based filters used to modify a request prior
|
// Filters is a list of HTTP-based filters used to modify a request prior
|
||||||
// to routing it to the upstream service
|
// to routing it to the upstream service
|
||||||
Filters HTTPFilters
|
Filters HTTPFilters
|
||||||
|
// ResponseFilters is a list of HTTP-based filters used to modify a response
|
||||||
|
// returned by the upstream service
|
||||||
|
ResponseFilters HTTPResponseFilters
|
||||||
// Matches specified the matching criteria used in the routing table. If a
|
// Matches specified the matching criteria used in the routing table. If a
|
||||||
// request matches the given HTTPMatch configuration, then traffic is routed
|
// request matches the given HTTPMatch configuration, then traffic is routed
|
||||||
// to services specified in the Services field.
|
// to services specified in the Services field.
|
||||||
|
@ -457,6 +466,10 @@ type HTTPService struct {
|
||||||
// to routing it to the upstream service
|
// to routing it to the upstream service
|
||||||
Filters HTTPFilters
|
Filters HTTPFilters
|
||||||
|
|
||||||
|
// ResponseFilters is a list of HTTP-based filters used to modify the
|
||||||
|
// response returned from the upstream service
|
||||||
|
ResponseFilters HTTPResponseFilters
|
||||||
|
|
||||||
acl.EnterpriseMeta `hcl:",squash" mapstructure:",squash"`
|
acl.EnterpriseMeta `hcl:",squash" mapstructure:",squash"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -383,6 +383,28 @@ func (o *HTTPRouteConfigEntry) DeepCopy() *HTTPRouteConfigEntry {
|
||||||
cp.Rules[i2].Filters.URLRewrite = new(URLRewrite)
|
cp.Rules[i2].Filters.URLRewrite = new(URLRewrite)
|
||||||
*cp.Rules[i2].Filters.URLRewrite = *o.Rules[i2].Filters.URLRewrite
|
*cp.Rules[i2].Filters.URLRewrite = *o.Rules[i2].Filters.URLRewrite
|
||||||
}
|
}
|
||||||
|
if o.Rules[i2].ResponseFilters.Headers != nil {
|
||||||
|
cp.Rules[i2].ResponseFilters.Headers = make([]HTTPHeaderFilter, len(o.Rules[i2].ResponseFilters.Headers))
|
||||||
|
copy(cp.Rules[i2].ResponseFilters.Headers, o.Rules[i2].ResponseFilters.Headers)
|
||||||
|
for i5 := range o.Rules[i2].ResponseFilters.Headers {
|
||||||
|
if o.Rules[i2].ResponseFilters.Headers[i5].Add != nil {
|
||||||
|
cp.Rules[i2].ResponseFilters.Headers[i5].Add = make(map[string]string, len(o.Rules[i2].ResponseFilters.Headers[i5].Add))
|
||||||
|
for k7, v7 := range o.Rules[i2].ResponseFilters.Headers[i5].Add {
|
||||||
|
cp.Rules[i2].ResponseFilters.Headers[i5].Add[k7] = v7
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if o.Rules[i2].ResponseFilters.Headers[i5].Remove != nil {
|
||||||
|
cp.Rules[i2].ResponseFilters.Headers[i5].Remove = make([]string, len(o.Rules[i2].ResponseFilters.Headers[i5].Remove))
|
||||||
|
copy(cp.Rules[i2].ResponseFilters.Headers[i5].Remove, o.Rules[i2].ResponseFilters.Headers[i5].Remove)
|
||||||
|
}
|
||||||
|
if o.Rules[i2].ResponseFilters.Headers[i5].Set != nil {
|
||||||
|
cp.Rules[i2].ResponseFilters.Headers[i5].Set = make(map[string]string, len(o.Rules[i2].ResponseFilters.Headers[i5].Set))
|
||||||
|
for k7, v7 := range o.Rules[i2].ResponseFilters.Headers[i5].Set {
|
||||||
|
cp.Rules[i2].ResponseFilters.Headers[i5].Set[k7] = v7
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
if o.Rules[i2].Matches != nil {
|
if o.Rules[i2].Matches != nil {
|
||||||
cp.Rules[i2].Matches = make([]HTTPMatch, len(o.Rules[i2].Matches))
|
cp.Rules[i2].Matches = make([]HTTPMatch, len(o.Rules[i2].Matches))
|
||||||
copy(cp.Rules[i2].Matches, o.Rules[i2].Matches)
|
copy(cp.Rules[i2].Matches, o.Rules[i2].Matches)
|
||||||
|
@ -427,6 +449,28 @@ func (o *HTTPRouteConfigEntry) DeepCopy() *HTTPRouteConfigEntry {
|
||||||
cp.Rules[i2].Services[i4].Filters.URLRewrite = new(URLRewrite)
|
cp.Rules[i2].Services[i4].Filters.URLRewrite = new(URLRewrite)
|
||||||
*cp.Rules[i2].Services[i4].Filters.URLRewrite = *o.Rules[i2].Services[i4].Filters.URLRewrite
|
*cp.Rules[i2].Services[i4].Filters.URLRewrite = *o.Rules[i2].Services[i4].Filters.URLRewrite
|
||||||
}
|
}
|
||||||
|
if o.Rules[i2].Services[i4].ResponseFilters.Headers != nil {
|
||||||
|
cp.Rules[i2].Services[i4].ResponseFilters.Headers = make([]HTTPHeaderFilter, len(o.Rules[i2].Services[i4].ResponseFilters.Headers))
|
||||||
|
copy(cp.Rules[i2].Services[i4].ResponseFilters.Headers, o.Rules[i2].Services[i4].ResponseFilters.Headers)
|
||||||
|
for i7 := range o.Rules[i2].Services[i4].ResponseFilters.Headers {
|
||||||
|
if o.Rules[i2].Services[i4].ResponseFilters.Headers[i7].Add != nil {
|
||||||
|
cp.Rules[i2].Services[i4].ResponseFilters.Headers[i7].Add = make(map[string]string, len(o.Rules[i2].Services[i4].ResponseFilters.Headers[i7].Add))
|
||||||
|
for k9, v9 := range o.Rules[i2].Services[i4].ResponseFilters.Headers[i7].Add {
|
||||||
|
cp.Rules[i2].Services[i4].ResponseFilters.Headers[i7].Add[k9] = v9
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if o.Rules[i2].Services[i4].ResponseFilters.Headers[i7].Remove != nil {
|
||||||
|
cp.Rules[i2].Services[i4].ResponseFilters.Headers[i7].Remove = make([]string, len(o.Rules[i2].Services[i4].ResponseFilters.Headers[i7].Remove))
|
||||||
|
copy(cp.Rules[i2].Services[i4].ResponseFilters.Headers[i7].Remove, o.Rules[i2].Services[i4].ResponseFilters.Headers[i7].Remove)
|
||||||
|
}
|
||||||
|
if o.Rules[i2].Services[i4].ResponseFilters.Headers[i7].Set != nil {
|
||||||
|
cp.Rules[i2].Services[i4].ResponseFilters.Headers[i7].Set = make(map[string]string, len(o.Rules[i2].Services[i4].ResponseFilters.Headers[i7].Set))
|
||||||
|
for k9, v9 := range o.Rules[i2].Services[i4].ResponseFilters.Headers[i7].Set {
|
||||||
|
cp.Rules[i2].Services[i4].ResponseFilters.Headers[i7].Set[k9] = v9
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -199,6 +199,12 @@ type HTTPFilters struct {
|
||||||
URLRewrite *URLRewrite
|
URLRewrite *URLRewrite
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HTTPResponseFilters specifies a list of filters used to modify a
|
||||||
|
// response returned by an upstream
|
||||||
|
type HTTPResponseFilters struct {
|
||||||
|
Headers []HTTPHeaderFilter
|
||||||
|
}
|
||||||
|
|
||||||
// HTTPHeaderFilter specifies how HTTP headers should be modified.
|
// HTTPHeaderFilter specifies how HTTP headers should be modified.
|
||||||
type HTTPHeaderFilter struct {
|
type HTTPHeaderFilter struct {
|
||||||
Add map[string]string
|
Add map[string]string
|
||||||
|
@ -216,6 +222,9 @@ type HTTPRouteRule struct {
|
||||||
// Filters is a list of HTTP-based filters used to modify a request prior
|
// Filters is a list of HTTP-based filters used to modify a request prior
|
||||||
// to routing it to the upstream service
|
// to routing it to the upstream service
|
||||||
Filters HTTPFilters
|
Filters HTTPFilters
|
||||||
|
// ResponseFilters is a list of HTTP-based filters used to modify a response
|
||||||
|
// returned by the upstream service
|
||||||
|
ResponseFilters HTTPResponseFilters
|
||||||
// Matches specified the matching criteria used in the routing table. If a
|
// Matches specified the matching criteria used in the routing table. If a
|
||||||
// request matches the given HTTPMatch configuration, then traffic is routed
|
// request matches the given HTTPMatch configuration, then traffic is routed
|
||||||
// to services specified in the Services field.
|
// to services specified in the Services field.
|
||||||
|
@ -231,10 +240,15 @@ type HTTPService struct {
|
||||||
// Weight is an arbitrary integer used in calculating how much
|
// Weight is an arbitrary integer used in calculating how much
|
||||||
// traffic should be sent to the given service.
|
// traffic should be sent to the given service.
|
||||||
Weight int
|
Weight int
|
||||||
|
|
||||||
// Filters is a list of HTTP-based filters used to modify a request prior
|
// Filters is a list of HTTP-based filters used to modify a request prior
|
||||||
// to routing it to the upstream service
|
// to routing it to the upstream service
|
||||||
Filters HTTPFilters
|
Filters HTTPFilters
|
||||||
|
|
||||||
|
// ResponseFilters is a list of HTTP-based filters used to modify the
|
||||||
|
// response returned from the upstream service
|
||||||
|
ResponseFilters HTTPResponseFilters
|
||||||
|
|
||||||
// Partition is the partition the config entry is associated with.
|
// Partition is the partition the config entry is associated with.
|
||||||
// Partitioning is a Consul Enterprise feature.
|
// Partitioning is a Consul Enterprise feature.
|
||||||
Partition string `json:",omitempty"`
|
Partition string `json:",omitempty"`
|
||||||
|
|
|
@ -524,6 +524,34 @@ func HTTPQueryMatchFromStructs(t *structs.HTTPQueryMatch, s *HTTPQueryMatch) {
|
||||||
s.Name = t.Name
|
s.Name = t.Name
|
||||||
s.Value = t.Value
|
s.Value = t.Value
|
||||||
}
|
}
|
||||||
|
func HTTPResponseFiltersToStructs(s *HTTPResponseFilters, t *structs.HTTPResponseFilters) {
|
||||||
|
if s == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
{
|
||||||
|
t.Headers = make([]structs.HTTPHeaderFilter, len(s.Headers))
|
||||||
|
for i := range s.Headers {
|
||||||
|
if s.Headers[i] != nil {
|
||||||
|
HTTPHeaderFilterToStructs(s.Headers[i], &t.Headers[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func HTTPResponseFiltersFromStructs(t *structs.HTTPResponseFilters, s *HTTPResponseFilters) {
|
||||||
|
if s == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
{
|
||||||
|
s.Headers = make([]*HTTPHeaderFilter, len(t.Headers))
|
||||||
|
for i := range t.Headers {
|
||||||
|
{
|
||||||
|
var x HTTPHeaderFilter
|
||||||
|
HTTPHeaderFilterFromStructs(&t.Headers[i], &x)
|
||||||
|
s.Headers[i] = &x
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
func HTTPRouteToStructs(s *HTTPRoute, t *structs.HTTPRouteConfigEntry) {
|
func HTTPRouteToStructs(s *HTTPRoute, t *structs.HTTPRouteConfigEntry) {
|
||||||
if s == nil {
|
if s == nil {
|
||||||
return
|
return
|
||||||
|
@ -589,6 +617,9 @@ func HTTPRouteRuleToStructs(s *HTTPRouteRule, t *structs.HTTPRouteRule) {
|
||||||
if s.Filters != nil {
|
if s.Filters != nil {
|
||||||
HTTPFiltersToStructs(s.Filters, &t.Filters)
|
HTTPFiltersToStructs(s.Filters, &t.Filters)
|
||||||
}
|
}
|
||||||
|
if s.ResponseFilters != nil {
|
||||||
|
HTTPResponseFiltersToStructs(s.ResponseFilters, &t.ResponseFilters)
|
||||||
|
}
|
||||||
{
|
{
|
||||||
t.Matches = make([]structs.HTTPMatch, len(s.Matches))
|
t.Matches = make([]structs.HTTPMatch, len(s.Matches))
|
||||||
for i := range s.Matches {
|
for i := range s.Matches {
|
||||||
|
@ -615,6 +646,11 @@ func HTTPRouteRuleFromStructs(t *structs.HTTPRouteRule, s *HTTPRouteRule) {
|
||||||
HTTPFiltersFromStructs(&t.Filters, &x)
|
HTTPFiltersFromStructs(&t.Filters, &x)
|
||||||
s.Filters = &x
|
s.Filters = &x
|
||||||
}
|
}
|
||||||
|
{
|
||||||
|
var x HTTPResponseFilters
|
||||||
|
HTTPResponseFiltersFromStructs(&t.ResponseFilters, &x)
|
||||||
|
s.ResponseFilters = &x
|
||||||
|
}
|
||||||
{
|
{
|
||||||
s.Matches = make([]*HTTPMatch, len(t.Matches))
|
s.Matches = make([]*HTTPMatch, len(t.Matches))
|
||||||
for i := range t.Matches {
|
for i := range t.Matches {
|
||||||
|
@ -645,6 +681,9 @@ func HTTPServiceToStructs(s *HTTPService, t *structs.HTTPService) {
|
||||||
if s.Filters != nil {
|
if s.Filters != nil {
|
||||||
HTTPFiltersToStructs(s.Filters, &t.Filters)
|
HTTPFiltersToStructs(s.Filters, &t.Filters)
|
||||||
}
|
}
|
||||||
|
if s.ResponseFilters != nil {
|
||||||
|
HTTPResponseFiltersToStructs(s.ResponseFilters, &t.ResponseFilters)
|
||||||
|
}
|
||||||
t.EnterpriseMeta = enterpriseMetaToStructs(s.EnterpriseMeta)
|
t.EnterpriseMeta = enterpriseMetaToStructs(s.EnterpriseMeta)
|
||||||
}
|
}
|
||||||
func HTTPServiceFromStructs(t *structs.HTTPService, s *HTTPService) {
|
func HTTPServiceFromStructs(t *structs.HTTPService, s *HTTPService) {
|
||||||
|
@ -658,6 +697,11 @@ func HTTPServiceFromStructs(t *structs.HTTPService, s *HTTPService) {
|
||||||
HTTPFiltersFromStructs(&t.Filters, &x)
|
HTTPFiltersFromStructs(&t.Filters, &x)
|
||||||
s.Filters = &x
|
s.Filters = &x
|
||||||
}
|
}
|
||||||
|
{
|
||||||
|
var x HTTPResponseFilters
|
||||||
|
HTTPResponseFiltersFromStructs(&t.ResponseFilters, &x)
|
||||||
|
s.ResponseFilters = &x
|
||||||
|
}
|
||||||
s.EnterpriseMeta = enterpriseMetaFromStructs(t.EnterpriseMeta)
|
s.EnterpriseMeta = enterpriseMetaFromStructs(t.EnterpriseMeta)
|
||||||
}
|
}
|
||||||
func HashPolicyToStructs(s *HashPolicy, t *structs.HashPolicy) {
|
func HashPolicyToStructs(s *HashPolicy, t *structs.HashPolicy) {
|
||||||
|
|
|
@ -617,6 +617,16 @@ func (msg *HTTPFilters) UnmarshalBinary(b []byte) error {
|
||||||
return proto.Unmarshal(b, msg)
|
return proto.Unmarshal(b, msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MarshalBinary implements encoding.BinaryMarshaler
|
||||||
|
func (msg *HTTPResponseFilters) MarshalBinary() ([]byte, error) {
|
||||||
|
return proto.Marshal(msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalBinary implements encoding.BinaryUnmarshaler
|
||||||
|
func (msg *HTTPResponseFilters) UnmarshalBinary(b []byte) error {
|
||||||
|
return proto.Unmarshal(b, msg)
|
||||||
|
}
|
||||||
|
|
||||||
// MarshalBinary implements encoding.BinaryMarshaler
|
// MarshalBinary implements encoding.BinaryMarshaler
|
||||||
func (msg *URLRewrite) MarshalBinary() ([]byte, error) {
|
func (msg *URLRewrite) MarshalBinary() ([]byte, error) {
|
||||||
return proto.Marshal(msg)
|
return proto.Marshal(msg)
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -793,6 +793,7 @@ message HTTPRouteRule {
|
||||||
HTTPFilters Filters = 1;
|
HTTPFilters Filters = 1;
|
||||||
repeated HTTPMatch Matches = 2;
|
repeated HTTPMatch Matches = 2;
|
||||||
repeated HTTPService Services = 3;
|
repeated HTTPService Services = 3;
|
||||||
|
HTTPResponseFilters ResponseFilters = 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
// mog annotation:
|
// mog annotation:
|
||||||
|
@ -886,6 +887,15 @@ message HTTPFilters {
|
||||||
URLRewrite URLRewrite = 2;
|
URLRewrite URLRewrite = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// mog annotation:
|
||||||
|
//
|
||||||
|
// target=github.com/hashicorp/consul/agent/structs.HTTPResponseFilters
|
||||||
|
// output=config_entry.gen.go
|
||||||
|
// name=Structs
|
||||||
|
message HTTPResponseFilters {
|
||||||
|
repeated HTTPHeaderFilter Headers = 1;
|
||||||
|
}
|
||||||
|
|
||||||
// mog annotation:
|
// mog annotation:
|
||||||
//
|
//
|
||||||
// target=github.com/hashicorp/consul/agent/structs.URLRewrite
|
// target=github.com/hashicorp/consul/agent/structs.URLRewrite
|
||||||
|
@ -918,6 +928,7 @@ message HTTPService {
|
||||||
HTTPFilters Filters = 3;
|
HTTPFilters Filters = 3;
|
||||||
// mog: func-to=enterpriseMetaToStructs func-from=enterpriseMetaFromStructs
|
// mog: func-to=enterpriseMetaToStructs func-from=enterpriseMetaFromStructs
|
||||||
common.EnterpriseMeta EnterpriseMeta = 4;
|
common.EnterpriseMeta EnterpriseMeta = 4;
|
||||||
|
HTTPResponseFilters ResponseFilters = 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
// mog annotation:
|
// mog annotation:
|
||||||
|
|
|
@ -223,9 +223,10 @@ func checkTCPRouteConfigEntry(t *testing.T, client *api.Client, routeName string
|
||||||
}
|
}
|
||||||
|
|
||||||
type checkOptions struct {
|
type checkOptions struct {
|
||||||
debug bool
|
debug bool
|
||||||
statusCode int
|
responseHeaders map[string]string
|
||||||
testName string
|
statusCode int
|
||||||
|
testName string
|
||||||
}
|
}
|
||||||
|
|
||||||
// checkRoute, customized version of libassert.RouteEchos to allow for headers/distinguishing between the server instances
|
// checkRoute, customized version of libassert.RouteEchos to allow for headers/distinguishing between the server instances
|
||||||
|
@ -274,6 +275,14 @@ func checkRoute(t *testing.T, port int, path string, headers map[string]string,
|
||||||
t.Logf("bad status code - expected: %d, actual: %d", expected.statusCode, res.StatusCode)
|
t.Logf("bad status code - expected: %d, actual: %d", expected.statusCode, res.StatusCode)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for name, value := range expected.responseHeaders {
|
||||||
|
if res.Header.Get(name) != value {
|
||||||
|
t.Logf("response missing header - expected: %s=%s, actual: %s=%s", name, value, name, res.Header.Get(name))
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if expected.debug {
|
if expected.debug {
|
||||||
if !strings.Contains(string(body), "debug") {
|
if !strings.Contains(string(body), "debug") {
|
||||||
t.Log("body does not contain 'debug'")
|
t.Log("body does not contain 'debug'")
|
||||||
|
|
|
@ -268,7 +268,7 @@ func TestHTTPRouteFlattening(t *testing.T) {
|
||||||
}, checkOptions{debug: false, statusCode: serviceOneResponseCode, testName: "service1, v2 path with v2 hostname"})
|
}, checkOptions{debug: false, statusCode: serviceOneResponseCode, testName: "service1, v2 path with v2 hostname"})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestHTTPRoutePathRewrite(t *testing.T) {
|
func TestHTTPRouteFilters(t *testing.T) {
|
||||||
if testing.Short() {
|
if testing.Short() {
|
||||||
t.Skip("too slow for testing.Short")
|
t.Skip("too slow for testing.Short")
|
||||||
}
|
}
|
||||||
|
@ -393,6 +393,12 @@ func TestHTTPRoutePathRewrite(t *testing.T) {
|
||||||
Path: fooPath,
|
Path: fooPath,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
ResponseFilters: api.HTTPResponseFilters{
|
||||||
|
Headers: []api.HTTPHeaderFilter{{
|
||||||
|
Add: map[string]string{"response-filters-add": "present"},
|
||||||
|
Set: map[string]string{"response-filters-set": "present"},
|
||||||
|
}},
|
||||||
|
},
|
||||||
Services: []api.HTTPService{
|
Services: []api.HTTPService{
|
||||||
{
|
{
|
||||||
Name: fooName,
|
Name: fooName,
|
||||||
|
@ -478,13 +484,23 @@ func TestHTTPRoutePathRewrite(t *testing.T) {
|
||||||
debugExpectedStatusCode := 200
|
debugExpectedStatusCode := 200
|
||||||
|
|
||||||
// hit foo, making sure path is being rewritten by hitting the debug page
|
// hit foo, making sure path is being rewritten by hitting the debug page
|
||||||
checkRoute(t, gatewayPort, fooUnrewritten, map[string]string{
|
// and that we get the expected response headers that we added modifiers for
|
||||||
"Host": "test.foo",
|
checkRoute(
|
||||||
}, checkOptions{debug: true, statusCode: debugExpectedStatusCode, testName: "foo service"})
|
t, gatewayPort, fooUnrewritten, map[string]string{"Host": "test.foo"},
|
||||||
|
checkOptions{
|
||||||
|
debug: true,
|
||||||
|
responseHeaders: map[string]string{"response-filters-add": "present", "response-filters-set": "present"},
|
||||||
|
statusCode: debugExpectedStatusCode,
|
||||||
|
testName: "foo service"})
|
||||||
// make sure foo is being sent to proper service
|
// make sure foo is being sent to proper service
|
||||||
checkRoute(t, gatewayPort, fooUnrewritten+"/foo", map[string]string{
|
// and that we get the expected response headers that we added modifiers for
|
||||||
"Host": "test.foo",
|
checkRoute(
|
||||||
}, checkOptions{debug: false, statusCode: fooStatusCode, testName: "foo service 2"})
|
t, gatewayPort, fooUnrewritten+"/foo", map[string]string{"Host": "test.foo"},
|
||||||
|
checkOptions{
|
||||||
|
debug: false,
|
||||||
|
responseHeaders: map[string]string{"response-filters-add": "present", "response-filters-set": "present"},
|
||||||
|
statusCode: fooStatusCode,
|
||||||
|
testName: "foo service 2"})
|
||||||
|
|
||||||
// hit bar, making sure its been rewritten
|
// hit bar, making sure its been rewritten
|
||||||
checkRoute(t, gatewayPort, barUnrewritten, map[string]string{
|
checkRoute(t, gatewayPort, barUnrewritten, map[string]string{
|
||||||
|
|
Loading…
Reference in New Issue