-
Notifications
You must be signed in to change notification settings - Fork 8.5k
[Enterprise Search] Request handler refactors/enhancements + update existing routes to use shared handler #76106
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 7 commits
4804aee
ffb659f
95c20aa
9fae604
134d13c
4d1f6f9
74a4149
f6a709d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -7,63 +7,90 @@ | |
| import fetch from 'node-fetch'; | ||
| import querystring from 'querystring'; | ||
| import { | ||
| RequestHandler, | ||
| RequestHandlerContext, | ||
| KibanaRequest, | ||
| KibanaResponseFactory, | ||
| Logger, | ||
| } from 'src/core/server'; | ||
| import { ConfigType } from '../index'; | ||
|
|
||
| interface IEnterpriseSearchRequestParams<ResponseBody> { | ||
| interface IConstructorDependencies { | ||
| config: ConfigType; | ||
| log: Logger; | ||
| } | ||
| interface IRequestParams<ResponseBody> { | ||
| path: string; | ||
| params?: string; | ||
| hasValidData?: (body?: ResponseBody) => boolean; | ||
| } | ||
| export interface IEnterpriseSearchRequestHandler { | ||
| createRequest(requestParams?: object): RequestHandler<unknown, Readonly<{}>, unknown>; | ||
| } | ||
|
|
||
| /** | ||
| * This helper function creates a single standard DRY way of handling | ||
| * This helper lib creates a single standard DRY way of handling | ||
| * Enterprise Search API requests. | ||
| * | ||
| * This handler assumes that it will essentially just proxy the | ||
| * Enterprise Search API request, so the request body and request | ||
| * parameters are simply passed through. | ||
| */ | ||
| export function createEnterpriseSearchRequestHandler<ResponseBody>({ | ||
| config, | ||
| log, | ||
| path, | ||
| hasValidData = () => true, | ||
| }: IEnterpriseSearchRequestParams<ResponseBody>) { | ||
| return async ( | ||
| _context: RequestHandlerContext, | ||
| request: KibanaRequest<unknown, Readonly<{}>, unknown>, | ||
| response: KibanaResponseFactory | ||
| ) => { | ||
| try { | ||
| const enterpriseSearchUrl = config.host as string; | ||
| const params = request.query ? `?${querystring.stringify(request.query)}` : ''; | ||
| const url = `${encodeURI(enterpriseSearchUrl)}${path}${params}`; | ||
| export class EnterpriseSearchRequestHandler { | ||
| private enterpriseSearchUrl: string; | ||
| private log: Logger; | ||
|
|
||
| const apiResponse = await fetch(url, { | ||
| headers: { Authorization: request.headers.authorization as string }, | ||
| }); | ||
| constructor({ config, log }: IConstructorDependencies) { | ||
| this.log = log; | ||
| this.enterpriseSearchUrl = config.host as string; | ||
| } | ||
|
|
||
| const body = await apiResponse.json(); | ||
| createRequest<ResponseBody>({ | ||
| path, | ||
| params, | ||
| hasValidData = () => true, | ||
| }: IRequestParams<ResponseBody>) { | ||
| return async ( | ||
| _context: RequestHandlerContext, | ||
| request: KibanaRequest<unknown, Readonly<{}>, unknown>, | ||
| response: KibanaResponseFactory | ||
| ) => { | ||
| try { | ||
| // Set up API URL | ||
| params = params ?? (request.query ? `?${querystring.stringify(request.query)}` : ''); | ||
| const url = encodeURI(this.enterpriseSearchUrl + path + params); | ||
|
|
||
| if (hasValidData(body)) { | ||
| return response.ok({ body }); | ||
| } else { | ||
| throw new Error(`Invalid data received: ${JSON.stringify(body)}`); | ||
| } | ||
| } catch (e) { | ||
| log.error(`Cannot connect to Enterprise Search: ${e.toString()}`); | ||
| if (e instanceof Error) log.debug(e.stack as string); | ||
| // Set up API options | ||
| const { method } = request.route; | ||
| const headers = { Authorization: request.headers.authorization as string }; | ||
| const body = Object.keys(request.body as object).length | ||
| ? JSON.stringify(request.body) | ||
| : undefined; | ||
|
||
|
|
||
| // Call the Enterprise Search API and pass back response to the front-end | ||
| const apiResponse = await fetch(url, { method, headers, body }); | ||
|
|
||
| if (apiResponse.url.endsWith('/login')) { | ||
| throw new Error('Cannot authenticate Enterprise Search user'); | ||
| } | ||
|
|
||
| return response.customError({ | ||
| statusCode: 502, | ||
| body: 'Error connecting or fetching data from Enterprise Search', | ||
| }); | ||
| } | ||
| }; | ||
| const { status } = apiResponse; | ||
| const json = await apiResponse.json(); | ||
|
|
||
| if (hasValidData(json)) { | ||
| return response.custom({ statusCode: status, body: json }); | ||
| } else { | ||
| this.log.debug(`Invalid data received from <${url}>: ${JSON.stringify(json)}`); | ||
| throw new Error('Invalid data received'); | ||
| } | ||
| } catch (e) { | ||
| const errorMessage = `Error connecting to Enterprise Search: ${e?.message || e.toString()}`; | ||
|
|
||
| this.log.error(errorMessage); | ||
| if (e instanceof Error) this.log.debug(e.stack as string); | ||
|
|
||
| return response.customError({ statusCode: 502, body: errorMessage }); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 😁 I was accidentally able to test the new functionality where we send custom error messages back to the console (instead of the previous generic "Error connecting" message) when implementing the VERY useful for debugging, and I'm happy w/ this change - the only thing to keep an eye on is if we start catching errors that leak sensitive info in the message somehow. |
||
| } | ||
| }; | ||
| } | ||
| } | ||

There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could params be an object rather than a string? This would let us "merge" params, if some need to be dynamic and some static.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah! Great call, thanks!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
f6a709d - this is so much better, thanks for the amazing suggestion!!
I also realized while testing it that
request.queryis also an empty obj so in reality ourrequest.query ?check was never failing 🤦♀️