Skip to content

Commit afb0cac

Browse files
authored
Merge pull request moby#52204 from thaJeztah/stable_customheaders
client: WithHTTPHeaders: improve doc, add test coverage, and error on duplicate headers
2 parents 978d9c4 + 28aeae2 commit afb0cac

File tree

3 files changed

+65
-6
lines changed

3 files changed

+65
-6
lines changed

client/client_options.go

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"strings"
1313
"time"
1414

15+
cerrdefs "github.com/containerd/errdefs"
1516
"github.com/docker/go-connections/sockets"
1617
"github.com/docker/go-connections/tlsconfig"
1718
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
@@ -194,11 +195,23 @@ func WithUserAgent(ua string) Opt {
194195
}
195196

196197
// WithHTTPHeaders appends custom HTTP headers to the client's default headers.
197-
// It does not allow for built-in headers (such as "User-Agent", if set) to
198-
// be overridden. Also see [WithUserAgent].
198+
// It does not allow overriding built-in headers (such as "User-Agent").
199+
// Also see [WithUserAgent].
200+
//
201+
// It replaces any existing custom headers. Keys are case-insensitive and
202+
// canonicalized using [http.CanonicalHeaderKey]. If multiple entries map
203+
// to the same canonical key, a [cerrdefs.ErrInvalidArgument] is returned.
199204
func WithHTTPHeaders(headers map[string]string) Opt {
200205
return func(c *clientConfig) error {
201-
c.customHTTPHeaders = headers
206+
c.customHTTPHeaders = make(map[string]string)
207+
for k, v := range headers {
208+
k = http.CanonicalHeaderKey(k)
209+
_, ok := c.customHTTPHeaders[k]
210+
if ok {
211+
return cerrdefs.ErrInvalidArgument.WithMessage(fmt.Sprintf("duplicate custom HTTP header (%s)", k))
212+
}
213+
c.customHTTPHeaders[k] = v
214+
}
202215
return nil
203216
}
204217
}

client/client_options_test.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"testing"
1010
"time"
1111

12+
cerrdefs "github.com/containerd/errdefs"
1213
"github.com/google/go-cmp/cmp/cmpopts"
1314
"gotest.tools/v3/assert"
1415
is "gotest.tools/v3/assert/cmp"
@@ -371,6 +372,38 @@ func TestWithUserAgent(t *testing.T) {
371372
})
372373
}
373374

375+
func TestWithHTTTPHeaders(t *testing.T) {
376+
t.Run("duplicates", func(t *testing.T) {
377+
c, err := New(
378+
WithHTTPHeaders(map[string]string{
379+
"custom-header": "custom-value-A",
380+
"Custom-Header": "custom-value-B",
381+
"CUSTOM-HEADER": "custom-value-C",
382+
}),
383+
)
384+
assert.Check(t, is.ErrorIs(err, cerrdefs.ErrInvalidArgument))
385+
assert.Check(t, is.Error(err, "duplicate custom HTTP header (Custom-Header)"))
386+
assert.Check(t, is.Nil(c))
387+
})
388+
t.Run("multiple", func(t *testing.T) {
389+
c, err := New(
390+
WithHTTPHeaders(map[string]string{"custom-header-1": "hello"}),
391+
WithHTTPHeaders(map[string]string{"custom-header-2": "custom-value-A"}),
392+
WithHTTPHeaders(map[string]string{"Custom-Header-2": "custom-value-B"}),
393+
WithHTTPHeaders(map[string]string{"CUSTOM-HEADER-2": "custom-value-C"}),
394+
WithBaseMockClient(func(req *http.Request) (*http.Response, error) {
395+
assert.Check(t, is.Equal(req.Header.Get("Custom-Header-1"), ""), "custom-header-1 should've been replaced")
396+
assert.Check(t, is.Equal(req.Header.Get("Custom-Header-2"), "custom-value-C"))
397+
return &http.Response{StatusCode: http.StatusOK}, nil
398+
}),
399+
)
400+
assert.NilError(t, err)
401+
_, err = c.Ping(t.Context(), PingOptions{})
402+
assert.NilError(t, err)
403+
assert.NilError(t, c.Close())
404+
})
405+
}
406+
374407
func TestWithHTTPClient(t *testing.T) {
375408
cookieJar, err := cookiejar.New(nil)
376409
assert.NilError(t, err)

vendor/github.com/moby/moby/client/client_options.go

Lines changed: 16 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)