Skip to content

Commit

Permalink
Add support for ResponseHeaderModifier for HTTPRouteRule objects (#1880)
Browse files Browse the repository at this point in the history
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]>
  • Loading branch information
salonichf5 and kevin85421 authored May 14, 2024
1 parent 0098b07 commit 4d0a3de
Show file tree
Hide file tree
Showing 26 changed files with 995 additions and 198 deletions.
2 changes: 1 addition & 1 deletion conformance/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ NGINX_PREFIX = $(PREFIX)/nginx
NGINX_PLUS_PREFIX ?= $(PREFIX)/nginx-plus
GW_API_VERSION ?= 1.0.0
GATEWAY_CLASS = nginx
SUPPORTED_FEATURES = HTTPRouteQueryParamMatching,HTTPRouteMethodMatching,HTTPRoutePortRedirect,HTTPRouteSchemeRedirect,HTTPRouteHostRewrite,HTTPRoutePathRewrite,GatewayPort8080
SUPPORTED_FEATURES = HTTPRouteQueryParamMatching,HTTPRouteMethodMatching,HTTPRoutePortRedirect,HTTPRouteSchemeRedirect,HTTPRouteHostRewrite,HTTPRoutePathRewrite,GatewayPort8080,HTTPRouteResponseHeaderModification
KIND_IMAGE ?= $(shell grep -m1 'FROM kindest/node' <tests/Dockerfile | awk -F'[ ]' '{print $$2}')
KIND_KUBE_CONFIG=$${HOME}/.kube/kind/config
CONFORMANCE_TAG = latest
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ headers to the request.
GW_PORT=<port number>
```

## 2. Deploy the Cafe Application
## 2. Deploy the Headers Application

1. Create the headers Deployment and Service:

Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
3 changes: 3 additions & 0 deletions examples/http-response-header-filter/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# HTTP Response Headers

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.
10 changes: 10 additions & 0 deletions examples/http-response-header-filter/gateway.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: gateway
spec:
gatewayClassName: nginx
listeners:
- name: http
port: 80
protocol: HTTP
70 changes: 70 additions & 0 deletions examples/http-response-header-filter/headers.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: headers
spec:
replicas: 1
selector:
matchLabels:
app: headers
template:
metadata:
labels:
app: headers
spec:
containers:
- name: headers
image: nginx
ports:
- containerPort: 8080
volumeMounts:
- name: config-volume
mountPath: /etc/nginx
readOnly: true
volumes:
- name: config-volume
configMap:
name: headers-config
---
apiVersion: v1
kind: ConfigMap
metadata:
name: headers-config
# yamllint disable rule:indentation
data:
nginx.conf: |-
user nginx;
worker_processes 1;
pid /var/run/nginx.pid;
events {}
http {
default_type text/plain;
server {
listen 8080;
add_header X-Header-Unmodified "unmodified";
add_header X-Header-Add "add-to";
add_header X-Header-Set "overwrite";
add_header X-Header-Remove "remove";
return 200 "ok";
}
}
# yamllint enable rule:indentation
---
apiVersion: v1
kind: Service
metadata:
name: headers
spec:
ports:
- port: 80
targetPort: 8080
protocol: TCP
name: http
selector:
app: headers
29 changes: 29 additions & 0 deletions examples/http-response-header-filter/http-route-filters.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: headers
spec:
parentRefs:
- name: gateway
sectionName: http
hostnames:
- "cafe.example.com"
rules:
- matches:
- path:
type: PathPrefix
value: /headers
filters:
- type: ResponseHeaderModifier
responseHeaderModifier:
set:
- name: X-Header-Set
value: overwritten-value
add:
- name: X-Header-Add
value: this-is-the-appended-value
remove:
- X-Header-Remove
backendRefs:
- name: headers
port: 80
18 changes: 18 additions & 0 deletions examples/http-response-header-filter/http-route.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: headers
spec:
parentRefs:
- name: gateway
sectionName: http
hostnames:
- "cafe.example.com"
rules:
- matches:
- path:
type: PathPrefix
value: /headers
backendRefs:
- name: headers
port: 80
11 changes: 10 additions & 1 deletion internal/mode/static/nginx/config/http/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,12 @@ type Location struct {
Path string
ProxyPass string
HTTPMatchKey string
HTTPMatchVar string
Rewrites []string
ProxySetHeaders []Header
ProxySSLVerify *ProxySSLVerify
Return *Return
Rewrites []string
ResponseHeaders ResponseHeaders
GRPC bool
}

Expand All @@ -29,6 +31,13 @@ type Header struct {
Value string
}

// ResponseHeaders holds all response headers to be added, set, or removed.
type ResponseHeaders struct {
Add []Header
Set []Header
Remove []string
}

// Return represents an HTTP return.
type Return struct {
Body string
Expand Down
28 changes: 24 additions & 4 deletions internal/mode/static/nginx/config/servers.go
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,7 @@ func updateLocationsForFilters(

rewrites := createRewritesValForRewriteFilter(filters.RequestURLRewrite, path)
proxySetHeaders := generateProxySetHeaders(&matchRule.Filters, grpc)
responseHeaders := generateResponseHeaders(&matchRule.Filters)
for i := range buildLocations {
if rewrites != nil {
if rewrites.Rewrite != "" {
Expand All @@ -308,6 +309,7 @@ func updateLocationsForFilters(
generateProtocolString(buildLocations[i].ProxySSLVerify, grpc),
grpc,
)
buildLocations[i].ResponseHeaders = responseHeaders
buildLocations[i].ProxyPass = proxyPass
buildLocations[i].GRPC = grpc
}
Expand Down Expand Up @@ -578,11 +580,11 @@ func generateProxySetHeaders(filters *dataplane.HTTPFilters, grpc bool) []http.H
headerLen := len(headerFilter.Add) + len(headerFilter.Set) + len(headerFilter.Remove) + len(headers)
proxySetHeaders := make([]http.Header, 0, headerLen)
if len(headerFilter.Add) > 0 {
addHeaders := convertAddHeaders(headerFilter.Add)
addHeaders := createHeadersWithVarName(headerFilter.Add)
proxySetHeaders = append(proxySetHeaders, addHeaders...)
}
if len(headerFilter.Set) > 0 {
setHeaders := convertSetHeaders(headerFilter.Set)
setHeaders := createHeaders(headerFilter.Set)
proxySetHeaders = append(proxySetHeaders, setHeaders...)
}
// If the value of a header field is an empty string then this field will not be passed to a proxied server
Expand All @@ -596,7 +598,25 @@ func generateProxySetHeaders(filters *dataplane.HTTPFilters, grpc bool) []http.H
return append(proxySetHeaders, headers...)
}

func convertAddHeaders(headers []dataplane.HTTPHeader) []http.Header {
func generateResponseHeaders(filters *dataplane.HTTPFilters) http.ResponseHeaders {
if filters == nil || filters.ResponseHeaderModifiers == nil {
return http.ResponseHeaders{}
}

headerFilter := filters.ResponseHeaderModifiers
responseRemoveHeaders := make([]string, len(headerFilter.Remove))

// Make a deep copy to prevent the slice from being accidentally modified.
copy(responseRemoveHeaders, headerFilter.Remove)

return http.ResponseHeaders{
Add: createHeaders(headerFilter.Add),
Set: createHeaders(headerFilter.Set),
Remove: responseRemoveHeaders,
}
}

func createHeadersWithVarName(headers []dataplane.HTTPHeader) []http.Header {
locHeaders := make([]http.Header, 0, len(headers))
for _, h := range headers {
mapVarName := "${" + generateAddHeaderMapVariableName(h.Name) + "}"
Expand All @@ -608,7 +628,7 @@ func convertAddHeaders(headers []dataplane.HTTPHeader) []http.Header {
return locHeaders
}

func convertSetHeaders(headers []dataplane.HTTPHeader) []http.Header {
func createHeaders(headers []dataplane.HTTPHeader) []http.Header {
locHeaders := make([]http.Header, 0, len(headers))
for _, h := range headers {
locHeaders = append(locHeaders, http.Header{
Expand Down
10 changes: 10 additions & 0 deletions internal/mode/static/nginx/config/servers_template.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,16 @@ server {
{{ $proxyOrGRPC }}_set_header {{ $h.Name }} "{{ $h.Value }}";
{{- end }}
{{ $proxyOrGRPC }}_pass {{ $l.ProxyPass }};
{{ range $h := $l.ResponseHeaders.Add }}
add_header {{ $h.Name }} "{{ $h.Value }}" always;
{{- end }}
{{ range $h := $l.ResponseHeaders.Set }}
proxy_hide_header {{ $h.Name }};
add_header {{ $h.Name }} "{{ $h.Value }}" always;
{{- end }}
{{ range $h := $l.ResponseHeaders.Remove }}
proxy_hide_header {{ $h }};
{{- end }}
proxy_http_version 1.1;
{{- if $l.ProxySSLVerify }}
{{ $proxyOrGRPC }}_ssl_server_name on;
Expand Down
Loading

0 comments on commit 4d0a3de

Please sign in to comment.