Skip to content

Commit 2008108

Browse files
authored
fix: merge duplicate x-goog-request-params header (#3547)
1 parent 2e86962 commit 2008108

2 files changed

Lines changed: 58 additions & 43 deletions

File tree

internal/gensupport/send.go

Lines changed: 40 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -40,36 +40,50 @@ func (e wrappedCallErr) Is(target error) bool {
4040
return errors.Is(e.ctxErr, target) || errors.Is(e.wrappedErr, target)
4141
}
4242

43+
// addContextHeaders adds headers set in context metadata.
44+
// x-goog-api-client and x-goog-request-params are merged properly.
45+
func addContextHeaders(ctx context.Context, req *http.Request) {
46+
if ctx == nil {
47+
return
48+
}
49+
headers := callctx.HeadersFromContext(ctx)
50+
for k, vals := range headers {
51+
if strings.EqualFold(k, "x-goog-api-client") {
52+
mergeHeader(req.Header, k, vals, ' ')
53+
} else if strings.EqualFold(k, "x-goog-request-params") {
54+
mergeHeader(req.Header, k, vals, '&')
55+
} else {
56+
for _, v := range vals {
57+
req.Header.Add(k, v)
58+
}
59+
}
60+
}
61+
}
62+
63+
// mergeHeader merges multiple values into a single header.
64+
func mergeHeader(header http.Header, key string, vals []string, separator rune) {
65+
var mergedVal strings.Builder
66+
baseHeader := header.Get(key)
67+
if baseHeader != "" {
68+
mergedVal.WriteString(baseHeader)
69+
mergedVal.WriteRune(separator)
70+
}
71+
for _, v := range vals {
72+
mergedVal.WriteString(v)
73+
mergedVal.WriteRune(separator)
74+
}
75+
if mergedVal.Len() > 0 {
76+
// Remove the last separator and replace the header on the request.
77+
header.Set(key, mergedVal.String()[:mergedVal.Len()-1])
78+
}
79+
}
80+
4381
// SendRequest sends a single HTTP request using the given client.
4482
// If ctx is non-nil, it calls all hooks, then sends the request with
4583
// req.WithContext, then calls any functions returned by the hooks in
4684
// reverse order.
4785
func SendRequest(ctx context.Context, client *http.Client, req *http.Request) (*http.Response, error) {
48-
// Add headers set in context metadata.
49-
if ctx != nil {
50-
headers := callctx.HeadersFromContext(ctx)
51-
for k, vals := range headers {
52-
if k == "x-goog-api-client" {
53-
// Merge all values into a single "x-goog-api-client" header.
54-
var mergedVal strings.Builder
55-
baseXGoogHeader := req.Header.Get("X-Goog-Api-Client")
56-
if baseXGoogHeader != "" {
57-
mergedVal.WriteString(baseXGoogHeader)
58-
mergedVal.WriteRune(' ')
59-
}
60-
for _, v := range vals {
61-
mergedVal.WriteString(v)
62-
mergedVal.WriteRune(' ')
63-
}
64-
// Remove the last space and replace the header on the request.
65-
req.Header.Set(k, mergedVal.String()[:mergedVal.Len()-1])
66-
} else {
67-
for _, v := range vals {
68-
req.Header.Add(k, v)
69-
}
70-
}
71-
}
72-
}
86+
addContextHeaders(ctx, req)
7387

7488
// Disallow Accept-Encoding because it interferes with the automatic gzip handling
7589
// done by the default http.Transport. See https://github.com/google/google-api-go-client/issues/219.
@@ -105,15 +119,7 @@ func send(ctx context.Context, client *http.Client, req *http.Request) (*http.Re
105119
// req.WithContext, then calls any functions returned by the hooks in
106120
// reverse order.
107121
func SendRequestWithRetry(ctx context.Context, client *http.Client, req *http.Request, retry *RetryConfig) (*http.Response, error) {
108-
// Add headers set in context metadata.
109-
if ctx != nil {
110-
headers := callctx.HeadersFromContext(ctx)
111-
for k, vals := range headers {
112-
for _, v := range vals {
113-
req.Header.Add(k, v)
114-
}
115-
}
116-
}
122+
addContextHeaders(ctx, req)
117123

118124
// Disallow Accept-Encoding because it interferes with the automatic gzip handling
119125
// done by the default http.Transport. See https://github.com/google/google-api-go-client/issues/219.

internal/gensupport/send_test.go

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,9 @@ func TestSendRequestWithRetry(t *testing.T) {
3737
}
3838

3939
type headerRoundTripper struct {
40-
wantHeader http.Header
41-
wantXgoogAPIRegex string // test x-goog-api-client separately
40+
wantHeader http.Header
41+
wantXgoogAPIRegex string // test x-goog-api-client separately
42+
wantXgoogReqParams string
4243
}
4344

4445
func (rt *headerRoundTripper) RoundTrip(r *http.Request) (*http.Response, error) {
@@ -51,9 +52,16 @@ func (rt *headerRoundTripper) RoundTrip(r *http.Request) (*http.Response, error)
5152
return nil, fmt.Errorf("X-Goog-Api-Client header has wrong format\ngot %v\nwant regex matching %v", r.Header.Get("X-Goog-Api-Client"), rt.wantXgoogAPIRegex)
5253
}
5354

55+
if rt.wantXgoogReqParams != "" {
56+
if got := r.Header.Get("X-Goog-Request-Params"); got != rt.wantXgoogReqParams {
57+
return nil, fmt.Errorf("X-Goog-Request-Params header has wrong format\ngot %v\nwant %v", got, rt.wantXgoogReqParams)
58+
}
59+
}
60+
5461
// Ignore x-goog headers sent by SendRequestWithRetry
5562
r.Header.Del("X-Goog-Gcs-Idempotency-Token")
56-
r.Header.Del("X-Goog-Api-Client") // this was tested above already
63+
r.Header.Del("X-Goog-Api-Client") // this was tested above already
64+
r.Header.Del("X-Goog-Request-Params") // this was tested above already
5765

5866
if diff := cmp.Diff(r.Header, rt.wantHeader); diff != "" {
5967
return nil, fmt.Errorf("headers don't match: %v", diff)
@@ -83,16 +91,17 @@ func TestSendRequestHeader(t *testing.T) {
8391
}
8492
}
8593

86-
// Ensure that x-goog-api-client headers set via the context are merged properly
94+
// Ensure that x-goog-api-client and x-goog-request-params headers set via the context are merged properly
8795
// and passed through to the request as expected.
88-
func TestSendRequestXgoogHeaderxxx(t *testing.T) {
96+
func TestSendRequestXgoogHeaders(t *testing.T) {
8997
ctx := context.Background()
90-
ctx = callctx.SetHeaders(ctx, "x-goog-api-client", "val/1", "bar", "200", "x-goog-api-client", "val/2")
91-
ctx = callctx.SetHeaders(ctx, "x-goog-api-client", "val/11 val/22")
98+
ctx = callctx.SetHeaders(ctx, "x-goog-api-client", "val/1", "bar", "200", "x-goog-api-client", "val/2", "x-goog-request-params", "param1=a")
99+
ctx = callctx.SetHeaders(ctx, "x-goog-api-client", "val/11 val/22", "x-goog-request-params", "param2=b")
92100

93101
transport := &headerRoundTripper{
94-
wantHeader: map[string][]string{"Bar": {"200"}},
95-
wantXgoogAPIRegex: "^val/1 val/2 val/11 val/22$",
102+
wantHeader: map[string][]string{"Bar": {"200"}},
103+
wantXgoogAPIRegex: "^val/1 val/2 val/11 val/22$",
104+
wantXgoogReqParams: "param1=a&param2=b",
96105
}
97106
client := http.Client{Transport: transport}
98107

0 commit comments

Comments
 (0)