1 // Copyright 2018, Google Inc. 2 // All rights reserved. 3 // 4 // Redistribution and use in source and binary forms, with or without 5 // modification, are permitted provided that the following conditions are 6 // met: 7 // 8 // * Redistributions of source code must retain the above copyright 9 // notice, this list of conditions and the following disclaimer. 10 // * Redistributions in binary form must reproduce the above 11 // copyright notice, this list of conditions and the following disclaimer 12 // in the documentation and/or other materials provided with the 13 // distribution. 14 // * Neither the name of Google Inc. nor the names of its 15 // contributors may be used to endorse or promote products derived from 16 // this software without specific prior written permission. 17 // 18 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 30 package gax 31 32 import ( 33 "bytes" 34 "context" 35 "fmt" 36 "net/http" 37 "runtime" 38 "strings" 39 "unicode" 40 41 "github.com/googleapis/gax-go/v2/callctx" 42 "google.golang.org/grpc/metadata" 43 ) 44 45 var ( 46 // GoVersion is a header-safe representation of the current runtime 47 // environment's Go version. This is for GAX consumers that need to 48 // report the Go runtime version in API calls. 49 GoVersion string 50 // version is a package internal global variable for testing purposes. 51 version = runtime.Version 52 ) 53 54 // versionUnknown is only used when the runtime version cannot be determined. 55 const versionUnknown = "UNKNOWN" 56 57 func init() { 58 GoVersion = goVersion() 59 } 60 61 // goVersion returns a Go runtime version derived from the runtime environment 62 // that is modified to be suitable for reporting in a header, meaning it has no 63 // whitespace. If it is unable to determine the Go runtime version, it returns 64 // versionUnknown. 65 func goVersion() string { 66 const develPrefix = "devel +" 67 68 s := version() 69 if strings.HasPrefix(s, develPrefix) { 70 s = s[len(develPrefix):] 71 if p := strings.IndexFunc(s, unicode.IsSpace); p >= 0 { 72 s = s[:p] 73 } 74 return s 75 } else if p := strings.IndexFunc(s, unicode.IsSpace); p >= 0 { 76 s = s[:p] 77 } 78 79 notSemverRune := func(r rune) bool { 80 return !strings.ContainsRune("0123456789.", r) 81 } 82 83 if strings.HasPrefix(s, "go1") { 84 s = s[2:] 85 var prerelease string 86 if p := strings.IndexFunc(s, notSemverRune); p >= 0 { 87 s, prerelease = s[:p], s[p:] 88 } 89 if strings.HasSuffix(s, ".") { 90 s += "0" 91 } else if strings.Count(s, ".") < 2 { 92 s += ".0" 93 } 94 if prerelease != "" { 95 // Some release candidates already have a dash in them. 96 if !strings.HasPrefix(prerelease, "-") { 97 prerelease = "-" + prerelease 98 } 99 s += prerelease 100 } 101 return s 102 } 103 return "UNKNOWN" 104 } 105 106 // XGoogHeader is for use by the Google Cloud Libraries only. See package 107 // [github.com/googleapis/gax-go/v2/callctx] for help setting/retrieving 108 // request/response headers. 109 // 110 // XGoogHeader formats key-value pairs. 111 // The resulting string is suitable for x-goog-api-client header. 112 func XGoogHeader(keyval ...string) string { 113 if len(keyval) == 0 { 114 return "" 115 } 116 if len(keyval)%2 != 0 { 117 panic("gax.Header: odd argument count") 118 } 119 var buf bytes.Buffer 120 for i := 0; i < len(keyval); i += 2 { 121 buf.WriteByte(' ') 122 buf.WriteString(keyval[i]) 123 buf.WriteByte('/') 124 buf.WriteString(keyval[i+1]) 125 } 126 return buf.String()[1:] 127 } 128 129 // InsertMetadataIntoOutgoingContext is for use by the Google Cloud Libraries 130 // only. See package [github.com/googleapis/gax-go/v2/callctx] for help 131 // setting/retrieving request/response headers. 132 // 133 // InsertMetadataIntoOutgoingContext returns a new context that merges the 134 // provided keyvals metadata pairs with any existing metadata/headers in the 135 // provided context. keyvals should have a corresponding value for every key 136 // provided. If there is an odd number of keyvals this method will panic. 137 // Existing values for keys will not be overwritten, instead provided values 138 // will be appended to the list of existing values. 139 func InsertMetadataIntoOutgoingContext(ctx context.Context, keyvals ...string) context.Context { 140 return metadata.NewOutgoingContext(ctx, insertMetadata(ctx, keyvals...)) 141 } 142 143 // BuildHeaders is for use by the Google Cloud Libraries only. See package 144 // [github.com/googleapis/gax-go/v2/callctx] for help setting/retrieving 145 // request/response headers. 146 // 147 // BuildHeaders returns a new http.Header that merges the provided 148 // keyvals header pairs with any existing metadata/headers in the provided 149 // context. keyvals should have a corresponding value for every key provided. 150 // If there is an odd number of keyvals this method will panic. 151 // Existing values for keys will not be overwritten, instead provided values 152 // will be appended to the list of existing values. 153 func BuildHeaders(ctx context.Context, keyvals ...string) http.Header { 154 return http.Header(insertMetadata(ctx, keyvals...)) 155 } 156 157 func insertMetadata(ctx context.Context, keyvals ...string) metadata.MD { 158 if len(keyvals)%2 != 0 { 159 panic(fmt.Sprintf("gax: an even number of key value pairs must be provided, got %d", len(keyvals))) 160 } 161 out, ok := metadata.FromOutgoingContext(ctx) 162 if !ok { 163 out = metadata.MD(make(map[string][]string)) 164 } 165 headers := callctx.HeadersFromContext(ctx) 166 for k, v := range headers { 167 out[k] = append(out[k], v...) 168 } 169 for i := 0; i < len(keyvals); i = i + 2 { 170 out[keyvals[i]] = append(out[keyvals[i]], keyvals[i+1]) 171 } 172 return out 173 } 174