elasticsearch-transport 7.9.0 → 7.17.10

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.
Files changed (37) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +10 -10
  3. data/Gemfile-faraday1.gemfile +47 -0
  4. data/README.md +23 -17
  5. data/Rakefile +47 -10
  6. data/elasticsearch-transport.gemspec +16 -18
  7. data/lib/elasticsearch/transport/client.rb +133 -56
  8. data/lib/elasticsearch/transport/meta_header.rb +135 -0
  9. data/lib/elasticsearch/transport/transport/base.rb +41 -22
  10. data/lib/elasticsearch/transport/transport/connections/collection.rb +2 -5
  11. data/lib/elasticsearch/transport/transport/connections/connection.rb +6 -4
  12. data/lib/elasticsearch/transport/transport/errors.rb +1 -0
  13. data/lib/elasticsearch/transport/transport/http/curb.rb +44 -32
  14. data/lib/elasticsearch/transport/transport/http/faraday.rb +15 -5
  15. data/lib/elasticsearch/transport/transport/http/manticore.rb +41 -29
  16. data/lib/elasticsearch/transport/transport/response.rb +1 -1
  17. data/lib/elasticsearch/transport/version.rb +1 -1
  18. data/lib/elasticsearch/transport.rb +19 -30
  19. data/spec/elasticsearch/connections/collection_spec.rb +12 -0
  20. data/spec/elasticsearch/transport/base_spec.rb +90 -33
  21. data/spec/elasticsearch/transport/client_spec.rb +569 -164
  22. data/spec/elasticsearch/transport/http/curb_spec.rb +126 -0
  23. data/spec/elasticsearch/transport/http/faraday_spec.rb +141 -0
  24. data/spec/elasticsearch/transport/http/manticore_spec.rb +161 -0
  25. data/spec/elasticsearch/transport/meta_header_spec.rb +301 -0
  26. data/spec/spec_helper.rb +13 -5
  27. data/test/integration/jruby_test.rb +43 -0
  28. data/test/integration/transport_test.rb +93 -43
  29. data/test/test_helper.rb +10 -22
  30. data/test/unit/adapters_test.rb +88 -0
  31. data/test/unit/connection_test.rb +7 -2
  32. data/test/unit/response_test.rb +1 -1
  33. data/test/unit/transport_base_test.rb +17 -8
  34. data/test/unit/transport_curb_test.rb +0 -1
  35. data/test/unit/transport_faraday_test.rb +2 -2
  36. data/test/unit/transport_manticore_test.rb +242 -155
  37. metadata +62 -84
@@ -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
@@ -18,7 +18,6 @@
18
18
  module Elasticsearch
19
19
  module Transport
20
20
  module Transport
21
-
22
21
  # @abstract Module with common functionality for transport implementations.
23
22
  #
24
23
  module Base
@@ -54,6 +53,7 @@ module Elasticsearch
54
53
  @options = arguments[:options] || {}
55
54
  @options[:http] ||= {}
56
55
  @options[:retry_on_status] ||= []
56
+ @options[:delay_on_retry] ||= 0
57
57
 
58
58
  @block = block
59
59
  @compression = !!@options[:compression]
@@ -223,7 +223,7 @@ module Elasticsearch
223
223
  # @api private
224
224
  #
225
225
  def __convert_to_json(o=nil, options={})
226
- o = o.is_a?(String) ? o : serializer.dump(o, options)
226
+ o.is_a?(String) ? o : serializer.dump(o, options)
227
227
  end
228
228
 
229
229
  # Returns a full URL based on information from host
@@ -264,6 +264,7 @@ module Elasticsearch
264
264
  start = Time.now
265
265
  tries = 0
266
266
  reload_on_failure = opts.fetch(:reload_on_failure, @options[:reload_on_failure])
267
+ delay_on_retry = opts.fetch(:delay_on_retry, @options[:delay_on_retry])
267
268
 
268
269
  max_retries = if opts.key?(:retry_on_failure)
269
270
  opts[:retry_on_failure] === true ? DEFAULT_MAX_RETRIES : opts[:retry_on_failure]
@@ -272,10 +273,10 @@ module Elasticsearch
272
273
  end
273
274
 
274
275
  params = params.clone
275
-
276
276
  ignore = Array(params.delete(:ignore)).compact.map { |s| s.to_i }
277
277
 
278
278
  begin
279
+ sleep(delay_on_retry / 1000.0) if tries > 0
279
280
  tries += 1
280
281
  connection = get_connection or raise Error.new('Cannot get new connection from pool.')
281
282
 
@@ -284,9 +285,7 @@ module Elasticsearch
284
285
  end
285
286
 
286
287
  url = connection.full_url(path, params)
287
-
288
288
  response = block.call(connection, url)
289
-
290
289
  connection.healthy! if connection.failures > 0
291
290
 
292
291
  # Raise an exception so we can catch it for `retry_on_status`
@@ -309,7 +308,6 @@ module Elasticsearch
309
308
  log_error "[#{e.class}] #{e.message} #{connection.host.inspect}"
310
309
 
311
310
  connection.dead!
312
-
313
311
  if reload_on_failure and tries < connections.all.size
314
312
  log_warn "[#{e.class}] Reloading connections (attempt #{tries} of #{connections.all.size})"
315
313
  reload_connections! and retry
@@ -336,14 +334,10 @@ module Elasticsearch
336
334
  duration = Time.now - start
337
335
 
338
336
  if response.status.to_i >= 300
339
- __log_response method, path, params, body, url, response, nil, 'N/A', duration
340
- __trace method, path, params, connection.connection.headers, body, url, response, nil, 'N/A', duration if tracer
341
-
337
+ __log_response(method, path, params, body, url, response, nil, 'N/A', duration)
338
+ __trace(method, path, params, connection_headers(connection), body, url, response, nil, 'N/A', duration) if tracer
342
339
  # Log the failure only when `ignore` doesn't match the response status
343
- unless ignore.include?(response.status.to_i)
344
- log_fatal "[#{response.status}] #{response.body}"
345
- end
346
-
340
+ log_fatal "[#{response.status}] #{response.body}" unless ignore.include?(response.status.to_i)
347
341
  __raise_transport_error response unless ignore.include?(response.status.to_i)
348
342
  end
349
343
 
@@ -354,10 +348,8 @@ module Elasticsearch
354
348
  __log_response method, path, params, body, url, response, json, took, duration
355
349
  end
356
350
 
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')
360
-
351
+ __trace(method, path, params, connection_headers(connection), body, url, response, nil, 'N/A', duration) if tracer
352
+ log_warn(response.headers['warning']) if response.headers&.[]('warning')
361
353
  Response.new response.status, json || response.body, response.headers
362
354
  ensure
363
355
  @last_request_at = Time.now
@@ -376,17 +368,38 @@ module Elasticsearch
376
368
 
377
369
  USER_AGENT_STR = 'User-Agent'.freeze
378
370
  USER_AGENT_REGEX = /user\-?\_?agent/
371
+ ACCEPT_ENCODING = 'Accept-Encoding'.freeze
372
+ CONTENT_ENCODING = 'Content-Encoding'.freeze
379
373
  CONTENT_TYPE_STR = 'Content-Type'.freeze
380
374
  CONTENT_TYPE_REGEX = /content\-?\_?type/
381
375
  DEFAULT_CONTENT_TYPE = 'application/json'.freeze
382
376
  GZIP = 'gzip'.freeze
383
- ACCEPT_ENCODING = 'Accept-Encoding'.freeze
384
377
  GZIP_FIRST_TWO_BYTES = '1f8b'.freeze
385
378
  HEX_STRING_DIRECTIVE = 'H*'.freeze
386
379
  RUBY_ENCODING = '1.9'.respond_to?(:force_encoding)
387
380
 
381
+ def compress_request(body, headers)
382
+ if body
383
+ headers ||= {}
384
+
385
+ if gzipped?(body)
386
+ headers[CONTENT_ENCODING] = GZIP
387
+ elsif use_compression?
388
+ headers[CONTENT_ENCODING] = GZIP
389
+ gzip = Zlib::GzipWriter.new(StringIO.new)
390
+ gzip << body
391
+ body = gzip.close.string
392
+ else
393
+ headers.delete(CONTENT_ENCODING)
394
+ end
395
+ elsif headers
396
+ headers.delete(CONTENT_ENCODING)
397
+ end
398
+
399
+ [body, headers]
400
+ end
401
+
388
402
  def decompress_response(body)
389
- return body unless use_compression?
390
403
  return body unless gzipped?(body)
391
404
 
392
405
  io = StringIO.new(body)
@@ -399,6 +412,8 @@ module Elasticsearch
399
412
  end
400
413
 
401
414
  def gzipped?(body)
415
+ return unless body && !body.empty?
416
+
402
417
  body[0..1].unpack(HEX_STRING_DIRECTIVE)[0] == GZIP_FIRST_TWO_BYTES
403
418
  end
404
419
 
@@ -415,7 +430,7 @@ module Elasticsearch
415
430
  end
416
431
 
417
432
  def find_value(hash, regex)
418
- key_value = hash.find { |k,v| k.to_s.downcase =~ regex }
433
+ key_value = hash.find { |k, _| k.to_s.downcase =~ regex }
419
434
  if key_value
420
435
  hash.delete(key_value[0])
421
436
  key_value[1]
@@ -432,8 +447,12 @@ module Elasticsearch
432
447
  end
433
448
  end
434
449
 
435
- def warnings(warning)
436
- warn("warning: #{warning}")
450
+ def connection_headers(connection)
451
+ if defined?(Elasticsearch::Transport::Transport::HTTP::Manticore) && self.class == Elasticsearch::Transport::Transport::HTTP::Manticore
452
+ @request_options[:headers]
453
+ else
454
+ connection.connection.headers
455
+ end
437
456
  end
438
457
  end
439
458
  end
@@ -78,16 +78,13 @@ module Elasticsearch
78
78
 
79
79
  # Returns a connection.
80
80
  #
81
- # If there are no alive connections, resurrects a connection with least failures.
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
- if connections.empty? && dead_connection = dead.sort { |a,b| a.failures <=> b.failures }.first
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)
@@ -19,7 +19,6 @@ module Elasticsearch
19
19
  module Transport
20
20
  module Transport
21
21
  module Connections
22
-
23
22
  # Wraps the connection information and logic.
24
23
  #
25
24
  # The Connection instance wraps the host information (hostname, port, attributes, etc),
@@ -34,6 +33,7 @@ module Elasticsearch
34
33
  DEFAULT_RESURRECT_TIMEOUT = 60
35
34
 
36
35
  attr_reader :host, :connection, :options, :failures, :dead_since
36
+ attr_accessor :verified
37
37
 
38
38
  # @option arguments [Hash] :host Host information (example: `{host: 'localhost', port: 9200}`)
39
39
  # @option arguments [Object] :connection The transport-specific physical connection or "session"
@@ -43,6 +43,7 @@ module Elasticsearch
43
43
  @host = arguments[:host].is_a?(Hash) ? Redacted.new(arguments[:host]) : arguments[:host]
44
44
  @connection = arguments[:connection]
45
45
  @options = arguments[:options] || {}
46
+ @verified = false
46
47
  @state_mutex = Mutex.new
47
48
 
48
49
  @options[:resurrect_timeout] ||= DEFAULT_RESURRECT_TIMEOUT
@@ -54,12 +55,14 @@ module Elasticsearch
54
55
  #
55
56
  # @return [String]
56
57
  #
57
- def full_url(path, params={})
58
+ def full_url(path, params = {})
58
59
  url = "#{host[:protocol]}://"
59
60
  url += "#{CGI.escape(host[:user])}:#{CGI.escape(host[:password])}@" if host[:user]
60
61
  url += "#{host[:host]}:#{host[:port]}"
61
62
  url += "#{host[:path]}" if host[:path]
62
- url += "/#{full_path(path, params)}"
63
+ full_path = full_path(path, params)
64
+ url += '/' unless full_path.match?(/^\//)
65
+ url += full_path
63
66
  end
64
67
 
65
68
  # Returns the complete endpoint path with serialized parameters.
@@ -152,7 +155,6 @@ module Elasticsearch
152
155
  "<#{self.class.name} host: #{host} (#{dead? ? 'dead since ' + dead_since.to_s : 'alive'})>"
153
156
  end
154
157
  end
155
-
156
158
  end
157
159
  end
158
160
  end
@@ -64,6 +64,7 @@ module Elasticsearch
64
64
  418 => 'ImATeapot',
65
65
  421 => 'TooManyConnectionsFromThisIP',
66
66
  426 => 'UpgradeRequired',
67
+ 429 => 'TooManyRequests',
67
68
  450 => 'BlockedByWindowsParentalControls',
68
69
  494 => 'RequestHeaderTooLarge',
69
70
  497 => 'HTTPToHTTPS',
@@ -19,51 +19,64 @@ module Elasticsearch
19
19
  module Transport
20
20
  module Transport
21
21
  module HTTP
22
-
23
22
  # Alternative HTTP transport implementation, using the [_Curb_](https://rubygems.org/gems/curb) client.
24
23
  #
25
24
  # @see Transport::Base
26
25
  #
27
26
  class Curb
28
27
  include Base
29
-
30
28
  # Performs the request by invoking {Transport::Base#perform_request} with a block.
31
29
  #
32
30
  # @return [Response]
33
31
  # @see Transport::Base#perform_request
34
32
  #
35
33
  def perform_request(method, path, params={}, body=nil, headers=nil, opts={})
36
- super do |connection, url|
34
+ super do |connection, _url|
37
35
  connection.connection.url = connection.full_url(path, params)
36
+ body = body ? __convert_to_json(body) : nil
37
+ body, headers = compress_request(body, headers)
38
38
 
39
39
  case method
40
- when 'HEAD'
41
- connection.connection.set :nobody, true
42
- when 'GET', 'POST', 'PUT', 'DELETE'
43
- connection.connection.set :nobody, false
44
-
45
- connection.connection.put_data = __convert_to_json(body) if body
46
-
47
- if headers
48
- if connection.connection.headers
49
- connection.connection.headers.merge!(headers)
50
- else
51
- connection.connection.headers = headers
52
- end
40
+ when 'HEAD'
41
+ connection.connection.set :nobody, true
42
+ when 'GET', 'POST', 'PUT', 'DELETE'
43
+ connection.connection.set :nobody, false
44
+
45
+ connection.connection.put_data = body if body
46
+
47
+ if headers
48
+ if connection.connection.headers
49
+ connection.connection.headers.merge!(headers)
50
+ else
51
+ connection.connection.headers = headers
53
52
  end
54
-
55
- else raise ArgumentError, "Unsupported HTTP method: #{method}"
53
+ end
54
+ else
55
+ raise ArgumentError, "Unsupported HTTP method: #{method}"
56
56
  end
57
57
 
58
58
  connection.connection.http(method.to_sym)
59
59
 
60
- response_headers = {}
61
- response_headers['content-type'] = 'application/json' if connection.connection.header_str =~ /\/json/
60
+ Response.new(
61
+ connection.connection.response_code,
62
+ decompress_response(connection.connection.body_str),
63
+ headers(connection)
64
+ )
65
+ end
66
+ end
67
+
68
+ def headers(connection)
69
+ headers_string = connection.connection.header_str
70
+ return nil if headers_string.nil?
62
71
 
63
- Response.new connection.connection.response_code,
64
- decompress_response(connection.connection.body_str),
65
- response_headers
72
+ response_headers = headers_string&.split(/\\r\\n|\r\n/).reject(&:empty?)
73
+ response_headers.shift # Removes HTTP status string
74
+ processed_header = response_headers.flat_map { |s| s.scan(/^(\S+): (.+)/) }
75
+ headers_hash = Hash[processed_header].transform_keys(&:downcase)
76
+ if headers_hash['content-type']&.match?(/application\/json/)
77
+ headers_hash['content-type'] = 'application/json'
66
78
  end
79
+ headers_hash
67
80
  end
68
81
 
69
82
  # Builds and returns a connection
@@ -72,9 +85,8 @@ module Elasticsearch
72
85
  #
73
86
  def __build_connection(host, options={}, block=nil)
74
87
  client = ::Curl::Easy.new
75
-
76
88
  apply_headers(client, options)
77
- client.url = __full_url(host)
89
+ client.url = __full_url(host)
78
90
 
79
91
  if host[:user]
80
92
  client.http_auth_types = host[:auth_type] || :basic
@@ -106,13 +118,13 @@ module Elasticsearch
106
118
 
107
119
  def user_agent_header(client)
108
120
  @user_agent ||= begin
109
- meta = ["RUBY_VERSION: #{RUBY_VERSION}"]
110
- if RbConfig::CONFIG && RbConfig::CONFIG['host_os']
111
- meta << "#{RbConfig::CONFIG['host_os'].split('_').first[/[a-z]+/i].downcase} #{RbConfig::CONFIG['target_cpu']}"
112
- end
113
- meta << "Curb #{Curl::CURB_VERSION}"
114
- "elasticsearch-ruby/#{VERSION} (#{meta.join('; ')})"
115
- end
121
+ meta = ["RUBY_VERSION: #{RUBY_VERSION}"]
122
+ if RbConfig::CONFIG && RbConfig::CONFIG['host_os']
123
+ meta << "#{RbConfig::CONFIG['host_os'].split('_').first[/[a-z]+/i].downcase} #{RbConfig::CONFIG['target_cpu']}"
124
+ end
125
+ meta << "Curb #{Curl::CURB_VERSION}"
126
+ "elasticsearch-ruby/#{VERSION} (#{meta.join('; ')})"
127
+ end
116
128
  end
117
129
  end
118
130
  end
@@ -19,7 +19,6 @@ module Elasticsearch
19
19
  module Transport
20
20
  module Transport
21
21
  module HTTP
22
-
23
22
  # The default transport implementation, using the [_Faraday_](https://rubygems.org/gems/faraday)
24
23
  # library for abstracting the HTTP client.
25
24
  #
@@ -33,13 +32,24 @@ module Elasticsearch
33
32
  # @return [Response]
34
33
  # @see Transport::Base#perform_request
35
34
  #
36
- def perform_request(method, path, params={}, body=nil, headers=nil, opts={})
35
+ def perform_request(method, path, params = {}, body = nil, headers = nil, opts = {})
37
36
  super do |connection, url|
38
- headers = headers || connection.connection.headers
37
+ headers = if connection.connection.headers
38
+ if !headers.nil?
39
+ connection.connection.headers.merge(headers)
40
+ else
41
+ connection.connection.headers
42
+ end
43
+ else
44
+ headers
45
+ end
46
+
47
+ body = body ? __convert_to_json(body) : nil
48
+ body, headers = compress_request(body, headers)
39
49
 
40
50
  response = connection.connection.run_request(method.downcase.to_sym,
41
51
  url,
42
- ( body ? __convert_to_json(body) : nil ),
52
+ body,
43
53
  headers)
44
54
 
45
55
  Response.new response.status, decompress_response(response.body), response.headers
@@ -53,7 +63,7 @@ module Elasticsearch
53
63
  def __build_connection(host, options={}, block=nil)
54
64
  client = ::Faraday.new(__full_url(host), options, &block)
55
65
  apply_headers(client, options)
56
- Connections::Connection.new :host => host, :connection => client
66
+ Connections::Connection.new(host: host, connection: client)
57
67
  end
58
68
 
59
69
  # Returns an array of implementation specific connection errors.
@@ -62,13 +62,20 @@ module Elasticsearch
62
62
  class Manticore
63
63
  include Base
64
64
 
65
- def initialize(arguments={}, &block)
65
+ def initialize(arguments = {}, &block)
66
+ @request_options = {
67
+ headers: (
68
+ arguments.dig(:transport_options, :headers) ||
69
+ arguments.dig(:options, :transport_options, :headers) ||
70
+ {}
71
+ )
72
+ }
66
73
  @manticore = build_client(arguments[:options] || {})
67
74
  super(arguments, &block)
68
75
  end
69
76
 
70
77
  # Should just be run once at startup
71
- def build_client(options={})
78
+ def build_client(options = {})
72
79
  client_options = options[:transport_options] || {}
73
80
  client_options[:ssl] = options[:ssl] || {}
74
81
 
@@ -80,26 +87,28 @@ module Elasticsearch
80
87
  # @return [Response]
81
88
  # @see Transport::Base#perform_request
82
89
  #
83
- def perform_request(method, path, params={}, body=nil, headers=nil, opts={})
90
+ def perform_request(method, path, params = {}, body = nil, headers = nil, opts = {})
84
91
  super do |connection, url|
85
- params[:body] = __convert_to_json(body) if body
92
+ body = body ? __convert_to_json(body) : nil
93
+ body, headers = compress_request(body, parse_headers(headers))
94
+ params[:body] = body if body
86
95
  params[:headers] = headers if headers
87
- params = params.merge @request_options
96
+
88
97
  case method
89
- when "GET"
98
+ when 'GET'
90
99
  resp = connection.connection.get(url, params)
91
- when "HEAD"
100
+ when 'HEAD'
92
101
  resp = connection.connection.head(url, params)
93
- when "PUT"
102
+ when 'PUT'
94
103
  resp = connection.connection.put(url, params)
95
- when "POST"
104
+ when 'POST'
96
105
  resp = connection.connection.post(url, params)
97
- when "DELETE"
106
+ when 'DELETE'
98
107
  resp = connection.connection.delete(url, params)
99
108
  else
100
109
  raise ArgumentError.new "Method #{method} not supported"
101
110
  end
102
- Response.new resp.code, resp.read_body, resp.headers
111
+ Response.new(resp.code, resp.read_body, resp.headers)
103
112
  end
104
113
  end
105
114
 
@@ -109,24 +118,21 @@ module Elasticsearch
109
118
  # @return [Connections::Collection]
110
119
  #
111
120
  def __build_connections
112
- @request_options = {}
113
- apply_headers(@request_options, options[:transport_options])
114
- apply_headers(@request_options, options)
121
+ apply_headers(options)
115
122
 
116
- Connections::Collection.new \
117
- :connections => hosts.map { |host|
118
- host[:protocol] = host[:scheme] || DEFAULT_PROTOCOL
119
- host[:port] ||= DEFAULT_PORT
123
+ Connections::Collection.new(
124
+ connections: hosts.map do |host|
125
+ host[:protocol] = host[:scheme] || DEFAULT_PROTOCOL
126
+ host[:port] ||= DEFAULT_PORT
120
127
 
121
128
  host.delete(:user) # auth is not supported here.
122
129
  host.delete(:password) # use the headers
123
130
 
124
- Connections::Connection.new \
125
- :host => host,
126
- :connection => @manticore
127
- },
128
- :selector_class => options[:selector_class],
129
- :selector => options[:selector]
131
+ Connections::Connection.new(host: host, connection: @manticore)
132
+ end,
133
+ selector_class: options[:selector_class],
134
+ selector: options[:selector]
135
+ )
130
136
  end
131
137
 
132
138
  # Closes all connections by marking them as dead
@@ -154,16 +160,22 @@ module Elasticsearch
154
160
 
155
161
  private
156
162
 
157
- def apply_headers(request_options, options)
158
- headers = (options && options[:headers]) || {}
163
+ def parse_headers(headers)
164
+ request_headers = @request_options.fetch(:headers, {})
165
+ headers = request_headers.merge(headers || {})
166
+ headers.empty? ? nil : headers
167
+ end
168
+
169
+ def apply_headers(options)
170
+ headers = options[:headers].clone || options.dig(:transport_options, :headers).clone || {}
159
171
  headers[CONTENT_TYPE_STR] = find_value(headers, CONTENT_TYPE_REGEX) || DEFAULT_CONTENT_TYPE
160
- headers[USER_AGENT_STR] = find_value(headers, USER_AGENT_REGEX) || user_agent_header
172
+ headers[USER_AGENT_STR] = find_value(headers, USER_AGENT_REGEX) || find_value(@request_options[:headers], USER_AGENT_REGEX) || user_agent_header
161
173
  headers[ACCEPT_ENCODING] = GZIP if use_compression?
162
- request_options.merge!(headers: headers)
174
+ @request_options[:headers].merge!(headers)
163
175
  end
164
176
 
165
177
  def user_agent_header
166
- @user_agent ||= begin
178
+ @user_agent_header ||= begin
167
179
  meta = ["RUBY_VERSION: #{JRUBY_VERSION}"]
168
180
  if RbConfig::CONFIG && RbConfig::CONFIG['host_os']
169
181
  meta << "#{RbConfig::CONFIG['host_os'].split('_').first[/[a-z]+/i].downcase} #{RbConfig::CONFIG['target_cpu']}"
@@ -28,7 +28,7 @@ module Elasticsearch
28
28
  # @param headers [Hash] Response headers
29
29
  def initialize(status, body, headers={})
30
30
  @status, @body, @headers = status, body, headers
31
- @body = body.force_encoding('UTF-8') if body.respond_to?(:force_encoding)
31
+ @body = body.force_encoding('UTF-8') if body.respond_to?(:force_encoding) && !body.frozen?
32
32
  end
33
33
  end
34
34