From 4a444de617dc441539c518149d701d599cafdf9b Mon Sep 17 00:00:00 2001 From: vlastahajek Date: Wed, 12 Oct 2022 16:46:08 +0200 Subject: [PATCH] feat: Allow setting of application name to the User-Agent header value --- CHANGELOG.md | 7 +++++ api/http/options.go | 13 +++++++++ api/http/options_test.go | 5 +++- api/http/service.go | 4 ++- api/writeAPIBlocking.go | 3 +- client.go | 3 ++ client_test.go | 58 +++++++++++++++++++++++++++++++++++++- internal/http/userAgent.go | 16 +++++++++-- options.go | 11 ++++++++ options_test.go | 5 +++- version.go | 2 +- 11 files changed, 118 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2e093f2c..4f31a807 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,11 @@ ## [unreleased] +### Features +- [#358](https://github.com/influxdata/influxdb-client-go/pull/358): + - Added possibility to set an application name, which will be part of the User-Agent HTTP header + - Set using `Options.SetApplicationName` + - Error message is written to log if an application name is not set. + - Added example how to fully override `User-Agent` header using `Doer` interface + ### Bug fixes - [#359](https://github.com/influxdata/influxdb-client-go/pull/359) `WriteAPIBlocking.Flush()` correctly returns nil error. diff --git a/api/http/options.go b/api/http/options.go index 66ef5393..0f275056 100644 --- a/api/http/options.go +++ b/api/http/options.go @@ -23,6 +23,8 @@ type Options struct { tlsConfig *tls.Config // HTTP request timeout in sec. Default 20 httpRequestTimeout uint + // Application name in the User-Agent HTTP header string + appName string } // HTTPClient returns the http.Client that is configured to be used @@ -119,6 +121,17 @@ func (o *Options) SetHTTPRequestTimeout(httpRequestTimeout uint) *Options { return o } +// ApplicationName returns application name used in the User-Agent HTTP header +func (o *Options) ApplicationName() string { + return o.appName +} + +// SetApplicationName sets an application name to the User-Agent HTTP header +func (o *Options) SetApplicationName(appName string) *Options { + o.appName = appName + return o +} + // DefaultOptions returns Options object with default values func DefaultOptions() *Options { return &Options{httpRequestTimeout: 20} diff --git a/api/http/options_test.go b/api/http/options_test.go index 09cb9a93..e1a35f39 100644 --- a/api/http/options_test.go +++ b/api/http/options_test.go @@ -20,6 +20,7 @@ func TestDefaultOptions(t *testing.T) { assert.Equal(t, uint(20), opts.HTTPRequestTimeout()) assert.NotNil(t, opts.HTTPClient()) assert.True(t, opts.OwnHTTPClient()) + assert.EqualValues(t, "", opts.ApplicationName()) } func TestOptionsSetting(t *testing.T) { @@ -28,9 +29,11 @@ func TestOptionsSetting(t *testing.T) { } opts := http.DefaultOptions(). SetTLSConfig(tlsConfig). - SetHTTPRequestTimeout(50) + SetHTTPRequestTimeout(50). + SetApplicationName("Monitor/1.1") assert.Equal(t, tlsConfig, opts.TLSConfig()) assert.Equal(t, uint(50), opts.HTTPRequestTimeout()) + assert.EqualValues(t, "Monitor/1.1", opts.ApplicationName()) if client := opts.HTTPClient(); assert.NotNil(t, client) { assert.Equal(t, 50*time.Second, client.Timeout) assert.Equal(t, tlsConfig, client.Transport.(*nethttp.Transport).TLSClientConfig) diff --git a/api/http/service.go b/api/http/service.go index aa8cd1b0..6316805d 100644 --- a/api/http/service.go +++ b/api/http/service.go @@ -56,6 +56,7 @@ type service struct { serverURL string authorization string client Doer + userAgent string } // NewService creates instance of http Service with given parameters @@ -73,6 +74,7 @@ func NewService(serverURL, authorization string, httpOptions *Options) Service { serverURL: serverURL, authorization: authorization, client: httpOptions.HTTPDoer(), + userAgent: http2.FormatUserAgent(httpOptions.ApplicationName()), } } @@ -128,7 +130,7 @@ func (s *service) DoHTTPRequestWithResponse(req *http.Request, requestCallback R req.Header.Set("Authorization", s.authorization) } if req.Header.Get("User-Agent") == "" { - req.Header.Set("User-Agent", http2.UserAgent) + req.Header.Set("User-Agent", s.userAgent) } if requestCallback != nil { requestCallback(req) diff --git a/api/writeAPIBlocking.go b/api/writeAPIBlocking.go index 4a6dcc9a..eb3a7687 100644 --- a/api/writeAPIBlocking.go +++ b/api/writeAPIBlocking.go @@ -86,9 +86,8 @@ func (w *writeAPIBlocking) write(ctx context.Context, line string) error { w.batch = append(w.batch, line) if len(w.batch) == int(w.writeOptions.BatchSize()) { return w.flush(ctx) - } else { - return nil } + return nil } err := w.service.WriteBatch(ctx, iwrite.NewBatch(line, w.writeOptions.MaxRetryTime())) if err != nil { diff --git a/client.go b/client.go index c41fd1af..8f9d4fc2 100644 --- a/client.go +++ b/client.go @@ -145,6 +145,9 @@ func NewClientWithOptions(serverURL string, authToken string, options *Options) } ilog.Infof("Using URL '%s'%s", serverURL, tokenStr) } + if options.ApplicationName() == "" { + ilog.Error("Application name is not set") + } return client } diff --git a/client_test.go b/client_test.go index 31cb611a..90ebf7ea 100644 --- a/client_test.go +++ b/client_test.go @@ -7,11 +7,15 @@ package influxdb2 import ( "context" "fmt" + "log" "net/http" "net/http/httptest" + "runtime" + "strings" "testing" "time" + ihttp "github.com/influxdata/influxdb-client-go/v2/api/http" "github.com/influxdata/influxdb-client-go/v2/domain" http2 "github.com/influxdata/influxdb-client-go/v2/internal/http" iwrite "github.com/influxdata/influxdb-client-go/v2/internal/write" @@ -76,10 +80,27 @@ func TestWriteAPIManagement(t *testing.T) { assert.Len(t, c.syncWriteAPIs, 0) } +func TestUserAgentBase(t *testing.T) { + ua := fmt.Sprintf("influxdb-client-go/%s (%s; %s)", Version, runtime.GOOS, runtime.GOARCH) + assert.Equal(t, ua, http2.UserAgentBase) + +} + +type doer struct { + userAgent string + doer ihttp.Doer +} + +func (d *doer) Do(req *http.Request) (*http.Response, error) { + req.Header.Set("User-Agent", d.userAgent) + return d.doer.Do(req) +} + func TestUserAgent(t *testing.T) { + ua := http2.UserAgentBase server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { <-time.After(100 * time.Millisecond) - if r.Header.Get("User-Agent") == http2.UserAgent { + if r.Header.Get("User-Agent") == ua { w.WriteHeader(http.StatusNoContent) } else { w.WriteHeader(http.StatusNotFound) @@ -87,13 +108,48 @@ func TestUserAgent(t *testing.T) { })) defer server.Close() + var sb strings.Builder + log.SetOutput(&sb) + log.SetFlags(0) c := NewClient(server.URL, "x") + assert.True(t, strings.Contains(sb.String(), "Application name is not set")) up, err := c.Ping(context.Background()) require.NoError(t, err) assert.True(t, up) err = c.WriteAPIBlocking("o", "b").WriteRecord(context.Background(), "a,a=a a=1i") assert.NoError(t, err) + + c.Close() + sb.Reset() + // Test setting application name + c = NewClientWithOptions(server.URL, "x", DefaultOptions().SetApplicationName("Monitor/1.1")) + ua = fmt.Sprintf("influxdb-client-go/%s (%s; %s) Monitor/1.1", Version, runtime.GOOS, runtime.GOARCH) + assert.False(t, strings.Contains(sb.String(), "Application name is not set")) + up, err = c.Ping(context.Background()) + require.NoError(t, err) + assert.True(t, up) + + err = c.WriteAPIBlocking("o", "b").WriteRecord(context.Background(), "a,a=a a=1i") + assert.NoError(t, err) + c.Close() + + ua = "Monitor/1.1" + opts := DefaultOptions() + opts.HTTPOptions().SetHTTPDoer(&doer{ + userAgent: ua, + doer: http.DefaultClient, + }) + + //Create client with custom user agent setter + c = NewClientWithOptions(server.URL, "x", opts) + up, err = c.Ping(context.Background()) + require.NoError(t, err) + assert.True(t, up) + + err = c.WriteAPIBlocking("o", "b").WriteRecord(context.Background(), "a,a=a a=1i") + assert.NoError(t, err) + c.Close() } func TestServerError429(t *testing.T) { diff --git a/internal/http/userAgent.go b/internal/http/userAgent.go index 0281bcba..cd27a9fe 100644 --- a/internal/http/userAgent.go +++ b/internal/http/userAgent.go @@ -5,5 +5,17 @@ // Package http hold internal HTTP related stuff package http -// UserAgent keeps once created User-Agent string -var UserAgent string +import ( + "fmt" +) + +// UserAgentBase keeps once created base User-Agent string +var UserAgentBase string + +// FormatUserAgent creates User-Agent header value for application name +func FormatUserAgent(appName string) string { + if appName != "" { + return fmt.Sprintf("%s %s", UserAgentBase, appName) + } + return UserAgentBase +} diff --git a/options.go b/options.go index 000bfc2f..a2c903cf 100644 --- a/options.go +++ b/options.go @@ -219,6 +219,17 @@ func (o *Options) AddDefaultTag(key, value string) *Options { return o } +// ApplicationName returns application name used in the User-Agent HTTP header +func (o *Options) ApplicationName() string { + return o.HTTPOptions().ApplicationName() +} + +// SetApplicationName sets an application name to the User-Agent HTTP header +func (o *Options) SetApplicationName(appName string) *Options { + o.HTTPOptions().SetApplicationName(appName) + return o +} + // DefaultOptions returns Options object with default values func DefaultOptions() *Options { return &Options{logLevel: 0, writeOptions: write.DefaultOptions(), httpOptions: http.DefaultOptions()} diff --git a/options_test.go b/options_test.go index 6afe1934..5c9dcdb5 100644 --- a/options_test.go +++ b/options_test.go @@ -33,6 +33,7 @@ func TestDefaultOptions(t *testing.T) { assert.EqualValues(t, (*tls.Config)(nil), opts.TLSConfig()) assert.EqualValues(t, 20, opts.HTTPRequestTimeout()) assert.EqualValues(t, 0, opts.LogLevel()) + assert.EqualValues(t, "", opts.ApplicationName()) } func TestSettingsOptions(t *testing.T) { @@ -53,7 +54,8 @@ func TestSettingsOptions(t *testing.T) { SetTLSConfig(tlsConfig). SetHTTPRequestTimeout(50). SetLogLevel(3). - AddDefaultTag("t", "a") + AddDefaultTag("t", "a"). + SetApplicationName("Monitor/1.1") assert.EqualValues(t, 5, opts.BatchSize()) assert.EqualValues(t, true, opts.UseGZip()) assert.EqualValues(t, 5_000, opts.FlushInterval()) @@ -66,6 +68,7 @@ func TestSettingsOptions(t *testing.T) { assert.EqualValues(t, 5, opts.ExponentialBase()) assert.EqualValues(t, tlsConfig, opts.TLSConfig()) assert.EqualValues(t, 50, opts.HTTPRequestTimeout()) + assert.EqualValues(t, "Monitor/1.1", opts.ApplicationName()) if client := opts.HTTPClient(); assert.NotNil(t, client) { assert.EqualValues(t, 50*time.Second, client.Timeout) assert.Equal(t, tlsConfig, client.Transport.(*http.Transport).TLSClientConfig) diff --git a/version.go b/version.go index 11f9d901..1e84253c 100644 --- a/version.go +++ b/version.go @@ -17,5 +17,5 @@ const ( ) func init() { - http.UserAgent = fmt.Sprintf("influxdb-client-go/%s (%s; %s)", Version, runtime.GOOS, runtime.GOARCH) + http.UserAgentBase = fmt.Sprintf("influxdb-client-go/%s (%s; %s)", Version, runtime.GOOS, runtime.GOARCH) }