elasticsearch-transport-pixlee 1.0.13
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 +7 -0
- data/.gitignore +17 -0
- data/Gemfile +16 -0
- data/LICENSE.txt +13 -0
- data/README.md +441 -0
- data/Rakefile +80 -0
- data/elasticsearch-transport.gemspec +74 -0
- data/lib/elasticsearch-transport.rb +1 -0
- data/lib/elasticsearch/transport.rb +30 -0
- data/lib/elasticsearch/transport/client.rb +195 -0
- data/lib/elasticsearch/transport/transport/base.rb +261 -0
- data/lib/elasticsearch/transport/transport/connections/collection.rb +93 -0
- data/lib/elasticsearch/transport/transport/connections/connection.rb +121 -0
- data/lib/elasticsearch/transport/transport/connections/selector.rb +63 -0
- data/lib/elasticsearch/transport/transport/errors.rb +73 -0
- data/lib/elasticsearch/transport/transport/http/curb.rb +87 -0
- data/lib/elasticsearch/transport/transport/http/faraday.rb +60 -0
- data/lib/elasticsearch/transport/transport/http/manticore.rb +124 -0
- data/lib/elasticsearch/transport/transport/response.rb +21 -0
- data/lib/elasticsearch/transport/transport/serializer/multi_json.rb +36 -0
- data/lib/elasticsearch/transport/transport/sniffer.rb +46 -0
- data/lib/elasticsearch/transport/version.rb +5 -0
- data/test/integration/client_test.rb +144 -0
- data/test/integration/transport_test.rb +73 -0
- data/test/profile/client_benchmark_test.rb +125 -0
- data/test/test_helper.rb +76 -0
- data/test/unit/client_test.rb +274 -0
- data/test/unit/connection_collection_test.rb +88 -0
- data/test/unit/connection_selector_test.rb +64 -0
- data/test/unit/connection_test.rb +100 -0
- data/test/unit/response_test.rb +15 -0
- data/test/unit/serializer_test.rb +16 -0
- data/test/unit/sniffer_test.rb +145 -0
- data/test/unit/transport_base_test.rb +478 -0
- data/test/unit/transport_curb_test.rb +97 -0
- data/test/unit/transport_faraday_test.rb +140 -0
- data/test/unit/transport_manticore_test.rb +118 -0
- metadata +408 -0
data/Rakefile
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
2
|
+
|
3
|
+
desc "Run unit tests"
|
4
|
+
task :default => 'test:unit'
|
5
|
+
task :test => 'test:unit'
|
6
|
+
|
7
|
+
# ----- Test tasks ------------------------------------------------------------
|
8
|
+
|
9
|
+
require 'rake/testtask'
|
10
|
+
namespace :test do
|
11
|
+
task :ci_reporter do
|
12
|
+
ENV['CI_REPORTS'] ||= 'tmp/reports'
|
13
|
+
if defined?(RUBY_VERSION) && RUBY_VERSION < '1.9'
|
14
|
+
require 'ci/reporter/rake/test_unit'
|
15
|
+
Rake::Task['ci:setup:testunit'].invoke
|
16
|
+
else
|
17
|
+
require 'ci/reporter/rake/minitest'
|
18
|
+
Rake::Task['ci:setup:minitest'].invoke
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
Rake::TestTask.new(:unit) do |test|
|
23
|
+
Rake::Task['test:ci_reporter'].invoke if ENV['CI']
|
24
|
+
test.libs << 'lib' << 'test'
|
25
|
+
test.test_files = FileList["test/unit/**/*_test.rb"]
|
26
|
+
# test.verbose = true
|
27
|
+
# test.warning = true
|
28
|
+
end
|
29
|
+
|
30
|
+
Rake::TestTask.new(:integration) do |test|
|
31
|
+
Rake::Task['test:ci_reporter'].invoke if ENV['CI']
|
32
|
+
test.libs << 'lib' << 'test'
|
33
|
+
test.test_files = FileList["test/integration/**/*_test.rb"]
|
34
|
+
end
|
35
|
+
|
36
|
+
Rake::TestTask.new(:all) do |test|
|
37
|
+
Rake::Task['test:ci_reporter'].invoke if ENV['CI']
|
38
|
+
test.libs << 'lib' << 'test'
|
39
|
+
test.test_files = FileList["test/unit/**/*_test.rb", "test/integration/**/*_test.rb"]
|
40
|
+
end
|
41
|
+
|
42
|
+
Rake::TestTask.new(:profile) do |test|
|
43
|
+
Rake::Task['test:ci_reporter'].invoke if ENV['CI']
|
44
|
+
test.libs << 'lib' << 'test'
|
45
|
+
test.test_files = FileList["test/profile/**/*_test.rb"]
|
46
|
+
end
|
47
|
+
|
48
|
+
namespace :cluster do
|
49
|
+
desc "Start Elasticsearch nodes for tests"
|
50
|
+
task :start do
|
51
|
+
$LOAD_PATH << File.expand_path('../lib', __FILE__) << File.expand_path('../test', __FILE__)
|
52
|
+
require 'elasticsearch/extensions/test/cluster'
|
53
|
+
Elasticsearch::Extensions::Test::Cluster.start
|
54
|
+
end
|
55
|
+
|
56
|
+
desc "Stop Elasticsearch nodes for tests"
|
57
|
+
task :stop do
|
58
|
+
$LOAD_PATH << File.expand_path('../lib', __FILE__) << File.expand_path('../test', __FILE__)
|
59
|
+
require 'elasticsearch/extensions/test/cluster'
|
60
|
+
Elasticsearch::Extensions::Test::Cluster.stop
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
# ----- Documentation tasks ---------------------------------------------------
|
66
|
+
|
67
|
+
require 'yard'
|
68
|
+
YARD::Rake::YardocTask.new(:doc) do |t|
|
69
|
+
t.options = %w| --embed-mixins --markup=markdown |
|
70
|
+
end
|
71
|
+
|
72
|
+
# ----- Code analysis tasks ---------------------------------------------------
|
73
|
+
|
74
|
+
if defined?(RUBY_VERSION) && RUBY_VERSION > '1.9'
|
75
|
+
require 'cane/rake_task'
|
76
|
+
Cane::RakeTask.new(:quality) do |cane|
|
77
|
+
cane.abc_max = 15
|
78
|
+
cane.no_style = true
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'elasticsearch/transport/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = "elasticsearch-transport-pixlee"
|
8
|
+
s.version = Elasticsearch::Transport::VERSION
|
9
|
+
s.authors = ["Karel Minarik"]
|
10
|
+
s.email = ["karel.minarik@elasticsearch.org"]
|
11
|
+
s.summary = "Ruby client for Elasticsearch."
|
12
|
+
s.homepage = "https://github.com/elasticsearch/elasticsearch-ruby/tree/master/elasticsearch-transport"
|
13
|
+
s.license = "Apache 2"
|
14
|
+
|
15
|
+
s.files = `git ls-files`.split($/)
|
16
|
+
s.executables = s.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
17
|
+
s.test_files = s.files.grep(%r{^(test|spec|features)/})
|
18
|
+
s.require_paths = ["lib"]
|
19
|
+
|
20
|
+
s.extra_rdoc_files = [ "README.md", "LICENSE.txt" ]
|
21
|
+
s.rdoc_options = [ "--charset=UTF-8" ]
|
22
|
+
|
23
|
+
s.add_dependency "multi_json"
|
24
|
+
s.add_dependency "faraday"
|
25
|
+
|
26
|
+
if defined?(RUBY_VERSION) && RUBY_VERSION < '1.9'
|
27
|
+
s.add_dependency "system_timer"
|
28
|
+
end
|
29
|
+
|
30
|
+
s.add_development_dependency "bundler", "> 1"
|
31
|
+
s.add_development_dependency "rake"
|
32
|
+
|
33
|
+
if defined?(RUBY_VERSION) && RUBY_VERSION > '1.9'
|
34
|
+
s.add_development_dependency "elasticsearch-extensions"
|
35
|
+
end
|
36
|
+
|
37
|
+
s.add_development_dependency "ansi"
|
38
|
+
s.add_development_dependency "shoulda-context"
|
39
|
+
s.add_development_dependency "mocha"
|
40
|
+
s.add_development_dependency "turn"
|
41
|
+
s.add_development_dependency "yard"
|
42
|
+
s.add_development_dependency "pry"
|
43
|
+
s.add_development_dependency "ci_reporter", "~> 1.9"
|
44
|
+
|
45
|
+
# Gems for testing integrations
|
46
|
+
s.add_development_dependency "curb" unless defined? JRUBY_VERSION
|
47
|
+
s.add_development_dependency "patron" unless defined? JRUBY_VERSION
|
48
|
+
s.add_development_dependency "typhoeus", '~> 0.6'
|
49
|
+
s.add_development_dependency "manticore", '~> 0.3.5' if defined? JRUBY_VERSION
|
50
|
+
s.add_development_dependency "hashie"
|
51
|
+
|
52
|
+
# Prevent unit test failures on Ruby 1.8
|
53
|
+
if defined?(RUBY_VERSION) && RUBY_VERSION < '1.9'
|
54
|
+
s.add_development_dependency "test-unit", '~> 2'
|
55
|
+
s.add_development_dependency "json"
|
56
|
+
end
|
57
|
+
|
58
|
+
if defined?(RUBY_VERSION) && RUBY_VERSION > '1.9'
|
59
|
+
s.add_development_dependency "minitest", "~> 4.0"
|
60
|
+
s.add_development_dependency "ruby-prof" unless defined?(JRUBY_VERSION) || defined?(Rubinius)
|
61
|
+
s.add_development_dependency "require-prof" unless defined?(JRUBY_VERSION) || defined?(Rubinius)
|
62
|
+
s.add_development_dependency "simplecov"
|
63
|
+
s.add_development_dependency "simplecov-rcov"
|
64
|
+
s.add_development_dependency "cane"
|
65
|
+
end
|
66
|
+
|
67
|
+
if defined?(RUBY_VERSION) && RUBY_VERSION > '2.2'
|
68
|
+
s.add_development_dependency "test-unit", '~> 2'
|
69
|
+
end
|
70
|
+
|
71
|
+
s.description = <<-DESC.gsub(/^ /, '')
|
72
|
+
Ruby client for Elasticsearch. See the `elasticsearch` gem for full integration.
|
73
|
+
DESC
|
74
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'elasticsearch/transport'
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require "uri"
|
2
|
+
require "time"
|
3
|
+
require "timeout"
|
4
|
+
require "multi_json"
|
5
|
+
require "faraday"
|
6
|
+
|
7
|
+
require "elasticsearch/transport/transport/serializer/multi_json"
|
8
|
+
require "elasticsearch/transport/transport/sniffer"
|
9
|
+
require "elasticsearch/transport/transport/response"
|
10
|
+
require "elasticsearch/transport/transport/errors"
|
11
|
+
require "elasticsearch/transport/transport/base"
|
12
|
+
require "elasticsearch/transport/transport/connections/selector"
|
13
|
+
require "elasticsearch/transport/transport/connections/connection"
|
14
|
+
require "elasticsearch/transport/transport/connections/collection"
|
15
|
+
require "elasticsearch/transport/transport/http/faraday"
|
16
|
+
require "elasticsearch/transport/client"
|
17
|
+
|
18
|
+
require "elasticsearch/transport/version"
|
19
|
+
|
20
|
+
module Elasticsearch
|
21
|
+
module Client
|
22
|
+
|
23
|
+
# A convenience wrapper for {::Elasticsearch::Transport::Client#initialize}.
|
24
|
+
#
|
25
|
+
def new(arguments={})
|
26
|
+
Elasticsearch::Transport::Client.new(arguments)
|
27
|
+
end
|
28
|
+
extend self
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,195 @@
|
|
1
|
+
module Elasticsearch
|
2
|
+
module Transport
|
3
|
+
|
4
|
+
# Handles communication with an Elasticsearch cluster.
|
5
|
+
#
|
6
|
+
# See {file:README.md README} for usage and code examples.
|
7
|
+
#
|
8
|
+
class Client
|
9
|
+
DEFAULT_TRANSPORT_CLASS = Transport::HTTP::Faraday
|
10
|
+
|
11
|
+
DEFAULT_LOGGER = lambda do
|
12
|
+
require 'logger'
|
13
|
+
logger = Logger.new(STDERR)
|
14
|
+
logger.progname = 'elasticsearch'
|
15
|
+
logger.formatter = proc { |severity, datetime, progname, msg| "#{datetime}: #{msg}\n" }
|
16
|
+
logger
|
17
|
+
end
|
18
|
+
|
19
|
+
DEFAULT_TRACER = lambda do
|
20
|
+
require 'logger'
|
21
|
+
logger = Logger.new(STDERR)
|
22
|
+
logger.progname = 'elasticsearch.tracer'
|
23
|
+
logger.formatter = proc { |severity, datetime, progname, msg| "#{msg}\n" }
|
24
|
+
logger
|
25
|
+
end
|
26
|
+
|
27
|
+
# Returns the transport object.
|
28
|
+
#
|
29
|
+
# @see Elasticsearch::Transport::Transport::Base
|
30
|
+
# @see Elasticsearch::Transport::Transport::HTTP::Faraday
|
31
|
+
#
|
32
|
+
attr_accessor :transport
|
33
|
+
|
34
|
+
# Create a client connected to an Elasticsearch cluster.
|
35
|
+
#
|
36
|
+
# Specify the URL via arguments or set the `ELASTICSEARCH_URL` environment variable.
|
37
|
+
#
|
38
|
+
# @option arguments [String,Array] :hosts Single host passed as a String or Hash, or multiple hosts
|
39
|
+
# passed as an Array; `host` or `url` keys are also valid
|
40
|
+
#
|
41
|
+
# @option arguments [Boolean] :log Use the default logger (disabled by default)
|
42
|
+
#
|
43
|
+
# @option arguments [Boolean] :trace Use the default tracer (disabled by default)
|
44
|
+
#
|
45
|
+
# @option arguments [Object] :logger An instance of a Logger-compatible object
|
46
|
+
#
|
47
|
+
# @option arguments [Object] :tracer An instance of a Logger-compatible object
|
48
|
+
#
|
49
|
+
# @option arguments [Number] :resurrect_after After how many seconds a dead connection should be tried again
|
50
|
+
#
|
51
|
+
# @option arguments [Boolean,Number] :reload_connections Reload connections after X requests (false by default)
|
52
|
+
#
|
53
|
+
# @option arguments [Boolean] :randomize_hosts Shuffle connections on initialization and reload (false by default)
|
54
|
+
#
|
55
|
+
# @option arguments [Integer] :sniffer_timeout Timeout for reloading connections in seconds (1 by default)
|
56
|
+
#
|
57
|
+
# @option arguments [Boolean,Number] :retry_on_failure Retry X times when request fails before raising and
|
58
|
+
# exception (false by default)
|
59
|
+
#
|
60
|
+
# @option arguments [Boolean] :reload_on_failure Reload connections after failure (false by default)
|
61
|
+
#
|
62
|
+
# @option arguments [Integer] :request_timeout The request timeout to be passed to transport in options
|
63
|
+
#
|
64
|
+
# @option arguments [Symbol] :adapter A specific adapter for Faraday (e.g. `:patron`)
|
65
|
+
#
|
66
|
+
# @option arguments [Hash] :transport_options Options to be passed to the `Faraday::Connection` constructor
|
67
|
+
#
|
68
|
+
# @option arguments [Constant] :transport_class A specific transport class to use, will be initialized by
|
69
|
+
# the client and passed hosts and all arguments
|
70
|
+
#
|
71
|
+
# @option arguments [Object] :transport A specific transport instance
|
72
|
+
#
|
73
|
+
# @option arguments [Constant] :serializer_class A specific serializer class to use, will be initialized by
|
74
|
+
# the transport and passed the transport instance
|
75
|
+
#
|
76
|
+
# @option arguments [Constant] :selector An instance of selector strategy implemented with
|
77
|
+
# {Elasticsearch::Transport::Transport::Connections::Selector::Base}.
|
78
|
+
#
|
79
|
+
# @option arguments [String] :send_get_body_as Specify the HTTP method to use for GET requests with a body.
|
80
|
+
# (Default: GET)
|
81
|
+
#
|
82
|
+
def initialize(arguments={})
|
83
|
+
hosts = arguments[:hosts] || \
|
84
|
+
arguments[:host] || \
|
85
|
+
arguments[:url] || \
|
86
|
+
arguments[:urls] || \
|
87
|
+
ENV.fetch('ELASTICSEARCH_URL', 'localhost:9200')
|
88
|
+
|
89
|
+
arguments[:logger] ||= arguments[:log] ? DEFAULT_LOGGER.call() : nil
|
90
|
+
arguments[:tracer] ||= arguments[:trace] ? DEFAULT_TRACER.call() : nil
|
91
|
+
arguments[:reload_connections] ||= false
|
92
|
+
arguments[:retry_on_failure] ||= false
|
93
|
+
arguments[:reload_on_failure] ||= false
|
94
|
+
arguments[:randomize_hosts] ||= false
|
95
|
+
arguments[:transport_options] ||= {}
|
96
|
+
|
97
|
+
arguments[:transport_options].update(:request => { :timeout => arguments[:request_timeout] } ) if arguments[:request_timeout]
|
98
|
+
|
99
|
+
@send_get_body_as = arguments[:send_get_body_as] || 'GET'
|
100
|
+
|
101
|
+
transport_class = arguments[:transport_class] || DEFAULT_TRANSPORT_CLASS
|
102
|
+
|
103
|
+
@transport = arguments[:transport] || begin
|
104
|
+
if transport_class == Transport::HTTP::Faraday
|
105
|
+
transport_class.new(:hosts => __extract_hosts(hosts, arguments), :options => arguments) do |faraday|
|
106
|
+
faraday.adapter(arguments[:adapter] || __auto_detect_adapter)
|
107
|
+
end
|
108
|
+
else
|
109
|
+
transport_class.new(:hosts => __extract_hosts(hosts, arguments), :options => arguments)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
# Performs a request through delegation to {#transport}.
|
115
|
+
#
|
116
|
+
def perform_request(method, path, params={}, body=nil)
|
117
|
+
method = @send_get_body_as if 'GET' == method && body
|
118
|
+
|
119
|
+
transport.perform_request method, path, params, body
|
120
|
+
end
|
121
|
+
|
122
|
+
# Normalizes and returns hosts configuration.
|
123
|
+
#
|
124
|
+
# Arrayifies the `hosts_config` argument and extracts `host` and `port` info from strings.
|
125
|
+
# Performs shuffling when the `randomize_hosts` option is set.
|
126
|
+
#
|
127
|
+
# TODO: Refactor, so it's available in Elasticsearch::Transport::Base as well
|
128
|
+
#
|
129
|
+
# @return [Array<Hash>]
|
130
|
+
# @raise [ArgumentError]
|
131
|
+
#
|
132
|
+
# @api private
|
133
|
+
#
|
134
|
+
def __extract_hosts(hosts_config, options={})
|
135
|
+
if hosts_config.is_a?(Hash)
|
136
|
+
hosts = [ hosts_config ]
|
137
|
+
else
|
138
|
+
if hosts_config.is_a?(String) && hosts_config.include?(',')
|
139
|
+
hosts = hosts_config.split(/\s*,\s*/)
|
140
|
+
else
|
141
|
+
hosts = Array(hosts_config)
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
result = hosts.map do |host|
|
146
|
+
host_parts = case host
|
147
|
+
when String
|
148
|
+
if host =~ /^[a-z]+\:\/\//
|
149
|
+
uri = URI.parse(host)
|
150
|
+
{ :scheme => uri.scheme, :user => uri.user, :password => uri.password, :host => uri.host, :path => uri.path, :port => uri.port }
|
151
|
+
else
|
152
|
+
host, port = host.split(':')
|
153
|
+
{ :host => host, :port => port }
|
154
|
+
end
|
155
|
+
when URI
|
156
|
+
{ :scheme => host.scheme, :user => host.user, :password => host.password, :host => host.host, :path => host.path, :port => host.port }
|
157
|
+
when Hash
|
158
|
+
host
|
159
|
+
else
|
160
|
+
raise ArgumentError, "Please pass host as a String, URI or Hash -- #{host.class} given."
|
161
|
+
end
|
162
|
+
|
163
|
+
host_parts[:port] = host_parts[:port].to_i unless host_parts[:port].nil?
|
164
|
+
host_parts
|
165
|
+
end
|
166
|
+
|
167
|
+
result.shuffle! if options[:randomize_hosts]
|
168
|
+
result
|
169
|
+
end
|
170
|
+
|
171
|
+
# Auto-detect the best adapter (HTTP "driver") available, based on libraries
|
172
|
+
# loaded by the user, preferring those with persistent connections
|
173
|
+
# ("keep-alive") by default
|
174
|
+
#
|
175
|
+
# @return [Symbol]
|
176
|
+
#
|
177
|
+
# @api private
|
178
|
+
#
|
179
|
+
def __auto_detect_adapter
|
180
|
+
case
|
181
|
+
when defined?(::Patron)
|
182
|
+
:patron
|
183
|
+
when defined?(::Typhoeus)
|
184
|
+
:typhoeus
|
185
|
+
when defined?(::HTTPClient)
|
186
|
+
:httpclient
|
187
|
+
when defined?(::Net::HTTP::Persistent)
|
188
|
+
:net_http_persistent
|
189
|
+
else
|
190
|
+
::Faraday.default_adapter
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|
@@ -0,0 +1,261 @@
|
|
1
|
+
module Elasticsearch
|
2
|
+
module Transport
|
3
|
+
module Transport
|
4
|
+
|
5
|
+
# @abstract Module with common functionality for transport implementations.
|
6
|
+
#
|
7
|
+
module Base
|
8
|
+
DEFAULT_PORT = 9200
|
9
|
+
DEFAULT_PROTOCOL = 'http'
|
10
|
+
DEFAULT_RELOAD_AFTER = 10_000 # Requests
|
11
|
+
DEFAULT_RESURRECT_AFTER = 60 # Seconds
|
12
|
+
DEFAULT_MAX_RETRIES = 3 # Requests
|
13
|
+
DEFAULT_SERIALIZER_CLASS = Serializer::MultiJson
|
14
|
+
|
15
|
+
attr_reader :hosts, :options, :connections, :counter, :last_request_at, :protocol
|
16
|
+
attr_accessor :serializer, :sniffer, :logger, :tracer,
|
17
|
+
:reload_connections, :reload_after,
|
18
|
+
:resurrect_after, :max_retries
|
19
|
+
|
20
|
+
# Creates a new transport object.
|
21
|
+
#
|
22
|
+
# @param arguments [Hash] Settings and options for the transport
|
23
|
+
# @param block [Proc] Lambda or Proc which can be evaluated in the context of the "session" object
|
24
|
+
#
|
25
|
+
# @option arguments [Array] :hosts An Array of normalized hosts information
|
26
|
+
# @option arguments [Array] :options A Hash with options (usually passed by {Client})
|
27
|
+
#
|
28
|
+
# @see Client#initialize
|
29
|
+
#
|
30
|
+
def initialize(arguments={}, &block)
|
31
|
+
@hosts = arguments[:hosts] || []
|
32
|
+
@options = arguments[:options] || {}
|
33
|
+
@block = block
|
34
|
+
@connections = __build_connections
|
35
|
+
|
36
|
+
@serializer = options[:serializer] || ( options[:serializer_class] ? options[:serializer_class].new(self) : DEFAULT_SERIALIZER_CLASS.new(self) )
|
37
|
+
@protocol = options[:protocol] || DEFAULT_PROTOCOL
|
38
|
+
|
39
|
+
@logger = options[:logger]
|
40
|
+
@tracer = options[:tracer]
|
41
|
+
|
42
|
+
@sniffer = options[:sniffer_class] ? options[:sniffer_class].new(self) : Sniffer.new(self)
|
43
|
+
@counter = 0
|
44
|
+
@last_request_at = Time.now
|
45
|
+
@reload_connections = options[:reload_connections]
|
46
|
+
@reload_after = options[:reload_connections].is_a?(Fixnum) ? options[:reload_connections] : DEFAULT_RELOAD_AFTER
|
47
|
+
@resurrect_after = options[:resurrect_after] || DEFAULT_RESURRECT_AFTER
|
48
|
+
@max_retries = options[:retry_on_failure].is_a?(Fixnum) ? options[:retry_on_failure] : DEFAULT_MAX_RETRIES
|
49
|
+
end
|
50
|
+
|
51
|
+
# Returns a connection from the connection pool by delegating to {Connections::Collection#get_connection}.
|
52
|
+
#
|
53
|
+
# Resurrects dead connection if the `resurrect_after` timeout has passed.
|
54
|
+
# Increments the counter and performs connection reloading if the `reload_connections` option is set.
|
55
|
+
#
|
56
|
+
# @return [Connections::Connection]
|
57
|
+
# @see Connections::Collection#get_connection
|
58
|
+
#
|
59
|
+
def get_connection(options={})
|
60
|
+
resurrect_dead_connections! if Time.now > @last_request_at + @resurrect_after
|
61
|
+
|
62
|
+
connection = connections.get_connection(options)
|
63
|
+
@counter += 1
|
64
|
+
|
65
|
+
reload_connections! if reload_connections && counter % reload_after == 0
|
66
|
+
connection
|
67
|
+
end
|
68
|
+
|
69
|
+
# Reloads and replaces the connection collection based on cluster information.
|
70
|
+
#
|
71
|
+
# @see Sniffer#hosts
|
72
|
+
#
|
73
|
+
def reload_connections!
|
74
|
+
hosts = sniffer.hosts
|
75
|
+
__rebuild_connections :hosts => hosts, :options => options
|
76
|
+
self
|
77
|
+
rescue SnifferTimeoutError
|
78
|
+
logger.error "[SnifferTimeoutError] Timeout when reloading connections." if logger
|
79
|
+
self
|
80
|
+
end
|
81
|
+
|
82
|
+
# Tries to "resurrect" all eligible dead connections.
|
83
|
+
#
|
84
|
+
# @see Connections::Connection#resurrect!
|
85
|
+
#
|
86
|
+
def resurrect_dead_connections!
|
87
|
+
connections.dead.each { |c| c.resurrect! }
|
88
|
+
end
|
89
|
+
|
90
|
+
# Replaces the connections collection.
|
91
|
+
#
|
92
|
+
# @api private
|
93
|
+
#
|
94
|
+
def __rebuild_connections(arguments={})
|
95
|
+
@hosts = arguments[:hosts] || []
|
96
|
+
@options = arguments[:options] || {}
|
97
|
+
@connections = __build_connections
|
98
|
+
end
|
99
|
+
|
100
|
+
# Log request and response information.
|
101
|
+
#
|
102
|
+
# @api private
|
103
|
+
#
|
104
|
+
def __log(method, path, params, body, url, response, json, took, duration)
|
105
|
+
logger.info "#{method.to_s.upcase} #{url} " +
|
106
|
+
"[status:#{response.status}, request:#{sprintf('%.3fs', duration)}, query:#{took}]"
|
107
|
+
logger.debug "> #{__convert_to_json(body)}" if body
|
108
|
+
logger.debug "< #{response.body}"
|
109
|
+
end
|
110
|
+
|
111
|
+
# Log failed request.
|
112
|
+
#
|
113
|
+
# @api private
|
114
|
+
def __log_failed(response)
|
115
|
+
logger.fatal "[#{response.status}] #{response.body}"
|
116
|
+
end
|
117
|
+
|
118
|
+
# Trace the request in the `curl` format.
|
119
|
+
#
|
120
|
+
# @api private
|
121
|
+
def __trace(method, path, params, body, url, response, json, took, duration)
|
122
|
+
trace_url = "http://localhost:9200/#{path}?pretty" +
|
123
|
+
( params.empty? ? '' : "&#{::Faraday::Utils::ParamsHash[params].to_query}" )
|
124
|
+
trace_body = body ? " -d '#{__convert_to_json(body, :pretty => true)}'" : ''
|
125
|
+
tracer.info "curl -X #{method.to_s.upcase} '#{trace_url}'#{trace_body}\n"
|
126
|
+
tracer.debug "# #{Time.now.iso8601} [#{response.status}] (#{format('%.3f', duration)}s)\n#"
|
127
|
+
tracer.debug json ? serializer.dump(json, :pretty => true).gsub(/^/, '# ').sub(/\}$/, "\n# }")+"\n" : "# #{response.body}\n"
|
128
|
+
end
|
129
|
+
|
130
|
+
# Raise error specific for the HTTP response status or a generic server error
|
131
|
+
#
|
132
|
+
# @api private
|
133
|
+
def __raise_transport_error(response)
|
134
|
+
error = ERRORS[response.status] || ServerError
|
135
|
+
raise error.new "[#{response.status}] #{response.body}"
|
136
|
+
end
|
137
|
+
|
138
|
+
# Converts any non-String object to JSON
|
139
|
+
#
|
140
|
+
# @api private
|
141
|
+
def __convert_to_json(o=nil, options={})
|
142
|
+
o = o.is_a?(String) ? o : serializer.dump(o, options)
|
143
|
+
end
|
144
|
+
|
145
|
+
# Returns a full URL based on information from host
|
146
|
+
#
|
147
|
+
# @param host [Hash] Host configuration passed in from {Client}
|
148
|
+
#
|
149
|
+
# @api private
|
150
|
+
def __full_url(host)
|
151
|
+
url = "#{host[:protocol]}://"
|
152
|
+
url += "#{host[:user]}:#{host[:password]}@" if host[:user]
|
153
|
+
url += "#{host[:host]}:#{host[:port]}"
|
154
|
+
url += "#{host[:path]}" if host[:path]
|
155
|
+
url
|
156
|
+
end
|
157
|
+
|
158
|
+
# Performs a request to Elasticsearch, while handling logging, tracing, marking dead connections,
|
159
|
+
# retrying the request and reloading the connections.
|
160
|
+
#
|
161
|
+
# @abstract The transport implementation has to implement this method either in full,
|
162
|
+
# or by invoking this method with a block. See {HTTP::Faraday#perform_request} for an example.
|
163
|
+
#
|
164
|
+
# @param method [String] Request method
|
165
|
+
# @param path [String] The API endpoint
|
166
|
+
# @param params [Hash] Request parameters (will be serialized by {Connections::Connection#full_url})
|
167
|
+
# @param body [Hash] Request body (will be serialized by the {#serializer})
|
168
|
+
# @param block [Proc] Code block to evaluate, passed from the implementation
|
169
|
+
#
|
170
|
+
# @return [Response]
|
171
|
+
# @raise [NoMethodError] If no block is passed
|
172
|
+
# @raise [ServerError] If request failed on server
|
173
|
+
# @raise [Error] If no connection is available
|
174
|
+
#
|
175
|
+
def perform_request(method, path, params={}, body=nil, &block)
|
176
|
+
raise NoMethodError, "Implement this method in your transport class" unless block_given?
|
177
|
+
start = Time.now if logger || tracer
|
178
|
+
tries = 0
|
179
|
+
|
180
|
+
begin
|
181
|
+
tries += 1
|
182
|
+
connection = get_connection or raise Error.new("Cannot get new connection from pool.")
|
183
|
+
|
184
|
+
if connection.connection.respond_to?(:params) && connection.connection.params.respond_to?(:to_hash)
|
185
|
+
params = connection.connection.params.merge(params.to_hash)
|
186
|
+
end
|
187
|
+
|
188
|
+
url = connection.full_url(path, params)
|
189
|
+
|
190
|
+
response = block.call(connection, url)
|
191
|
+
|
192
|
+
connection.healthy! if connection.failures > 0
|
193
|
+
|
194
|
+
rescue *host_unreachable_exceptions => e
|
195
|
+
logger.error "[#{e.class}] #{e.message} #{connection.host.inspect}" if logger
|
196
|
+
|
197
|
+
connection.dead!
|
198
|
+
|
199
|
+
if @options[:reload_on_failure] and tries < connections.all.size
|
200
|
+
logger.warn "[#{e.class}] Reloading connections (attempt #{tries} of #{connections.all.size})" if logger
|
201
|
+
reload_connections! and retry
|
202
|
+
end
|
203
|
+
|
204
|
+
if @options[:retry_on_failure]
|
205
|
+
logger.warn "[#{e.class}] Attempt #{tries} connecting to #{connection.host.inspect}" if logger
|
206
|
+
if tries <= max_retries
|
207
|
+
retry
|
208
|
+
else
|
209
|
+
logger.fatal "[#{e.class}] Cannot connect to #{connection.host.inspect} after #{tries} tries" if logger
|
210
|
+
raise e
|
211
|
+
end
|
212
|
+
else
|
213
|
+
raise e
|
214
|
+
end
|
215
|
+
|
216
|
+
rescue Exception => e
|
217
|
+
logger.fatal "[#{e.class}] #{e.message} (#{connection.host.inspect if connection})" if logger
|
218
|
+
raise e
|
219
|
+
end
|
220
|
+
|
221
|
+
duration = Time.now-start if logger || tracer
|
222
|
+
|
223
|
+
if response.status.to_i >= 300
|
224
|
+
__log method, path, params, body, url, response, nil, 'N/A', duration if logger
|
225
|
+
__trace method, path, params, body, url, response, nil, 'N/A', duration if tracer
|
226
|
+
__log_failed response if logger
|
227
|
+
__raise_transport_error response
|
228
|
+
end
|
229
|
+
|
230
|
+
json = serializer.load(response.body) if response.headers && response.headers["content-type"] =~ /json/
|
231
|
+
took = (json['took'] ? sprintf('%.3fs', json['took']/1000.0) : 'n/a') rescue 'n/a' if logger || tracer
|
232
|
+
|
233
|
+
__log method, path, params, body, url, response, json, took, duration if logger
|
234
|
+
__trace method, path, params, body, url, response, json, took, duration if tracer
|
235
|
+
|
236
|
+
Response.new response.status, json || response.body, response.headers
|
237
|
+
ensure
|
238
|
+
@last_request_at = Time.now
|
239
|
+
end
|
240
|
+
|
241
|
+
# @abstract Returns an Array of connection errors specific to the transport implementation.
|
242
|
+
# See {HTTP::Faraday#host_unreachable_exceptions} for an example.
|
243
|
+
#
|
244
|
+
# @return [Array]
|
245
|
+
#
|
246
|
+
def host_unreachable_exceptions
|
247
|
+
[Errno::ECONNREFUSED, Elasticsearch::Transport::Transport::Errors::BadGateway]
|
248
|
+
end
|
249
|
+
|
250
|
+
# @abstract A transport implementation must implement this method.
|
251
|
+
# See {HTTP::Faraday#__build_connections} for an example.
|
252
|
+
#
|
253
|
+
# @return [Connections::Collection]
|
254
|
+
# @api private
|
255
|
+
def __build_connections
|
256
|
+
raise NoMethodError, "Implement this method in your class"
|
257
|
+
end
|
258
|
+
end
|
259
|
+
end
|
260
|
+
end
|
261
|
+
end
|