elasticsearch-transport 7.13.3 → 7.17.11

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 (32) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +9 -9
  3. data/Gemfile-faraday1.gemfile +47 -0
  4. data/README.md +3 -8
  5. data/Rakefile +47 -10
  6. data/elasticsearch-transport.gemspec +17 -18
  7. data/lib/elasticsearch/transport/client.rb +34 -3
  8. data/lib/elasticsearch/transport/transport/base.rb +41 -22
  9. data/lib/elasticsearch/transport/transport/connections/connection.rb +2 -1
  10. data/lib/elasticsearch/transport/transport/errors.rb +1 -0
  11. data/lib/elasticsearch/transport/transport/http/curb.rb +44 -32
  12. data/lib/elasticsearch/transport/transport/http/faraday.rb +5 -3
  13. data/lib/elasticsearch/transport/transport/http/manticore.rb +41 -29
  14. data/lib/elasticsearch/transport/transport/response.rb +1 -1
  15. data/lib/elasticsearch/transport/version.rb +1 -1
  16. data/lib/elasticsearch/transport.rb +19 -30
  17. data/spec/elasticsearch/transport/base_spec.rb +50 -9
  18. data/spec/elasticsearch/transport/client_spec.rb +138 -64
  19. data/spec/elasticsearch/transport/http/curb_spec.rb +126 -0
  20. data/spec/elasticsearch/transport/http/faraday_spec.rb +141 -0
  21. data/spec/elasticsearch/transport/http/manticore_spec.rb +161 -0
  22. data/spec/elasticsearch/transport/meta_header_spec.rb +66 -30
  23. data/spec/spec_helper.rb +13 -5
  24. data/test/integration/jruby_test.rb +43 -0
  25. data/test/integration/transport_test.rb +89 -52
  26. data/test/test_helper.rb +10 -22
  27. data/test/unit/adapters_test.rb +88 -0
  28. data/test/unit/response_test.rb +1 -1
  29. data/test/unit/transport_base_test.rb +16 -7
  30. data/test/unit/transport_curb_test.rb +0 -1
  31. data/test/unit/transport_manticore_test.rb +242 -155
  32. metadata +68 -79
@@ -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
  #
@@ -45,9 +44,12 @@ module Elasticsearch
45
44
  headers
46
45
  end
47
46
 
47
+ body = body ? __convert_to_json(body) : nil
48
+ body, headers = compress_request(body, headers)
49
+
48
50
  response = connection.connection.run_request(method.downcase.to_sym,
49
51
  url,
50
- ( body ? __convert_to_json(body) : nil ),
52
+ body,
51
53
  headers)
52
54
 
53
55
  Response.new response.status, decompress_response(response.body), response.headers
@@ -61,7 +63,7 @@ module Elasticsearch
61
63
  def __build_connection(host, options={}, block=nil)
62
64
  client = ::Faraday.new(__full_url(host), options, &block)
63
65
  apply_headers(client, options)
64
- Connections::Connection.new :host => host, :connection => client
66
+ Connections::Connection.new(host: host, connection: client)
65
67
  end
66
68
 
67
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
 
@@ -17,6 +17,6 @@
17
17
 
18
18
  module Elasticsearch
19
19
  module Transport
20
- VERSION = '7.13.3'.freeze
20
+ VERSION = '7.17.11'.freeze
21
21
  end
22
22
  end
@@ -15,35 +15,24 @@
15
15
  # specific language governing permissions and limitations
16
16
  # under the License.
17
17
 
18
- require "uri"
19
- require "time"
20
- require "timeout"
21
- require "multi_json"
22
- require "faraday"
18
+ require 'uri'
19
+ require 'time'
20
+ require 'timeout'
21
+ require 'zlib'
22
+ require 'multi_json'
23
+ require 'faraday'
23
24
 
24
- require "elasticsearch/transport/transport/loggable"
25
- require "elasticsearch/transport/transport/serializer/multi_json"
26
- require "elasticsearch/transport/transport/sniffer"
27
- require "elasticsearch/transport/transport/response"
28
- require "elasticsearch/transport/transport/errors"
29
- require "elasticsearch/transport/transport/base"
30
- require "elasticsearch/transport/transport/connections/selector"
31
- require "elasticsearch/transport/transport/connections/connection"
32
- require "elasticsearch/transport/transport/connections/collection"
33
- require "elasticsearch/transport/transport/http/faraday"
34
- require "elasticsearch/transport/client"
35
- require "elasticsearch/transport/redacted"
25
+ require 'elasticsearch/transport/transport/loggable'
26
+ require 'elasticsearch/transport/transport/serializer/multi_json'
27
+ require 'elasticsearch/transport/transport/sniffer'
28
+ require 'elasticsearch/transport/transport/response'
29
+ require 'elasticsearch/transport/transport/errors'
30
+ require 'elasticsearch/transport/transport/base'
31
+ require 'elasticsearch/transport/transport/connections/selector'
32
+ require 'elasticsearch/transport/transport/connections/connection'
33
+ require 'elasticsearch/transport/transport/connections/collection'
34
+ require 'elasticsearch/transport/transport/http/faraday'
35
+ require 'elasticsearch/transport/client'
36
+ require 'elasticsearch/transport/redacted'
36
37
 
37
- require "elasticsearch/transport/version"
38
-
39
- module Elasticsearch
40
- module Client
41
-
42
- # A convenience wrapper for {::Elasticsearch::Transport::Client#initialize}.
43
- #
44
- def new(arguments={}, &block)
45
- Elasticsearch::Transport::Client.new(arguments, &block)
46
- end
47
- extend self
48
- end
49
- end
38
+ require 'elasticsearch/transport/version'
@@ -32,14 +32,14 @@ describe Elasticsearch::Transport::Transport::Base do
32
32
  expect(logger).not_to receive(:error).with(/secret_password/)
33
33
 
34
34
  expect {
35
- client.cluster.stats
35
+ client.perform_request('GET', '_cluster/stats')
36
36
  }.to raise_exception(Faraday::ConnectionFailed)
37
37
  end
38
38
 
39
39
  it 'replaces the password with the string \'REDACTED\'' do
40
40
  expect(logger).to receive(:error).with(/REDACTED/)
41
41
  expect {
42
- client.cluster.stats
42
+ client.perform_request('GET', '_cluster/stats')
43
43
  }.to raise_exception(Faraday::ConnectionFailed)
44
44
  end
45
45
  end
@@ -65,7 +65,27 @@ describe Elasticsearch::Transport::Transport::Base do
65
65
  }
66
66
  end
67
67
 
68
- it_behaves_like 'a redacted string'
68
+ if jruby?
69
+ let(:client) { Elasticsearch::Transport::Client.new(arguments) }
70
+ let(:logger) { double('logger', fatal?: true, fatal: '') }
71
+
72
+ it 'does not include the password in the logged string' do
73
+ expect(logger).not_to receive(:fatal).with(/secret_password/)
74
+
75
+ expect {
76
+ client.perform_request('GET', '_cluster/stats')
77
+ }.to raise_exception(Faraday::SSLError)
78
+ end
79
+
80
+ it 'replaces the password with the string \'REDACTED\'' do
81
+ expect(logger).to receive(:fatal).with(/REDACTED/)
82
+ expect {
83
+ client.perform_request('GET', '_cluster/stats')
84
+ }.to raise_exception(Faraday::SSLError)
85
+ end
86
+ else
87
+ it_behaves_like 'a redacted string'
88
+ end
69
89
  end
70
90
 
71
91
  context 'when the user and password are provided in the URI object' do
@@ -75,8 +95,27 @@ describe Elasticsearch::Transport::Transport::Base do
75
95
  logger: logger
76
96
  }
77
97
  end
78
-
79
- it_behaves_like 'a redacted string'
98
+ if jruby?
99
+ let(:client) { Elasticsearch::Transport::Client.new(arguments) }
100
+ let(:logger) { double('logger', fatal?: true, fatal: '') }
101
+
102
+ it 'does not include the password in the logged string' do
103
+ expect(logger).not_to receive(:fatal).with(/secret_password/)
104
+
105
+ expect {
106
+ client.perform_request('GET', '_cluster/stats')
107
+ }.to raise_exception(Faraday::SSLError)
108
+ end
109
+
110
+ it 'replaces the password with the string \'REDACTED\'' do
111
+ expect(logger).to receive(:fatal).with(/REDACTED/)
112
+ expect {
113
+ client.perform_request('GET', '_cluster/stats')
114
+ }.to raise_exception(Faraday::SSLError)
115
+ end
116
+ else
117
+ it_behaves_like 'a redacted string'
118
+ end
80
119
  end
81
120
  end
82
121
 
@@ -94,7 +133,7 @@ describe Elasticsearch::Transport::Transport::Base do
94
133
  end
95
134
 
96
135
  it 'raises an exception' do
97
- expect { client.info }.to raise_exception(Faraday::ConnectionFailed)
136
+ expect { client.perform_request('GET', '/') }.to raise_exception(Faraday::ConnectionFailed)
98
137
  end
99
138
  end
100
139
 
@@ -106,7 +145,8 @@ describe Elasticsearch::Transport::Transport::Base do
106
145
  let(:arguments) do
107
146
  {
108
147
  hosts: ['http://unavailable:9200', 'http://unavailable:9201'],
109
- retry_on_failure: 2
148
+ retry_on_failure: 2,
149
+ adapter: :net_http
110
150
  }
111
151
  end
112
152
 
@@ -129,8 +169,9 @@ describe Elasticsearch::Transport::Transport::Base do
129
169
 
130
170
  let(:arguments) do
131
171
  {
132
- hosts: ['http://localhost:9250'],
133
- retry_on_status: ['404']
172
+ hosts: ELASTICSEARCH_HOSTS,
173
+ retry_on_status: ['404'],
174
+ adapter: :net_http
134
175
  }
135
176
  end
136
177