From 5b136ffe471c978c03bf93cad909d397a66959b4 Mon Sep 17 00:00:00 2001 From: storyicon Date: Fri, 21 Oct 2022 19:19:06 +0800 Subject: [PATCH 01/14] rpc: support inject http headers using outgoing context Signed-off-by: storyicon --- rpc/http.go | 2 ++ rpc/http_test.go | 27 +++++++++++++++++++++++++++ rpc/metadata.go | 42 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 71 insertions(+) create mode 100644 rpc/metadata.go diff --git a/rpc/http.go b/rpc/http.go index 8595959afb66..cfccf65681d6 100644 --- a/rpc/http.go +++ b/rpc/http.go @@ -214,6 +214,8 @@ func (hc *httpConn) doRequest(ctx context.Context, msg interface{}) (io.ReadClos hc.mu.Lock() req.Header = hc.headers.Clone() hc.mu.Unlock() + mergeHeadersFromOutgoingContext(ctx, req.Header) + if hc.auth != nil { if err := hc.auth(req.Header); err != nil { return nil, err diff --git a/rpc/http_test.go b/rpc/http_test.go index c84d7705f205..2339a9c4345d 100644 --- a/rpc/http_test.go +++ b/rpc/http_test.go @@ -17,6 +17,7 @@ package rpc import ( + "context" "net/http" "net/http/httptest" "strings" @@ -198,3 +199,29 @@ func TestHTTPPeerInfo(t *testing.T) { t.Errorf("wrong HTTP.Origin %q", info.HTTP.UserAgent) } } + +func TestNewOutgoingContext(t *testing.T) { + const ( + testHeaderKey = "test-header-key" + testHeaderValue = "test-header-value" + ) + server := httptest.NewServer(http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) { + if got := request.Header.Get(testHeaderKey); got != testHeaderValue { + t.Errorf("wrong request headers for %s, expected: %s, actual: %s", testHeaderKey, testHeaderValue, got) + } + writer.WriteHeader(http.StatusOK) + _, _ = writer.Write([]byte(`{}`)) + })) + defer server.Close() + client, err := Dial(server.URL) + if err != nil { + panic(err) + } + defer client.Close() + header := http.Header{} + header.Set(testHeaderKey, testHeaderValue) + ctx := NewOutgoingContext(context.TODO(), header) + if err := client.CallContext(ctx, &struct{}{}, "test"); err != ErrNoResult { + t.Errorf("failed to call context: %s", err) + } +} diff --git a/rpc/metadata.go b/rpc/metadata.go new file mode 100644 index 000000000000..f1781f7f80e3 --- /dev/null +++ b/rpc/metadata.go @@ -0,0 +1,42 @@ +package rpc + +import ( + "context" + "net/http" +) + +type rawMd struct { + headers http.Header +} + +type mdOutgoingKey struct{} + +// NewOutgoingContext is used to attach http headers into the context +func NewOutgoingContext(ctx context.Context, header http.Header) context.Context { + return context.WithValue(ctx, mdOutgoingKey{}, rawMd{headers: header}) +} + +// HeadersFromOutgoingContext is used to extract http headers from the context +func HeadersFromOutgoingContext(ctx context.Context) (http.Header, bool) { + value := ctx.Value(mdOutgoingKey{}) + if value == nil { + return nil, false + } + headers := value.(rawMd).headers + if headers == nil { + return nil, false + } + return headers, true +} + +// mergeHeadersFromOutgoingContext is used to extract http headers from the context and inject it into the provided headers +func mergeHeadersFromOutgoingContext(ctx context.Context, headers http.Header) { + if kvs, ok := HeadersFromOutgoingContext(ctx); ok { + for key, values := range kvs { + headers.Del(key) + for _, val := range values { + headers.Add(key, val) + } + } + } +} From 2b7cd3fbf1c0624e81b63fd96ca8dcc02840e52e Mon Sep 17 00:00:00 2001 From: storyicon Date: Sun, 23 Oct 2022 10:17:44 +0800 Subject: [PATCH 02/14] Update rpc/http_test.go Co-authored-by: Martin Holst Swende --- rpc/http_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rpc/http_test.go b/rpc/http_test.go index 2339a9c4345d..85a791fe1731 100644 --- a/rpc/http_test.go +++ b/rpc/http_test.go @@ -215,7 +215,7 @@ func TestNewOutgoingContext(t *testing.T) { defer server.Close() client, err := Dial(server.URL) if err != nil { - panic(err) + t.Fatalf(err) } defer client.Close() header := http.Header{} From c3f402ae096d7cb5e36fac5041c6da2e9c0d1f6a Mon Sep 17 00:00:00 2001 From: storyicon Date: Sun, 23 Oct 2022 10:17:55 +0800 Subject: [PATCH 03/14] Update rpc/metadata.go Co-authored-by: Martin Holst Swende --- rpc/metadata.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rpc/metadata.go b/rpc/metadata.go index f1781f7f80e3..0897136f6c03 100644 --- a/rpc/metadata.go +++ b/rpc/metadata.go @@ -30,7 +30,7 @@ func HeadersFromOutgoingContext(ctx context.Context) (http.Header, bool) { } // mergeHeadersFromOutgoingContext is used to extract http headers from the context and inject it into the provided headers -func mergeHeadersFromOutgoingContext(ctx context.Context, headers http.Header) { +func addHeadersFromContext(ctx context.Context, headers http.Header) { if kvs, ok := HeadersFromOutgoingContext(ctx); ok { for key, values := range kvs { headers.Del(key) From 8812d6972cbb070fc3836f9ea7494d193421137b Mon Sep 17 00:00:00 2001 From: storyicon Date: Sun, 23 Oct 2022 10:37:38 +0800 Subject: [PATCH 04/14] rpc: adjust metadata func Signed-off-by: storyicon --- rpc/http.go | 2 +- rpc/http_test.go | 2 +- rpc/metadata.go | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/rpc/http.go b/rpc/http.go index cfccf65681d6..fe96ff9532ed 100644 --- a/rpc/http.go +++ b/rpc/http.go @@ -214,7 +214,7 @@ func (hc *httpConn) doRequest(ctx context.Context, msg interface{}) (io.ReadClos hc.mu.Lock() req.Header = hc.headers.Clone() hc.mu.Unlock() - mergeHeadersFromOutgoingContext(ctx, req.Header) + addHeadersFromContext(ctx, req.Header) if hc.auth != nil { if err := hc.auth(req.Header); err != nil { diff --git a/rpc/http_test.go b/rpc/http_test.go index 85a791fe1731..bd8292e94ae9 100644 --- a/rpc/http_test.go +++ b/rpc/http_test.go @@ -215,7 +215,7 @@ func TestNewOutgoingContext(t *testing.T) { defer server.Close() client, err := Dial(server.URL) if err != nil { - t.Fatalf(err) + t.Fatalf("failed to dial: %s", err) } defer client.Close() header := http.Header{} diff --git a/rpc/metadata.go b/rpc/metadata.go index 0897136f6c03..711b62c90ba6 100644 --- a/rpc/metadata.go +++ b/rpc/metadata.go @@ -29,7 +29,7 @@ func HeadersFromOutgoingContext(ctx context.Context) (http.Header, bool) { return headers, true } -// mergeHeadersFromOutgoingContext is used to extract http headers from the context and inject it into the provided headers +// addHeadersFromContext is used to extract http headers from the context and inject it into the provided headers func addHeadersFromContext(ctx context.Context, headers http.Header) { if kvs, ok := HeadersFromOutgoingContext(ctx); ok { for key, values := range kvs { From 075e917cd39575aef4a621cc83d3411751434871 Mon Sep 17 00:00:00 2001 From: storyicon Date: Sun, 23 Oct 2022 10:59:23 +0800 Subject: [PATCH 05/14] rpc: simplify some design about passing http headers Signed-off-by: storyicon --- rpc/http_test.go | 2 +- rpc/metadata.go | 28 ++++++++++------------------ 2 files changed, 11 insertions(+), 19 deletions(-) diff --git a/rpc/http_test.go b/rpc/http_test.go index bd8292e94ae9..ac6ffd11a957 100644 --- a/rpc/http_test.go +++ b/rpc/http_test.go @@ -220,7 +220,7 @@ func TestNewOutgoingContext(t *testing.T) { defer client.Close() header := http.Header{} header.Set(testHeaderKey, testHeaderValue) - ctx := NewOutgoingContext(context.TODO(), header) + ctx := NewContextWithHeaders(context.TODO(), header) if err := client.CallContext(ctx, &struct{}{}, "test"); err != ErrNoResult { t.Errorf("failed to call context: %s", err) } diff --git a/rpc/metadata.go b/rpc/metadata.go index 711b62c90ba6..5faa23578f7b 100644 --- a/rpc/metadata.go +++ b/rpc/metadata.go @@ -5,33 +5,25 @@ import ( "net/http" ) -type rawMd struct { - headers http.Header -} - -type mdOutgoingKey struct{} +type mdHeaderKey struct{} -// NewOutgoingContext is used to attach http headers into the context -func NewOutgoingContext(ctx context.Context, header http.Header) context.Context { - return context.WithValue(ctx, mdOutgoingKey{}, rawMd{headers: header}) +// NewContextWithHeaders is used to attach http headers into the context +func NewContextWithHeaders(ctx context.Context, header http.Header) context.Context { + return context.WithValue(ctx, mdHeaderKey{}, header) } -// HeadersFromOutgoingContext is used to extract http headers from the context -func HeadersFromOutgoingContext(ctx context.Context) (http.Header, bool) { - value := ctx.Value(mdOutgoingKey{}) +// HeadersFromContext is used to extract http headers from the context +func HeadersFromContext(ctx context.Context) http.Header { + value := ctx.Value(mdHeaderKey{}) if value == nil { - return nil, false - } - headers := value.(rawMd).headers - if headers == nil { - return nil, false + return nil } - return headers, true + return value.(http.Header) } // addHeadersFromContext is used to extract http headers from the context and inject it into the provided headers func addHeadersFromContext(ctx context.Context, headers http.Header) { - if kvs, ok := HeadersFromOutgoingContext(ctx); ok { + if kvs := HeadersFromContext(ctx); kvs != nil { for key, values := range kvs { headers.Del(key) for _, val := range values { From cae3913805edcd595b1a5b7d03182d66e6136543 Mon Sep 17 00:00:00 2001 From: storyicon Date: Tue, 25 Oct 2022 01:31:21 +0800 Subject: [PATCH 06/14] rpc: simplify the code of addHeadersFromContext Signed-off-by: storyicon --- rpc/metadata.go | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/rpc/metadata.go b/rpc/metadata.go index 5faa23578f7b..df2fa1cf9227 100644 --- a/rpc/metadata.go +++ b/rpc/metadata.go @@ -23,12 +23,10 @@ func HeadersFromContext(ctx context.Context) http.Header { // addHeadersFromContext is used to extract http headers from the context and inject it into the provided headers func addHeadersFromContext(ctx context.Context, headers http.Header) { - if kvs := HeadersFromContext(ctx); kvs != nil { - for key, values := range kvs { - headers.Del(key) - for _, val := range values { - headers.Add(key, val) - } + for key, values := range HeadersFromContext(ctx) { + headers.Del(key) + for _, val := range values { + headers.Add(key, val) } } } From 60c0a29d4c1ecfc27f3bf683c2a6a33b89ef15d7 Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Tue, 25 Oct 2022 11:56:12 +0200 Subject: [PATCH 07/14] rpc: simplify context, support chaining --- rpc/context_headers.go | 54 ++++++++++++++++++++++++++++++++++++++++++ rpc/http_test.go | 26 +++++++++++++------- rpc/metadata.go | 32 ------------------------- 3 files changed, 71 insertions(+), 41 deletions(-) create mode 100644 rpc/context_headers.go delete mode 100644 rpc/metadata.go diff --git a/rpc/context_headers.go b/rpc/context_headers.go new file mode 100644 index 000000000000..cbad141e78b2 --- /dev/null +++ b/rpc/context_headers.go @@ -0,0 +1,54 @@ +// Copyright 2022 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package rpc + +import ( + "context" + "net/http" +) + +type mdHeaderKey struct{} + +// NewContextWithHeaders is used to add the http headers from source into the context. +func NewContextWithHeaders(ctx context.Context, source http.Header) context.Context { + dest, ok := ctx.Value(mdHeaderKey{}).(http.Header) + if !ok { + return context.WithValue(ctx, mdHeaderKey{}, source) + } + for key, values := range source { + dest.Del(key) + for _, val := range values { + dest.Add(key, val) + } + } + return context.WithValue(ctx, mdHeaderKey{}, dest) +} + +// addHeadersFromContext takes any previously added http headers and adds them +// to the dest http.Header. +func addHeadersFromContext(ctx context.Context, dest http.Header) { + source, ok := ctx.Value(mdHeaderKey{}).(http.Header) + if !ok { + return + } + for key, values := range source { + dest.Del(key) + for _, val := range values { + dest.Add(key, val) + } + } +} diff --git a/rpc/http_test.go b/rpc/http_test.go index ac6ffd11a957..bfe6196d3bf3 100644 --- a/rpc/http_test.go +++ b/rpc/http_test.go @@ -18,6 +18,7 @@ package rpc import ( "context" + "fmt" "net/http" "net/http/httptest" "strings" @@ -201,13 +202,14 @@ func TestHTTPPeerInfo(t *testing.T) { } func TestNewOutgoingContext(t *testing.T) { - const ( - testHeaderKey = "test-header-key" - testHeaderValue = "test-header-value" - ) + numHeaders := 3 + server := httptest.NewServer(http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) { - if got := request.Header.Get(testHeaderKey); got != testHeaderValue { - t.Errorf("wrong request headers for %s, expected: %s, actual: %s", testHeaderKey, testHeaderValue, got) + for i := 0; i < numHeaders; i++ { + key, want := fmt.Sprintf("key-%d", i), fmt.Sprintf("val-%d", i) + if have := request.Header.Get(key); have != want { + t.Errorf("wrong request headers for %s, want: %s, have: %s", key, want, have) + } } writer.WriteHeader(http.StatusOK) _, _ = writer.Write([]byte(`{}`)) @@ -218,9 +220,15 @@ func TestNewOutgoingContext(t *testing.T) { t.Fatalf("failed to dial: %s", err) } defer client.Close() - header := http.Header{} - header.Set(testHeaderKey, testHeaderValue) - ctx := NewContextWithHeaders(context.TODO(), header) + newHdr := func(k, v string) http.Header { + header := http.Header{} + header.Set(k, v) + return header + } + ctx := NewContextWithHeaders(context.TODO(), newHdr("key-0", "val-0")) + ctx = NewContextWithHeaders(ctx, newHdr("key-1", "val-1")) + ctx = NewContextWithHeaders(ctx, newHdr("key-2", "val-2")) + if err := client.CallContext(ctx, &struct{}{}, "test"); err != ErrNoResult { t.Errorf("failed to call context: %s", err) } diff --git a/rpc/metadata.go b/rpc/metadata.go deleted file mode 100644 index df2fa1cf9227..000000000000 --- a/rpc/metadata.go +++ /dev/null @@ -1,32 +0,0 @@ -package rpc - -import ( - "context" - "net/http" -) - -type mdHeaderKey struct{} - -// NewContextWithHeaders is used to attach http headers into the context -func NewContextWithHeaders(ctx context.Context, header http.Header) context.Context { - return context.WithValue(ctx, mdHeaderKey{}, header) -} - -// HeadersFromContext is used to extract http headers from the context -func HeadersFromContext(ctx context.Context) http.Header { - value := ctx.Value(mdHeaderKey{}) - if value == nil { - return nil - } - return value.(http.Header) -} - -// addHeadersFromContext is used to extract http headers from the context and inject it into the provided headers -func addHeadersFromContext(ctx context.Context, headers http.Header) { - for key, values := range HeadersFromContext(ctx) { - headers.Del(key) - for _, val := range values { - headers.Add(key, val) - } - } -} From f3f8f3b29df6dd44b16f6dbb515fc0cbbc7583f3 Mon Sep 17 00:00:00 2001 From: storyicon Date: Tue, 25 Oct 2022 22:28:18 +0800 Subject: [PATCH 08/14] rpc: make the logic of context header more concise and clear Signed-off-by: storyicon --- rpc/context_headers.go | 34 +++++++++++++++++----------------- rpc/http.go | 2 +- rpc/http_test.go | 2 +- 3 files changed, 19 insertions(+), 19 deletions(-) diff --git a/rpc/context_headers.go b/rpc/context_headers.go index cbad141e78b2..6621e8c4b6a5 100644 --- a/rpc/context_headers.go +++ b/rpc/context_headers.go @@ -24,31 +24,31 @@ import ( type mdHeaderKey struct{} // NewContextWithHeaders is used to add the http headers from source into the context. -func NewContextWithHeaders(ctx context.Context, source http.Header) context.Context { - dest, ok := ctx.Value(mdHeaderKey{}).(http.Header) +func NewContextWithHeaders(ctx context.Context, src http.Header) context.Context { + dst, ok := ctx.Value(mdHeaderKey{}).(http.Header) if !ok { - return context.WithValue(ctx, mdHeaderKey{}, source) + dst = http.Header{} + ctx = context.WithValue(ctx, mdHeaderKey{}, dst) } - for key, values := range source { - dest.Del(key) - for _, val := range values { - dest.Add(key, val) - } - } - return context.WithValue(ctx, mdHeaderKey{}, dest) + mergeHeaders(dst, src) + return ctx } -// addHeadersFromContext takes any previously added http headers and adds them -// to the dest http.Header. -func addHeadersFromContext(ctx context.Context, dest http.Header) { +// headersFromContext is used to extract http.Header from context +func headersFromContext(ctx context.Context) http.Header { source, ok := ctx.Value(mdHeaderKey{}).(http.Header) if !ok { - return + return nil } - for key, values := range source { - dest.Del(key) + return source +} + +// mergeHeaders is used to merge src into dst +func mergeHeaders(dst http.Header, src http.Header) { + for key, values := range src { + dst.Del(key) for _, val := range values { - dest.Add(key, val) + dst.Add(key, val) } } } diff --git a/rpc/http.go b/rpc/http.go index fe96ff9532ed..f65343f20dc7 100644 --- a/rpc/http.go +++ b/rpc/http.go @@ -214,7 +214,7 @@ func (hc *httpConn) doRequest(ctx context.Context, msg interface{}) (io.ReadClos hc.mu.Lock() req.Header = hc.headers.Clone() hc.mu.Unlock() - addHeadersFromContext(ctx, req.Header) + mergeHeaders(req.Header, headersFromContext(ctx)) if hc.auth != nil { if err := hc.auth(req.Header); err != nil { diff --git a/rpc/http_test.go b/rpc/http_test.go index bfe6196d3bf3..8f93af202da2 100644 --- a/rpc/http_test.go +++ b/rpc/http_test.go @@ -201,7 +201,7 @@ func TestHTTPPeerInfo(t *testing.T) { } } -func TestNewOutgoingContext(t *testing.T) { +func TestNewContextWithHeaders(t *testing.T) { numHeaders := 3 server := httptest.NewServer(http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) { From e97caa61d67676d52c4974e2e5aaa442eb25dbb7 Mon Sep 17 00:00:00 2001 From: storyicon Date: Sat, 29 Oct 2022 17:42:07 +0800 Subject: [PATCH 09/14] Update rpc/context_headers.go Co-authored-by: Martin Holst Swende --- rpc/context_headers.go | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/rpc/context_headers.go b/rpc/context_headers.go index 6621e8c4b6a5..a28abdc590f9 100644 --- a/rpc/context_headers.go +++ b/rpc/context_headers.go @@ -36,10 +36,7 @@ func NewContextWithHeaders(ctx context.Context, src http.Header) context.Context // headersFromContext is used to extract http.Header from context func headersFromContext(ctx context.Context) http.Header { - source, ok := ctx.Value(mdHeaderKey{}).(http.Header) - if !ok { - return nil - } + source, _ := ctx.Value(mdHeaderKey{}).(http.Header) return source } From 561eec0bb0fbe1bd8512d9ffd86c0e32d8e4277f Mon Sep 17 00:00:00 2001 From: storyicon Date: Sat, 29 Oct 2022 17:42:16 +0800 Subject: [PATCH 10/14] Update rpc/http_test.go Co-authored-by: Martin Holst Swende --- rpc/http_test.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/rpc/http_test.go b/rpc/http_test.go index 8f93af202da2..62194b3d85bb 100644 --- a/rpc/http_test.go +++ b/rpc/http_test.go @@ -232,4 +232,8 @@ func TestNewContextWithHeaders(t *testing.T) { if err := client.CallContext(ctx, &struct{}{}, "test"); err != ErrNoResult { t.Errorf("failed to call context: %s", err) } + numHeaders = 0 + if err := client.CallContext(context.TODO(), &struct{}{}, "test"); err != ErrNoResult { + t.Errorf("failed to call context: %s", err) + } } From 043c89f884cc134bd22d6d7215afb032cb933ee9 Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Wed, 16 Nov 2022 11:10:31 +0100 Subject: [PATCH 11/14] rpc: fix so context layering is correct --- rpc/context_headers.go | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/rpc/context_headers.go b/rpc/context_headers.go index a28abdc590f9..4329627f989e 100644 --- a/rpc/context_headers.go +++ b/rpc/context_headers.go @@ -25,22 +25,23 @@ type mdHeaderKey struct{} // NewContextWithHeaders is used to add the http headers from source into the context. func NewContextWithHeaders(ctx context.Context, src http.Header) context.Context { - dst, ok := ctx.Value(mdHeaderKey{}).(http.Header) - if !ok { - dst = http.Header{} - ctx = context.WithValue(ctx, mdHeaderKey{}, dst) + var dst = make(http.Header) + if prev, ok := ctx.Value(mdHeaderKey{}).(http.Header); ok { + // Merge with previous layered values + mergeHeaders(dst, prev) } + // And merge with the provided ones mergeHeaders(dst, src) - return ctx + return context.WithValue(ctx, mdHeaderKey{}, dst) } -// headersFromContext is used to extract http.Header from context +// headersFromContext is used to extract http.Header from context. func headersFromContext(ctx context.Context) http.Header { source, _ := ctx.Value(mdHeaderKey{}).(http.Header) return source } -// mergeHeaders is used to merge src into dst +// mergeHeaders is used to merge src into dst. func mergeHeaders(dst http.Header, src http.Header) { for key, values := range src { dst.Del(key) From aae69b9c90c85617f8fc5dfb5107f72265ea7c9a Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Wed, 16 Nov 2022 12:44:52 +0100 Subject: [PATCH 12/14] rpc: improve context headers --- rpc/context_headers.go | 28 +++++++++++++++------------- rpc/http.go | 2 +- rpc/http_test.go | 25 ++++++++++++++----------- 3 files changed, 30 insertions(+), 25 deletions(-) diff --git a/rpc/context_headers.go b/rpc/context_headers.go index 4329627f989e..86c8905c687f 100644 --- a/rpc/context_headers.go +++ b/rpc/context_headers.go @@ -25,14 +25,18 @@ type mdHeaderKey struct{} // NewContextWithHeaders is used to add the http headers from source into the context. func NewContextWithHeaders(ctx context.Context, src http.Header) context.Context { - var dst = make(http.Header) - if prev, ok := ctx.Value(mdHeaderKey{}).(http.Header); ok { - // Merge with previous layered values - mergeHeaders(dst, prev) + if len(src) == 0 { + return ctx } - // And merge with the provided ones - mergeHeaders(dst, src) - return context.WithValue(ctx, mdHeaderKey{}, dst) + + var h http.Header + prev, ok := ctx.Value(mdHeaderKey{}).(http.Header) + if ok { + h = setHeaders(prev.Clone(), src) + } else { + h = src.Clone() + } + return context.WithValue(ctx, mdHeaderKey{}, h) } // headersFromContext is used to extract http.Header from context. @@ -41,12 +45,10 @@ func headersFromContext(ctx context.Context) http.Header { return source } -// mergeHeaders is used to merge src into dst. -func mergeHeaders(dst http.Header, src http.Header) { +// setHeaders sets all headers from src in dst. +func setHeaders(dst http.Header, src http.Header) http.Header { for key, values := range src { - dst.Del(key) - for _, val := range values { - dst.Add(key, val) - } + dst[http.CanonicalHeaderKey(key)] = values } + return dst } diff --git a/rpc/http.go b/rpc/http.go index f65343f20dc7..1962ff5847ec 100644 --- a/rpc/http.go +++ b/rpc/http.go @@ -214,7 +214,7 @@ func (hc *httpConn) doRequest(ctx context.Context, msg interface{}) (io.ReadClos hc.mu.Lock() req.Header = hc.headers.Clone() hc.mu.Unlock() - mergeHeaders(req.Header, headersFromContext(ctx)) + setHeaders(req.Header, headersFromContext(ctx)) if hc.auth != nil { if err := hc.auth(req.Header); err != nil { diff --git a/rpc/http_test.go b/rpc/http_test.go index 62194b3d85bb..466db4893341 100644 --- a/rpc/http_test.go +++ b/rpc/http_test.go @@ -202,10 +202,9 @@ func TestHTTPPeerInfo(t *testing.T) { } func TestNewContextWithHeaders(t *testing.T) { - numHeaders := 3 - + expectedHeaders := 0 server := httptest.NewServer(http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) { - for i := 0; i < numHeaders; i++ { + for i := 0; i < expectedHeaders; i++ { key, want := fmt.Sprintf("key-%d", i), fmt.Sprintf("val-%d", i) if have := request.Header.Get(key); have != want { t.Errorf("wrong request headers for %s, want: %s, have: %s", key, want, have) @@ -215,25 +214,29 @@ func TestNewContextWithHeaders(t *testing.T) { _, _ = writer.Write([]byte(`{}`)) })) defer server.Close() + client, err := Dial(server.URL) if err != nil { t.Fatalf("failed to dial: %s", err) } defer client.Close() + newHdr := func(k, v string) http.Header { header := http.Header{} header.Set(k, v) return header } - ctx := NewContextWithHeaders(context.TODO(), newHdr("key-0", "val-0")) - ctx = NewContextWithHeaders(ctx, newHdr("key-1", "val-1")) - ctx = NewContextWithHeaders(ctx, newHdr("key-2", "val-2")) + ctx1 := NewContextWithHeaders(context.Background(), newHdr("key-0", "val-0")) + ctx2 := NewContextWithHeaders(ctx1, newHdr("key-1", "val-1")) + ctx3 := NewContextWithHeaders(ctx2, newHdr("key-2", "val-2")) - if err := client.CallContext(ctx, &struct{}{}, "test"); err != ErrNoResult { - t.Errorf("failed to call context: %s", err) + expectedHeaders = 3 + if err := client.CallContext(ctx3, &struct{}{}, "test"); err != ErrNoResult { + t.Error("call failed", err) } - numHeaders = 0 - if err := client.CallContext(context.TODO(), &struct{}{}, "test"); err != ErrNoResult { - t.Errorf("failed to call context: %s", err) + + expectedHeaders = 2 + if err := client.CallContext(ctx2, &struct{}{}, "test"); err != ErrNoResult { + t.Error("call failed:", err) } } From 1cd0667300ccc9119bcc81c78b9a00a8e72198a5 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Wed, 16 Nov 2022 12:52:29 +0100 Subject: [PATCH 13/14] rpc: improve comment --- rpc/context_headers.go | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/rpc/context_headers.go b/rpc/context_headers.go index 86c8905c687f..29a58150e33b 100644 --- a/rpc/context_headers.go +++ b/rpc/context_headers.go @@ -23,20 +23,22 @@ import ( type mdHeaderKey struct{} -// NewContextWithHeaders is used to add the http headers from source into the context. -func NewContextWithHeaders(ctx context.Context, src http.Header) context.Context { - if len(src) == 0 { +// NewContextWithHeaders wraps the given context, adding HTTP headers. These headers will +// be applied by Client when making a request using the returned context. +func NewContextWithHeaders(ctx context.Context, h http.Header) context.Context { + if len(h) == 0 { + // This check ensures the header map set in context will never be nil. return ctx } - var h http.Header + var ctxh http.Header prev, ok := ctx.Value(mdHeaderKey{}).(http.Header) if ok { - h = setHeaders(prev.Clone(), src) + ctxh = setHeaders(prev.Clone(), h) } else { - h = src.Clone() + ctxh = h.Clone() } - return context.WithValue(ctx, mdHeaderKey{}, h) + return context.WithValue(ctx, mdHeaderKey{}, ctxh) } // headersFromContext is used to extract http.Header from context. From ce5d72d1331c81a6fbc7395c2f93342a5fa09e36 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Wed, 16 Nov 2022 15:20:27 +0100 Subject: [PATCH 14/14] rpc: remove pointer in test --- rpc/http_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rpc/http_test.go b/rpc/http_test.go index 466db4893341..528e1bcfc5e7 100644 --- a/rpc/http_test.go +++ b/rpc/http_test.go @@ -231,12 +231,12 @@ func TestNewContextWithHeaders(t *testing.T) { ctx3 := NewContextWithHeaders(ctx2, newHdr("key-2", "val-2")) expectedHeaders = 3 - if err := client.CallContext(ctx3, &struct{}{}, "test"); err != ErrNoResult { + if err := client.CallContext(ctx3, nil, "test"); err != ErrNoResult { t.Error("call failed", err) } expectedHeaders = 2 - if err := client.CallContext(ctx2, &struct{}{}, "test"); err != ErrNoResult { + if err := client.CallContext(ctx2, nil, "test"); err != ErrNoResult { t.Error("call failed:", err) } }