Skip to content

Commit 5f747da

Browse files
authored
Client: Reeintegrate compatibility header with external transport (#379)
1 parent 811d514 commit 5f747da

File tree

2 files changed

+117
-11
lines changed

2 files changed

+117
-11
lines changed

elasticsearch.go

+23-9
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import (
2626
"os"
2727
"regexp"
2828
"runtime"
29+
"strconv"
2930
"strings"
3031
"sync"
3132
"time"
@@ -45,8 +46,9 @@ const (
4546
)
4647

4748
var (
48-
userAgent string
49-
reGoVersion = regexp.MustCompile(`go(\d+\.\d+\..+)`)
49+
esCompatHeader = "ELASTIC_CLIENT_APIVERSIONING"
50+
userAgent string
51+
reGoVersion = regexp.MustCompile(`go(\d+\.\d+\..+)`)
5052
)
5153

5254
func init() {
@@ -82,14 +84,15 @@ type Config struct {
8284
DiscoverNodesOnStart bool // Discover nodes when initializing the client. Default: false.
8385
DiscoverNodesInterval time.Duration // Discover nodes periodically. Default: disabled.
8486

85-
EnableMetrics bool // Enable the metrics collection.
86-
EnableDebugLogger bool // Enable the debug logging.
87+
EnableMetrics bool // Enable the metrics collection.
88+
EnableDebugLogger bool // Enable the debug logging.
89+
EnableCompatibilityMode bool // Enable sends compatibility header
8790

8891
DisableMetaHeader bool // Disable the additional "X-Elastic-Client-Meta" HTTP header.
8992

9093
RetryBackoff func(attempt int) time.Duration // Optional backoff duration. Default: nil.
9194

92-
Transport http.RoundTripper // The HTTP transport object.
95+
Transport http.RoundTripper // The HTTP transport object.
9396
Logger elastictransport.Logger // The logger object.
9497
Selector elastictransport.Selector // The selector object.
9598

@@ -100,8 +103,9 @@ type Config struct {
100103
// Client represents the Elasticsearch client.
101104
//
102105
type Client struct {
103-
*esapi.API // Embeds the API methods
104-
Transport elastictransport.Interface
106+
*esapi.API // Embeds the API methods
107+
Transport elastictransport.Interface
108+
compatibilityHeader bool
105109

106110
disableMetaHeader bool
107111
productCheckMu sync.RWMutex
@@ -206,9 +210,13 @@ func NewClient(cfg Config) (*Client, error) {
206210
return nil, fmt.Errorf("error creating transport: %s", err)
207211
}
208212

213+
compatHeaderEnv := os.Getenv(esCompatHeader)
214+
compatibilityHeader, _ := strconv.ParseBool(compatHeaderEnv)
215+
209216
client := &Client{
210-
Transport: tp,
211-
disableMetaHeader: cfg.DisableMetaHeader,
217+
Transport: tp,
218+
disableMetaHeader: cfg.DisableMetaHeader,
219+
compatibilityHeader: cfg.EnableCompatibilityMode || compatibilityHeader,
212220
}
213221
client.API = esapi.New(client)
214222

@@ -222,6 +230,12 @@ func NewClient(cfg Config) (*Client, error) {
222230
// Perform delegates to Transport to execute a request and return a response.
223231
//
224232
func (c *Client) Perform(req *http.Request) (*http.Response, error) {
233+
// Compatibility Header
234+
if c.compatibilityHeader {
235+
req.Header.Set("Content-Type", "application/vnd.elasticsearch+json;compatible-with=8")
236+
req.Header.Set("Accept", "application/vnd.elasticsearch+json;compatible-with=8")
237+
}
238+
225239
if !c.disableMetaHeader {
226240
existingMetaHeader := req.Header.Get(HeaderClientMeta)
227241
if existingMetaHeader != "" {

elasticsearch_internal_test.go

+94-2
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import (
3232
"os"
3333
"reflect"
3434
"regexp"
35+
"strconv"
3536
"strings"
3637
"testing"
3738

@@ -407,7 +408,7 @@ func TestResponseCheckOnly(t *testing.T) {
407408
name: "Valid answer without header",
408409
response: &http.Response{
409410
StatusCode: http.StatusOK,
410-
Body: ioutil.NopCloser(strings.NewReader("{}")),
411+
Body: ioutil.NopCloser(strings.NewReader("{}")),
411412
},
412413
wantErr: true,
413414
},
@@ -423,7 +424,7 @@ func TestResponseCheckOnly(t *testing.T) {
423424
name: "Valid answer without header and response check",
424425
response: &http.Response{
425426
StatusCode: http.StatusOK,
426-
Body: ioutil.NopCloser(strings.NewReader("{}")),
427+
Body: ioutil.NopCloser(strings.NewReader("{}")),
427428
},
428429
wantErr: true,
429430
},
@@ -558,3 +559,94 @@ func TestFingerprint(t *testing.T) {
558559
t.Fatalf("unexpected payload returned: expected: %s, got: %s", body, data)
559560
}
560561
}
562+
563+
func TestCompatibilityHeader(t *testing.T) {
564+
tests := []struct {
565+
name string
566+
envVar bool
567+
configVar bool
568+
compatibilityHeader bool
569+
bodyPresent bool
570+
expectsHeader []string
571+
}{
572+
{
573+
name: "Compatibility header disabled",
574+
compatibilityHeader: false,
575+
bodyPresent: false,
576+
envVar: false,
577+
configVar: false,
578+
expectsHeader: []string{"application/json"},
579+
},
580+
{
581+
name: "Compatibility header enabled with env var",
582+
compatibilityHeader: true,
583+
bodyPresent: false,
584+
envVar: true,
585+
configVar: false,
586+
expectsHeader: []string{"application/vnd.elasticsearch+json;compatible-with=8"},
587+
},
588+
{
589+
name: "Compatibility header enabled with body with env var",
590+
compatibilityHeader: true,
591+
bodyPresent: true,
592+
envVar: true,
593+
configVar: false,
594+
expectsHeader: []string{"application/vnd.elasticsearch+json;compatible-with=8"},
595+
},
596+
{
597+
name: "Compatibility header enabled in conf",
598+
compatibilityHeader: true,
599+
bodyPresent: false,
600+
envVar: false,
601+
configVar: true,
602+
expectsHeader: []string{"application/vnd.elasticsearch+json;compatible-with=8"},
603+
},
604+
{
605+
name: "Compatibility header enabled with body in conf",
606+
compatibilityHeader: true,
607+
bodyPresent: true,
608+
envVar: false,
609+
configVar: true,
610+
expectsHeader: []string{"application/vnd.elasticsearch+json;compatible-with=8"},
611+
},
612+
}
613+
614+
for _, test := range tests {
615+
t.Run(test.name, func(t *testing.T) {
616+
t.Setenv(esCompatHeader, strconv.FormatBool(test.compatibilityHeader))
617+
618+
c, _ := NewClient(Config{
619+
EnableCompatibilityMode: test.configVar,
620+
Addresses: []string{},
621+
Transport: &mockTransp{
622+
RoundTripFunc: func(req *http.Request) (*http.Response, error) {
623+
if test.compatibilityHeader {
624+
if !reflect.DeepEqual(req.Header["Accept"], test.expectsHeader) {
625+
t.Errorf("Compatibility header enabled but header is, not in request headers, got: %s, want: %s", req.Header["Accept"], test.expectsHeader)
626+
}
627+
628+
if test.bodyPresent {
629+
if !reflect.DeepEqual(req.Header["Content-Type"], test.expectsHeader) {
630+
t.Errorf("Compatibility header with Body enabled, not in request headers, got: %s, want: %s", req.Header["Content-Type"], test.expectsHeader)
631+
}
632+
}
633+
}
634+
635+
return &http.Response{
636+
StatusCode: http.StatusOK,
637+
Status: "MOCK",
638+
Body: ioutil.NopCloser(strings.NewReader("{}")),
639+
}, nil
640+
},
641+
},
642+
})
643+
644+
req := &http.Request{URL: &url.URL{}, Header: make(http.Header)}
645+
if test.bodyPresent {
646+
req.Body = ioutil.NopCloser(strings.NewReader("{}"))
647+
}
648+
649+
_, _ = c.Perform(req)
650+
})
651+
}
652+
}

0 commit comments

Comments
 (0)