Skip to content

Commit 9daf8f3

Browse files
committed
Merge branch '7.x' of https://github.com/elastic/elasticsearch-js into 7.x
2 parents f6180fa + 996ee29 commit 9daf8f3

File tree

4 files changed

+147
-13
lines changed

4 files changed

+147
-13
lines changed

docs/connecting.asciidoc

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ This page contains the information you need to connect and use the Client with
88

99
* <<auth-reference, Authentication options>>
1010
* <<client-usage, Using the client>>
11+
* <<client-faas-env, Using the Client in a Function-as-a-Service Environment>>
1112
* <<client-connect-proxy, Connecting through a proxy>>
1213
* <<client-error-handling, Handling errors>>
1314
* <<product-check, Automatic product check>>
@@ -419,6 +420,76 @@ _Default:_ `null`
419420
_Default:_ `null`
420421
|===
421422

423+
[discrete]
424+
[[client-faas-env]]
425+
=== Using the Client in a Function-as-a-Service Environment
426+
427+
This section illustrates the best practices for leveraging the {es} client in a Function-as-a-Service (FaaS) environment.
428+
The most influential optimization is to initialize the client outside of the function, the global scope.
429+
This practice does not only improve performance but also enables background functionality as – for example – https://www.elastic.co/blog/elasticsearch-sniffing-best-practices-what-when-why-how[sniffing].
430+
The following examples provide a skeleton for the best practices.
431+
432+
[discrete]
433+
==== GCP Cloud Functions
434+
435+
[source,js]
436+
----
437+
'use strict'
438+
439+
const { Client } = require('@elastic/elasticsearch')
440+
441+
const client = new Client({
442+
// client initialisation
443+
})
444+
445+
exports.testFunction = async function (req, res) {
446+
// use the client
447+
}
448+
----
449+
450+
[discrete]
451+
==== AWS Lambda
452+
453+
[source,js]
454+
----
455+
'use strict'
456+
457+
const { Client } = require('@elastic/elasticsearch')
458+
459+
const client = new Client({
460+
// client initialisation
461+
})
462+
463+
exports.handler = async function (event, context) {
464+
// use the client
465+
}
466+
----
467+
468+
[discrete]
469+
==== Azure Functions
470+
471+
[source,js]
472+
----
473+
'use strict'
474+
475+
const { Client } = require('@elastic/elasticsearch')
476+
477+
const client = new Client({
478+
// client initialisation
479+
})
480+
481+
module.exports = async function (context, req) {
482+
// use the client
483+
}
484+
----
485+
486+
Resources used to assess these recommendations:
487+
488+
- https://cloud.google.com/functions/docs/bestpractices/tips#use_global_variables_to_reuse_objects_in_future_invocations[GCP Cloud Functions: Tips & Tricks]
489+
- https://docs.aws.amazon.com/lambda/latest/dg/best-practices.html[Best practices for working with AWS Lambda functions]
490+
- https://docs.microsoft.com/en-us/azure/azure-functions/functions-reference-python?tabs=azurecli-linux%2Capplication-level#global-variables[Azure Functions Python developer guide]
491+
- https://docs.aws.amazon.com/lambda/latest/operatorguide/global-scope.html[AWS Lambda: Comparing the effect of global scope]
492+
422493

423494
[discrete]
424495
[[client-connect-proxy]]

lib/Transport.js

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -36,13 +36,13 @@ const {
3636

3737
const noop = () => {}
3838

39-
const productCheckEmitter = new EventEmitter()
4039
const clientVersion = require('../package.json').version
4140
const userAgent = `elasticsearch-js/${clientVersion} (${os.platform()} ${os.release()}-${os.arch()}; Node.js ${process.version})`
4241
const MAX_BUFFER_LENGTH = buffer.constants.MAX_LENGTH
4342
const MAX_STRING_LENGTH = buffer.constants.MAX_STRING_LENGTH
4443
const kProductCheck = Symbol('product check')
4544
const kApiVersioning = Symbol('api versioning')
45+
const kEventEmitter = Symbol('event emitter')
4646

4747
class Transport {
4848
constructor (opts) {
@@ -71,6 +71,7 @@ class Transport {
7171
this.opaqueIdPrefix = opts.opaqueIdPrefix
7272
this[kProductCheck] = 0 // 0 = to be checked, 1 = checking, 2 = checked-ok, 3 checked-notok, 4 checked-nodefault
7373
this[kApiVersioning] = process.env.ELASTIC_CLIENT_APIVERSIONING === 'true'
74+
this[kEventEmitter] = new EventEmitter()
7475

7576
this.nodeFilter = opts.nodeFilter || defaultNodeFilter
7677
if (typeof opts.nodeSelector === 'function') {
@@ -182,6 +183,7 @@ class Transport {
182183

183184
const makeRequest = () => {
184185
if (meta.aborted === true) {
186+
this.emit('request', new RequestAbortedError(), result)
185187
return process.nextTick(callback, new RequestAbortedError(), result)
186188
}
187189
meta.connection = this.getConnection({ requestId: meta.request.id })
@@ -459,7 +461,7 @@ class Transport {
459461
prepareRequest()
460462
} else {
461463
// wait for product check to finish
462-
productCheckEmitter.once('product-check', (error, status) => {
464+
this[kEventEmitter].once('product-check', (error, status) => {
463465
if (status === false) {
464466
const err = error || new ProductNotSupportedError(result)
465467
if (this[kProductCheck] === 4) {
@@ -563,48 +565,48 @@ class Transport {
563565
'The client is unable to verify that the server is Elasticsearch due to security privileges on the server side. Some functionality may not be compatible if the server is running an unsupported product.',
564566
'ProductNotSupportedSecurityError'
565567
)
566-
productCheckEmitter.emit('product-check', null, true)
568+
this[kEventEmitter].emit('product-check', null, true)
567569
} else {
568570
this[kProductCheck] = 0
569-
productCheckEmitter.emit('product-check', err, false)
571+
this[kEventEmitter].emit('product-check', err, false)
570572
}
571573
} else {
572574
debug('Checking elasticsearch version', result.body, result.headers)
573575
if (result.body.version == null || typeof result.body.version.number !== 'string') {
574576
debug('Can\'t access Elasticsearch version')
575-
return productCheckEmitter.emit('product-check', null, false)
577+
return this[kEventEmitter].emit('product-check', null, false)
576578
}
577579
const tagline = result.body.tagline
578580
const version = result.body.version.number.split('.')
579581
const major = Number(version[0])
580582
const minor = Number(version[1])
581583
if (major < 6) {
582-
return productCheckEmitter.emit('product-check', null, false)
584+
return this[kEventEmitter].emit('product-check', null, false)
583585
} else if (major >= 6 && major < 7) {
584586
if (tagline !== 'You Know, for Search') {
585587
debug('Bad tagline')
586-
return productCheckEmitter.emit('product-check', null, false)
588+
return this[kEventEmitter].emit('product-check', null, false)
587589
}
588590
} else if (major === 7 && minor < 14) {
589591
if (tagline !== 'You Know, for Search') {
590592
debug('Bad tagline')
591-
return productCheckEmitter.emit('product-check', null, false)
593+
return this[kEventEmitter].emit('product-check', null, false)
592594
}
593595

594596
if (result.body.version.build_flavor !== 'default') {
595597
debug('Bad build_flavor')
596598
this[kProductCheck] = 4
597-
return productCheckEmitter.emit('product-check', null, false)
599+
return this[kEventEmitter].emit('product-check', null, false)
598600
}
599601
} else {
600602
if (result.headers['x-elastic-product'] !== 'Elasticsearch') {
601603
debug('x-elastic-product not recognized')
602-
return productCheckEmitter.emit('product-check', null, false)
604+
return this[kEventEmitter].emit('product-check', null, false)
603605
}
604606
}
605607
debug('Valid Elasticsearch distribution')
606608
this[kProductCheck] = 2
607-
productCheckEmitter.emit('product-check', null, true)
609+
this[kEventEmitter].emit('product-check', null, true)
608610
}
609611
})
610612
}

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
},
1313
"homepage": "http://www.elastic.co/guide/en/elasticsearch/client/javascript-api/current/index.html",
1414
"version": "7.16.0",
15-
"versionCanary": "7.16.0-canary.1",
15+
"versionCanary": "7.16.0-canary.2",
1616
"keywords": [
1717
"elasticsearch",
1818
"elastic",

test/acceptance/product-check.test.js

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
'use strict'
2121

2222
const { test } = require('tap')
23-
const { Client } = require('../../')
23+
const { Client, errors } = require('../../')
2424
const {
2525
connection: {
2626
MockConnectionTimeout,
@@ -1285,3 +1285,64 @@ test('Observability events should have all the expected properties', t => {
12851285
t.equal(err.message, 'The client noticed that the server is not Elasticsearch and we do not support this unknown product.')
12861286
})
12871287
})
1288+
1289+
test('Abort a request while running the product check', t => {
1290+
t.plan(4)
1291+
const MockConnection = buildMockConnection({
1292+
onRequest (params) {
1293+
return {
1294+
statusCode: 200,
1295+
headers: {
1296+
'x-elastic-product': 'Elasticsearch'
1297+
},
1298+
body: {
1299+
name: '1ef419078577',
1300+
cluster_name: 'docker-cluster',
1301+
cluster_uuid: 'cQ5pAMvRRTyEzObH4L5mTA',
1302+
version: {
1303+
number: '8.0.0-SNAPSHOT',
1304+
build_flavor: 'default',
1305+
build_type: 'docker',
1306+
build_hash: '5fb4c050958a6b0b6a70a6fb3e616d0e390eaac3',
1307+
build_date: '2021-07-10T01:45:02.136546168Z',
1308+
build_snapshot: true,
1309+
lucene_version: '8.9.0',
1310+
minimum_wire_compatibility_version: '7.15.0',
1311+
minimum_index_compatibility_version: '7.0.0'
1312+
},
1313+
tagline: 'You Know, for Search'
1314+
}
1315+
}
1316+
}
1317+
})
1318+
1319+
const client = new Client({
1320+
node: 'http://localhost:9200',
1321+
Connection: MockConnection
1322+
})
1323+
1324+
client.on('request', (err, event) => {
1325+
if (event.meta.request.params.path.includes('search')) {
1326+
t.ok(err instanceof errors.RequestAbortedError)
1327+
}
1328+
})
1329+
1330+
// the response event won't be executed for the search
1331+
client.on('response', (err, event) => {
1332+
t.error(err)
1333+
t.equal(event.meta.request.params.path, '/')
1334+
})
1335+
1336+
const req = client.search({
1337+
index: 'foo',
1338+
body: {
1339+
query: {
1340+
match_all: {}
1341+
}
1342+
}
1343+
}, (err, result) => {
1344+
t.ok(err instanceof errors.RequestAbortedError)
1345+
})
1346+
1347+
setImmediate(() => req.abort())
1348+
})

0 commit comments

Comments
 (0)