Skip to content

Commit 47bfa98

Browse files
authored
Merge pull request #358 from bonitoo-io/feat/custom-app-name
feat: Allow setting of application name to the User-Agent header value
2 parents e52048f + e9594f0 commit 47bfa98

12 files changed

+175
-10
lines changed

CHANGELOG.md

+8
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,12 @@
11
## [unreleased]
2+
### Features
3+
- [#358](https://github.com/influxdata/influxdb-client-go/pull/358):
4+
- Added possibility to set an application name, which will be part of the User-Agent HTTP header:
5+
- Set using `Options.SetApplicationName`
6+
- Warning message is written to log if an application name is not set
7+
- This may change to be logged as an error in a future release
8+
- Added example how to fully override `User-Agent` header using `Doer` interface
9+
210
### Bug fixes
311
- [#359](https://github.com/influxdata/influxdb-client-go/pull/359) `WriteAPIBlocking.Flush()` correctly returns nil error.
412

api/http/options.go

+13
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ type Options struct {
2323
tlsConfig *tls.Config
2424
// HTTP request timeout in sec. Default 20
2525
httpRequestTimeout uint
26+
// Application name in the User-Agent HTTP header string
27+
appName string
2628
}
2729

2830
// HTTPClient returns the http.Client that is configured to be used
@@ -119,6 +121,17 @@ func (o *Options) SetHTTPRequestTimeout(httpRequestTimeout uint) *Options {
119121
return o
120122
}
121123

124+
// ApplicationName returns application name used in the User-Agent HTTP header
125+
func (o *Options) ApplicationName() string {
126+
return o.appName
127+
}
128+
129+
// SetApplicationName sets an application name to the User-Agent HTTP header
130+
func (o *Options) SetApplicationName(appName string) *Options {
131+
o.appName = appName
132+
return o
133+
}
134+
122135
// DefaultOptions returns Options object with default values
123136
func DefaultOptions() *Options {
124137
return &Options{httpRequestTimeout: 20}

api/http/options_test.go

+4-1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ func TestDefaultOptions(t *testing.T) {
2020
assert.Equal(t, uint(20), opts.HTTPRequestTimeout())
2121
assert.NotNil(t, opts.HTTPClient())
2222
assert.True(t, opts.OwnHTTPClient())
23+
assert.EqualValues(t, "", opts.ApplicationName())
2324
}
2425

2526
func TestOptionsSetting(t *testing.T) {
@@ -28,9 +29,11 @@ func TestOptionsSetting(t *testing.T) {
2829
}
2930
opts := http.DefaultOptions().
3031
SetTLSConfig(tlsConfig).
31-
SetHTTPRequestTimeout(50)
32+
SetHTTPRequestTimeout(50).
33+
SetApplicationName("Monitor/1.1")
3234
assert.Equal(t, tlsConfig, opts.TLSConfig())
3335
assert.Equal(t, uint(50), opts.HTTPRequestTimeout())
36+
assert.EqualValues(t, "Monitor/1.1", opts.ApplicationName())
3437
if client := opts.HTTPClient(); assert.NotNil(t, client) {
3538
assert.Equal(t, 50*time.Second, client.Timeout)
3639
assert.Equal(t, tlsConfig, client.Transport.(*nethttp.Transport).TLSClientConfig)

api/http/service.go

+3-1
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ type service struct {
5656
serverURL string
5757
authorization string
5858
client Doer
59+
userAgent string
5960
}
6061

6162
// NewService creates instance of http Service with given parameters
@@ -73,6 +74,7 @@ func NewService(serverURL, authorization string, httpOptions *Options) Service {
7374
serverURL: serverURL,
7475
authorization: authorization,
7576
client: httpOptions.HTTPDoer(),
77+
userAgent: http2.FormatUserAgent(httpOptions.ApplicationName()),
7678
}
7779
}
7880

@@ -128,7 +130,7 @@ func (s *service) DoHTTPRequestWithResponse(req *http.Request, requestCallback R
128130
req.Header.Set("Authorization", s.authorization)
129131
}
130132
if req.Header.Get("User-Agent") == "" {
131-
req.Header.Set("User-Agent", http2.UserAgent)
133+
req.Header.Set("User-Agent", s.userAgent)
132134
}
133135
if requestCallback != nil {
134136
requestCallback(req)

api/writeAPIBlocking.go

+1-2
Original file line numberDiff line numberDiff line change
@@ -86,9 +86,8 @@ func (w *writeAPIBlocking) write(ctx context.Context, line string) error {
8686
w.batch = append(w.batch, line)
8787
if len(w.batch) == int(w.writeOptions.BatchSize()) {
8888
return w.flush(ctx)
89-
} else {
90-
return nil
9189
}
90+
return nil
9291
}
9392
err := w.service.WriteBatch(ctx, iwrite.NewBatch(line, w.writeOptions.MaxRetryTime()))
9493
if err != nil {

client.go

+3
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,9 @@ func NewClientWithOptions(serverURL string, authToken string, options *Options)
145145
}
146146
ilog.Infof("Using URL '%s'%s", serverURL, tokenStr)
147147
}
148+
if options.ApplicationName() == "" {
149+
ilog.Warn("Application name is not set")
150+
}
148151
return client
149152
}
150153

client_test.go

+59-2
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,16 @@ package influxdb2
77
import (
88
"context"
99
"fmt"
10+
ilog "github.com/influxdata/influxdb-client-go/v2/log"
11+
"log"
1012
"net/http"
1113
"net/http/httptest"
14+
"runtime"
15+
"strings"
1216
"testing"
1317
"time"
1418

19+
ihttp "github.com/influxdata/influxdb-client-go/v2/api/http"
1520
"github.com/influxdata/influxdb-client-go/v2/domain"
1621
http2 "github.com/influxdata/influxdb-client-go/v2/internal/http"
1722
iwrite "github.com/influxdata/influxdb-client-go/v2/internal/write"
@@ -76,24 +81,76 @@ func TestWriteAPIManagement(t *testing.T) {
7681
assert.Len(t, c.syncWriteAPIs, 0)
7782
}
7883

84+
func TestUserAgentBase(t *testing.T) {
85+
ua := fmt.Sprintf("influxdb-client-go/%s (%s; %s)", Version, runtime.GOOS, runtime.GOARCH)
86+
assert.Equal(t, ua, http2.UserAgentBase)
87+
88+
}
89+
90+
type doer struct {
91+
userAgent string
92+
doer ihttp.Doer
93+
}
94+
95+
func (d *doer) Do(req *http.Request) (*http.Response, error) {
96+
req.Header.Set("User-Agent", d.userAgent)
97+
return d.doer.Do(req)
98+
}
99+
79100
func TestUserAgent(t *testing.T) {
101+
ua := http2.UserAgentBase
80102
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
81103
<-time.After(100 * time.Millisecond)
82-
if r.Header.Get("User-Agent") == http2.UserAgent {
104+
if r.Header.Get("User-Agent") == ua {
83105
w.WriteHeader(http.StatusNoContent)
84106
} else {
85107
w.WriteHeader(http.StatusNotFound)
86108
}
87109
}))
88110

89111
defer server.Close()
90-
c := NewClient(server.URL, "x")
112+
var sb strings.Builder
113+
log.SetOutput(&sb)
114+
log.SetFlags(0)
115+
c := NewClientWithOptions(server.URL, "x", DefaultOptions().SetLogLevel(ilog.WarningLevel))
116+
assert.True(t, strings.Contains(sb.String(), "Application name is not set"))
91117
up, err := c.Ping(context.Background())
92118
require.NoError(t, err)
93119
assert.True(t, up)
94120

95121
err = c.WriteAPIBlocking("o", "b").WriteRecord(context.Background(), "a,a=a a=1i")
96122
assert.NoError(t, err)
123+
124+
c.Close()
125+
sb.Reset()
126+
// Test setting application name
127+
c = NewClientWithOptions(server.URL, "x", DefaultOptions().SetApplicationName("Monitor/1.1"))
128+
ua = fmt.Sprintf("influxdb-client-go/%s (%s; %s) Monitor/1.1", Version, runtime.GOOS, runtime.GOARCH)
129+
assert.False(t, strings.Contains(sb.String(), "Application name is not set"))
130+
up, err = c.Ping(context.Background())
131+
require.NoError(t, err)
132+
assert.True(t, up)
133+
134+
err = c.WriteAPIBlocking("o", "b").WriteRecord(context.Background(), "a,a=a a=1i")
135+
assert.NoError(t, err)
136+
c.Close()
137+
138+
ua = "Monitor/1.1"
139+
opts := DefaultOptions()
140+
opts.HTTPOptions().SetHTTPDoer(&doer{
141+
userAgent: ua,
142+
doer: http.DefaultClient,
143+
})
144+
145+
//Create client with custom user agent setter
146+
c = NewClientWithOptions(server.URL, "x", opts)
147+
up, err = c.Ping(context.Background())
148+
require.NoError(t, err)
149+
assert.True(t, up)
150+
151+
err = c.WriteAPIBlocking("o", "b").WriteRecord(context.Background(), "a,a=a a=1i")
152+
assert.NoError(t, err)
153+
c.Close()
97154
}
98155

99156
func TestServerError429(t *testing.T) {

example_ua_set_test.go

+54
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
// Copyright 2022 InfluxData, Inc. All rights reserved.
2+
// Use of this source code is governed by MIT
3+
// license that can be found in the LICENSE file.
4+
5+
package influxdb2_test
6+
7+
import (
8+
"context"
9+
"fmt"
10+
"net/http"
11+
12+
"github.com/influxdata/influxdb-client-go/v2"
13+
ihttp "github.com/influxdata/influxdb-client-go/v2/api/http"
14+
)
15+
16+
// UserAgentSetter is the implementation of Doer interface for setting User-Agent header
17+
type UserAgentSetter struct {
18+
UserAgent string
19+
RequestDoer ihttp.Doer
20+
}
21+
22+
// Do fulfills the Doer interface
23+
func (u *UserAgentSetter) Do(req *http.Request) (*http.Response, error) {
24+
// Set User-Agent header to request
25+
req.Header.Set("User-Agent", u.UserAgent)
26+
// Call original Doer to proceed with request
27+
return u.RequestDoer.Do(req)
28+
}
29+
30+
func ExampleClient_customUserAgentHeader() {
31+
// Set custom Doer to HTTPOptions
32+
opts := influxdb2.DefaultOptions()
33+
opts.HTTPOptions().SetHTTPDoer(&UserAgentSetter{
34+
UserAgent: "NetMonitor/1.1",
35+
RequestDoer: http.DefaultClient,
36+
})
37+
38+
//Create client with customized options
39+
client := influxdb2.NewClientWithOptions("http://localhost:8086", "my-token", opts)
40+
41+
// Always close client at the end
42+
defer client.Close()
43+
44+
// Issue a call with custom User-Agent header
45+
resp, err := client.Ping(context.Background())
46+
if err != nil {
47+
panic(err)
48+
}
49+
if resp {
50+
fmt.Println("Server is up")
51+
} else {
52+
fmt.Println("Server is down")
53+
}
54+
}

internal/http/userAgent.go

+14-2
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,17 @@
55
// Package http hold internal HTTP related stuff
66
package http
77

8-
// UserAgent keeps once created User-Agent string
9-
var UserAgent string
8+
import (
9+
"fmt"
10+
)
11+
12+
// UserAgentBase keeps once created base User-Agent string
13+
var UserAgentBase string
14+
15+
// FormatUserAgent creates User-Agent header value for application name
16+
func FormatUserAgent(appName string) string {
17+
if appName != "" {
18+
return fmt.Sprintf("%s %s", UserAgentBase, appName)
19+
}
20+
return UserAgentBase
21+
}

options.go

+11
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,17 @@ func (o *Options) AddDefaultTag(key, value string) *Options {
219219
return o
220220
}
221221

222+
// ApplicationName returns application name used in the User-Agent HTTP header
223+
func (o *Options) ApplicationName() string {
224+
return o.HTTPOptions().ApplicationName()
225+
}
226+
227+
// SetApplicationName sets an application name to the User-Agent HTTP header
228+
func (o *Options) SetApplicationName(appName string) *Options {
229+
o.HTTPOptions().SetApplicationName(appName)
230+
return o
231+
}
232+
222233
// DefaultOptions returns Options object with default values
223234
func DefaultOptions() *Options {
224235
return &Options{logLevel: 0, writeOptions: write.DefaultOptions(), httpOptions: http.DefaultOptions()}

options_test.go

+4-1
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ func TestDefaultOptions(t *testing.T) {
3333
assert.EqualValues(t, (*tls.Config)(nil), opts.TLSConfig())
3434
assert.EqualValues(t, 20, opts.HTTPRequestTimeout())
3535
assert.EqualValues(t, 0, opts.LogLevel())
36+
assert.EqualValues(t, "", opts.ApplicationName())
3637
}
3738

3839
func TestSettingsOptions(t *testing.T) {
@@ -53,7 +54,8 @@ func TestSettingsOptions(t *testing.T) {
5354
SetTLSConfig(tlsConfig).
5455
SetHTTPRequestTimeout(50).
5556
SetLogLevel(3).
56-
AddDefaultTag("t", "a")
57+
AddDefaultTag("t", "a").
58+
SetApplicationName("Monitor/1.1")
5759
assert.EqualValues(t, 5, opts.BatchSize())
5860
assert.EqualValues(t, true, opts.UseGZip())
5961
assert.EqualValues(t, 5_000, opts.FlushInterval())
@@ -66,6 +68,7 @@ func TestSettingsOptions(t *testing.T) {
6668
assert.EqualValues(t, 5, opts.ExponentialBase())
6769
assert.EqualValues(t, tlsConfig, opts.TLSConfig())
6870
assert.EqualValues(t, 50, opts.HTTPRequestTimeout())
71+
assert.EqualValues(t, "Monitor/1.1", opts.ApplicationName())
6972
if client := opts.HTTPClient(); assert.NotNil(t, client) {
7073
assert.EqualValues(t, 50*time.Second, client.Timeout)
7174
assert.Equal(t, tlsConfig, client.Transport.(*http.Transport).TLSClientConfig)

version.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,5 +17,5 @@ const (
1717
)
1818

1919
func init() {
20-
http.UserAgent = fmt.Sprintf("influxdb-client-go/%s (%s; %s)", Version, runtime.GOOS, runtime.GOARCH)
20+
http.UserAgentBase = fmt.Sprintf("influxdb-client-go/%s (%s; %s)", Version, runtime.GOOS, runtime.GOARCH)
2121
}

0 commit comments

Comments
 (0)