Skip to content

Commit 7564662

Browse files
authored
Client: Implement 8.x response header check behaviour (#292)
1 parent 06c0d4f commit 7564662

File tree

2 files changed

+99
-22
lines changed

2 files changed

+99
-22
lines changed

elasticsearch.go

Lines changed: 39 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ import (
3838
)
3939

4040
var (
41-
reVersion *regexp.Regexp
41+
reVersion *regexp.Regexp
4242
)
4343

4444
func init() {
@@ -47,9 +47,9 @@ func init() {
4747
}
4848

4949
const (
50-
defaultURL = "http://localhost:9200"
51-
tagline = "You Know, for Search"
52-
unknownProduct = "the client noticed that the server is not Elasticsearch and we do not support this unknown product"
50+
defaultURL = "http://localhost:9200"
51+
tagline = "You Know, for Search"
52+
unknownProduct = "the client noticed that the server is not Elasticsearch and we do not support this unknown product"
5353
)
5454

5555
// Version returns the package version as a string.
@@ -85,7 +85,8 @@ type Config struct {
8585
EnableMetrics bool // Enable the metrics collection.
8686
EnableDebugLogger bool // Enable the debug logging.
8787

88-
DisableMetaHeader bool // Disable the additional "X-Elastic-Client-Meta" HTTP header.
88+
DisableMetaHeader bool // Disable the additional "X-Elastic-Client-Meta" HTTP header.
89+
UseResponseCheckOnly bool
8990

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

@@ -100,21 +101,23 @@ type Config struct {
100101
// Client represents the Elasticsearch client.
101102
//
102103
type Client struct {
103-
*esapi.API// Embeds the API methods
104-
Transport estransport.Interface
104+
*esapi.API // Embeds the API methods
105+
Transport estransport.Interface
105106

106-
once sync.Once
107-
productCheckError error
107+
productCheckOnce sync.Once
108+
responseCheckOnce sync.Once
109+
productCheckError error
110+
useResponseCheckOnly bool
108111
}
109112

110113
type esVersion struct {
111-
Number string `json:"number"`
112-
BuildFlavor string `json:"build_flavor"`
114+
Number string `json:"number"`
115+
BuildFlavor string `json:"build_flavor"`
113116
}
114117

115118
type info struct {
116-
Version esVersion `json:"version"`
117-
Tagline string `json:"tagline"`
119+
Version esVersion `json:"version"`
120+
Tagline string `json:"tagline"`
118121
}
119122

120123
// NewDefaultClient creates a new client with default options.
@@ -212,7 +215,7 @@ func NewClient(cfg Config) (*Client, error) {
212215
return nil, fmt.Errorf("error creating transport: %s", err)
213216
}
214217

215-
client := &Client{Transport: tp}
218+
client := &Client{Transport: tp, useResponseCheckOnly: cfg.UseResponseCheckOnly}
216219
client.API = esapi.New(client)
217220

218221
if cfg.DiscoverNodesOnStart {
@@ -277,23 +280,38 @@ func ParseElasticsearchVersion(version string) (int64, int64, int64, error) {
277280
// Perform delegates to Transport to execute a request and return a response.
278281
//
279282
func (c *Client) Perform(req *http.Request) (*http.Response, error) {
280-
c.once.Do(func() {
281-
err := c.productCheck()
282-
if err != nil {
283-
c.productCheckError = err
283+
// ProductCheck validation
284+
c.productCheckOnce.Do(func() {
285+
// We skip this validation of we only want the header validation.
286+
// ResponseCheck path continues after original request.
287+
if c.useResponseCheckOnly {
288+
return
284289
}
290+
291+
// Launch product check for 7.x, request info, check header then payload.
292+
c.productCheckError = c.productCheck()
285293
return
286294
})
295+
296+
// Retrieve the original request.
297+
res, err := c.Transport.Perform(req)
298+
299+
c.responseCheckOnce.Do(func() {
300+
// ResponseCheck path continues, we run the header check on the first answer from ES.
301+
if c.useResponseCheckOnly {
302+
c.productCheckError = genuineCheckHeader(res.Header)
303+
}
304+
})
305+
287306
if c.productCheckError != nil {
288307
return nil, c.productCheckError
289308
}
290-
291-
return c.Transport.Perform(req)
309+
return res, err
292310
}
293311

294312
// productCheck runs an esapi.Info query to retrieve informations of the current cluster
295313
// decodes the response and decides if the cluster is a genuine Elasticsearch product.
296-
func (c *Client) productCheck() (error) {
314+
func (c *Client) productCheck() error {
297315
var info info
298316

299317
req := esapi.InfoRequest{}

elasticsearch_internal_test.go

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -568,4 +568,63 @@ func TestGenuineCheckHeader(t *testing.T) {
568568
}
569569
})
570570
}
571-
}
571+
}
572+
573+
func TestResponseCheckOnly(t *testing.T) {
574+
tests := []struct {
575+
name string
576+
useResponseCheckOnly bool
577+
response *http.Response
578+
wantErr bool
579+
} {
580+
{
581+
name: "Valid answer with header",
582+
useResponseCheckOnly: false,
583+
response: &http.Response{
584+
Header: http.Header{"X-Elastic-Product": []string{"Elasticsearch"}},
585+
Body: ioutil.NopCloser(strings.NewReader("{}")),
586+
},
587+
wantErr: false,
588+
},
589+
{
590+
name: "Valid answer without header",
591+
useResponseCheckOnly: false,
592+
response: &http.Response{
593+
Body: ioutil.NopCloser(strings.NewReader("{}")),
594+
},
595+
wantErr: true,
596+
},
597+
{
598+
name: "Valid answer with header and response check",
599+
useResponseCheckOnly: true,
600+
response: &http.Response{
601+
Header: http.Header{"X-Elastic-Product": []string{"Elasticsearch"}},
602+
Body: ioutil.NopCloser(strings.NewReader("{}")),
603+
},
604+
wantErr: false,
605+
},
606+
{
607+
name: "Valid answer withouth header and response check",
608+
useResponseCheckOnly: true,
609+
response: &http.Response{
610+
Body: ioutil.NopCloser(strings.NewReader("{}")),
611+
},
612+
wantErr: true,
613+
},
614+
615+
}
616+
for _, tt := range tests {
617+
t.Run(tt.name, func(t *testing.T) {
618+
c, _ := NewClient(Config{
619+
Transport: &mockTransp{RoundTripFunc: func(request *http.Request) (*http.Response, error) {
620+
return tt.response, nil
621+
}},
622+
UseResponseCheckOnly: tt.useResponseCheckOnly,
623+
})
624+
_, err := c.Cat.Indices()
625+
if (err != nil) != tt.wantErr {
626+
t.Errorf("Unexpected error, got %v, wantErr %v", err, tt.wantErr)
627+
}
628+
})
629+
}
630+
}

0 commit comments

Comments
 (0)