Skip to content

Commit 4d0a3de

Browse files
Add support for ResponseHeaderModifier for HTTPRouteRule objects (#1880)
Add support for ResponseHeaderModifier for HTTPRouteRule objects Problem: Users want to add, set, and remove response headers. Solution: Use add_header NGINX directive to support ResponseHeaderModifier. ---- Co-authored-by: kaihsun <[email protected]>
1 parent 0098b07 commit 4d0a3de

File tree

26 files changed

+995
-198
lines changed

26 files changed

+995
-198
lines changed

conformance/Makefile

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ NGINX_PREFIX = $(PREFIX)/nginx
55
NGINX_PLUS_PREFIX ?= $(PREFIX)/nginx-plus
66
GW_API_VERSION ?= 1.0.0
77
GATEWAY_CLASS = nginx
8-
SUPPORTED_FEATURES = HTTPRouteQueryParamMatching,HTTPRouteMethodMatching,HTTPRoutePortRedirect,HTTPRouteSchemeRedirect,HTTPRouteHostRewrite,HTTPRoutePathRewrite,GatewayPort8080
8+
SUPPORTED_FEATURES = HTTPRouteQueryParamMatching,HTTPRouteMethodMatching,HTTPRoutePortRedirect,HTTPRouteSchemeRedirect,HTTPRouteHostRewrite,HTTPRoutePathRewrite,GatewayPort8080,HTTPRouteResponseHeaderModification
99
KIND_IMAGE ?= $(shell grep -m1 'FROM kindest/node' <tests/Dockerfile | awk -F'[ ]' '{print $$2}')
1010
KIND_KUBE_CONFIG=$${HOME}/.kube/kind/config
1111
CONFORMANCE_TAG = latest

examples/http-header-filter/README.md renamed to examples/http-request-header-filter/README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ headers to the request.
2222
GW_PORT=<port number>
2323
```
2424

25-
## 2. Deploy the Cafe Application
25+
## 2. Deploy the Headers Application
2626

2727
1. Create the headers Deployment and Service:
2828

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# HTTP Response Headers
2+
3+
This directory contains the YAML files used in the [HTTP Response Headers](https://docs.nginx.com/nginx-gateway-fabric/how-to/traffic-management/response-headers.md) guide.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
apiVersion: gateway.networking.k8s.io/v1
2+
kind: Gateway
3+
metadata:
4+
name: gateway
5+
spec:
6+
gatewayClassName: nginx
7+
listeners:
8+
- name: http
9+
port: 80
10+
protocol: HTTP
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
apiVersion: apps/v1
2+
kind: Deployment
3+
metadata:
4+
name: headers
5+
spec:
6+
replicas: 1
7+
selector:
8+
matchLabels:
9+
app: headers
10+
template:
11+
metadata:
12+
labels:
13+
app: headers
14+
spec:
15+
containers:
16+
- name: headers
17+
image: nginx
18+
ports:
19+
- containerPort: 8080
20+
volumeMounts:
21+
- name: config-volume
22+
mountPath: /etc/nginx
23+
readOnly: true
24+
volumes:
25+
- name: config-volume
26+
configMap:
27+
name: headers-config
28+
---
29+
apiVersion: v1
30+
kind: ConfigMap
31+
metadata:
32+
name: headers-config
33+
# yamllint disable rule:indentation
34+
data:
35+
nginx.conf: |-
36+
user nginx;
37+
worker_processes 1;
38+
39+
pid /var/run/nginx.pid;
40+
41+
events {}
42+
43+
http {
44+
default_type text/plain;
45+
46+
server {
47+
listen 8080;
48+
49+
add_header X-Header-Unmodified "unmodified";
50+
add_header X-Header-Add "add-to";
51+
add_header X-Header-Set "overwrite";
52+
add_header X-Header-Remove "remove";
53+
54+
return 200 "ok";
55+
}
56+
}
57+
# yamllint enable rule:indentation
58+
---
59+
apiVersion: v1
60+
kind: Service
61+
metadata:
62+
name: headers
63+
spec:
64+
ports:
65+
- port: 80
66+
targetPort: 8080
67+
protocol: TCP
68+
name: http
69+
selector:
70+
app: headers
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
apiVersion: gateway.networking.k8s.io/v1
2+
kind: HTTPRoute
3+
metadata:
4+
name: headers
5+
spec:
6+
parentRefs:
7+
- name: gateway
8+
sectionName: http
9+
hostnames:
10+
- "cafe.example.com"
11+
rules:
12+
- matches:
13+
- path:
14+
type: PathPrefix
15+
value: /headers
16+
filters:
17+
- type: ResponseHeaderModifier
18+
responseHeaderModifier:
19+
set:
20+
- name: X-Header-Set
21+
value: overwritten-value
22+
add:
23+
- name: X-Header-Add
24+
value: this-is-the-appended-value
25+
remove:
26+
- X-Header-Remove
27+
backendRefs:
28+
- name: headers
29+
port: 80
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
apiVersion: gateway.networking.k8s.io/v1
2+
kind: HTTPRoute
3+
metadata:
4+
name: headers
5+
spec:
6+
parentRefs:
7+
- name: gateway
8+
sectionName: http
9+
hostnames:
10+
- "cafe.example.com"
11+
rules:
12+
- matches:
13+
- path:
14+
type: PathPrefix
15+
value: /headers
16+
backendRefs:
17+
- name: headers
18+
port: 80

internal/mode/static/nginx/config/http/config.go

+10-1
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,12 @@ type Location struct {
1616
Path string
1717
ProxyPass string
1818
HTTPMatchKey string
19+
HTTPMatchVar string
20+
Rewrites []string
1921
ProxySetHeaders []Header
2022
ProxySSLVerify *ProxySSLVerify
2123
Return *Return
22-
Rewrites []string
24+
ResponseHeaders ResponseHeaders
2325
GRPC bool
2426
}
2527

@@ -29,6 +31,13 @@ type Header struct {
2931
Value string
3032
}
3133

34+
// ResponseHeaders holds all response headers to be added, set, or removed.
35+
type ResponseHeaders struct {
36+
Add []Header
37+
Set []Header
38+
Remove []string
39+
}
40+
3241
// Return represents an HTTP return.
3342
type Return struct {
3443
Body string

internal/mode/static/nginx/config/servers.go

+24-4
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,7 @@ func updateLocationsForFilters(
294294

295295
rewrites := createRewritesValForRewriteFilter(filters.RequestURLRewrite, path)
296296
proxySetHeaders := generateProxySetHeaders(&matchRule.Filters, grpc)
297+
responseHeaders := generateResponseHeaders(&matchRule.Filters)
297298
for i := range buildLocations {
298299
if rewrites != nil {
299300
if rewrites.Rewrite != "" {
@@ -308,6 +309,7 @@ func updateLocationsForFilters(
308309
generateProtocolString(buildLocations[i].ProxySSLVerify, grpc),
309310
grpc,
310311
)
312+
buildLocations[i].ResponseHeaders = responseHeaders
311313
buildLocations[i].ProxyPass = proxyPass
312314
buildLocations[i].GRPC = grpc
313315
}
@@ -578,11 +580,11 @@ func generateProxySetHeaders(filters *dataplane.HTTPFilters, grpc bool) []http.H
578580
headerLen := len(headerFilter.Add) + len(headerFilter.Set) + len(headerFilter.Remove) + len(headers)
579581
proxySetHeaders := make([]http.Header, 0, headerLen)
580582
if len(headerFilter.Add) > 0 {
581-
addHeaders := convertAddHeaders(headerFilter.Add)
583+
addHeaders := createHeadersWithVarName(headerFilter.Add)
582584
proxySetHeaders = append(proxySetHeaders, addHeaders...)
583585
}
584586
if len(headerFilter.Set) > 0 {
585-
setHeaders := convertSetHeaders(headerFilter.Set)
587+
setHeaders := createHeaders(headerFilter.Set)
586588
proxySetHeaders = append(proxySetHeaders, setHeaders...)
587589
}
588590
// If the value of a header field is an empty string then this field will not be passed to a proxied server
@@ -596,7 +598,25 @@ func generateProxySetHeaders(filters *dataplane.HTTPFilters, grpc bool) []http.H
596598
return append(proxySetHeaders, headers...)
597599
}
598600

599-
func convertAddHeaders(headers []dataplane.HTTPHeader) []http.Header {
601+
func generateResponseHeaders(filters *dataplane.HTTPFilters) http.ResponseHeaders {
602+
if filters == nil || filters.ResponseHeaderModifiers == nil {
603+
return http.ResponseHeaders{}
604+
}
605+
606+
headerFilter := filters.ResponseHeaderModifiers
607+
responseRemoveHeaders := make([]string, len(headerFilter.Remove))
608+
609+
// Make a deep copy to prevent the slice from being accidentally modified.
610+
copy(responseRemoveHeaders, headerFilter.Remove)
611+
612+
return http.ResponseHeaders{
613+
Add: createHeaders(headerFilter.Add),
614+
Set: createHeaders(headerFilter.Set),
615+
Remove: responseRemoveHeaders,
616+
}
617+
}
618+
619+
func createHeadersWithVarName(headers []dataplane.HTTPHeader) []http.Header {
600620
locHeaders := make([]http.Header, 0, len(headers))
601621
for _, h := range headers {
602622
mapVarName := "${" + generateAddHeaderMapVariableName(h.Name) + "}"
@@ -608,7 +628,7 @@ func convertAddHeaders(headers []dataplane.HTTPHeader) []http.Header {
608628
return locHeaders
609629
}
610630

611-
func convertSetHeaders(headers []dataplane.HTTPHeader) []http.Header {
631+
func createHeaders(headers []dataplane.HTTPHeader) []http.Header {
612632
locHeaders := make([]http.Header, 0, len(headers))
613633
for _, h := range headers {
614634
locHeaders = append(locHeaders, http.Header{

internal/mode/static/nginx/config/servers_template.go

+10
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,16 @@ server {
5858
{{ $proxyOrGRPC }}_set_header {{ $h.Name }} "{{ $h.Value }}";
5959
{{- end }}
6060
{{ $proxyOrGRPC }}_pass {{ $l.ProxyPass }};
61+
{{ range $h := $l.ResponseHeaders.Add }}
62+
add_header {{ $h.Name }} "{{ $h.Value }}" always;
63+
{{- end }}
64+
{{ range $h := $l.ResponseHeaders.Set }}
65+
proxy_hide_header {{ $h.Name }};
66+
add_header {{ $h.Name }} "{{ $h.Value }}" always;
67+
{{- end }}
68+
{{ range $h := $l.ResponseHeaders.Remove }}
69+
proxy_hide_header {{ $h }};
70+
{{- end }}
6171
proxy_http_version 1.1;
6272
{{- if $l.ProxySSLVerify }}
6373
{{ $proxyOrGRPC }}_ssl_server_name on;

0 commit comments

Comments
 (0)