Repository: snowflakedb/gosnowflake
Pull request #399 (2021-03-11 18:29:34)

SNOW-95626 User Agent Headers
### Description
Change user agent header to conform to RFC 7231.

### Checklist
- [x] Code compiles correctly
- [x] Run ``make fmt`` to fix inconsistent formats
- [x] Run ``make lint`` to get lint errors and fix all of them
- [x] Created tests which fail without the change (if possible)
- [x] All tests passing
- [x] Extended the README / documentation, if necessary



Patch:
diff --git a/auth.go b/auth.go
index f6d080a..3c48aab 100644
--- a/auth.go
+++ b/auth.go
@@ -117,13 +117,13 @@ var platform = fmt.Sprintf("%v-%v", runtime.Compiler, runtime.GOARCH)
 var operatingSystem = runtime.GOOS
 
 // userAgent shows up in User-Agent HTTP header
-var userAgent = fmt.Sprintf("%v/%v/%v/%v/%v-%v",
+var userAgent = fmt.Sprintf("%v/%v (%v-%v) %v/%v",
 	clientType,
 	SnowflakeGoDriverVersion,
-	runtime.Compiler,
-	runtime.Version(),
 	operatingSystem,
-	runtime.GOARCH)
+	runtime.GOARCH,
+	runtime.Compiler,
+	runtime.Version())
 
 type authRequestClientEnvironment struct {
 	Application string `json:"APPLICATION"`
@@ -254,9 +254,9 @@ func postAuth(
 // with Snowflake.
 func getHeaders() map[string]string {
 	headers := make(map[string]string)
-	headers["Content-Type"] = headerContentTypeApplicationJSON
-	headers["accept"] = headerAcceptTypeApplicationSnowflake
-	headers["User-Agent"] = userAgent
+	headers[httpHeaderContentType] = headerContentTypeApplicationJSON
+	headers[httpHeaderAccept] = headerAcceptTypeApplicationSnowflake
+	headers[httpHeaderUserAgent] = userAgent
 	return headers
 }
 
diff --git a/authexternalbrowser.go b/authexternalbrowser.go
index 6533efe..e8ea0ac 100644
--- a/authexternalbrowser.go
+++ b/authexternalbrowser.go
@@ -90,9 +90,9 @@ func getIdpURLProofKey(
 	callbackPort int) (string, string, error) {
 
 	headers := make(map[string]string)
-	headers["Content-Type"] = headerContentTypeApplicationJSON
-	headers["accept"] = headerContentTypeApplicationJSON
-	headers["User-Agent"] = userAgent
+	headers[httpHeaderContentType] = headerContentTypeApplicationJSON
+	headers[httpHeaderAccept] = headerContentTypeApplicationJSON
+	headers[httpHeaderUserAgent] = userAgent
 
 	clientEnvironment := authRequestClientEnvironment{
 		Application: application,
diff --git a/authokta.go b/authokta.go
index 5084ee4..c7b15ca 100644
--- a/authokta.go
+++ b/authokta.go
@@ -59,9 +59,9 @@ func authenticateBySAML(
 ) (samlResponse []byte, err error) {
 	logger.WithContext(ctx).Info("step 1: query GS to obtain IDP token and SSO url")
 	headers := make(map[string]string)
-	headers["Content-Type"] = headerContentTypeApplicationJSON
-	headers["accept"] = headerContentTypeApplicationJSON
-	headers["User-Agent"] = userAgent
+	headers[httpHeaderContentType] = headerContentTypeApplicationJSON
+	headers[httpHeaderAccept] = headerContentTypeApplicationJSON
+	headers[httpHeaderUserAgent] = userAgent
 
 	clientEnvironment := authRequestClientEnvironment{
 		Application: application,
@@ -138,7 +138,7 @@ func authenticateBySAML(
 	params.Add("onetimetoken", respa.CookieToken)
 
 	headers = make(map[string]string)
-	headers["accept"] = "*/*"
+	headers[httpHeaderAccept] = "*/*"
 	bd, err := sr.FuncGetSSO(ctx, sr, params, headers, respd.Data.SSOURL, sr.LoginTimeout)
 	if err != nil {
 		return nil, err
diff --git a/connection.go b/connection.go
index ad9401b..2403cba 100644
--- a/connection.go
+++ b/connection.go
@@ -17,6 +17,15 @@ import (
 	"github.com/google/uuid"
 )
 
+const (
+	httpHeaderContentType   = "Content-Type"
+	httpHeaderAccept        = "accept"
+	httpHeaderUserAgent     = "User-Agent"
+	httpHeaderServiceName   = "X-Snowflake-Service"
+	httpHeaderContentLength = "Content-Length"
+	httpHeaderHost          = "Host"
+)
+
 const (
 	statementTypeIDMulti = int64(0x1000)
 
@@ -38,7 +47,7 @@ const (
 type resultType string
 
 const (
-	snowflakeResultType paramKey   = "snowflakeResultType"
+	snowflakeResultType contextKey = "snowflakeResultType"
 	execResultType      resultType = "exec"
 	queryResultType     resultType = "query"
 )
@@ -112,15 +121,12 @@ func (sc *snowflakeConn) exec(
 	}
 	logger.WithContext(ctx).Infof("bindings: %v", req.Bindings)
 
-	headers := make(map[string]string)
-	headers["Content-Type"] = headerContentTypeApplicationJSON
-	headers["accept"] = headerAcceptTypeApplicationSnowflake
+	headers := getHeaders()
 	if isFileTransfer(query) {
-		headers["accept"] = headerContentTypeApplicationJSON
+		headers[httpHeaderUserAgent] = headerContentTypeApplicationJSON
 	}
-	headers["User-Agent"] = userAgent
 	if serviceName, ok := sc.cfg.Params[serviceName]; ok {
-		headers["X-Snowflake-Service"] = *serviceName
+		headers[httpHeaderServiceName] = *serviceName
 	}
 
 	jsonBody, err := json.Marshal(req)
@@ -575,12 +581,9 @@ func getChildResults(IDs string, types string) []childResult {
 }
 
 func (sc *snowflakeConn) getQueryResult(ctx context.Context, resultPath string) (*execResponse, error) {
-	headers := make(map[string]string)
-	headers["Content-Type"] = headerContentTypeApplicationJSON
-	headers["accept"] = headerAcceptTypeApplicationSnowflake
-	headers["User-Agent"] = userAgent
+	headers := getHeaders()
 	if serviceName, ok := sc.cfg.Params[serviceName]; ok {
-		headers["X-Snowflake-Service"] = *serviceName
+		headers[httpHeaderServiceName] = *serviceName
 	}
 	param := make(url.Values)
 	param.Add(requestIDKey, getOrGenerateRequestIDFromContext(ctx).String())
diff --git a/heartbeat.go b/heartbeat.go
index c852816..3e7b203 100644
--- a/heartbeat.go
+++ b/heartbeat.go
@@ -58,10 +58,7 @@ func (hc *heartbeat) heartbeatMain() error {
 	params := &url.Values{}
 	params.Add(requestIDKey, uuid.New().String())
 	params.Add(requestGUIDKey, uuid.New().String())
-	headers := make(map[string]string)
-	headers["Content-Type"] = headerContentTypeApplicationJSON
-	headers["accept"] = headerAcceptTypeApplicationSnowflake
-	headers["User-Agent"] = userAgent
+	headers := getHeaders()
 	token, _, _ := hc.restful.TokenAccessor.GetTokens()
 	headers[headerAuthorizationKey] = fmt.Sprintf(headerSnowflakeToken, token)
 
diff --git a/ocsp.go b/ocsp.go
index cbc5f5e..9ec87ab 100644
--- a/ocsp.go
+++ b/ocsp.go
@@ -495,10 +495,10 @@ func getRevocationStatus(ctx context.Context, subject, issuer *x509.Certificate)
 		}
 	}
 	headers := make(map[string]string)
-	headers["Content-Type"] = "application/ocsp-request"
-	headers["Accept"] = "application/ocsp-response"
-	headers["Content-Length"] = strconv.Itoa(len(ocspReq))
-	headers["Host"] = hostname
+	headers[httpHeaderContentType] = "application/ocsp-request"
+	headers[httpHeaderAccept] = "application/ocsp-response"
+	headers[httpHeaderContentLength] = strconv.Itoa(len(ocspReq))
+	headers[httpHeaderHost] = hostname
 	timeoutStr := os.Getenv(ocspTestResponderTimeoutEnv)
 	timeout := defaultOCSPResponderTimeout
 	if timeoutStr != "" {
diff --git a/restful.go b/restful.go
index 0be7545..36a8a1b 100644
--- a/restful.go
+++ b/restful.go
@@ -294,10 +294,7 @@ func closeSession(ctx context.Context, sr *snowflakeRestful, timeout time.Durati
 	params.Add(requestGUIDKey, uuid.New().String())
 	fullURL := sr.getFullURL(sessionRequestPath, params)
 
-	headers := make(map[string]string)
-	headers["Content-Type"] = headerContentTypeApplicationJSON
-	headers["accept"] = headerAcceptTypeApplicationSnowflake
-	headers["User-Agent"] = userAgent
+	headers := getHeaders()
 	token, _, _ := sr.TokenAccessor.GetTokens()
 	headers[headerAuthorizationKey] = fmt.Sprintf(headerSnowflakeToken, token)
 
@@ -348,10 +345,7 @@ func renewRestfulSession(ctx context.Context, sr *snowflakeRestful, timeout time
 	fullURL := sr.getFullURL(tokenRequestPath, params)
 
 	token, masterToken, _ := sr.TokenAccessor.GetTokens()
-	headers := make(map[string]string)
-	headers["Content-Type"] = headerContentTypeApplicationJSON
-	headers["accept"] = headerAcceptTypeApplicationSnowflake
-	headers["User-Agent"] = userAgent
+	headers := getHeaders()
 	headers[headerAuthorizationKey] = fmt.Sprintf(headerSnowflakeToken, masterToken)
 
 	body := make(map[string]string)
@@ -412,10 +406,7 @@ func cancelQuery(ctx context.Context, sr *snowflakeRestful, requestID uuid.UUID,
 
 	fullURL := sr.getFullURL(abortRequestPath, params)
 
-	headers := make(map[string]string)
-	headers["Content-Type"] = headerContentTypeApplicationJSON
-	headers["accept"] = headerAcceptTypeApplicationSnowflake
-	headers["User-Agent"] = userAgent
+	headers := getHeaders()
 	token, _, _ := sr.TokenAccessor.GetTokens()
 	headers[headerAuthorizationKey] = fmt.Sprintf(headerSnowflakeToken, token)
 


Test patch:
diff --git a/connection_test.go b/connection_test.go
index 750cb8c..708ba62 100644
--- a/connection_test.go
+++ b/connection_test.go
@@ -22,7 +22,7 @@ const serviceNameAppend = "a"
 // This way it could test both the send and receive logic
 func postQueryMock(_ context.Context, _ *snowflakeRestful, _ *url.Values, headers map[string]string, _ []byte, _ time.Duration, _ uuid.UUID, _ *Config) (*execResponse, error) {
 	var serviceName string
-	if serviceHeader, ok := headers["X-Snowflake-Service"]; ok {
+	if serviceHeader, ok := headers[httpHeaderServiceName]; ok {
 		serviceName = serviceHeader + serviceNameAppend
 	} else {
 		serviceName = serviceNameStub