diff --git a/lib/Transport.js b/lib/Transport.js index d15288313..0edf78347 100644 --- a/lib/Transport.js +++ b/lib/Transport.js @@ -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 +} diff --git a/test/unit/client.test.js b/test/unit/client.test.js index 902bee469..885a824d5 100644 --- a/test/unit/client.test.js +++ b/test/unit/client.test.js @@ -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() + }) + }) +}) diff --git a/test/unit/helpers/bulk.test.js b/test/unit/helpers/bulk.test.js index b19ca12b5..cb9ab8ed7 100644 --- a/test/unit/helpers/bulk.test.js +++ b/test/unit/helpers/bulk.test.js @@ -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: [{}] } } } diff --git a/test/unit/transport.test.js b/test/unit/transport.test.js index 3971ff82f..e3875b291 100644 --- a/test/unit/transport.test.js +++ b/test/unit/transport.test.js @@ -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) +})