Skip to content

Commit

Permalink
chore(gensupport): merge x-goog-api-client vals into a single header (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
BrennaEpp committed Jun 10, 2024
1 parent bec8b5c commit e760319
Show file tree
Hide file tree
Showing 2 changed files with 68 additions and 9 deletions.
24 changes: 21 additions & 3 deletions internal/gensupport/send.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,24 @@ func SendRequest(ctx context.Context, client *http.Client, req *http.Request) (*
if ctx != nil {
headers := callctx.HeadersFromContext(ctx)
for k, vals := range headers {
for _, v := range vals {
req.Header.Add(k, v)
if k == "x-goog-api-client" {
// Merge all values into a single "x-goog-api-client" header.
var mergedVal strings.Builder
baseXGoogHeader := req.Header.Get("X-Goog-Api-Client")
if baseXGoogHeader != "" {
mergedVal.WriteString(baseXGoogHeader)
mergedVal.WriteRune(' ')
}
for _, v := range vals {
mergedVal.WriteString(v)
mergedVal.WriteRune(' ')
}
// Remove the last space and replace the header on the request.
req.Header.Set(k, mergedVal.String()[:mergedVal.Len()-1])
} else {
for _, v := range vals {
req.Header.Add(k, v)
}
}
}
}
Expand Down Expand Up @@ -118,7 +134,9 @@ func sendAndRetry(ctx context.Context, client *http.Client, req *http.Request, r
var err error
attempts := 1
invocationID := uuid.New().String()
baseXGoogHeader := req.Header.Get("X-Goog-Api-Client")

xGoogHeaderVals := req.Header.Values("X-Goog-Api-Client")
baseXGoogHeader := strings.Join(xGoogHeaderVals, " ")

// Loop to retry the request, up to the context deadline.
var pause time.Duration
Expand Down
53 changes: 47 additions & 6 deletions internal/gensupport/send_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"errors"
"fmt"
"net/http"
"regexp"
"testing"

"github.com/google/go-cmp/cmp"
Expand Down Expand Up @@ -36,13 +37,24 @@ func TestSendRequestWithRetry(t *testing.T) {
}

type headerRoundTripper struct {
wantHeader http.Header
wantHeader http.Header
wantXgoogAPIRegex string // test x-goog-api-client separately
}

func (rt *headerRoundTripper) RoundTrip(r *http.Request) (*http.Response, error) {
// Test x-goog-api-client with a regex, since invocation ids are randomly generated
match, err := regexp.MatchString(rt.wantXgoogAPIRegex, r.Header.Get("X-Goog-Api-Client"))
if err != nil {
return nil, fmt.Errorf("compiling regexp: %v", err)
}
if !match {
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)
}

// Ignore x-goog headers sent by SendRequestWithRetry
r.Header.Del("X-Goog-Api-Client")
r.Header.Del("X-Goog-Gcs-Idempotency-Token")
r.Header.Del("X-Goog-Api-Client") // this was tested above already

if diff := cmp.Diff(r.Header, rt.wantHeader); diff != "" {
return nil, fmt.Errorf("headers don't match: %v", diff)
}
Expand All @@ -53,19 +65,48 @@ func (rt *headerRoundTripper) RoundTrip(r *http.Request) (*http.Response, error)
func TestSendRequestHeader(t *testing.T) {
ctx := context.Background()
ctx = callctx.SetHeaders(ctx, "foo", "100", "bar", "200")
client := http.Client{
Transport: &headerRoundTripper{
wantHeader: map[string][]string{"Foo": {"100"}, "Bar": {"200"}},
},
transport := &headerRoundTripper{
wantHeader: map[string][]string{"Foo": {"100"}, "Bar": {"200"}},
}
client := http.Client{Transport: transport}

req, _ := http.NewRequest("GET", "url", nil)
if _, err := SendRequest(ctx, &client, req); err != nil {
t.Errorf("SendRequest: %v", err)
}

// SendRequestWithRetry adds retry and idempotency headers
transport.wantXgoogAPIRegex = "^gccl-invocation-id/.{36} gccl-attempt-count/[0-9]+ $"
req2, _ := http.NewRequest("GET", "url", nil)
if _, err := SendRequestWithRetry(ctx, &client, req2, nil); err != nil {
t.Errorf("SendRequestWithRetry: %v", err)
}
}

// Ensure that x-goog-api-client headers set via the context are merged properly
// and passed through to the request as expected.
func TestSendRequestXgoogHeaderxxx(t *testing.T) {
ctx := context.Background()
ctx = callctx.SetHeaders(ctx, "x-goog-api-client", "val/1", "bar", "200", "x-goog-api-client", "val/2")
ctx = callctx.SetHeaders(ctx, "x-goog-api-client", "val/11 val/22")

transport := &headerRoundTripper{
wantHeader: map[string][]string{"Bar": {"200"}},
wantXgoogAPIRegex: "^val/1 val/2 val/11 val/22$",
}
client := http.Client{Transport: transport}

req, _ := http.NewRequest("GET", "url", nil)
if _, err := SendRequest(ctx, &client, req); err != nil {
t.Errorf("SendRequest: %v", err)
}

// SendRequestWithRetry adds retry and idempotency headers
transport.wantXgoogAPIRegex = fmt.Sprintf("^gccl-invocation-id/.{36} gccl-attempt-count/[0-9]+ %s$", transport.wantXgoogAPIRegex[1:len(transport.wantXgoogAPIRegex)-1])
req2, _ := http.NewRequest("GET", "url", nil)
if _, err := SendRequestWithRetry(ctx, &client, req2, nil); err != nil {
t.Errorf("SendRequestWithRetry: %v", err)
}
}

type brokenRoundTripper struct{}
Expand Down

0 comments on commit e760319

Please sign in to comment.