Skip to content

Commit 17cabc6

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 c3b1657 commit 17cabc6

File tree

3 files changed

+115
-51
lines changed

3 files changed

+115
-51
lines changed

docs/reference/filters.md

Lines changed: 8 additions & 1 deletion
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 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 specific 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

filters/builtin/header_test.go

Lines changed: 92 additions & 47 deletions
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,40 @@ 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: "name parameter is case-insensitive",
199+
args: []interface{}{"x-test-name"},
200+
valid: true,
201+
requestHeader: http.Header{"X-Test-Name": []string{"value0", "value1"}},
202+
expectedHeader: http.Header{},
203+
}, {
204+
msg: "drop matching value",
205+
args: []interface{}{"X-Test-Name", "bar"},
206+
valid: true,
207+
requestHeader: http.Header{"X-Test-Name": []string{"foo", "bar", "baz"}},
208+
expectedHeader: http.Header{"X-Test-Request-Name": []string{"foo", "baz"}},
209+
}, {
210+
msg: "ignore non-matching",
211+
args: []interface{}{"X-Test-Name", "qux"},
212+
valid: true,
213+
requestHeader: http.Header{"X-Test-Name": []string{"foo", "bar", "baz"}},
214+
expectedHeader: http.Header{"X-Test-Request-Name": []string{"foo", "bar", "baz"}},
215+
}, {
216+
msg: "drop matching value name parameter is case-insensitive",
217+
args: []interface{}{"x-test-name", "bar"},
218+
valid: true,
219+
requestHeader: http.Header{"X-Test-Name": []string{"foo", "bar", "baz"}},
220+
expectedHeader: http.Header{"X-Test-Request-Name": []string{"foo", "baz"}},
202221
}},
203222
"setResponseHeader": {{
204223
msg: "set response header when none",
@@ -220,9 +239,10 @@ func TestHeader(t *testing.T) {
220239
valid: true,
221240
expectedHeader: http.Header{"X-Test-Name": []string{"a small barter"}},
222241
}, {
223-
msg: "set response header from path params when missing",
224-
args: []interface{}{"X-Test-Name", "a ${foo}ter"},
225-
valid: true,
242+
msg: "set response header from path params when missing",
243+
args: []interface{}{"X-Test-Name", "a ${foo}ter"},
244+
valid: true,
245+
expectedHeader: http.Header{},
226246
}, {
227247
msg: "name parameter is case-insensitive",
228248
args: []interface{}{"x-test-name", "Value"},
@@ -261,19 +281,40 @@ func TestHeader(t *testing.T) {
261281
expectedHeader: http.Header{"X-Test-Name": []string{"Value"}},
262282
}},
263283
"dropResponseHeader": {{
264-
msg: "drop response header when none",
265-
args: []interface{}{"X-Test-Name"},
266-
valid: true,
284+
msg: "drop response header when none",
285+
args: []interface{}{"X-Test-Name"},
286+
valid: true,
287+
expectedHeader: http.Header{},
267288
}, {
268289
msg: "drop response header when exists",
269290
args: []interface{}{"X-Test-Name"},
270291
valid: true,
271292
responseHeader: http.Header{"X-Test-Name": []string{"value0", "value1"}},
293+
expectedHeader: http.Header{},
272294
}, {
273295
msg: "name parameter is case-insensitive",
274296
args: []interface{}{"x-test-name"},
275297
valid: true,
276298
responseHeader: http.Header{"X-Test-Name": []string{"value0", "value1"}},
299+
expectedHeader: http.Header{},
300+
}, {
301+
msg: "drop matching value",
302+
args: []interface{}{"X-Test-Name", "bar"},
303+
valid: true,
304+
responseHeader: http.Header{"X-Test-Name": []string{"foo", "bar", "baz"}},
305+
expectedHeader: http.Header{"X-Test-Name": []string{"foo", "baz"}},
306+
}, {
307+
msg: "ignore non-matching",
308+
args: []interface{}{"X-Test-Name", "qux"},
309+
valid: true,
310+
responseHeader: http.Header{"X-Test-Name": []string{"foo", "bar", "baz"}},
311+
expectedHeader: http.Header{"X-Test-Name": []string{"foo", "bar", "baz"}},
312+
}, {
313+
msg: "drop matching value name parameter is case-insensitive",
314+
args: []interface{}{"x-test-name", "bar"},
315+
valid: true,
316+
responseHeader: http.Header{"X-Test-Name": []string{"foo", "bar", "baz"}},
317+
expectedHeader: http.Header{"X-Test-Name": []string{"foo", "baz"}},
277318
}},
278319
"setContextRequestHeader": {{
279320
msg: "set request header from context",
@@ -282,11 +323,12 @@ func TestHeader(t *testing.T) {
282323
valid: true,
283324
expectedHeader: http.Header{"X-Test-Request-Foo": []string{"bar"}},
284325
}, {
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",
326+
msg: "set request host header from context",
327+
args: []interface{}{"Host", "foo"},
328+
context: map[string]interface{}{"foo": "www.example.org"},
329+
valid: true,
330+
host: "www.example.org",
331+
expectedHeader: http.Header{},
290332
}, {
291333
msg: "name parameter is case-insensitive",
292334
args: []interface{}{"x-test-foo", "foo"},
@@ -302,11 +344,12 @@ func TestHeader(t *testing.T) {
302344
requestHeader: http.Header{"X-Test-Foo": []string{"bar"}},
303345
expectedHeader: http.Header{"X-Test-Request-Foo": []string{"bar", "baz"}},
304346
}, {
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",
347+
msg: "append request host header from context",
348+
args: []interface{}{"Host", "foo"},
349+
context: map[string]interface{}{"foo": "www.example.org"},
350+
valid: true,
351+
host: "www.example.org",
352+
expectedHeader: http.Header{},
310353
}, {
311354
msg: "name parameter is case-insensitive",
312355
args: []interface{}{"x-test-foo", "foo"},
@@ -356,9 +399,10 @@ func TestHeader(t *testing.T) {
356399
msg: "invalid target header name",
357400
args: []interface{}{"X-Test-Foo", 42},
358401
}, {
359-
msg: "no header to copy",
360-
args: []interface{}{"X-Test-Foo", "X-Test-Bar"},
361-
valid: true,
402+
msg: "no header to copy",
403+
args: []interface{}{"X-Test-Foo", "X-Test-Bar"},
404+
valid: true,
405+
expectedHeader: http.Header{},
362406
}, {
363407
msg: "copy header",
364408
args: []interface{}{"X-Test-Foo", "X-Test-Bar"},
@@ -414,9 +458,10 @@ func TestHeader(t *testing.T) {
414458
msg: "invalid target header name",
415459
args: []interface{}{"X-Test-Foo", 42},
416460
}, {
417-
msg: "no header to copy",
418-
args: []interface{}{"X-Test-Foo", "X-Test-Bar"},
419-
valid: true,
461+
msg: "no header to copy",
462+
args: []interface{}{"X-Test-Foo", "X-Test-Bar"},
463+
valid: true,
464+
expectedHeader: http.Header{},
420465
}, {
421466
msg: "copy header",
422467
args: []interface{}{"X-Test-Foo", "X-Test-Bar"},

filters/builtin/headerfilter.go

Lines changed: 15 additions & 3 deletions
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)