Updated abort behavior (#1141)

* Updated abort behavior

- Support for aborting a request with the promise api
- Aborting a request will cause a RequestAbortedError
- Normalized Connection class errors, now every error returned is
wrapped by the client errors constructors

* Updated test

* Updated docs

* Updated code generation script

* Renamed test

* Code coverage

* Avoid calling twice transport.request
This commit is contained in:
Tomas Della Vedova
2020-04-06 11:21:19 +02:00
committed by GitHub
parent 953a8033ab
commit 27a8e2a9bf
16 changed files with 824 additions and 648 deletions

View File

@ -210,7 +210,7 @@ test('Sniff on connection fault', t => {
class MyConnection extends Connection {
request (params, callback) {
if (this.id === 'http://localhost:9200/') {
callback(new Error('kaboom'), null)
callback(new errors.ConnectionError('kaboom'), null)
return {}
} else {
return super.request(params, callback)

View File

@ -4,7 +4,7 @@
import { expectType } from 'tsd'
import { Client, ApiError, ApiResponse, RequestEvent, ResurrectEvent } from '../../'
import { TransportRequestCallback } from '../..//lib/Transport';
import { TransportRequestCallback, TransportRequestPromise } from '../..//lib/Transport';
const client = new Client({
node: 'http://localhost:9200'
@ -39,6 +39,7 @@ client.on('resurrect', (err, meta) => {
expectType<ApiResponse>(result)
})
expectType<TransportRequestCallback>(result)
expectType<void>(result.abort())
}
{
@ -47,6 +48,7 @@ client.on('resurrect', (err, meta) => {
expectType<ApiResponse>(result)
})
expectType<TransportRequestCallback>(result)
expectType<void>(result.abort())
}
{
@ -55,37 +57,42 @@ client.on('resurrect', (err, meta) => {
expectType<ApiResponse>(result)
})
expectType<TransportRequestCallback>(result)
expectType<void>(result.abort())
}
// Promise style
{
const promise = client.info()
expectType<Promise<ApiResponse>>(promise)
expectType<TransportRequestPromise<ApiResponse>>(promise)
promise
.then(result => expectType<ApiResponse>(result))
.catch((err: ApiError) => expectType<ApiError>(err))
expectType<void>(promise.abort())
}
{
const promise = client.info({ pretty: true })
expectType<Promise<ApiResponse>>(promise)
expectType<TransportRequestPromise<ApiResponse>>(promise)
promise
.then(result => expectType<ApiResponse>(result))
.catch((err: ApiError) => expectType<ApiError>(err))
expectType<void>(promise.abort())
}
{
const promise = client.info({ pretty: true }, { ignore: [404] })
expectType<Promise<ApiResponse>>(promise)
expectType<TransportRequestPromise<ApiResponse>>(promise)
promise
.then(result => expectType<ApiResponse>(result))
.catch((err: ApiError) => expectType<ApiError>(err))
expectType<void>(promise.abort())
}
// Promise style with async await
{
const promise = client.info()
expectType<Promise<ApiResponse>>(promise)
expectType<TransportRequestPromise<ApiResponse>>(promise)
expectType<void>(promise.abort())
try {
expectType<ApiResponse>(await promise)
} catch (err) {
@ -95,7 +102,8 @@ client.on('resurrect', (err, meta) => {
{
const promise = client.info({ pretty: true })
expectType<Promise<ApiResponse>>(promise)
expectType<TransportRequestPromise<ApiResponse>>(promise)
expectType<void>(promise.abort())
try {
expectType<ApiResponse>(await promise)
} catch (err) {
@ -105,7 +113,8 @@ client.on('resurrect', (err, meta) => {
{
const promise = client.info({ pretty: true }, { ignore: [404] })
expectType<Promise<ApiResponse>>(promise)
expectType<TransportRequestPromise<ApiResponse>>(promise)
expectType<void>(promise.abort())
try {
expectType<ApiResponse>(await promise)
} catch (err) {

View File

@ -81,3 +81,10 @@ const response = {
expectType<number>(err.statusCode)
expectType<Record<string, any>>(err.headers)
}
{
const err = new errors.RequestAbortedError('message', response)
expectType<string>(err.name)
expectType<string>(err.message)
expectType<ApiResponse>(err.meta)
}

View File

@ -135,7 +135,7 @@ test('Abort method (callback)', t => {
})
})
test('Abort is not supported in promises', t => {
test('Abort method (promises)', t => {
t.plan(2)
function handler (req, res) {
@ -160,7 +160,7 @@ test('Abort is not supported in promises', t => {
})
.catch(t.fail)
t.type(request.abort, 'undefined')
t.type(request.abort, 'function')
})
})

View File

@ -12,7 +12,7 @@ const { Agent } = require('http')
const intoStream = require('into-stream')
const { buildServer } = require('../utils')
const Connection = require('../../lib/Connection')
const { TimeoutError, ConfigurationError } = require('../../lib/errors')
const { TimeoutError, ConfigurationError, RequestAbortedError } = require('../../lib/errors')
test('Basic (http)', t => {
t.plan(4)
@ -855,3 +855,48 @@ test('Should not add agent and ssl to the serialized connection', t => {
t.end()
})
test('Abort a request syncronously', t => {
t.plan(1)
function handler (req, res) {
t.fail('The server should not be contacted')
}
buildServer(handler, ({ port }, server) => {
const connection = new Connection({
url: new URL(`http://localhost:${port}`)
})
const request = connection.request({
path: '/hello',
method: 'GET'
}, (err, res) => {
t.ok(err instanceof RequestAbortedError)
server.stop()
})
request.abort()
})
})
test('Abort a request asyncronously', t => {
t.plan(1)
function handler (req, res) {
// might be called or not
res.end('ok')
}
buildServer(handler, ({ port }, server) => {
const connection = new Connection({
url: new URL(`http://localhost:${port}`)
})
const request = connection.request({
path: '/hello',
method: 'GET'
}, (err, res) => {
t.ok(err instanceof RequestAbortedError)
server.stop()
})
setImmediate(() => request.abort())
})
})

View File

@ -80,3 +80,11 @@ test('ResponseError', t => {
t.ok(err.headers)
t.end()
})
test('RequestAbortedError', t => {
const err = new errors.RequestAbortedError()
t.true(err instanceof Error)
t.true(err instanceof errors.ElasticsearchClientError)
t.true(err.hasOwnProperty('meta'))
t.end()
})

View File

@ -21,7 +21,8 @@ const {
TimeoutError,
ResponseError,
ConnectionError,
ConfigurationError
ConfigurationError,
RequestAbortedError
} = require('../../lib/errors')
const ConnectionPool = require('../../lib/pool/ConnectionPool')
@ -88,6 +89,32 @@ test('Basic (promises support)', t => {
.catch(t.fail)
})
test('Basic - failing (promises support)', t => {
t.plan(1)
const pool = new ConnectionPool({ Connection: MockConnectionTimeout })
pool.addConnection('http://localhost:9200')
const transport = new Transport({
emit: () => {},
connectionPool: pool,
serializer: new Serializer(),
maxRetries: 3,
requestTimeout: 30000,
sniffInterval: false,
sniffOnStart: false
})
transport
.request({
method: 'GET',
path: '/hello'
})
.catch(err => {
t.ok(err instanceof TimeoutError)
})
})
test('Basic (options + promises support)', t => {
t.plan(1)
@ -764,7 +791,7 @@ test('Should call resurrect on every request', t => {
test('Should return a request aborter utility', t => {
t.plan(1)
const pool = new ConnectionPool({ Connection, MockConnection })
const pool = new ConnectionPool({ Connection: MockConnection })
pool.addConnection({
url: new URL('http://localhost:9200'),
id: 'node1'
@ -783,12 +810,11 @@ test('Should return a request aborter utility', t => {
const request = transport.request({
method: 'GET',
path: '/hello'
}, (_err, body) => {
t.fail('Should not be called')
}, (err, result) => {
t.ok(err instanceof RequestAbortedError)
})
request.abort()
t.pass('ok')
})
test('Retry mechanism and abort', t => {
@ -819,8 +845,6 @@ test('Retry mechanism and abort', t => {
emit: event => {
if (event === 'request' && count++ > 0) {
request.abort()
server.stop()
t.pass('ok')
}
},
connectionPool: pool,
@ -834,12 +858,48 @@ test('Retry mechanism and abort', t => {
const request = transport.request({
method: 'GET',
path: '/hello'
}, (e, { body }) => {
t.fail('Should not be called')
}, (err, result) => {
t.ok(err instanceof RequestAbortedError)
server.stop()
})
})
})
test('Abort a request with the promise API', t => {
t.plan(1)
const pool = new ConnectionPool({ Connection: MockConnection })
pool.addConnection({
url: new URL('http://localhost:9200'),
id: 'node1'
})
const transport = new Transport({
emit: () => {},
connectionPool: pool,
serializer: new Serializer(),
maxRetries: 3,
requestTimeout: 30000,
sniffInterval: false,
sniffOnStart: false
})
const request = transport.request({
method: 'GET',
path: '/hello'
})
request
.then(() => {
t.fail('Should not be called')
})
.catch(err => {
t.ok(err instanceof RequestAbortedError)
})
request.abort()
})
test('ResponseError', t => {
t.plan(3)

View File

@ -6,7 +6,11 @@
const assert = require('assert')
const { Connection } = require('../../index')
const { TimeoutError } = require('../../lib/errors')
const {
ConnectionError,
RequestAbortedError,
TimeoutError
} = require('../../lib/errors')
const intoStream = require('into-stream')
class MockConnection extends Connection {
@ -23,6 +27,8 @@ class MockConnection extends Connection {
process.nextTick(() => {
if (!aborted) {
callback(null, stream)
} else {
callback(new RequestAbortedError(), null)
}
})
return {
@ -37,6 +43,8 @@ class MockConnectionTimeout extends Connection {
process.nextTick(() => {
if (!aborted) {
callback(new TimeoutError('Request timed out', params), null)
} else {
callback(new RequestAbortedError(), null)
}
})
return {
@ -50,7 +58,9 @@ class MockConnectionError extends Connection {
var aborted = false
process.nextTick(() => {
if (!aborted) {
callback(new Error('Kaboom'), null)
callback(new ConnectionError('Kaboom'), null)
} else {
callback(new RequestAbortedError(), null)
}
})
return {
@ -93,6 +103,8 @@ class MockConnectionSniff extends Connection {
} else {
callback(null, stream)
}
} else {
callback(new RequestAbortedError(), null)
}
})
return {
@ -122,6 +134,8 @@ function buildMockConnection (opts) {
process.nextTick(() => {
if (!aborted) {
callback(null, stream)
} else {
callback(new RequestAbortedError(), null)
}
})
return {