[[transport]]
=== Transport

The `elastic-transport` library provides a low-level Ruby client for connecting to an {es} cluster. It currently powers the https://www.elastic.co/guide/en/elasticsearch/client/ruby-api/current/index.html[Elasticsearch Ruby] and the https://www.elastic.co/guide/en/enterprise-search-clients/ruby/current/index.html[Enterprise Search Ruby] clients.

When available, it handles connecting to multiple nodes in the cluster, rotating across connections, logging and tracing requests and responses, maintaining failed connections, discovering nodes in the cluster, and provides an abstraction for data serialization and transport.

It does not handle calling the {es} or Enterprise Search APIs.

This library uses https://github.com/lostisland/faraday[Faraday] by default as the HTTP transport implementation. We test it with Faraday versions 1.x and 2.x.

For optimal performance, use a HTTP library which supports persistent ("keep-alive") connections, such as https://github.com/toland/patron[patron] or https://github.com/typhoeus/typhoeus[Typhoeus]. Require the library (`require 'patron'`) in your code for Faraday 1.x or the adapter (`require 'faraday/patron'`) for Faraday 2.x, and it will be automatically used.

Currently these libraries are supported:

* https://github.com/toland/patron[Patron]
* https://github.com/typhoeus/typhoeus[Typhoeus]
* https://rubygems.org/gems/httpclient[HTTPClient]
* https://rubygems.org/gems/net-http-persistent[Net::HTTP::Persistent]

NOTE: Use https://github.com/typhoeus/typhoeus[Typhoeus] v1.4.0 or up since older versions are not compatible with Faraday 1.0.

You can customize Faraday and implement your own HTTP transport. For detailed information, see the example configurations and more information <<transport-implementations,below>>.

Features overview:

* Pluggable logging and tracing
* Pluggable connection selection strategies (round-robin, random, custom)
* Pluggable transport implementation, customizable and extendable
* Pluggable serializer implementation
* Request retries and dead connections handling
* Node reloading (based on cluster state) on errors or on demand

Refer to <<advanced-config,Advanced Configuration>> to read about more configuration options.

[discrete]
[[transport-install]]
==== Installation

Install the package from https://rubygems.org/[Rubygems]:

[source,bash]
----------------------------
gem install elastic-transport
----------------------------

To use an unreleased version, either add it to your `Gemfile` for 
http://gembundler.com/[Bundler]:

[source,bash]
----------------------------
gem 'elastic-transport', git: 'git@github.com:elastic/elastic-transport-ruby.git'
----------------------------

or install it from a source code checkout:

[source,bash]
----------------------------
git clone https://github.com/elastic/elastic-transport-ruby.git
cd elastic-transport
bundle install
rake install
----------------------------

[discrete]
[[transport-example-usage]]
==== Example usage

In the simplest form, connect to {es} running on http://localhost:9200 without any configuration:

[source,rb]
----------------------------
require 'elastic/transport'

client = Elastic::Transport::Client.new
response = client.perform_request('GET', '_cluster/health')
# => #<Elastic::Transport::Transport::Response:0x007fc5d506ce38 @status=200, @body={ ... } >
----------------------------

Documentation is included as RDoc annotations in the source code and available online at http://rubydoc.info/gems/elastic-transport[RubyDoc].

[discrete]
[[transport-implementations]]
==== Transport implementations

By default, the client uses the https://rubygems.org/gems/faraday[Faraday] HTTP library as a transport implementation.

The Client auto-detects and uses an _adapter_ for _Faraday_ based on gems loaded in your code, preferring HTTP clients with support for persistent connections. Faraday 2 changed the way adapters are used (https://github.com/lostisland/faraday/blob/main/UPGRADING.md#adapters-have-moved[read more here]). If you're using Faraday 1.x, you can require the HTTP library. To use the https://github.com/toland/patron[_Patron_] HTTP, for example, require it:


To use the https://github.com/toland/patron[Patron] HTTP, for example, require it:

[source,rb]
----------------------------
require 'patron'
----------------------------

If you're using Faraday 2.x, you need to add the corresponding adapter gem to your Gemfile and require it after you require `faraday`:

[source,rb]
----------------------------
# Gemfile
gem 'faraday-patron'

# Code
require 'faraday'
require 'faraday/patron'
----------------------------


Then, create a new client, and the Patron gem will be used as the "driver":

[source,rb]
----------------------------
client = Elastic::Transport::Client.new

client.transport.connections.first.connection.builder.adapter
# => Faraday::Adapter::Patron

10.times do
  client.nodes.stats(metric: 'http')['nodes'].values.each do |n|
    puts "#{n['name']} : #{n['http']['total_opened']}"
  end
end

# => Stiletoo : 24
# => Stiletoo : 24
# => Stiletoo : 24
# => ...
----------------------------

To use a specific adapter for Faraday, pass it as the `adapter` argument:

[source,rb]
----------------------------
client = Elastic::Client.new(adapter: :net_http_persistent)

client.transport.connections.first.connection.builder.handlers
# => [Faraday::Adapter::NetHttpPersistent]
----------------------------

If you see this error:

[source,rb]
----------------------------
Faraday::Error: :net_http_persistent is not registered on Faraday::Adapter
----------------------------
When you're using Faraday 2, you need to require the adapter before instantiating the client:

[source,rb]
----------------------------
> client = Elasticsearch::Client.new(adapter: :net_http_persistent)
Faraday::Error: :net_http_persistent is not registered on Faraday::Adapter
> require 'faraday/net_http_persistent'
=> true
> client = Elasticsearch::Client.new(adapter: :net_http_persistent)
=> #<Elasticsearch::Client:0x00007eff2e7728e0
----------------------------


When using the Elasticsearch or Enterprise Search clients, you can pass the `adapter` parameter when initializing the clients.

To pass options to the https://github.com/lostisland/faraday/blob/master/lib/faraday/connection.rb[`Faraday::Connection`] constructor, use the `transport_options` key:

[source,rb]
----------------------------
client = Elastic::Client.new(
  transport_options: {
    request: { open_timeout: 1 },
    headers: { user_agent:   'MyApp' },
    params:  { :format => 'yaml' },
    ssl:     { verify: false }
  }
)
----------------------------

To configure the Faraday instance directly, use a block:

[source,rb]
----------------------------
require 'patron'

client = Elastic::Client.new(host: 'localhost', port: '9200') do |f|
  f.response :logger
  f.adapter  :patron
end
----------------------------

You can use any standard Faraday middleware and plugins in the configuration block.

You can also initialize the transport class yourself, and pass it to the client constructor as the `transport` argument. The Elasticsearch and Enterprise Search clients accept `:transport` as parameter when initializing a client. So you can pass in a transport you've initialized with the following options:

[source,rb]
----------------------------
require 'patron'

transport_configuration = lambda do |f|
  f.response :logger
  f.adapter  :patron
end

transport = Elastic::Transport::Transport::HTTP::Faraday.new(
  hosts: [ { host: 'localhost', port: '9200' } ],
  &transport_configuration
)

# Pass the transport to the client
#
client = Elastic::Client.new(transport: transport)
----------------------------

Instead of passing the transport to the constructor, you can inject it at run time:

[source,rb]
----------------------------
# Set up the transport
#
faraday_configuration = lambda do |f|
  f.instance_variable_set :@ssl, { verify: false }
  f.adapter :excon
end

faraday_client = Elastic::Transport::Transport::HTTP::Faraday.new(
  hosts: [
    {
      host: 'my-protected-host',
      port: '443',
      user: 'USERNAME',
      password: 'PASSWORD',
      scheme: 'https'
    }
  ],
  &faraday_configuration
)

# Create a default client
#
client = Elastic::Client.new

# Inject the transport to the client
#
client.transport = faraday_client
----------------------------

You can also use a bundled https://rubygems.org/gems/curb[Curb] based transport implementation:

[source,rb]
----------------------------
require 'curb'
require 'elastic/transport/transport/http/curb'

client = Elastic::Client.new(transport_class: Elastic::Transport::Transport::HTTP::Curb)

client.transport.connections.first.connection
# => #<Curl::Easy http://localhost:9200/>
----------------------------

It's possible to customize the Curb instance by passing a block to the constructor as well (in this case, as an inline block):

[source,rb]
----------------------------
transport = Elastic::Transport::Transport::HTTP::Curb.new(
  hosts: [ { host: 'localhost', port: '9200' } ],
  & lambda { |c| c.verbose = true }
)

client = Elastic::Client.new(transport: transport)
----------------------------

You can write your own transport implementation by including the {Elastic::Transport::Transport::Base} module, implementing the required contract, and passing it to the client as the `transport_class` parameter – or by injecting it directly.

[discrete]
[[transport-architecture]]
==== Transport architecture

* `Elastic::Transport::Client` is composed of `Elastic::Transport::Transport`.
* `Elastic::Transport::Transport` is composed of `Elastic::Transport::Transport::Connections`, and an instance of logger, tracer, serializer and sniffer.
* Logger and tracer can be any object conforming to Ruby logging interface, for example, an instance of https://ruby-doc.org/stdlib-1.9.3/libdoc/logger/rdoc/Logger.html[`Logger`], https://rubygems.org/gems/log4r[log4r], https://github.com/TwP/logging/[logging], and so on.
* The `Elastic::Transport::Transport::Serializer::Base` implementations handle converting data for {es} (for example, to JSON). You can implement your own serializer.
* `Elastic::Transport::Transport::Sniffer` allows to discover nodes in the cluster and use them as connections.
* `Elastic::Transport::Transport::Connections::Collection` is composed of `Elastic::Transport::Transport::Connections::Connection` instances and a selector instance.
* `Elastic::Transport::Transport::Connections::Connection` contains the connection attributes such as hostname and port, as well as the concrete persistent "session" connected to a specific node.
* The `Elastic::Transport::Transport::Connections::Selector::Base` implementations allow to choose connections from the pool, for example, in a round-robin or random fashion. You can implement your own selector strategy.
* The `Elastic::Transport::Transport::Response` object wraps the Elasticsearch JSON response. It provides `body`, `status`, and `headers` methods but you can treat it as a hash and access the keys directly.
