elasticsearch-transport 7.1.0 → 7.13.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Gemfile +13 -9
- data/{LICENSE.txt → LICENSE} +0 -0
- data/README.md +175 -76
- data/Rakefile +1 -1
- data/elasticsearch-transport.gemspec +42 -60
- data/lib/elasticsearch/transport/client.rb +154 -57
- data/lib/elasticsearch/transport/meta_header.rb +135 -0
- data/lib/elasticsearch/transport/redacted.rb +1 -1
- data/lib/elasticsearch/transport/transport/base.rb +93 -18
- data/lib/elasticsearch/transport/transport/connections/collection.rb +3 -6
- data/lib/elasticsearch/transport/transport/connections/connection.rb +8 -6
- data/lib/elasticsearch/transport/transport/connections/selector.rb +18 -6
- data/lib/elasticsearch/transport/transport/errors.rb +1 -1
- data/lib/elasticsearch/transport/transport/http/curb.rb +26 -9
- data/lib/elasticsearch/transport/transport/http/faraday.rb +27 -5
- data/lib/elasticsearch/transport/transport/http/manticore.rb +25 -10
- data/lib/elasticsearch/transport/transport/loggable.rb +1 -1
- data/lib/elasticsearch/transport/transport/response.rb +1 -2
- data/lib/elasticsearch/transport/transport/serializer/multi_json.rb +1 -1
- data/lib/elasticsearch/transport/transport/sniffer.rb +20 -12
- data/lib/elasticsearch/transport/version.rb +2 -2
- data/lib/elasticsearch/transport.rb +1 -1
- data/lib/elasticsearch-transport.rb +1 -1
- data/spec/elasticsearch/connections/collection_spec.rb +266 -0
- data/spec/elasticsearch/connections/selector_spec.rb +174 -0
- data/spec/elasticsearch/transport/base_spec.rb +197 -13
- data/spec/elasticsearch/transport/client_spec.rb +945 -118
- data/spec/elasticsearch/transport/meta_header_spec.rb +265 -0
- data/spec/elasticsearch/transport/sniffer_spec.rb +1 -14
- data/spec/spec_helper.rb +25 -1
- data/test/integration/transport_test.rb +15 -2
- data/test/profile/client_benchmark_test.rb +1 -1
- data/test/test_helper.rb +1 -1
- data/test/unit/connection_test.rb +8 -3
- data/test/unit/response_test.rb +2 -2
- data/test/unit/serializer_test.rb +1 -1
- data/test/unit/transport_base_test.rb +2 -2
- data/test/unit/transport_curb_test.rb +2 -2
- data/test/unit/transport_faraday_test.rb +3 -3
- data/test/unit/transport_manticore_test.rb +30 -14
- metadata +87 -60
- data/test/unit/connection_collection_test.rb +0 -147
- data/test/unit/connection_selector_test.rb +0 -81
@@ -6,7 +6,7 @@
|
|
6
6
|
# not use this file except in compliance with the License.
|
7
7
|
# You may obtain a copy of the License at
|
8
8
|
#
|
9
|
-
#
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
10
|
#
|
11
11
|
# Unless required by applicable law or agreed to in writing,
|
12
12
|
# software distributed under the License is distributed on an
|
@@ -15,6 +15,9 @@
|
|
15
15
|
# specific language governing permissions and limitations
|
16
16
|
# under the License.
|
17
17
|
|
18
|
+
require 'base64'
|
19
|
+
require 'elasticsearch/transport/meta_header'
|
20
|
+
|
18
21
|
module Elasticsearch
|
19
22
|
module Transport
|
20
23
|
|
@@ -23,6 +26,7 @@ module Elasticsearch
|
|
23
26
|
# See {file:README.md README} for usage and code examples.
|
24
27
|
#
|
25
28
|
class Client
|
29
|
+
include MetaHeader
|
26
30
|
DEFAULT_TRANSPORT_CLASS = Transport::HTTP::Faraday
|
27
31
|
|
28
32
|
DEFAULT_LOGGER = lambda do
|
@@ -46,6 +50,17 @@ module Elasticsearch
|
|
46
50
|
# @since 7.0.0
|
47
51
|
DEFAULT_HOST = 'localhost:9200'.freeze
|
48
52
|
|
53
|
+
# The default port to use if connecting using a Cloud ID.
|
54
|
+
# Updated from 9243 to 443 in client version 7.10.1
|
55
|
+
#
|
56
|
+
# @since 7.2.0
|
57
|
+
DEFAULT_CLOUD_PORT = 443
|
58
|
+
|
59
|
+
# The default port to use if not otherwise specified.
|
60
|
+
#
|
61
|
+
# @since 7.2.0
|
62
|
+
DEFAULT_PORT = 9200
|
63
|
+
|
49
64
|
# Returns the transport object.
|
50
65
|
#
|
51
66
|
# @see Elasticsearch::Transport::Transport::Base
|
@@ -101,6 +116,17 @@ module Elasticsearch
|
|
101
116
|
#
|
102
117
|
# @option arguments [String] :send_get_body_as Specify the HTTP method to use for GET requests with a body.
|
103
118
|
# (Default: GET)
|
119
|
+
# @option arguments [true, false] :compression Whether to compress requests. Gzip compression will be used.
|
120
|
+
# The default is false. Responses will automatically be inflated if they are compressed.
|
121
|
+
# If a custom transport object is used, it must handle the request compression and response inflation.
|
122
|
+
#
|
123
|
+
# @option api_key [String, Hash] :api_key Use API Key Authentication, either the base64 encoding of `id` and `api_key`
|
124
|
+
# joined by a colon as a String, or a hash with the `id` and `api_key` values.
|
125
|
+
# @option opaque_id_prefix [String] :opaque_id_prefix set a prefix for X-Opaque-Id when initializing the client.
|
126
|
+
# This will be prepended to the id you set before each request
|
127
|
+
# if you're using X-Opaque-Id
|
128
|
+
# @option enable_meta_header [Boolean] :enable_meta_header Enable sending the meta data header to Cloud.
|
129
|
+
# (Default: true)
|
104
130
|
#
|
105
131
|
# @yield [faraday] Access and configure the `Faraday::Connection` instance directly with a block
|
106
132
|
#
|
@@ -115,53 +141,109 @@ module Elasticsearch
|
|
115
141
|
@arguments[:randomize_hosts] ||= false
|
116
142
|
@arguments[:transport_options] ||= {}
|
117
143
|
@arguments[:http] ||= {}
|
118
|
-
@
|
144
|
+
@arguments[:enable_meta_header] = arguments.fetch(:enable_meta_header) { true }
|
145
|
+
@options[:http] ||= {}
|
146
|
+
|
147
|
+
set_api_key if (@api_key = @arguments[:api_key])
|
148
|
+
set_compatibility_header if ENV['ELASTIC_CLIENT_APIVERSIONING']
|
119
149
|
|
120
|
-
@seeds =
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
150
|
+
@seeds = extract_cloud_creds(@arguments)
|
151
|
+
@seeds ||= __extract_hosts(@arguments[:hosts] ||
|
152
|
+
@arguments[:host] ||
|
153
|
+
@arguments[:url] ||
|
154
|
+
@arguments[:urls] ||
|
155
|
+
ENV['ELASTICSEARCH_URL'] ||
|
156
|
+
DEFAULT_HOST)
|
126
157
|
|
127
158
|
@send_get_body_as = @arguments[:send_get_body_as] || 'GET'
|
159
|
+
@opaque_id_prefix = @arguments[:opaque_id_prefix] || nil
|
128
160
|
|
129
161
|
if @arguments[:request_timeout]
|
130
|
-
@arguments[:transport_options][:request] = { :
|
131
|
-
end
|
132
|
-
|
133
|
-
@arguments[:transport_options][:headers] ||= {}
|
134
|
-
|
135
|
-
unless @arguments[:transport_options][:headers].keys.any? {|k| k.to_s.downcase =~ /content\-?\_?type/}
|
136
|
-
@arguments[:transport_options][:headers]['Content-Type'] = 'application/json'
|
162
|
+
@arguments[:transport_options][:request] = { timeout: @arguments[:request_timeout] }
|
137
163
|
end
|
138
164
|
|
139
165
|
if @arguments[:transport]
|
140
166
|
@transport = @arguments[:transport]
|
141
167
|
else
|
142
|
-
transport_class
|
143
|
-
if transport_class == Transport::HTTP::Faraday
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
168
|
+
@transport_class = @arguments[:transport_class] || DEFAULT_TRANSPORT_CLASS
|
169
|
+
@transport = if @transport_class == Transport::HTTP::Faraday
|
170
|
+
@arguments[:adapter] ||= __auto_detect_adapter
|
171
|
+
set_meta_header # from include MetaHeader
|
172
|
+
@transport_class.new(hosts: @seeds, options: @arguments) do |faraday|
|
173
|
+
faraday.adapter(@arguments[:adapter])
|
174
|
+
block&.call faraday
|
175
|
+
end
|
176
|
+
else
|
177
|
+
set_meta_header # from include MetaHeader
|
178
|
+
@transport_class.new(hosts: @seeds, options: @arguments)
|
179
|
+
end
|
153
180
|
end
|
154
181
|
end
|
155
182
|
|
156
183
|
# Performs a request through delegation to {#transport}.
|
157
184
|
#
|
158
|
-
def perform_request(method, path, params={}, body=nil, headers=nil)
|
185
|
+
def perform_request(method, path, params = {}, body = nil, headers = nil)
|
159
186
|
method = @send_get_body_as if 'GET' == method && body
|
187
|
+
if (opaque_id = params.delete(:opaque_id))
|
188
|
+
headers = {} if headers.nil?
|
189
|
+
opaque_id = @opaque_id_prefix ? "#{@opaque_id_prefix}#{opaque_id}" : opaque_id
|
190
|
+
headers.merge!('X-Opaque-Id' => opaque_id)
|
191
|
+
end
|
160
192
|
transport.perform_request(method, path, params, body, headers)
|
161
193
|
end
|
162
194
|
|
163
195
|
private
|
164
196
|
|
197
|
+
def set_api_key
|
198
|
+
@api_key = __encode(@api_key) if @api_key.is_a? Hash
|
199
|
+
add_header('Authorization' => "ApiKey #{@api_key}")
|
200
|
+
@arguments.delete(:user)
|
201
|
+
@arguments.delete(:password)
|
202
|
+
end
|
203
|
+
|
204
|
+
def set_compatibility_header
|
205
|
+
return unless ['1', 'true'].include?(ENV['ELASTIC_CLIENT_APIVERSIONING'])
|
206
|
+
|
207
|
+
add_header(
|
208
|
+
{
|
209
|
+
'Accept' => 'application/vnd.elasticsearch+json;compatible-with=7',
|
210
|
+
'Content-Type' => 'application/vnd.elasticsearch+json; compatible-with=7'
|
211
|
+
}
|
212
|
+
)
|
213
|
+
end
|
214
|
+
|
215
|
+
def add_header(header)
|
216
|
+
headers = @arguments[:transport_options]&.[](:headers) || {}
|
217
|
+
headers.merge!(header)
|
218
|
+
@arguments[:transport_options].merge!(
|
219
|
+
headers: headers
|
220
|
+
)
|
221
|
+
end
|
222
|
+
|
223
|
+
def extract_cloud_creds(arguments)
|
224
|
+
return unless arguments[:cloud_id] && !arguments[:cloud_id].empty?
|
225
|
+
|
226
|
+
name = arguments[:cloud_id].split(':')[0]
|
227
|
+
cloud_url, elasticsearch_instance = Base64.decode64(arguments[:cloud_id].gsub("#{name}:", '')).split('$')
|
228
|
+
|
229
|
+
if cloud_url.include?(':')
|
230
|
+
url, port = cloud_url.split(':')
|
231
|
+
host = "#{elasticsearch_instance}.#{url}"
|
232
|
+
else
|
233
|
+
host = "#{elasticsearch_instance}.#{cloud_url}"
|
234
|
+
port = arguments[:port] || DEFAULT_CLOUD_PORT
|
235
|
+
end
|
236
|
+
[
|
237
|
+
{
|
238
|
+
scheme: 'https',
|
239
|
+
user: arguments[:user],
|
240
|
+
password: arguments[:password],
|
241
|
+
host: host,
|
242
|
+
port: port.to_i
|
243
|
+
}
|
244
|
+
]
|
245
|
+
end
|
246
|
+
|
165
247
|
# Normalizes and returns hosts configuration.
|
166
248
|
#
|
167
249
|
# Arrayifies the `hosts_config` argument and extracts `host` and `port` info from strings.
|
@@ -192,39 +274,47 @@ module Elasticsearch
|
|
192
274
|
|
193
275
|
def __parse_host(host)
|
194
276
|
host_parts = case host
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
277
|
+
when String
|
278
|
+
if host =~ /^[a-z]+\:\/\//
|
279
|
+
# Construct a new `URI::Generic` directly from the array returned by URI::split.
|
280
|
+
# This avoids `URI::HTTP` and `URI::HTTPS`, which supply default ports.
|
281
|
+
uri = URI::Generic.new(*URI.split(host))
|
282
|
+
default_port = uri.scheme == 'https' ? 443 : DEFAULT_PORT
|
283
|
+
{
|
284
|
+
scheme: uri.scheme,
|
285
|
+
user: uri.user,
|
286
|
+
password: uri.password,
|
287
|
+
host: uri.host,
|
288
|
+
path: uri.path,
|
289
|
+
port: uri.port || default_port
|
290
|
+
}
|
291
|
+
else
|
292
|
+
host, port = host.split(':')
|
293
|
+
{ host: host, port: port }
|
294
|
+
end
|
295
|
+
when URI
|
296
|
+
{
|
297
|
+
scheme: host.scheme,
|
298
|
+
user: host.user,
|
299
|
+
password: host.password,
|
300
|
+
host: host.host,
|
301
|
+
path: host.path,
|
302
|
+
port: host.port
|
303
|
+
}
|
304
|
+
when Hash
|
305
|
+
host
|
306
|
+
else
|
307
|
+
raise ArgumentError, "Please pass host as a String, URI or Hash -- #{host.class} given."
|
308
|
+
end
|
309
|
+
if @api_key
|
310
|
+
# Remove Basic Auth if using API KEY
|
311
|
+
host_parts.delete(:user)
|
312
|
+
host_parts.delete(:password)
|
221
313
|
else
|
222
|
-
|
314
|
+
@options[:http][:user] ||= host_parts[:user]
|
315
|
+
@options[:http][:password] ||= host_parts[:password]
|
223
316
|
end
|
224
317
|
|
225
|
-
@options[:http][:user] ||= host_parts[:user]
|
226
|
-
@options[:http][:password] ||= host_parts[:password]
|
227
|
-
|
228
318
|
host_parts[:port] = host_parts[:port].to_i if host_parts[:port]
|
229
319
|
host_parts[:path].chomp!('/') if host_parts[:path]
|
230
320
|
host_parts
|
@@ -252,6 +342,13 @@ module Elasticsearch
|
|
252
342
|
::Faraday.default_adapter
|
253
343
|
end
|
254
344
|
end
|
345
|
+
|
346
|
+
# Encode credentials for the Authorization Header
|
347
|
+
# Credentials is the base64 encoding of id and api_key joined by a colon
|
348
|
+
# @see https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-create-api-key.html
|
349
|
+
def __encode(api_key)
|
350
|
+
Base64.strict_encode64([api_key[:id], api_key[:api_key]].join(':'))
|
351
|
+
end
|
255
352
|
end
|
256
353
|
end
|
257
354
|
end
|
@@ -0,0 +1,135 @@
|
|
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 'base64'
|
19
|
+
|
20
|
+
module Elasticsearch
|
21
|
+
module Transport
|
22
|
+
# Methods for the Elastic meta header used by Cloud.
|
23
|
+
# X-Elastic-Client-Meta HTTP header which is used by Elastic Cloud and can be disabled when
|
24
|
+
# instantiating the Client with the :enable_meta_header parameter set to `false`.
|
25
|
+
#
|
26
|
+
module MetaHeader
|
27
|
+
def set_meta_header
|
28
|
+
return if @arguments[:enable_meta_header] == false
|
29
|
+
|
30
|
+
service, version = meta_header_service_version
|
31
|
+
|
32
|
+
meta_headers = {
|
33
|
+
service.to_sym => version,
|
34
|
+
rb: RUBY_VERSION,
|
35
|
+
t: Elasticsearch::Transport::VERSION
|
36
|
+
}
|
37
|
+
meta_headers.merge!(meta_header_engine) if meta_header_engine
|
38
|
+
meta_headers.merge!(meta_header_adapter) if meta_header_adapter
|
39
|
+
|
40
|
+
add_header({ 'x-elastic-client-meta' => meta_headers.map { |k, v| "#{k}=#{v}" }.join(',') })
|
41
|
+
end
|
42
|
+
|
43
|
+
def meta_header_service_version
|
44
|
+
if enterprise_search?
|
45
|
+
Elastic::ENTERPRISE_SERVICE_VERSION
|
46
|
+
elsif elasticsearch?
|
47
|
+
Elastic::ELASTICSEARCH_SERVICE_VERSION
|
48
|
+
elsif defined?(Elasticsearch::VERSION)
|
49
|
+
[:es, client_meta_version(Elasticsearch::VERSION)]
|
50
|
+
else
|
51
|
+
[:es, client_meta_version(Elasticsearch::Transport::VERSION)]
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def enterprise_search?
|
56
|
+
defined?(Elastic::ENTERPRISE_SERVICE_VERSION) &&
|
57
|
+
called_from?('enterprise-search-ruby')
|
58
|
+
end
|
59
|
+
|
60
|
+
def elasticsearch?
|
61
|
+
defined?(Elastic::ELASTICSEARCH_SERVICE_VERSION) &&
|
62
|
+
called_from?('elasticsearch')
|
63
|
+
end
|
64
|
+
|
65
|
+
def called_from?(service)
|
66
|
+
!caller.select { |c| c.match?(service) }.empty?
|
67
|
+
end
|
68
|
+
|
69
|
+
# We return the current version if it's a release, but if it's a pre/alpha/beta release we
|
70
|
+
# return <VERSION_NUMBER>p
|
71
|
+
#
|
72
|
+
def client_meta_version(version)
|
73
|
+
regexp = /^([0-9]+\.[0-9]+\.[0-9]+)(\.?[a-z0-9.-]+)?$/
|
74
|
+
match = version.match(regexp)
|
75
|
+
return "#{match[1]}p" if (match[2])
|
76
|
+
|
77
|
+
version
|
78
|
+
end
|
79
|
+
|
80
|
+
def meta_header_engine
|
81
|
+
case RUBY_ENGINE
|
82
|
+
when 'ruby'
|
83
|
+
{}
|
84
|
+
when 'jruby'
|
85
|
+
{ jv: ENV_JAVA['java.version'], jr: JRUBY_VERSION }
|
86
|
+
when 'rbx'
|
87
|
+
{ rbx: RUBY_VERSION }
|
88
|
+
else
|
89
|
+
{ RUBY_ENGINE.to_sym => RUBY_VERSION }
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
# This function tries to define the version for the Faraday adapter. If it hasn't been loaded
|
94
|
+
# by the time we're calling this method, it's going to report the adapter (if we know it) but
|
95
|
+
# return 0 as the version. It won't report anything when using a custom adapter we don't
|
96
|
+
# identify.
|
97
|
+
#
|
98
|
+
# Returns a Hash<adapter_alias, version>
|
99
|
+
#
|
100
|
+
def meta_header_adapter
|
101
|
+
if @transport_class == Transport::HTTP::Faraday
|
102
|
+
version = '0'
|
103
|
+
adapter_version = case @arguments[:adapter]
|
104
|
+
when :patron
|
105
|
+
version = Patron::VERSION if defined?(::Patron::VERSION)
|
106
|
+
{pt: version}
|
107
|
+
when :net_http
|
108
|
+
version = if defined?(Net::HTTP::VERSION)
|
109
|
+
Net::HTTP::VERSION
|
110
|
+
elsif defined?(Net::HTTP::HTTPVersion)
|
111
|
+
Net::HTTP::HTTPVersion
|
112
|
+
end
|
113
|
+
{nh: version}
|
114
|
+
when :typhoeus
|
115
|
+
version = Typhoeus::VERSION if defined?(::Typhoeus::VERSION)
|
116
|
+
{ty: version}
|
117
|
+
when :httpclient
|
118
|
+
version = HTTPClient::VERSION if defined?(HTTPClient::VERSION)
|
119
|
+
{hc: version}
|
120
|
+
when :net_http_persistent
|
121
|
+
version = Net::HTTP::Persistent::VERSION if defined?(Net::HTTP::Persistent::VERSION)
|
122
|
+
{np: version}
|
123
|
+
else
|
124
|
+
{}
|
125
|
+
end
|
126
|
+
{fd: Faraday::VERSION}.merge(adapter_version)
|
127
|
+
elsif defined?(Transport::HTTP::Curb) && @transport_class == Transport::HTTP::Curb
|
128
|
+
{cl: Curl::CURB_VERSION}
|
129
|
+
elsif defined?(Transport::HTTP::Manticore) && @transport_class == Transport::HTTP::Manticore
|
130
|
+
{mc: Manticore::VERSION}
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
@@ -6,7 +6,7 @@
|
|
6
6
|
# not use this file except in compliance with the License.
|
7
7
|
# You may obtain a copy of the License at
|
8
8
|
#
|
9
|
-
#
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
10
|
#
|
11
11
|
# Unless required by applicable law or agreed to in writing,
|
12
12
|
# software distributed under the License is distributed on an
|
@@ -6,7 +6,7 @@
|
|
6
6
|
# not use this file except in compliance with the License.
|
7
7
|
# You may obtain a copy of the License at
|
8
8
|
#
|
9
|
-
#
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
10
|
#
|
11
11
|
# Unless required by applicable law or agreed to in writing,
|
12
12
|
# software distributed under the License is distributed on an
|
@@ -35,7 +35,7 @@ module Elasticsearch
|
|
35
35
|
attr_reader :hosts, :options, :connections, :counter, :last_request_at, :protocol
|
36
36
|
attr_accessor :serializer, :sniffer, :logger, :tracer,
|
37
37
|
:reload_connections, :reload_after,
|
38
|
-
:resurrect_after
|
38
|
+
:resurrect_after
|
39
39
|
|
40
40
|
# Creates a new transport object
|
41
41
|
#
|
@@ -47,7 +47,7 @@ module Elasticsearch
|
|
47
47
|
#
|
48
48
|
# @see Client#initialize
|
49
49
|
#
|
50
|
-
def initialize(arguments={}, &block)
|
50
|
+
def initialize(arguments = {}, &block)
|
51
51
|
@state_mutex = Mutex.new
|
52
52
|
|
53
53
|
@hosts = arguments[:hosts] || []
|
@@ -56,6 +56,7 @@ module Elasticsearch
|
|
56
56
|
@options[:retry_on_status] ||= []
|
57
57
|
|
58
58
|
@block = block
|
59
|
+
@compression = !!@options[:compression]
|
59
60
|
@connections = __build_connections
|
60
61
|
|
61
62
|
@serializer = options[:serializer] || ( options[:serializer_class] ? options[:serializer_class].new(self) : DEFAULT_SERIALIZER_CLASS.new(self) )
|
@@ -71,7 +72,6 @@ module Elasticsearch
|
|
71
72
|
@reload_connections = options[:reload_connections]
|
72
73
|
@reload_after = options[:reload_connections].is_a?(Integer) ? options[:reload_connections] : DEFAULT_RELOAD_AFTER
|
73
74
|
@resurrect_after = options[:resurrect_after] || DEFAULT_RESURRECT_AFTER
|
74
|
-
@max_retries = options[:retry_on_failure].is_a?(Integer) ? options[:retry_on_failure] : DEFAULT_MAX_RETRIES
|
75
75
|
@retry_on_status = Array(options[:retry_on_status]).map { |d| d.to_i }
|
76
76
|
end
|
77
77
|
|
@@ -202,7 +202,7 @@ module Elasticsearch
|
|
202
202
|
( params.empty? ? '' : "&#{::Faraday::Utils::ParamsHash[params].to_query}" )
|
203
203
|
trace_body = body ? " -d '#{__convert_to_json(body, :pretty => true)}'" : ''
|
204
204
|
trace_command = "curl -X #{method.to_s.upcase}"
|
205
|
-
trace_command += " -H '#{headers.
|
205
|
+
trace_command += " -H '#{headers.collect { |k,v| "#{k}: #{v}" }.join(", ")}'" if headers && !headers.empty?
|
206
206
|
trace_command += " '#{trace_url}'#{trace_body}\n"
|
207
207
|
tracer.info trace_command
|
208
208
|
tracer.debug "# #{Time.now.iso8601} [#{response.status}] (#{format('%.3f', duration)}s)\n#"
|
@@ -234,8 +234,9 @@ module Elasticsearch
|
|
234
234
|
def __full_url(host)
|
235
235
|
url = "#{host[:protocol]}://"
|
236
236
|
url += "#{CGI.escape(host[:user])}:#{CGI.escape(host[:password])}@" if host[:user]
|
237
|
-
url +=
|
238
|
-
url += "
|
237
|
+
url += host[:host]
|
238
|
+
url += ":#{host[:port]}" if host[:port]
|
239
|
+
url += host[:path] if host[:path]
|
239
240
|
url
|
240
241
|
end
|
241
242
|
|
@@ -257,10 +258,18 @@ module Elasticsearch
|
|
257
258
|
# @raise [ServerError] If request failed on server
|
258
259
|
# @raise [Error] If no connection is available
|
259
260
|
#
|
260
|
-
def perform_request(method, path, params={}, body=nil, headers=nil, &block)
|
261
|
-
raise NoMethodError,
|
261
|
+
def perform_request(method, path, params = {}, body = nil, headers = nil, opts = {}, &block)
|
262
|
+
raise NoMethodError, 'Implement this method in your transport class' unless block_given?
|
263
|
+
|
262
264
|
start = Time.now
|
263
265
|
tries = 0
|
266
|
+
reload_on_failure = opts.fetch(:reload_on_failure, @options[:reload_on_failure])
|
267
|
+
|
268
|
+
max_retries = if opts.key?(:retry_on_failure)
|
269
|
+
opts[:retry_on_failure] === true ? DEFAULT_MAX_RETRIES : opts[:retry_on_failure]
|
270
|
+
elsif options.key?(:retry_on_failure)
|
271
|
+
options[:retry_on_failure] === true ? DEFAULT_MAX_RETRIES : options[:retry_on_failure]
|
272
|
+
end
|
264
273
|
|
265
274
|
params = params.clone
|
266
275
|
|
@@ -268,15 +277,15 @@ module Elasticsearch
|
|
268
277
|
|
269
278
|
begin
|
270
279
|
tries += 1
|
271
|
-
connection = get_connection or raise Error.new(
|
280
|
+
connection = get_connection or raise Error.new('Cannot get new connection from pool.')
|
272
281
|
|
273
282
|
if connection.connection.respond_to?(:params) && connection.connection.params.respond_to?(:to_hash)
|
274
283
|
params = connection.connection.params.merge(params.to_hash)
|
275
284
|
end
|
276
285
|
|
277
|
-
url
|
286
|
+
url = connection.full_url(path, params)
|
278
287
|
|
279
|
-
response
|
288
|
+
response = block.call(connection, url)
|
280
289
|
|
281
290
|
connection.healthy! if connection.failures > 0
|
282
291
|
|
@@ -286,7 +295,7 @@ module Elasticsearch
|
|
286
295
|
rescue Elasticsearch::Transport::Transport::ServerError => e
|
287
296
|
if response && @retry_on_status.include?(response.status)
|
288
297
|
log_warn "[#{e.class}] Attempt #{tries} to get response from #{url}"
|
289
|
-
if tries <= max_retries
|
298
|
+
if tries <= (max_retries || DEFAULT_MAX_RETRIES)
|
290
299
|
retry
|
291
300
|
else
|
292
301
|
log_fatal "[#{e.class}] Cannot get response from #{url} after #{tries} tries"
|
@@ -301,12 +310,12 @@ module Elasticsearch
|
|
301
310
|
|
302
311
|
connection.dead!
|
303
312
|
|
304
|
-
if
|
313
|
+
if reload_on_failure and tries < connections.all.size
|
305
314
|
log_warn "[#{e.class}] Reloading connections (attempt #{tries} of #{connections.all.size})"
|
306
315
|
reload_connections! and retry
|
307
316
|
end
|
308
317
|
|
309
|
-
if
|
318
|
+
if max_retries
|
310
319
|
log_warn "[#{e.class}] Attempt #{tries} connecting to #{connection.host.inspect}"
|
311
320
|
if tries <= max_retries
|
312
321
|
retry
|
@@ -328,7 +337,7 @@ module Elasticsearch
|
|
328
337
|
|
329
338
|
if response.status.to_i >= 300
|
330
339
|
__log_response method, path, params, body, url, response, nil, 'N/A', duration
|
331
|
-
__trace method, path, params, headers, body, url, response, nil, 'N/A', duration if tracer
|
340
|
+
__trace method, path, params, connection.connection.headers, body, url, response, nil, 'N/A', duration if tracer
|
332
341
|
|
333
342
|
# Log the failure only when `ignore` doesn't match the response status
|
334
343
|
unless ignore.include?(response.status.to_i)
|
@@ -345,7 +354,9 @@ module Elasticsearch
|
|
345
354
|
__log_response method, path, params, body, url, response, json, took, duration
|
346
355
|
end
|
347
356
|
|
348
|
-
__trace
|
357
|
+
__trace method, path, params, connection.connection.headers, body, url, response, nil, 'N/A', duration if tracer
|
358
|
+
|
359
|
+
warnings(response.headers['warning']) if response.headers&.[]('warning')
|
349
360
|
|
350
361
|
Response.new response.status, json || response.body, response.headers
|
351
362
|
ensure
|
@@ -360,7 +371,71 @@ module Elasticsearch
|
|
360
371
|
def host_unreachable_exceptions
|
361
372
|
[Errno::ECONNREFUSED]
|
362
373
|
end
|
374
|
+
|
375
|
+
private
|
376
|
+
|
377
|
+
USER_AGENT_STR = 'User-Agent'.freeze
|
378
|
+
USER_AGENT_REGEX = /user\-?\_?agent/
|
379
|
+
CONTENT_TYPE_STR = 'Content-Type'.freeze
|
380
|
+
CONTENT_TYPE_REGEX = /content\-?\_?type/
|
381
|
+
DEFAULT_CONTENT_TYPE = 'application/json'.freeze
|
382
|
+
GZIP = 'gzip'.freeze
|
383
|
+
ACCEPT_ENCODING = 'Accept-Encoding'.freeze
|
384
|
+
GZIP_FIRST_TWO_BYTES = '1f8b'.freeze
|
385
|
+
HEX_STRING_DIRECTIVE = 'H*'.freeze
|
386
|
+
RUBY_ENCODING = '1.9'.respond_to?(:force_encoding)
|
387
|
+
|
388
|
+
def decompress_response(body)
|
389
|
+
return body unless use_compression?
|
390
|
+
return body unless gzipped?(body)
|
391
|
+
|
392
|
+
io = StringIO.new(body)
|
393
|
+
gzip_reader = if RUBY_ENCODING
|
394
|
+
Zlib::GzipReader.new(io, :encoding => 'ASCII-8BIT')
|
395
|
+
else
|
396
|
+
Zlib::GzipReader.new(io)
|
397
|
+
end
|
398
|
+
gzip_reader.read
|
399
|
+
end
|
400
|
+
|
401
|
+
def gzipped?(body)
|
402
|
+
body[0..1].unpack(HEX_STRING_DIRECTIVE)[0] == GZIP_FIRST_TWO_BYTES
|
403
|
+
end
|
404
|
+
|
405
|
+
def use_compression?
|
406
|
+
@compression
|
407
|
+
end
|
408
|
+
|
409
|
+
def apply_headers(client, options)
|
410
|
+
headers = options[:headers] || {}
|
411
|
+
headers[CONTENT_TYPE_STR] = find_value(headers, CONTENT_TYPE_REGEX) || DEFAULT_CONTENT_TYPE
|
412
|
+
headers[USER_AGENT_STR] = find_value(headers, USER_AGENT_REGEX) || user_agent_header(client)
|
413
|
+
client.headers[ACCEPT_ENCODING] = GZIP if use_compression?
|
414
|
+
client.headers.merge!(headers)
|
415
|
+
end
|
416
|
+
|
417
|
+
def find_value(hash, regex)
|
418
|
+
key_value = hash.find { |k,v| k.to_s.downcase =~ regex }
|
419
|
+
if key_value
|
420
|
+
hash.delete(key_value[0])
|
421
|
+
key_value[1]
|
422
|
+
end
|
423
|
+
end
|
424
|
+
|
425
|
+
def user_agent_header(client)
|
426
|
+
@user_agent ||= begin
|
427
|
+
meta = ["RUBY_VERSION: #{RUBY_VERSION}"]
|
428
|
+
if RbConfig::CONFIG && RbConfig::CONFIG['host_os']
|
429
|
+
meta << "#{RbConfig::CONFIG['host_os'].split('_').first[/[a-z]+/i].downcase} #{RbConfig::CONFIG['target_cpu']}"
|
430
|
+
end
|
431
|
+
"elasticsearch-ruby/#{VERSION} (#{meta.join('; ')})"
|
432
|
+
end
|
433
|
+
end
|
434
|
+
|
435
|
+
def warnings(warning)
|
436
|
+
warn("warning: #{warning}")
|
437
|
+
end
|
363
438
|
end
|
364
439
|
end
|
365
440
|
end
|
366
|
-
end
|
441
|
+
end
|
@@ -6,7 +6,7 @@
|
|
6
6
|
# not use this file except in compliance with the License.
|
7
7
|
# You may obtain a copy of the License at
|
8
8
|
#
|
9
|
-
#
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
10
|
#
|
11
11
|
# Unless required by applicable law or agreed to in writing,
|
12
12
|
# software distributed under the License is distributed on an
|
@@ -78,16 +78,13 @@ module Elasticsearch
|
|
78
78
|
|
79
79
|
# Returns a connection.
|
80
80
|
#
|
81
|
-
# If there are no alive connections,
|
81
|
+
# If there are no alive connections, returns a connection with least failures.
|
82
82
|
# Delegates to selector's `#select` method to get the connection.
|
83
83
|
#
|
84
84
|
# @return [Connection]
|
85
85
|
#
|
86
86
|
def get_connection(options={})
|
87
|
-
|
88
|
-
dead_connection.alive!
|
89
|
-
end
|
90
|
-
selector.select(options)
|
87
|
+
selector.select(options) || @connections.min_by(&:failures)
|
91
88
|
end
|
92
89
|
|
93
90
|
def each(&block)
|