Skip to content

Commit fc717bb

Browse files
filters/builtin: drop specified header value
Extend `dropRequestHeader` and `dropResponseHeader` to support optional second value argument and drop only this value when specified. Signed-off-by: Alexander Yastrebov <[email protected]>
1 parent 6b448b1 commit fc717bb

File tree

3 files changed

+127
-51
lines changed

3 files changed

+127
-51
lines changed

Diff for: docs/reference/filters.md

+8-1
Original file line numberDiff line numberDiff line change
@@ -163,18 +163,25 @@ but appends the provided value to the already existing ones.
163163

164164
### dropRequestHeader
165165

166-
Removes a header from the request
166+
Removes a header or a specific value from the request.
167167

168168
Parameters:
169169

170170
* header name (string)
171+
* header value (string) - optional
171172

172173
Example:
173174

174175
```
175176
foo: * -> dropRequestHeader("User-Agent") -> "https://backend.example.org";
176177
```
177178

179+
Drop exactly matching value and keep others:
180+
181+
```
182+
foo: * -> dropRequestHeader("Connection", "Upgrade") -> "https://backend.example.org";
183+
```
184+
178185
### modResponseHeader
179186

180187
Same as [modRequestHeader](#modrequestheader), only for responses

Diff for: filters/builtin/header_test.go

+104-47
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"strings"
77
"testing"
88

9+
"github.com/stretchr/testify/assert"
910
"github.com/zalando/skipper/eskip"
1011
"github.com/zalando/skipper/filters"
1112
"github.com/zalando/skipper/filters/filtertest"
@@ -85,12 +86,7 @@ func testHeaders(t *testing.T, got, expected http.Header) {
8586
delete(got, n)
8687
}
8788
}
88-
89-
if !compareHeaders(got, expected) {
90-
printHeader(t, expected, "invalid header", "expected")
91-
printHeader(t, got, "invalid header", "got")
92-
t.Error("invalid header")
93-
}
89+
assert.Equal(t, expected, got)
9490
}
9591

9692
func TestHeader(t *testing.T) {
@@ -132,10 +128,11 @@ func TestHeader(t *testing.T) {
132128
requestHeader: http.Header{"X-Test-Name": []string{"value0", "value1"}},
133129
expectedHeader: http.Header{"X-Test-Request-Name": []string{"value"}},
134130
}, {
135-
msg: "set outgoing host on set",
136-
args: []interface{}{"Host", "www.example.org"},
137-
valid: true,
138-
host: "www.example.org",
131+
msg: "set outgoing host on set",
132+
args: []interface{}{"Host", "www.example.org"},
133+
valid: true,
134+
host: "www.example.org",
135+
expectedHeader: http.Header{},
139136
}, {
140137
msg: "set request header from path params",
141138
args: []interface{}{"X-Test-Name", "Mit ${was} zu ${wo}"},
@@ -161,10 +158,11 @@ func TestHeader(t *testing.T) {
161158
requestHeader: http.Header{"X-Test-Name": []string{"value0", "value1"}},
162159
expectedHeader: http.Header{"X-Test-Request-Name": []string{"value0", "value1", "value"}},
163160
}, {
164-
msg: "append outgoing host on set",
165-
args: []interface{}{"Host", "www.example.org"},
166-
valid: true,
167-
host: "www.example.org",
161+
msg: "append outgoing host on set",
162+
args: []interface{}{"Host", "www.example.org"},
163+
valid: true,
164+
host: "www.example.org",
165+
expectedHeader: http.Header{},
168166
}, {
169167
msg: "append request header from path params",
170168
args: []interface{}{"X-Test-Name", "a ${foo}ter"},
@@ -186,19 +184,46 @@ func TestHeader(t *testing.T) {
186184
expectedHeader: http.Header{"X-Test-Request-Name": []string{"Value"}},
187185
}},
188186
"dropRequestHeader": {{
189-
msg: "drop request header when none",
190-
args: []interface{}{"X-Test-Name"},
191-
valid: true,
187+
msg: "drop request header when none",
188+
args: []interface{}{"X-Test-Name"},
189+
valid: true,
190+
expectedHeader: http.Header{},
192191
}, {
193-
msg: "drop request header when exists",
194-
args: []interface{}{"X-Test-Name"},
195-
valid: true,
196-
requestHeader: http.Header{"X-Test-Name": []string{"value0", "value1"}},
192+
msg: "drop request header when exists",
193+
args: []interface{}{"X-Test-Name"},
194+
valid: true,
195+
requestHeader: http.Header{"X-Test-Name": []string{"value0", "value1"}},
196+
expectedHeader: http.Header{},
197197
}, {
198-
msg: "name parameter is case-insensitive",
199-
args: []interface{}{"x-test-name"},
200-
valid: true,
201-
requestHeader: http.Header{"X-Test-Name": []string{"value0", "value1"}},
198+
msg: "drop request header when does not exist",
199+
args: []interface{}{"X-Test-Does-Not-Exist"},
200+
valid: true,
201+
requestHeader: http.Header{"X-Test-Name": []string{"value0", "value1"}},
202+
expectedHeader: http.Header{"X-Test-Request-Name": []string{"value0", "value1"}},
203+
}, {
204+
msg: "name parameter is case-insensitive",
205+
args: []interface{}{"x-test-name"},
206+
valid: true,
207+
requestHeader: http.Header{"X-Test-Name": []string{"value0", "value1"}},
208+
expectedHeader: http.Header{},
209+
}, {
210+
msg: "drop matching value",
211+
args: []interface{}{"X-Test-Name", "bar"},
212+
valid: true,
213+
requestHeader: http.Header{"X-Test-Name": []string{"foo", "bar", "baz"}, "X-Test-Name2": []string{"qux"}},
214+
expectedHeader: http.Header{"X-Test-Request-Name": []string{"foo", "baz"}, "X-Test-Request-Name2": []string{"qux"}},
215+
}, {
216+
msg: "ignore non-matching",
217+
args: []interface{}{"X-Test-Name", "qux"},
218+
valid: true,
219+
requestHeader: http.Header{"X-Test-Name": []string{"foo", "bar", "baz"}, "X-Test-Name2": []string{"qux"}},
220+
expectedHeader: http.Header{"X-Test-Request-Name": []string{"foo", "bar", "baz"}, "X-Test-Request-Name2": []string{"qux"}},
221+
}, {
222+
msg: "drop matching value name parameter is case-insensitive",
223+
args: []interface{}{"x-test-name", "bar"},
224+
valid: true,
225+
requestHeader: http.Header{"X-Test-Name": []string{"foo", "bar", "baz"}, "X-Test-Name2": []string{"qux"}},
226+
expectedHeader: http.Header{"X-Test-Request-Name": []string{"foo", "baz"}, "X-Test-Request-Name2": []string{"qux"}},
202227
}},
203228
"setResponseHeader": {{
204229
msg: "set response header when none",
@@ -220,9 +245,10 @@ func TestHeader(t *testing.T) {
220245
valid: true,
221246
expectedHeader: http.Header{"X-Test-Name": []string{"a small barter"}},
222247
}, {
223-
msg: "set response header from path params when missing",
224-
args: []interface{}{"X-Test-Name", "a ${foo}ter"},
225-
valid: true,
248+
msg: "set response header from path params when missing",
249+
args: []interface{}{"X-Test-Name", "a ${foo}ter"},
250+
valid: true,
251+
expectedHeader: http.Header{},
226252
}, {
227253
msg: "name parameter is case-insensitive",
228254
args: []interface{}{"x-test-name", "Value"},
@@ -261,19 +287,46 @@ func TestHeader(t *testing.T) {
261287
expectedHeader: http.Header{"X-Test-Name": []string{"Value"}},
262288
}},
263289
"dropResponseHeader": {{
264-
msg: "drop response header when none",
265-
args: []interface{}{"X-Test-Name"},
266-
valid: true,
290+
msg: "drop response header when none",
291+
args: []interface{}{"X-Test-Name"},
292+
valid: true,
293+
expectedHeader: http.Header{},
267294
}, {
268295
msg: "drop response header when exists",
269296
args: []interface{}{"X-Test-Name"},
270297
valid: true,
271298
responseHeader: http.Header{"X-Test-Name": []string{"value0", "value1"}},
299+
expectedHeader: http.Header{},
300+
}, {
301+
msg: "drop response header when does not exist",
302+
args: []interface{}{"X-Test-Does-Not-Exist"},
303+
valid: true,
304+
responseHeader: http.Header{"X-Test-Name": []string{"value0", "value1"}},
305+
expectedHeader: http.Header{"X-Test-Name": []string{"value0", "value1"}},
272306
}, {
273307
msg: "name parameter is case-insensitive",
274308
args: []interface{}{"x-test-name"},
275309
valid: true,
276310
responseHeader: http.Header{"X-Test-Name": []string{"value0", "value1"}},
311+
expectedHeader: http.Header{},
312+
}, {
313+
msg: "drop matching value",
314+
args: []interface{}{"X-Test-Name", "bar"},
315+
valid: true,
316+
responseHeader: http.Header{"X-Test-Name": []string{"foo", "bar", "baz"}, "X-Test-Name2": []string{"qux"}},
317+
expectedHeader: http.Header{"X-Test-Name": []string{"foo", "baz"}, "X-Test-Name2": []string{"qux"}},
318+
}, {
319+
msg: "ignore non-matching",
320+
args: []interface{}{"X-Test-Name", "qux"},
321+
valid: true,
322+
responseHeader: http.Header{"X-Test-Name": []string{"foo", "bar", "baz"}, "X-Test-Name2": []string{"qux"}},
323+
expectedHeader: http.Header{"X-Test-Name": []string{"foo", "bar", "baz"}, "X-Test-Name2": []string{"qux"}},
324+
}, {
325+
msg: "drop matching value name parameter is case-insensitive",
326+
args: []interface{}{"x-test-name", "bar"},
327+
valid: true,
328+
responseHeader: http.Header{"X-Test-Name": []string{"foo", "bar", "baz"}, "X-Test-Name2": []string{"qux"}},
329+
expectedHeader: http.Header{"X-Test-Name": []string{"foo", "baz"}, "X-Test-Name2": []string{"qux"}},
277330
}},
278331
"setContextRequestHeader": {{
279332
msg: "set request header from context",
@@ -282,11 +335,12 @@ func TestHeader(t *testing.T) {
282335
valid: true,
283336
expectedHeader: http.Header{"X-Test-Request-Foo": []string{"bar"}},
284337
}, {
285-
msg: "set request host header from context",
286-
args: []interface{}{"Host", "foo"},
287-
context: map[string]interface{}{"foo": "www.example.org"},
288-
valid: true,
289-
host: "www.example.org",
338+
msg: "set request host header from context",
339+
args: []interface{}{"Host", "foo"},
340+
context: map[string]interface{}{"foo": "www.example.org"},
341+
valid: true,
342+
host: "www.example.org",
343+
expectedHeader: http.Header{},
290344
}, {
291345
msg: "name parameter is case-insensitive",
292346
args: []interface{}{"x-test-foo", "foo"},
@@ -302,11 +356,12 @@ func TestHeader(t *testing.T) {
302356
requestHeader: http.Header{"X-Test-Foo": []string{"bar"}},
303357
expectedHeader: http.Header{"X-Test-Request-Foo": []string{"bar", "baz"}},
304358
}, {
305-
msg: "append request host header from context",
306-
args: []interface{}{"Host", "foo"},
307-
context: map[string]interface{}{"foo": "www.example.org"},
308-
valid: true,
309-
host: "www.example.org",
359+
msg: "append request host header from context",
360+
args: []interface{}{"Host", "foo"},
361+
context: map[string]interface{}{"foo": "www.example.org"},
362+
valid: true,
363+
host: "www.example.org",
364+
expectedHeader: http.Header{},
310365
}, {
311366
msg: "name parameter is case-insensitive",
312367
args: []interface{}{"x-test-foo", "foo"},
@@ -356,9 +411,10 @@ func TestHeader(t *testing.T) {
356411
msg: "invalid target header name",
357412
args: []interface{}{"X-Test-Foo", 42},
358413
}, {
359-
msg: "no header to copy",
360-
args: []interface{}{"X-Test-Foo", "X-Test-Bar"},
361-
valid: true,
414+
msg: "no header to copy",
415+
args: []interface{}{"X-Test-Foo", "X-Test-Bar"},
416+
valid: true,
417+
expectedHeader: http.Header{},
362418
}, {
363419
msg: "copy header",
364420
args: []interface{}{"X-Test-Foo", "X-Test-Bar"},
@@ -414,9 +470,10 @@ func TestHeader(t *testing.T) {
414470
msg: "invalid target header name",
415471
args: []interface{}{"X-Test-Foo", 42},
416472
}, {
417-
msg: "no header to copy",
418-
args: []interface{}{"X-Test-Foo", "X-Test-Bar"},
419-
valid: true,
473+
msg: "no header to copy",
474+
args: []interface{}{"X-Test-Foo", "X-Test-Bar"},
475+
valid: true,
476+
expectedHeader: http.Header{},
420477
}, {
421478
msg: "copy header",
422479
args: []interface{}{"X-Test-Foo", "X-Test-Bar"},

Diff for: filters/builtin/headerfilter.go

+15-3
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ package builtin
22

33
import (
44
"fmt"
5+
"net/textproto"
6+
"slices"
57
"strings"
68

79
"github.com/zalando/skipper/eskip"
@@ -47,7 +49,7 @@ type headerFilter struct {
4749
func headerFilterConfig(typ headerType, config []interface{}) (string, string, *eskip.Template, error) {
4850
switch typ {
4951
case dropRequestHeader, dropResponseHeader:
50-
if len(config) != 1 {
52+
if len(config) < 1 || len(config) > 2 {
5153
return "", "", nil, filters.ErrInvalidFilterParameters
5254
}
5355
default:
@@ -281,7 +283,12 @@ func (f *headerFilter) Request(ctx filters.FilterContext) {
281283
ctx.SetOutgoingHost(f.value)
282284
}
283285
case dropRequestHeader:
284-
header.Del(f.key)
286+
if f.value == "" {
287+
header.Del(f.key)
288+
} else {
289+
k := textproto.CanonicalMIMEHeaderKey(f.key)
290+
header[k] = slices.DeleteFunc(header[k], func(v string) bool { return v == f.value })
291+
}
285292
case setContextRequestHeader:
286293
valueFromContext(ctx, f.key, f.value, true, header.Set)
287294
case appendContextRequestHeader:
@@ -313,7 +320,12 @@ func (f *headerFilter) Response(ctx filters.FilterContext) {
313320
case depResponseHeader:
314321
header.Add(f.key, f.value)
315322
case dropResponseHeader:
316-
header.Del(f.key)
323+
if f.value == "" {
324+
header.Del(f.key)
325+
} else {
326+
k := textproto.CanonicalMIMEHeaderKey(f.key)
327+
header[k] = slices.DeleteFunc(header[k], func(v string) bool { return v == f.value })
328+
}
317329
case setContextResponseHeader:
318330
valueFromContext(ctx, f.key, f.value, false, header.Set)
319331
case appendContextResponseHeader:

0 commit comments

Comments
 (0)