Skip to content

Commit

Permalink
Allow passing in a signal to abort a cluster client request (#37563)
Browse files Browse the repository at this point in the history
* Allow passing in a signal to abort an Elasticsearch request using the cluster client

* Go back to using promises (which still return abort method) and update test

* Update docs

* Explicitly return Promise<any> instead of {}
  • Loading branch information
lukasolson committed Jun 12, 2019
1 parent 4785301 commit 9e472e6
Show file tree
Hide file tree
Showing 6 changed files with 50 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,6 @@ export interface CallAPIOptions

| Property | Type | Description |
| --- | --- | --- |
| [signal](./kibana-plugin-server.callapioptions.signal.md) | <code>AbortSignal</code> | A signal object that allows you to abort the request via an AbortController object. |
| [wrap401Errors](./kibana-plugin-server.callapioptions.wrap401errors.md) | <code>boolean</code> | Indicates whether <code>401 Unauthorized</code> errors returned from the Elasticsearch API should be wrapped into <code>Boom</code> error instances with properly set <code>WWW-Authenticate</code> header that could have been returned by the API itself. If API didn't specify that then <code>Basic realm=&quot;Authorization Required&quot;</code> is used as <code>WWW-Authenticate</code>. |

Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->

[Home](./index.md) &gt; [kibana-plugin-server](./kibana-plugin-server.md) &gt; [CallAPIOptions](./kibana-plugin-server.callapioptions.md) &gt; [signal](./kibana-plugin-server.callapioptions.signal.md)

## CallAPIOptions.signal property

A signal object that allows you to abort the request via an AbortController object.

<b>Signature:</b>

```typescript
signal?: AbortSignal;
```
19 changes: 19 additions & 0 deletions src/core/server/elasticsearch/cluster_client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,25 @@ describe('#callAsInternalUser', () => {
).rejects.toStrictEqual(mockAuthenticationError);
});

test('aborts the request and rejects if a signal is provided and aborted', async () => {
const controller = new AbortController();

// The ES client returns a promise with an additional `abort` method to abort the request
const mockValue: any = Promise.resolve();
mockValue.abort = jest.fn();
mockEsClientInstance.ping.mockReturnValue(mockValue);

const promise = clusterClient.callAsInternalUser('ping', undefined, {
wrap401Errors: false,
signal: controller.signal,
});

controller.abort();

expect(mockValue.abort).toHaveBeenCalled();
await expect(promise).rejects.toThrowErrorMatchingInlineSnapshot(`"Request was aborted"`);
});

test('does not override WWW-Authenticate if returned by Elasticsearch', async () => {
const mockAuthenticationError = new (errors.AuthenticationException as any)(
'Authentication Exception',
Expand Down
17 changes: 15 additions & 2 deletions src/core/server/elasticsearch/cluster_client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@ export interface CallAPIOptions {
* then `Basic realm="Authorization Required"` is used as `WWW-Authenticate`.
*/
wrap401Errors: boolean;
/**
* A signal object that allows you to abort the request via an AbortController object.
*/
signal?: AbortSignal;
}

/**
Expand All @@ -57,7 +61,7 @@ async function callAPI(
endpoint: string,
clientParams: Record<string, unknown> = {},
options: CallAPIOptions = { wrap401Errors: true }
) {
): Promise<any> {
const clientPath = endpoint.split('.');
const api: any = get(client, clientPath);
if (!api) {
Expand All @@ -66,7 +70,16 @@ async function callAPI(

const apiContext = clientPath.length === 1 ? client : get(client, clientPath.slice(0, -1));
try {
return await api.call(apiContext, clientParams);
return await new Promise((resolve, reject) => {
const request = api.call(apiContext, clientParams);
if (options.signal) {
options.signal.addEventListener('abort', () => {
request.abort();
reject(new Error('Request was aborted'));
});
}
return request.then(resolve, reject);
});
} catch (err) {
if (!options.wrap401Errors || err.statusCode !== 401) {
throw err;
Expand Down
1 change: 1 addition & 0 deletions src/core/server/server.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ export function bootstrap({ configs, cliArgs, applyConfigOverrides, features, }:

// @public
export interface CallAPIOptions {
signal?: AbortSignal;
wrap401Errors: boolean;
}

Expand Down
1 change: 1 addition & 0 deletions src/legacy/core_plugins/elasticsearch/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,7 @@ export interface DeprecationAPIResponse {

export interface CallClusterOptions {
wrap401Errors?: boolean;
signal?: AbortSignal;
}

export interface CallClusterWithRequest {
Expand Down

0 comments on commit 9e472e6

Please sign in to comment.