Compare commits

..

4 Commits

9 changed files with 129 additions and 28 deletions

View File

@ -63,7 +63,7 @@ jobs:
- name: Test
run: |
npm run test:unit -- --node-arg=--harmony-async-iteration
npm run test:node8
helpers-integration-test:
name: Helpers integration test

View File

@ -1,6 +1,19 @@
[[changelog-client]]
== Changelog
=== 7.7.1
==== Fixes
===== Disable client Helpers in Node.js < 10 - https://github.com/elastic/elasticsearch-js/pull/1194[#1194]
The client helpers can't be used in Node.js < 10 because it needs a custom flag to be able to use them.
Given that not every provider allows the user to specify cuatom Node.js flags, the Helpers has been disabled completely in Node.js < 10.
===== Force lowercase in all headers - https://github.com/elastic/elasticsearch-js/pull/1187[#1187]
Now all the user-provided headers names will be lowercased by default, so there will be no conflicts in case of the same header with different casing.
=== 7.7.0
==== Features

View File

@ -4,8 +4,7 @@
The client comes with an handy collection of helpers to give you a more comfortable experience with some APIs.
CAUTION: The client helpers are experimental, and the API may change in the next minor releases.
If you are using the client with Node.js v8 you should run your code with the `--harmony-async-iteration` argument. +
eg: `node --harmony-async-iteration index.js`
The helpers will not work in any Node.js version lower than 10.
=== Bulk Helper
Running Bulk requests can be complex due to the shape of the API, this helper aims to provide a nicer developer experience around the Bulk API.

View File

@ -4,13 +4,16 @@
'use strict'
const nodeMajor = Number(process.versions.node.split('.')[0])
const { EventEmitter } = require('events')
const { URL } = require('url')
const debug = require('debug')('elasticsearch')
const Transport = require('./lib/Transport')
const Connection = require('./lib/Connection')
const { ConnectionPool, CloudConnectionPool } = require('./lib/pool')
const Helpers = require('./lib/Helpers')
// Helpers works only in Node.js >= 10
const Helpers = nodeMajor < 10 ? null : require('./lib/Helpers')
const Serializer = require('./lib/Serializer')
const errors = require('./lib/errors')
const { ConfigurationError } = errors
@ -127,7 +130,9 @@ class Client extends EventEmitter {
opaqueIdPrefix: options.opaqueIdPrefix
})
this.helpers = new Helpers({ client: this, maxRetries: options.maxRetries })
if (Helpers !== null) {
this.helpers = new Helpers({ client: this, maxRetries: options.maxRetries })
}
const apis = buildApi({
makeRequest: this.transport.request.bind(this.transport),

View File

@ -34,9 +34,9 @@ class Transport {
this.suggestCompression = opts.suggestCompression === true
this.compression = opts.compression || false
this.headers = Object.assign({},
{ 'User-Agent': userAgent },
opts.suggestCompression === true ? { 'Accept-Encoding': 'gzip,deflate' } : null,
opts.headers
{ 'user-agent': userAgent },
opts.suggestCompression === true ? { 'accept-encoding': 'gzip,deflate' } : null,
lowerCaseHeaders(opts.headers)
)
this.sniffInterval = opts.sniffInterval
this.sniffOnConnectionFault = opts.sniffOnConnectionFault
@ -243,10 +243,10 @@ class Transport {
})
}
const headers = Object.assign({}, this.headers, options.headers)
const headers = Object.assign({}, this.headers, lowerCaseHeaders(options.headers))
if (options.opaqueId !== undefined) {
headers['X-Opaque-Id'] = this.opaqueIdPrefix !== null
headers['x-opaque-id'] = this.opaqueIdPrefix !== null
? this.opaqueIdPrefix + options.opaqueId
: options.opaqueId
}
@ -262,7 +262,7 @@ class Transport {
}
if (params.body !== '') {
headers['Content-Type'] = headers['Content-Type'] || 'application/json'
headers['content-type'] = headers['content-type'] || 'application/json'
}
// handle ndjson body
@ -277,7 +277,7 @@ class Transport {
params.body = params.bulkBody
}
if (params.body !== '') {
headers['Content-Type'] = headers['Content-Type'] || 'application/x-ndjson'
headers['content-type'] = headers['content-type'] || 'application/x-ndjson'
}
}
@ -301,7 +301,7 @@ class Transport {
if (params.body !== '' && params.body != null) {
if (isStream(params.body) === true) {
if (compression === 'gzip') {
params.headers['Content-Encoding'] = compression
params.headers['content-encoding'] = compression
params.body = params.body.pipe(createGzip())
}
makeRequest()
@ -311,13 +311,13 @@ class Transport {
if (err) {
return callback(err, result)
}
params.headers['Content-Encoding'] = compression
params.headers['Content-Length'] = '' + Buffer.byteLength(buffer)
params.headers['content-encoding'] = compression
params.headers['content-length'] = '' + Buffer.byteLength(buffer)
params.body = buffer
makeRequest()
})
} else {
params.headers['Content-Length'] = '' + Buffer.byteLength(params.body)
params.headers['content-length'] = '' + Buffer.byteLength(params.body)
makeRequest()
}
} else {
@ -453,5 +453,21 @@ function generateRequestId () {
return (nextReqId = (nextReqId + 1) & maxInt)
}
}
function lowerCaseHeaders (oldHeaders) {
if (oldHeaders == null) return oldHeaders
const newHeaders = {}
for (const header in oldHeaders) {
newHeaders[header.toLowerCase()] = oldHeaders[header]
}
return newHeaders
}
module.exports = Transport
module.exports.internals = { defaultNodeFilter, roundRobinSelector, randomSelector, generateRequestId }
module.exports.internals = {
defaultNodeFilter,
roundRobinSelector,
randomSelector,
generateRequestId,
lowerCaseHeaders
}

View File

@ -4,7 +4,7 @@
"main": "index.js",
"types": "index.d.ts",
"homepage": "http://www.elastic.co/guide/en/elasticsearch/client/javascript-api/current/index.html",
"version": "7.7.0",
"version": "7.7.1",
"keywords": [
"elasticsearch",
"elastic",
@ -17,6 +17,7 @@
],
"scripts": {
"test": "npm run lint && npm run test:unit && npm run test:behavior && npm run test:types",
"test:node8": "npm run lint && tap test/unit/*.test.js -t 300 --no-coverage && npm run test:behavior && npm run test:types",
"test:unit": "tap test/unit/*.test.js test/unit/**/*.test.js -t 300 --no-coverage",
"test:behavior": "tap test/behavior/*.test.js -t 300 --no-coverage",
"test:integration": "node test/integration/index.js",

View File

@ -1029,3 +1029,41 @@ test('Opaque Id support', t => {
t.end()
})
test('Correctly handles the same header cased differently', t => {
t.plan(4)
function handler (req, res) {
t.strictEqual(req.headers['authorization'], 'Basic foobar')
t.strictEqual(req.headers['foo'], 'baz')
res.setHeader('Content-Type', 'application/json;utf=8')
res.end(JSON.stringify({ hello: 'world' }))
}
buildServer(handler, ({ port }, server) => {
const client = new Client({
node: `http://localhost:${port}`,
auth: {
username: 'hello',
password: 'world'
},
headers: {
Authorization: 'Basic foobar',
Foo: 'bar'
}
})
client.search({
index: 'test',
q: 'foo:bar'
}, {
headers: {
foo: 'baz'
}
}, (err, { body }) => {
t.error(err)
t.deepEqual(body, { hello: 'world' })
server.stop()
})
})
})

View File

@ -25,7 +25,7 @@ test('bulk index', t => {
const MockConnection = connection.buildMockConnection({
onRequest (params) {
t.strictEqual(params.path, '/_bulk')
t.match(params.headers, { 'Content-Type': 'application/x-ndjson' })
t.match(params.headers, { 'content-type': 'application/x-ndjson' })
const [action, payload] = params.body.split('\n')
t.deepEqual(JSON.parse(action), { index: { _index: 'test' } })
t.deepEqual(JSON.parse(payload), dataset[count++])
@ -67,7 +67,7 @@ test('bulk index', t => {
const MockConnection = connection.buildMockConnection({
onRequest (params) {
t.strictEqual(params.path, '/_bulk')
t.match(params.headers, { 'Content-Type': 'application/x-ndjson' })
t.match(params.headers, { 'content-type': 'application/x-ndjson' })
const [action, payload] = params.body.split('\n')
t.deepEqual(JSON.parse(action), { index: { _index: 'test' } })
t.deepEqual(JSON.parse(payload), dataset[count++])
@ -108,7 +108,7 @@ test('bulk index', t => {
const MockConnection = connection.buildMockConnection({
onRequest (params) {
t.strictEqual(params.path, '/_bulk')
t.match(params.headers, { 'Content-Type': 'application/x-ndjson' })
t.match(params.headers, { 'content-type': 'application/x-ndjson' })
t.strictEqual(params.body.split('\n').filter(Boolean).length, 6)
return { body: { errors: false, items: new Array(3).fill({}) } }
}
@ -152,7 +152,7 @@ test('bulk index', t => {
return { body: { acknowledged: true } }
} else {
t.strictEqual(params.path, '/_bulk')
t.match(params.headers, { 'Content-Type': 'application/x-ndjson' })
t.match(params.headers, { 'content-type': 'application/x-ndjson' })
const [action, payload] = params.body.split('\n')
t.deepEqual(JSON.parse(action), { index: { _index: 'test' } })
t.deepEqual(JSON.parse(payload), dataset[count++])
@ -193,7 +193,7 @@ test('bulk index', t => {
const MockConnection = connection.buildMockConnection({
onRequest (params) {
t.strictEqual(params.path, '/_bulk')
t.match(params.headers, { 'Content-Type': 'application/x-ndjson' })
t.match(params.headers, { 'content-type': 'application/x-ndjson' })
const [action, payload] = params.body.split('\n')
t.deepEqual(JSON.parse(action), { index: { _index: 'test', _id: count } })
t.deepEqual(JSON.parse(payload), dataset[count++])
@ -607,7 +607,7 @@ test('bulk index', t => {
const MockConnection = connection.buildMockConnection({
onRequest (params) {
t.strictEqual(params.path, '/_bulk')
t.match(params.headers, { 'Content-Type': 'application/x-ndjson' })
t.match(params.headers, { 'content-type': 'application/x-ndjson' })
const [action, payload] = params.body.split('\n')
t.deepEqual(JSON.parse(action), { index: { _index: 'test', _id: count } })
t.deepEqual(JSON.parse(payload), dataset[count++])
@ -659,7 +659,7 @@ test('bulk index', t => {
const MockConnection = connection.buildMockConnection({
onRequest (params) {
t.strictEqual(params.path, '/_bulk')
t.match(params.headers, { 'Content-Type': 'application/x-ndjson' })
t.match(params.headers, { 'content-type': 'application/x-ndjson' })
const [action, payload] = params.body.split('\n')
t.deepEqual(JSON.parse(action), { index: { _index: 'test' } })
t.deepEqual(JSON.parse(payload), dataset[count++])
@ -715,7 +715,7 @@ test('bulk create', t => {
const MockConnection = connection.buildMockConnection({
onRequest (params) {
t.strictEqual(params.path, '/_bulk')
t.match(params.headers, { 'Content-Type': 'application/x-ndjson' })
t.match(params.headers, { 'content-type': 'application/x-ndjson' })
const [action, payload] = params.body.split('\n')
t.deepEqual(JSON.parse(action), { create: { _index: 'test', _id: count } })
t.deepEqual(JSON.parse(payload), dataset[count++])
@ -764,7 +764,7 @@ test('bulk update', t => {
const MockConnection = connection.buildMockConnection({
onRequest (params) {
t.strictEqual(params.path, '/_bulk')
t.match(params.headers, { 'Content-Type': 'application/x-ndjson' })
t.match(params.headers, { 'content-type': 'application/x-ndjson' })
const [action, payload] = params.body.split('\n')
t.deepEqual(JSON.parse(action), { update: { _index: 'test', _id: count } })
t.deepEqual(JSON.parse(payload), { doc: dataset[count++], doc_as_upsert: true })
@ -815,7 +815,7 @@ test('bulk delete', t => {
const MockConnection = connection.buildMockConnection({
onRequest (params) {
t.strictEqual(params.path, '/_bulk')
t.match(params.headers, { 'Content-Type': 'application/x-ndjson' })
t.match(params.headers, { 'content-type': 'application/x-ndjson' })
t.deepEqual(JSON.parse(params.body), { delete: { _index: 'test', _id: count++ } })
return { body: { errors: false, items: [{}] } }
}

View File

@ -2416,3 +2416,32 @@ test('Secure json parsing', t => {
t.end()
})
test('Lowercase headers utilty', t => {
t.plan(4)
const { lowerCaseHeaders } = Transport.internals
t.deepEqual(lowerCaseHeaders({
Foo: 'bar',
Faz: 'baz',
'X-Hello': 'world'
}), {
foo: 'bar',
faz: 'baz',
'x-hello': 'world'
})
t.deepEqual(lowerCaseHeaders({
Foo: 'bar',
faz: 'baz',
'X-hello': 'world'
}), {
foo: 'bar',
faz: 'baz',
'x-hello': 'world'
})
t.strictEqual(lowerCaseHeaders(null), null)
t.strictEqual(lowerCaseHeaders(undefined), undefined)
})