Repository: Unleash/unleash-client-go
Pull request #189 (2025-03-04 12:02:46)

feat: add interval to header and connection Id to body
1. Metrics - add connection id to payload and interval to header
2. Features - add interval to header
3. Register - add connection id to payload.


Patch:
diff --git a/client.go b/client.go
index f9601b6..3fac86d 100644
--- a/client.go
+++ b/client.go
@@ -165,13 +165,15 @@ func NewClient(options ...ConfigOption) (*Client, error) {
 		uc.options.instanceId = generateInstanceId()
 	}
 
+	connectionId := getConnectionId()
+
 	headers := make(http.Header)
 	if uc.options.customHeaders != nil {
 		headers = uc.options.customHeaders
 	}
 	headers.Set("unleash-appname", uc.options.appName)
 	headers.Set("unleash-sdk", fmt.Sprintf("%s:%s", clientName, clientVersion))
-	headers.Set("unleash-connection-id", getConnectionId())
+	headers.Set("unleash-connection-id", connectionId)
 
 	uc.repository = newRepository(
 		repositoryOptions{
@@ -202,6 +204,7 @@ func NewClient(options ...ConfigOption) (*Client, error) {
 		metricsOptions{
 			appName:         uc.options.appName,
 			instanceId:      uc.options.instanceId,
+			connectionId:    connectionId,
 			strategies:      strategyNames,
 			metricsInterval: uc.options.metricsInterval,
 			url:             *parsedUrl,
diff --git a/config.go b/config.go
index 6a549d4..775366c 100644
--- a/config.go
+++ b/config.go
@@ -259,6 +259,7 @@ type repositoryOptions struct {
 type metricsOptions struct {
 	appName         string
 	instanceId      string
+	connectionId    string
 	url             url.URL
 	strategies      []string
 	metricsInterval time.Duration
diff --git a/metrics.go b/metrics.go
index 551bfd7..a167e4b 100644
--- a/metrics.go
+++ b/metrics.go
@@ -23,6 +23,9 @@ type MetricsData struct {
 	// InstanceID is the instance identifier.
 	InstanceID string `json:"instanceId"`
 
+	// ConnectionId is the connection id for instance.
+	ConnectionId string `json:"connectionId"`
+
 	// Bucket is the payload data sent to the server.
 	Bucket api.Bucket `json:"bucket"`
 
@@ -50,6 +53,9 @@ type ClientData struct {
 	// InstanceID is the instance identifier.
 	InstanceID string `json:"instanceId"`
 
+	// ConnectionId is the connection id for instance.
+	ConnectionId string `json:"connectionId"`
+
 	// Optional field that describes the sdk version (name:version)
 	SDKVersion string `json:"sdkVersion"`
 
@@ -201,6 +207,7 @@ func (m *metrics) sendMetrics() {
 	payload := MetricsData{
 		AppName:          m.options.appName,
 		InstanceID:       m.options.instanceId,
+		ConnectionId:     m.options.connectionId,
 		Bucket:           bucket,
 		SDKVersion:       fmt.Sprintf("%s:%s", clientName, clientVersion),
 		PlatformName:     "go",
@@ -258,6 +265,7 @@ func (m *metrics) doPost(url *url.URL, payload interface{}) (*http.Response, err
 	req.Header.Add("UNLEASH-APPNAME", m.options.appName)
 	req.Header.Add("UNLEASH-INSTANCEID", m.options.instanceId)
 	req.Header.Add("User-Agent", m.options.appName)
+	req.Header.Add("Unleash-Interval", fmt.Sprintf("%d", m.options.metricsInterval.Milliseconds()))
 
 	for k, v := range m.options.headers {
 		req.Header[k] = v
@@ -331,6 +339,7 @@ func (m *metrics) getClientData() ClientData {
 	return ClientData{
 		m.options.appName,
 		m.options.instanceId,
+		m.options.connectionId,
 		fmt.Sprintf("%s:%s", clientName, clientVersion),
 		m.options.strategies,
 		m.started,
diff --git a/repository.go b/repository.go
index 59fce31..b28c76e 100644
--- a/repository.go
+++ b/repository.go
@@ -125,6 +125,7 @@ func (r *repository) fetch() error {
 
 	req.Header.Add("UNLEASH-APPNAME", r.options.appName)
 	req.Header.Add("UNLEASH-INSTANCEID", r.options.instanceId)
+	req.Header.Add("Unleash-Interval", fmt.Sprintf("%d", r.options.refreshInterval.Milliseconds()))
 	req.Header.Add("User-Agent", r.options.appName)
 	// Needs to reference a version of the client specifications that include
 	// global segments


Test patch:
diff --git a/client_test.go b/client_test.go
index 434c837..daff04f 100644
--- a/client_test.go
+++ b/client_test.go
@@ -1450,3 +1450,49 @@ func TestSendIdentificationHeaders(t *testing.T) {
 
 	assert.True(gock.IsDone(), "there should be no more mocks")
 }
+
+func TestConnectionAndIntervalHeadersAndBody(t *testing.T) {
+	assert := assert.New(t)
+	defer gock.OffAll()
+
+	gock.Observe(gock.DumpRequest)
+
+	gock.New(mockerServer).
+		Post("/client/register").
+		MatchHeader("UNLEASH-CONNECTION-ID", `[0-9a-f\-]{36}`).
+		BodyString(`.*connectionId.*`).
+		Reply(200)
+
+	gock.New(mockerServer).
+		Post("/client/metrics").
+		MatchHeader("UNLEASH-CONNECTION-ID", `[0-9a-f\-]{36}`).
+		MatchHeader("Unleash-Interval", "50").
+		BodyString(`.*connectionId.*`).
+		Reply(200)
+
+	gock.New(mockerServer).
+		Get("/client/features").
+		MatchHeader("UNLEASH-CONNECTION-ID", `[0-9a-f\-]{36}`).
+		MatchHeader("Unleash-Interval", "100").
+		Reply(200).
+		JSON(api.FeatureResponse{})
+
+	client, err := NewClient(
+		WithUrl(mockerServer),
+		WithAppName(mockAppName),
+		WithMetricsInterval(50*time.Millisecond),
+		WithRefreshInterval(100*time.Millisecond),
+		WithInstanceId(mockInstanceId),
+		WithListener(&NoopListener{}),
+		WithDisableMetrics(false),
+	)
+
+	assert.NoError(err)
+
+	client.IsEnabled("foo")
+
+	time.Sleep(100 * time.Millisecond)
+	err = client.Close()
+
+	assert.True(gock.IsDone(), "there should be no more mocks")
+}