Files
elasticsearch-js/index.js
Tomas Della Vedova 5b856cd4c2 Child client support (#768)
With this pr we introduce the `client.child` API, which returns a new client instance that shares the connection pool with the parent client.
This feature can be handy if you need to have multiple client instances with different configurations, but with a shared connection pool.

Example:

```js
const { Client } = require('@elastic/elasticsearch')
const client = new Client({ node: 'http://localhost:9200' })
const child = client.child({
  headers: { 'x-foo': 'bar' },
  requestTimeout: 1000
})

client.info(console.log)
child.info(console.log)
```

**Open questions:**

* Currently, the event emitter is shared between the parent and the child(ren), is it ok?
* Currently, if you extend the parent client, the child client will have the same extensions, while if the child client adds an extension, the parent client will not be extended. Is it ok?

**Caveats:**

* You can override _any_ option except for the connection pool specific options (`ssl`, `agent`, `pingTimeout`, `Connection`, and `resurrectStrategy`).
* You can't specify a new `Connection` class.
* If you call `close` in any of the parent/child clients, every client will be closed.

_Note: the `nodeFilter` and `nodeSelector` options are now `Transport` options and no longer `ConnectionPool` options._
2019-02-21 12:48:49 +01:00

199 lines
5.8 KiB
JavaScript

'use strict'
const { EventEmitter } = require('events')
const debug = require('debug')('elasticsearch')
const Transport = require('./lib/Transport')
const Connection = require('./lib/Connection')
const ConnectionPool = require('./lib/ConnectionPool')
const Serializer = require('./lib/Serializer')
const errors = require('./lib/errors')
const { ConfigurationError } = errors
const kInitialOptions = Symbol('elasticsearchjs-initial-options')
const kChild = Symbol('elasticsearchjs-child')
const kExtensions = Symbol('elasticsearchjs-extensions')
const buildApi = require('./api')
class Client extends EventEmitter {
constructor (opts = {}) {
super()
if (opts.cloud) {
const { id, username, password } = opts.cloud
// the cloud id is `cluster-name:base64encodedurl`
// the url is a string divided by two '$', the first is the cloud url
// the second the elasticsearch instance, the third the kibana instance
const cloudUrls = Buffer.from(id.split(':')[1], 'base64').toString().split('$')
opts.node = `https://${username}:${password}@${cloudUrls[1]}.${cloudUrls[0]}`
}
if (!opts.node && !opts.nodes) {
throw new ConfigurationError('Missing node(s) option')
}
if (opts.log === true) {
this.on('request', console.log)
this.on('response', console.log)
this.on('error', console.log)
}
const options = Object.assign({}, {
Connection,
ConnectionPool,
Transport,
Serializer,
maxRetries: 3,
requestTimeout: 30000,
pingTimeout: 3000,
sniffInterval: false,
sniffOnStart: false,
sniffEndpoint: '_nodes/_all/http',
sniffOnConnectionFault: false,
resurrectStrategy: 'ping',
suggestCompression: false,
compression: false,
ssl: null,
agent: null,
headers: {},
nodeFilter: null,
nodeSelector: 'round-robin'
}, opts)
this[kInitialOptions] = options
this[kExtensions] = []
this.serializer = new options.Serializer()
this.connectionPool = new options.ConnectionPool({
pingTimeout: options.pingTimeout,
resurrectStrategy: options.resurrectStrategy,
ssl: options.ssl,
agent: options.agent,
Connection: options.Connection,
emit: this.emit.bind(this),
sniffEnabled: options.sniffInterval !== false ||
options.sniffOnStart !== false ||
options.sniffOnConnectionFault !== false
})
// Add the connections before initialize the Transport
if (opts[kChild] !== true) {
this.connectionPool.addConnection(options.node || options.nodes)
}
this.transport = new options.Transport({
emit: this.emit.bind(this),
connectionPool: this.connectionPool,
serializer: this.serializer,
maxRetries: options.maxRetries,
requestTimeout: options.requestTimeout,
sniffInterval: options.sniffInterval,
sniffOnStart: options.sniffOnStart,
sniffOnConnectionFault: options.sniffOnConnectionFault,
sniffEndpoint: options.sniffEndpoint,
suggestCompression: options.suggestCompression,
compression: options.compression,
headers: options.headers,
nodeFilter: options.nodeFilter,
nodeSelector: options.nodeSelector
})
const apis = buildApi({
makeRequest: this.transport.request.bind(this.transport),
result: { body: null, statusCode: null, headers: null, warnings: null },
ConfigurationError
})
Object.keys(apis).forEach(api => {
this[api] = apis[api]
})
}
extend (name, opts, fn) {
if (typeof opts === 'function') {
fn = opts
opts = {}
}
var [namespace, method] = name.split('.')
if (method == null) {
method = namespace
namespace = null
}
if (namespace != null) {
if (this[namespace] != null && this[namespace][method] != null && opts.force !== true) {
throw new Error(`The method "${method}" already exists on namespace "${namespace}"`)
}
this[namespace] = this[namespace] || {}
this[namespace][method] = fn({
makeRequest: this.transport.request.bind(this.transport),
result: { body: null, statusCode: null, headers: null, warnings: null },
ConfigurationError
})
} else {
if (this[method] != null && opts.force !== true) {
throw new Error(`The method "${method}" already exists`)
}
this[method] = fn({
makeRequest: this.transport.request.bind(this.transport),
result: { body: null, statusCode: null, headers: null, warnings: null },
ConfigurationError
})
}
this[kExtensions].push({ name, opts, fn })
}
child (opts) {
// Merge the new options with the initial ones
const initialOptions = Object.assign({}, this[kInitialOptions], opts)
// Tell to the client that we are creating a child client
initialOptions[kChild] = true
const client = new Client(initialOptions)
// Reuse the same connection pool
client.connectionPool = this.connectionPool
client.transport.connectionPool = this.connectionPool
// Share event listener
const emitter = this.emit.bind(this)
client.emit = emitter
client.connectionPool.emit = emitter
client.transport.emit = emitter
client.on = this.on.bind(this)
// Add parent extensions
this[kExtensions].forEach(({ name, opts, fn }) => {
client.extend(name, opts, fn)
})
return client
}
close (callback) {
if (callback == null) {
return new Promise((resolve, reject) => {
this.close(resolve)
})
}
debug('Closing the client')
this.connectionPool.empty(callback)
}
}
const events = {
RESPONSE: 'response',
REQUEST: 'request',
SNIFF: 'sniff',
RESURRECT: 'resurrect'
}
module.exports = {
Client,
Transport,
ConnectionPool,
Connection,
Serializer,
events,
errors
}