Skip to content

Commit 45ed8cd

Browse files
committed
[CLIENT] Adds meta header
1 parent 4518615 commit 45ed8cd

File tree

7 files changed

+315
-17
lines changed

7 files changed

+315
-17
lines changed

elasticsearch-transport/elasticsearch-transport.gemspec

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ Gem::Specification.new do |s|
4141
# Gems for testing integrations
4242
s.add_development_dependency 'cane'
4343
s.add_development_dependency 'hashie'
44+
s.add_development_dependency 'httpclient'
4445
s.add_development_dependency 'manticore', '~> 0.5.2' if defined? JRUBY_VERSION
4546
s.add_development_dependency 'minitest', '~> 4.0'
4647
s.add_development_dependency 'net-http-persistent'

elasticsearch-transport/lib/elasticsearch/transport/client.rb

Lines changed: 93 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,10 @@ class Client
9292
# @option api_key [String, Hash] :api_key Use API Key Authentication, either the base64 encoding of `id` and `api_key`
9393
# joined by a colon as a String, or a hash with the `id` and `api_key` values.
9494
# @option opaque_id_prefix [String] :opaque_id_prefix set a prefix for X-Opaque-Id when initializing the client. This
95-
# will be prepended to the id you set before each request if you're using X-Opaque-Id
95+
# will be prepended to the id you set before each request if
96+
# you're using X-Opaque-Id
97+
# @option enable_meta_header [Boolean] :enable_meta_header Enable sending the meta data header to Cloud.
98+
# (Default: true)
9699
#
97100
# @yield [faraday] Access and configure the `Faraday::Connection` instance directly with a block
98101
#
@@ -107,6 +110,7 @@ def initialize(arguments = {}, &block)
107110
@arguments[:randomize_hosts] ||= false
108111
@arguments[:transport_options] ||= {}
109112
@arguments[:http] ||= {}
113+
@arguments[:enable_meta_header] = arguments.fetch(:enable_meta_header) { true }
110114
@options[:http] ||= {}
111115

112116
set_api_key if (@api_key = @arguments[:api_key])
@@ -135,15 +139,18 @@ def initialize(arguments = {}, &block)
135139
if @arguments[:transport]
136140
@transport = @arguments[:transport]
137141
else
138-
transport_class = @arguments[:transport_class] || DEFAULT_TRANSPORT_CLASS
139-
if transport_class == Transport::HTTP::Faraday
140-
@transport = transport_class.new(hosts: @seeds, options: @arguments) do |faraday|
141-
faraday.adapter(@arguments[:adapter] || __auto_detect_adapter)
142-
block&.call faraday
143-
end
144-
else
145-
@transport = transport_class.new(hosts: @seeds, options: @arguments)
146-
end
142+
@transport_class = @arguments[:transport_class] || DEFAULT_TRANSPORT_CLASS
143+
@transport = if @transport_class == Transport::HTTP::Faraday
144+
@arguments[:adapter] ||= __auto_detect_adapter
145+
set_meta_header
146+
@transport_class.new(hosts: @seeds, options: @arguments) do |faraday|
147+
faraday.adapter(@arguments[:adapter])
148+
block&.call faraday
149+
end
150+
else
151+
set_meta_header
152+
@transport_class.new(hosts: @seeds, options: @arguments)
153+
end
147154
end
148155
end
149156

@@ -163,13 +170,86 @@ def perform_request(method, path, params = {}, body = nil, headers = nil)
163170

164171
def set_api_key
165172
@api_key = __encode(@api_key) if @api_key.is_a? Hash
173+
add_header('Authorization' => "ApiKey #{@api_key}")
174+
@arguments.delete(:user)
175+
@arguments.delete(:password)
176+
# headers = @arguments[:transport_options]&.[](:headers) || {}
177+
# headers.merge!('Authorization' => "ApiKey #{@api_key}")
178+
# @arguments[:transport_options].merge!(
179+
# headers: headers
180+
# )
181+
# @arguments.delete(:user)
182+
# @arguments.delete(:password)
183+
end
184+
185+
def add_header(header)
166186
headers = @arguments[:transport_options]&.[](:headers) || {}
167-
headers.merge!('Authorization' => "ApiKey #{@api_key}")
187+
headers.merge!(header)
168188
@arguments[:transport_options].merge!(
169189
headers: headers
170190
)
171-
@arguments.delete(:user)
172-
@arguments.delete(:password)
191+
end
192+
193+
def set_meta_header
194+
return if @arguments[:enable_meta_header] == false
195+
196+
service, version = meta_header_service_version
197+
198+
meta_headers = {
199+
service.to_sym => version,
200+
rb: RUBY_VERSION,
201+
t: Elasticsearch::Transport::VERSION
202+
}
203+
meta_headers.merge!(meta_header_engine) if meta_header_engine
204+
meta_headers.merge!(meta_header_adapter) if meta_header_adapter
205+
206+
add_header({ 'x-elastic-client-meta' => meta_headers.map { |k, v| "#{k}=#{v}" }.join(',') })
207+
end
208+
209+
def meta_header_service_version
210+
if defined?(Elastic::META_HEADER_SERVICE_VERSION)
211+
Elastic::META_HEADER_SERVICE_VERSION
212+
elsif defined?(Elasticsearch::VERSION)
213+
['es', Elasticsearch::VERSION]
214+
else
215+
['es', Elasticsearch::Transport::VERSION]
216+
end
217+
end
218+
219+
def meta_header_engine
220+
case RUBY_ENGINE
221+
when 'ruby'
222+
{}
223+
when 'jruby'
224+
{ jv: ENV_JAVA['java.version'], jr: JRUBY_VERSION }
225+
when 'rbx'
226+
{ rbx: RUBY_VERSION }
227+
else
228+
{ RUBY_ENGINE.to_sym => RUBY_VERSION }
229+
end
230+
end
231+
232+
def meta_header_adapter
233+
if @transport_class == Transport::HTTP::Faraday
234+
{fd: Faraday::VERSION}.merge(
235+
case @arguments[:adapter]
236+
when :patron
237+
{pt: Patron::VERSION}
238+
when :net_http
239+
{nh: defined?(Net::HTTP::VERSION) ? Net::HTTP::VERSION : Net::HTTP::HTTPVersion}
240+
when :typhoeus
241+
{ty: Typhoeus::VERSION}
242+
when :httpclient
243+
{hc: HTTPClient::VERSION}
244+
when :net_http_persistent
245+
{np: Net::HTTP::Persistent::VERSION}
246+
end
247+
)
248+
elsif defined?(Transport::HTTP::Curb) && @transport_class == Transport::HTTP::Curb
249+
{cl: Curl::CURB_VERSION}
250+
elsif defined?(Transport::HTTP::Manticore) && @transport_class == Transport::HTTP::Manticore
251+
{mc: Manticore::VERSION}
252+
end
173253
end
174254

175255
# Normalizes and returns hosts configuration.

elasticsearch-transport/spec/elasticsearch/transport/client_spec.rb

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@
109109
end
110110

111111
let(:client) do
112-
described_class.new(adapter: :patron)
112+
described_class.new(adapter: :patron, enable_meta_header: false)
113113
end
114114

115115
it 'uses Faraday with the adapter' do
@@ -123,7 +123,7 @@
123123
end
124124

125125
let(:client) do
126-
described_class.new(adapter: :typhoeus)
126+
described_class.new(adapter: :typhoeus, enable_meta_header: false)
127127
end
128128

129129
it 'uses Faraday with the adapter' do
@@ -137,7 +137,7 @@
137137
end
138138

139139
let(:client) do
140-
described_class.new('adapter' => :patron)
140+
described_class.new('adapter' => :patron, enable_meta_header: false)
141141
end
142142

143143
it 'uses Faraday with the adapter' do
@@ -230,7 +230,6 @@
230230
end
231231

232232
context 'when credentials are specified' do
233-
234233
let(:host) do
235234
'http://USERNAME:PASSWORD@myhost:8080'
236235
end
Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
# Licensed to Elasticsearch B.V. under one or more contributor
2+
# license agreements. See the NOTICE file distributed with
3+
# this work for additional information regarding copyright
4+
# ownership. Elasticsearch B.V. licenses this file to you under
5+
# the Apache License, Version 2.0 (the "License"); you may
6+
# not use this file except in compliance with the License.
7+
# You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing,
12+
# software distributed under the License is distributed on an
13+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
# KIND, either express or implied. See the License for the
15+
# specific language governing permissions and limitations
16+
# under the License.
17+
18+
require 'spec_helper'
19+
20+
describe Elasticsearch::Transport::Client do
21+
context 'meta-header' do
22+
let(:subject) { client.transport.connections.first.connection.headers }
23+
let(:regexp) { /^[a-z]{1,}=[a-z0-9.\-]{1,}(?:,[a-z]{1,}=[a-z0-9._\-]+)*$/ }
24+
let(:adapter) { :net_http }
25+
let(:adapter_code) { "nh=#{defined?(Net::HTTP::VERSION) ? Net::HTTP::VERSION : Net::HTTP::HTTPVersion}" }
26+
let(:meta_header) do
27+
if jruby?
28+
"es=#{Elasticsearch::VERSION},rb=#{RUBY_VERSION},t=#{Elasticsearch::Transport::VERSION},jv=#{ENV_JAVA['java.version']},jr=#{JRUBY_VERSION},fd=#{Faraday::VERSION},#{adapter_code}"
29+
else
30+
"es=#{Elasticsearch::VERSION},rb=#{RUBY_VERSION},t=#{Elasticsearch::Transport::VERSION},fd=#{Faraday::VERSION},#{adapter_code}"
31+
end
32+
end
33+
34+
context 'single use of meta header' do
35+
let(:client) do
36+
described_class.new(adapter: adapter).tap do |klient|
37+
allow(klient).to receive(:__build_connections)
38+
end
39+
end
40+
41+
it 'x-elastic-client-header value matches regexp' do
42+
expect(subject['x-elastic-client-meta']).to match(regexp)
43+
expect(subject).to include('x-elastic-client-meta' => meta_header)
44+
end
45+
end
46+
47+
context 'when using user-agent headers' do
48+
let(:client) do
49+
transport_options = { headers: { user_agent: 'My Ruby App' } }
50+
described_class.new(transport_options: transport_options, adapter: adapter).tap do |klient|
51+
allow(klient).to receive(:__build_connections)
52+
end
53+
end
54+
55+
it 'is friendly to previously set headers' do
56+
expect(subject).to include(user_agent: 'My Ruby App')
57+
expect(subject['x-elastic-client-meta']).to match(regexp)
58+
expect(subject).to include('x-elastic-client-meta' => meta_header)
59+
end
60+
end
61+
62+
context 'when using API Key' do
63+
let(:client) do
64+
described_class.new(api_key: 'an_api_key', adapter: adapter)
65+
end
66+
67+
let(:authorization_header) do
68+
client.transport.connections.first.connection.headers['Authorization']
69+
end
70+
71+
it 'adds the ApiKey header to the connection' do
72+
expect(authorization_header).to eq('ApiKey an_api_key')
73+
expect(subject['x-elastic-client-meta']).to match(regexp)
74+
expect(subject).to include('x-elastic-client-meta' => meta_header)
75+
end
76+
end
77+
78+
context 'adapters' do
79+
let(:meta_header) do
80+
if jruby?
81+
"es=#{Elasticsearch::VERSION},rb=#{RUBY_VERSION},t=#{Elasticsearch::Transport::VERSION},jv=#{ENV_JAVA['java.version']},jr=#{JRUBY_VERSION},fd=#{Faraday::VERSION}"
82+
else
83+
"es=#{Elasticsearch::VERSION},rb=#{RUBY_VERSION},t=#{Elasticsearch::Transport::VERSION},fd=#{Faraday::VERSION}"
84+
end
85+
end
86+
let(:client) { described_class.new(adapter: adapter) }
87+
let(:headers) { client.transport.connections.first.connection.headers }
88+
89+
context 'using net/http/persistent' do
90+
let(:adapter) { :net_http_persistent }
91+
92+
it 'sets adapter in the meta header' do
93+
require 'net/http/persistent'
94+
expect(headers['x-elastic-client-meta']).to match(regexp)
95+
meta = "#{meta_header},np=#{Net::HTTP::Persistent::VERSION}"
96+
expect(headers).to include('x-elastic-client-meta' => meta)
97+
end
98+
end
99+
100+
context 'using httpclient' do
101+
let(:adapter) { :httpclient }
102+
103+
it 'sets adapter in the meta header' do
104+
require 'httpclient'
105+
expect(headers['x-elastic-client-meta']).to match(regexp)
106+
meta = "#{meta_header},hc=#{HTTPClient::VERSION}"
107+
expect(headers).to include('x-elastic-client-meta' => meta)
108+
end
109+
end
110+
111+
context 'using typhoeus' do
112+
let(:adapter) { :typhoeus }
113+
114+
it 'sets adapter in the meta header' do
115+
require 'typhoeus'
116+
expect(headers['x-elastic-client-meta']).to match(regexp)
117+
meta = "#{meta_header},ty=#{Typhoeus::VERSION}"
118+
expect(headers).to include('x-elastic-client-meta' => meta)
119+
end
120+
end
121+
122+
unless defined?(JRUBY_VERSION)
123+
let(:adapter) { :patron }
124+
125+
context 'using patron' do
126+
it 'sets adapter in the meta header' do
127+
require 'patron'
128+
expect(headers['x-elastic-client-meta']).to match(regexp)
129+
meta = "#{meta_header},pt=#{Patron::VERSION}"
130+
expect(headers).to include('x-elastic-client-meta' => meta)
131+
end
132+
end
133+
end
134+
end
135+
136+
if defined?(JRUBY_VERSION)
137+
context 'when using manticore' do
138+
let(:client) do
139+
Elasticsearch::Client.new(transport_class: Elasticsearch::Transport::Transport::HTTP::Manticore)
140+
end
141+
let(:subject) { client.transport.connections.first.connection.instance_variable_get("@options")[:headers]}
142+
143+
it 'sets manticore in the metaheader' do
144+
expect(subject['x-elastic-client-meta']).to match(regexp)
145+
expect(subject['x-elastic-client-meta']).to match(/mc=[0-9.]+/)
146+
end
147+
end
148+
else
149+
context 'when using curb' do
150+
let(:client) do
151+
Elasticsearch::Client.new(transport_class: Elasticsearch::Transport::Transport::HTTP::Curb)
152+
end
153+
154+
it 'sets curb in the metaheader' do
155+
expect(subject['x-elastic-client-meta']).to match(regexp)
156+
expect(subject['x-elastic-client-meta']).to match(/cl=[0-9.]+/)
157+
end
158+
end
159+
end
160+
161+
context 'when using custom transport implementation' do
162+
class MyTransport
163+
include Elasticsearch::Transport::Transport::Base
164+
def initialize(args); end
165+
end
166+
let(:client) { Elasticsearch::Client.new(transport_class: MyTransport) }
167+
let(:subject){ client.instance_variable_get("@arguments")[:transport_options][:headers] }
168+
let(:meta_header) do
169+
if jruby?
170+
"es=#{Elasticsearch::VERSION},rb=#{RUBY_VERSION},t=#{Elasticsearch::Transport::VERSION},jv=#{ENV_JAVA['java.version']},jr=#{JRUBY_VERSION}"
171+
else
172+
"es=#{Elasticsearch::VERSION},rb=#{RUBY_VERSION},t=#{Elasticsearch::Transport::VERSION}"
173+
end
174+
end
175+
176+
it 'doesnae set any info about the implementation in the metaheader' do
177+
expect(subject['x-elastic-client-meta']).to match(regexp)
178+
expect(subject).to include('x-elastic-client-meta' => meta_header)
179+
end
180+
end
181+
182+
context 'when using a different service version' do
183+
before do
184+
module Elastic
185+
META_HEADER_SERVICE_VERSION = [:ent, '8.0.0']
186+
end
187+
end
188+
189+
after do
190+
module Elastic
191+
META_HEADER_SERVICE_VERSION = [:es, Elasticsearch::VERSION]
192+
end
193+
end
194+
195+
let(:client) { Elasticsearch::Client.new }
196+
197+
it 'sets the service version in the metaheader' do
198+
expect(subject['x-elastic-client-meta']).to match(regexp)
199+
expect(subject['x-elastic-client-meta']).to start_with('ent=8.0.0')
200+
end
201+
end
202+
end
203+
end

elasticsearch-transport/spec/spec_helper.rb

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,13 @@
88
require 'ansi/code'
99
require 'hashie/mash'
1010
require 'pry-nav'
11+
if defined?(JRUBY_VERSION)
12+
require 'elasticsearch/transport/transport/http/manticore'
13+
require 'pry-nav'
14+
else
15+
require 'elasticsearch/transport/transport/http/curb'
16+
require 'curb'
17+
end
1118

1219
# The hosts to use for creating a elasticsearch client.
1320
#

0 commit comments

Comments
 (0)