mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
# Backport This will backport the following commits from `main` to `8.11`: - [Add custom representation for request objects (#171155)](https://github.com/elastic/kibana/pull/171155) <!--- Backport version: 8.9.7 --> ### Questions ? Please refer to the [Backport tool documentation](https://github.com/sqren/backport) <!--BACKPORT [{"author":{"name":"Pierre Gayvallet","email":"pierre.gayvallet@elastic.co"},"sourceCommit":{"committedDate":"2023-11-14T11:16:32Z","message":"Add custom representation for request objects (#171155)\n\n## Summary\r\n\r\nAdd custom string/json/inspect representations for `KibanaRequest`,\r\n`hapi.Request` and `http.IncomingMessage`","sha":"4aa9ed52fd01ab25fe97bbd917f873e6da273ed0","branchLabelMapping":{"^v8.12.0$":"main","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:skip","backport:prev-minor","v8.12.0","v8.11.2"],"number":171155,"url":"https://github.com/elastic/kibana/pull/171155","mergeCommit":{"message":"Add custom representation for request objects (#171155)\n\n## Summary\r\n\r\nAdd custom string/json/inspect representations for `KibanaRequest`,\r\n`hapi.Request` and `http.IncomingMessage`","sha":"4aa9ed52fd01ab25fe97bbd917f873e6da273ed0"}},"sourceBranch":"main","suggestedTargetBranches":["8.11"],"targetPullRequestStates":[{"branch":"main","label":"v8.12.0","labelRegex":"^v8.12.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/171155","number":171155,"mergeCommit":{"message":"Add custom representation for request objects (#171155)\n\n## Summary\r\n\r\nAdd custom string/json/inspect representations for `KibanaRequest`,\r\n`hapi.Request` and `http.IncomingMessage`","sha":"4aa9ed52fd01ab25fe97bbd917f873e6da273ed0"}},{"branch":"8.11","label":"v8.11.2","labelRegex":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"}]}] BACKPORT--> Co-authored-by: Pierre Gayvallet <pierre.gayvallet@elastic.co>
This commit is contained in:
parent
1adb557415
commit
9522ef2414
3 changed files with 329 additions and 0 deletions
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
// @ts-expect-error not in the definition file
|
||||
import HapiRequest from '@hapi/hapi/lib/request.js';
|
||||
import { IncomingMessage } from 'http';
|
||||
import { inspect } from 'util';
|
||||
|
||||
export const patchRequest = () => {
|
||||
// HAPI request
|
||||
HapiRequest.prototype.toString = function () {
|
||||
return `[HAPI.Request method="${this.method}" url="${this.url}"]`;
|
||||
};
|
||||
|
||||
HapiRequest.prototype.toJSON = function () {
|
||||
return {
|
||||
method: this.method,
|
||||
url: String(this.url),
|
||||
};
|
||||
};
|
||||
|
||||
HapiRequest.prototype[inspect.custom] = function () {
|
||||
return this.toJSON();
|
||||
};
|
||||
|
||||
// http.IncomingMessage
|
||||
const IncomingMessageProto = IncomingMessage.prototype;
|
||||
|
||||
IncomingMessageProto.toString = function () {
|
||||
return `[http.IncomingMessage method="${this.method}" url="${this.url}" complete="${this.complete}" aborted="${this.aborted}"]`;
|
||||
};
|
||||
|
||||
// @ts-expect-error missing definition
|
||||
IncomingMessageProto.toJSON = function () {
|
||||
return {
|
||||
method: this.method,
|
||||
url: this.url,
|
||||
complete: this.complete,
|
||||
aborted: this.aborted,
|
||||
};
|
||||
};
|
||||
|
||||
// @ts-expect-error missing definition
|
||||
IncomingMessageProto[inspect.custom] = function () {
|
||||
// @ts-expect-error missing definition
|
||||
return this.toJSON();
|
||||
};
|
||||
};
|
|
@ -8,6 +8,7 @@
|
|||
|
||||
import { URL } from 'url';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { inspect } from 'util';
|
||||
import type { Request, RouteOptions } from '@hapi/hapi';
|
||||
import { fromEvent, NEVER } from 'rxjs';
|
||||
import { shareReplay, first, filter } from 'rxjs/operators';
|
||||
|
@ -36,6 +37,10 @@ import {
|
|||
import { RouteValidator } from './validator';
|
||||
import { isSafeMethod } from './route';
|
||||
import { KibanaSocket } from './socket';
|
||||
import { patchRequest } from './patch_requests';
|
||||
|
||||
// patching at module load
|
||||
patchRequest();
|
||||
|
||||
const requestSymbol = Symbol('request');
|
||||
|
||||
|
@ -185,6 +190,29 @@ export class CoreKibanaRequest<
|
|||
};
|
||||
}
|
||||
|
||||
toString() {
|
||||
return `[CoreKibanaRequest id="${this.id}" method="${this.route.method}" url="${this.url}" fake="${this.isFakeRequest}" system="${this.isSystemRequest}" api="${this.isInternalApiRequest}"]`;
|
||||
}
|
||||
|
||||
toJSON() {
|
||||
return {
|
||||
id: this.id,
|
||||
uuid: this.uuid,
|
||||
url: `${this.url}`,
|
||||
isFakeRequest: this.isFakeRequest,
|
||||
isSystemRequest: this.isSystemRequest,
|
||||
isInternalApiRequest: this.isInternalApiRequest,
|
||||
auth: {
|
||||
isAuthenticated: this.auth.isAuthenticated,
|
||||
},
|
||||
route: this.route,
|
||||
};
|
||||
}
|
||||
|
||||
[inspect.custom]() {
|
||||
return this.toJSON();
|
||||
}
|
||||
|
||||
private getEvents(request: RawRequest): KibanaRequestEvents {
|
||||
if (isFakeRawRequest(request)) {
|
||||
return {
|
||||
|
|
248
src/core/server/integration_tests/http/request_representation.ts
Normal file
248
src/core/server/integration_tests/http/request_representation.ts
Normal file
|
@ -0,0 +1,248 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
jest.mock('uuid', () => ({
|
||||
v4: jest.fn().mockReturnValue('xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx'),
|
||||
}));
|
||||
|
||||
import supertest from 'supertest';
|
||||
import { loggingSystemMock } from '@kbn/core-logging-server-mocks';
|
||||
import { executionContextServiceMock } from '@kbn/core-execution-context-server-mocks';
|
||||
import { contextServiceMock } from '@kbn/core-http-context-server-mocks';
|
||||
import type { HttpService } from '@kbn/core-http-server-internal';
|
||||
import { ensureRawRequest } from '@kbn/core-http-router-server-internal';
|
||||
import { createHttpServer } from '@kbn/core-http-server-mocks';
|
||||
import { inspect } from 'util';
|
||||
|
||||
let server: HttpService;
|
||||
|
||||
let logger: ReturnType<typeof loggingSystemMock.create>;
|
||||
const contextSetup = contextServiceMock.createSetupContract();
|
||||
|
||||
const setupDeps = {
|
||||
context: contextSetup,
|
||||
executionContext: executionContextServiceMock.createInternalSetupContract(),
|
||||
};
|
||||
|
||||
beforeEach(async () => {
|
||||
logger = loggingSystemMock.create();
|
||||
|
||||
server = createHttpServer({ logger });
|
||||
await server.preboot({ context: contextServiceMock.createPrebootContract() });
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await server.stop();
|
||||
});
|
||||
|
||||
const replacePorts = (input: string): string => input.replace(/[:][0-9]+[/]/g, ':XXXX/');
|
||||
|
||||
describe('request logging', () => {
|
||||
describe('KibanaRequest', () => {
|
||||
it('has expected string representation', async () => {
|
||||
const { server: innerServer, createRouter } = await server.setup(setupDeps);
|
||||
const router = createRouter('/');
|
||||
router.get(
|
||||
{ path: '/', validate: false, options: { authRequired: true } },
|
||||
(context, req, res) => {
|
||||
return res.ok({ body: { req: String(req) } });
|
||||
}
|
||||
);
|
||||
await server.start();
|
||||
|
||||
const response = await supertest(innerServer.listener).get('/').expect(200);
|
||||
expect(replacePorts(response.body.req)).toEqual(
|
||||
`[CoreKibanaRequest id="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" method="get" url="http://127.0.0.1:XXXX/" fake="false" system="false" api="false"]`
|
||||
);
|
||||
});
|
||||
|
||||
it('has expected JSON representation', async () => {
|
||||
const { server: innerServer, createRouter } = await server.setup(setupDeps);
|
||||
const router = createRouter('/');
|
||||
router.get(
|
||||
{ path: '/', validate: false, options: { authRequired: true } },
|
||||
(context, req, res) => {
|
||||
return res.ok({ body: { req: JSON.stringify(req) } });
|
||||
}
|
||||
);
|
||||
await server.start();
|
||||
|
||||
const response = await supertest(innerServer.listener).get('/').expect(200);
|
||||
|
||||
expect(JSON.parse(replacePorts(response.body.req))).toEqual({
|
||||
id: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx',
|
||||
url: 'http://127.0.0.1:XXXX/',
|
||||
isFakeRequest: false,
|
||||
isInternalApiRequest: false,
|
||||
isSystemRequest: false,
|
||||
auth: {
|
||||
isAuthenticated: false,
|
||||
},
|
||||
route: {
|
||||
method: 'get',
|
||||
path: '/',
|
||||
options: expect.any(Object),
|
||||
},
|
||||
uuid: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx',
|
||||
});
|
||||
});
|
||||
|
||||
it('has expected inspect representation', async () => {
|
||||
const { server: innerServer, createRouter } = await server.setup(setupDeps);
|
||||
const router = createRouter('/');
|
||||
router.get(
|
||||
{ path: '/', validate: false, options: { authRequired: true } },
|
||||
(context, req, res) => {
|
||||
return res.ok({ body: { req: inspect(req) } });
|
||||
}
|
||||
);
|
||||
await server.start();
|
||||
|
||||
const response = await supertest(innerServer.listener).get('/').expect(200);
|
||||
expect(replacePorts(response.body.req)).toMatchInlineSnapshot(`
|
||||
"{
|
||||
id: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx',
|
||||
uuid: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx',
|
||||
url: 'http://127.0.0.1:XXXX/',
|
||||
isFakeRequest: false,
|
||||
isSystemRequest: false,
|
||||
isInternalApiRequest: false,
|
||||
auth: { isAuthenticated: false },
|
||||
route: {
|
||||
path: '/',
|
||||
method: 'get',
|
||||
options: {
|
||||
authRequired: true,
|
||||
xsrfRequired: false,
|
||||
access: 'internal',
|
||||
tags: [],
|
||||
timeout: [Object],
|
||||
body: undefined
|
||||
}
|
||||
}
|
||||
}"
|
||||
`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('HAPI request', () => {
|
||||
it('has expected string representation', async () => {
|
||||
const { server: innerServer, createRouter } = await server.setup(setupDeps);
|
||||
const router = createRouter('/');
|
||||
router.get(
|
||||
{ path: '/', validate: false, options: { authRequired: true } },
|
||||
(context, req, res) => {
|
||||
const rawRequest = ensureRawRequest(req);
|
||||
return res.ok({ body: { req: String(rawRequest) } });
|
||||
}
|
||||
);
|
||||
await server.start();
|
||||
|
||||
const response = await supertest(innerServer.listener).get('/').expect(200);
|
||||
expect(replacePorts(response.body.req)).toEqual(
|
||||
`[HAPI.Request method="get" url="http://127.0.0.1:XXXX/"]`
|
||||
);
|
||||
});
|
||||
|
||||
it('has expected JSON representation', async () => {
|
||||
const { server: innerServer, createRouter } = await server.setup(setupDeps);
|
||||
const router = createRouter('/');
|
||||
router.get(
|
||||
{ path: '/', validate: false, options: { authRequired: true } },
|
||||
(context, req, res) => {
|
||||
const rawRequest = ensureRawRequest(req);
|
||||
return res.ok({ body: { req: JSON.stringify(rawRequest) } });
|
||||
}
|
||||
);
|
||||
await server.start();
|
||||
|
||||
const response = await supertest(innerServer.listener).get('/').expect(200);
|
||||
expect(JSON.parse(replacePorts(response.body.req))).toEqual({
|
||||
method: 'get',
|
||||
url: 'http://127.0.0.1:XXXX/',
|
||||
});
|
||||
});
|
||||
|
||||
it('has expected inspect representation', async () => {
|
||||
const { server: innerServer, createRouter } = await server.setup(setupDeps);
|
||||
const router = createRouter('/');
|
||||
router.get(
|
||||
{ path: '/', validate: false, options: { authRequired: true } },
|
||||
(context, req, res) => {
|
||||
const rawRequest = ensureRawRequest(req);
|
||||
return res.ok({ body: { req: inspect(rawRequest) } });
|
||||
}
|
||||
);
|
||||
await server.start();
|
||||
|
||||
const response = await supertest(innerServer.listener).get('/').expect(200);
|
||||
expect(replacePorts(response.body.req)).toMatchInlineSnapshot(
|
||||
`"{ method: 'get', url: 'http://127.0.0.1:XXXX/' }"`
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('http.IncomingMessage', () => {
|
||||
it('has expected string representation', async () => {
|
||||
const { server: innerServer, createRouter } = await server.setup(setupDeps);
|
||||
const router = createRouter('/');
|
||||
router.get(
|
||||
{ path: '/', validate: false, options: { authRequired: true } },
|
||||
(context, req, res) => {
|
||||
const rawRawRequest = ensureRawRequest(req).raw.req;
|
||||
return res.ok({ body: { req: String(rawRawRequest) } });
|
||||
}
|
||||
);
|
||||
await server.start();
|
||||
|
||||
const response = await supertest(innerServer.listener).get('/').expect(200);
|
||||
expect(replacePorts(response.body.req)).toEqual(
|
||||
`[http.IncomingMessage method="GET" url="/" complete="true" aborted="false"]`
|
||||
);
|
||||
});
|
||||
|
||||
it('has expected JSON representation', async () => {
|
||||
const { server: innerServer, createRouter } = await server.setup(setupDeps);
|
||||
const router = createRouter('/');
|
||||
router.get(
|
||||
{ path: '/', validate: false, options: { authRequired: true } },
|
||||
(context, req, res) => {
|
||||
const rawRawRequest = ensureRawRequest(req).raw.req;
|
||||
return res.ok({ body: { req: JSON.stringify(rawRawRequest) } });
|
||||
}
|
||||
);
|
||||
await server.start();
|
||||
|
||||
const response = await supertest(innerServer.listener).get('/').expect(200);
|
||||
expect(JSON.parse(replacePorts(response.body.req))).toEqual({
|
||||
aborted: false,
|
||||
complete: true,
|
||||
method: 'GET',
|
||||
url: '/',
|
||||
});
|
||||
});
|
||||
|
||||
it('has expected inspect representation', async () => {
|
||||
const { server: innerServer, createRouter } = await server.setup(setupDeps);
|
||||
const router = createRouter('/');
|
||||
router.get(
|
||||
{ path: '/', validate: false, options: { authRequired: true } },
|
||||
(context, req, res) => {
|
||||
const rawRawRequest = ensureRawRequest(req).raw.req;
|
||||
return res.ok({ body: { req: inspect(rawRawRequest) } });
|
||||
}
|
||||
);
|
||||
await server.start();
|
||||
|
||||
const response = await supertest(innerServer.listener).get('/').expect(200);
|
||||
expect(replacePorts(response.body.req)).toMatchInlineSnapshot(
|
||||
`"{ method: 'GET', url: '/', complete: true, aborted: false }"`
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
Loading…
Add table
Add a link
Reference in a new issue