mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 09:19:04 -04:00
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Pierre Gayvallet <pierre.gayvallet@elastic.co>
This commit is contained in:
parent
b53bec9a83
commit
4975c1c444
1 changed files with 394 additions and 43 deletions
|
@ -3,24 +3,105 @@ id: kibDevTutorialServerEndpoint
|
|||
slug: /kibana-dev-docs/tutorials/registering-endpoints
|
||||
title: Registering and accessing an endpoint
|
||||
summary: Learn how to register a new endpoint and access it
|
||||
date: 2021-10-05
|
||||
date: 2021-11-24
|
||||
tags: ['kibana', 'dev', 'architecture', 'tutorials']
|
||||
---
|
||||
|
||||
## Registering an endpoint
|
||||
## The HTTP service API
|
||||
|
||||
### The server-side HTTP service
|
||||
|
||||
The server-side `HttpService` allows server-side plugins to register endpoints with built-in support for request validation. These endpoints may be used by client-side code or be exposed as a public API for users. Most plugins integrate directly with this service.
|
||||
|
||||
The service allows plugins to:
|
||||
- to extend the Kibana server with custom HTTP API.
|
||||
- to execute custom logic on an incoming request or server response.
|
||||
- to implement custom authentication and authorization strategy.
|
||||
- extend the Kibana server with custom HTTP API.
|
||||
- execute custom logic on an incoming request or server response.
|
||||
- implement custom authentication and authorization strategy.
|
||||
|
||||
<DocCallOut>
|
||||
See [the server-side HTTP service API docs](https://github.com/elastic/kibana/blob/main/docs/development/core/server/kibana-plugin-core-server.httpservicesetup.md)
|
||||
</DocCallOut>
|
||||
|
||||
**Registering a basic GET endpoint**
|
||||
### The client-side HTTP service
|
||||
|
||||
The client-side counterpart of the HTTP service provides an API to communicate with the Kibana server via HTTP interface.
|
||||
The client-side `HttpService` is a preconfigured wrapper around `window.fetch` that includes some default behavior and automatically handles common errors (such as session expiration).
|
||||
|
||||
**The service should only be used for access to backend endpoints registered by the same plugin.** Feel free to use another HTTP client library to request 3rd party services.
|
||||
|
||||
<DocCallOut>
|
||||
See [the client-side HTTP service API docs](https://github.com/elastic/kibana/blob/main/docs/development/core/public/kibana-plugin-core-public.httpsetup.md)
|
||||
</DocCallOut>
|
||||
|
||||
## Registering an endpoint
|
||||
|
||||
Registering an endpoint, or `route`, is done during the `setup` lifecycle stage of a plugin. The first step to register a route
|
||||
is to create a [router](https://github.com/elastic/kibana/blob/main/docs/development/core/server/kibana-plugin-core-server.irouter.md)
|
||||
using the `http` core service.
|
||||
|
||||
Once the router is instantiated, it is possible to use its APIs, such as `router.get` or `router.post` to create a route for the equivalent
|
||||
HTTP method. All these APIs share the same signature, and receive two parameters:
|
||||
- `route` - the route configuration, such as the `path` of the route, or the parameter validation schemas
|
||||
- `handler` - the handler function that will be called when a request matching the route configuration is received
|
||||
|
||||
When invoked, the `handler` receive three parameters: `context`, `request`, and `response`, and must return a response that will be sent to serve
|
||||
the request.
|
||||
- `context` is a request-bound context exposed for the request. It allows for example to use an elasticsearch client bound to the request's credentials.
|
||||
- `request` contains information related to the request, such as the path and query parameter
|
||||
- `response` contains factory helpers to create the response to return from the endpoint
|
||||
|
||||
<DocCallOut>
|
||||
See the [request](https://github.com/elastic/kibana/blob/main/docs/development/core/server/kibana-plugin-core-server.kibanarequest.md)
|
||||
and [response](https://github.com/elastic/kibana/blob/main/docs/development/core/server/kibana-plugin-core-server.kibanaresponsefactory.md)
|
||||
documentation
|
||||
</DocCallOut>
|
||||
|
||||
## Basic examples
|
||||
|
||||
### Registering a GET endpoint
|
||||
|
||||
The following snippet demonstrate how to create a basic `GET` endpoint on the `/api/my_plugin/get_object` path:
|
||||
|
||||
```ts
|
||||
import type { CoreSetup, Plugin } from 'kibana/server';
|
||||
|
||||
export class MyPlugin implements Plugin {
|
||||
public setup(core: CoreSetup) {
|
||||
const router = core.http.createRouter();
|
||||
router.get(
|
||||
{
|
||||
path: '/api/my_plugin/get_object',
|
||||
validate: false,
|
||||
},
|
||||
async (context, request, response) => {
|
||||
return response.ok({
|
||||
body: { result: 'everything is alright'},
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
consuming the endpoint from the client-side using core's `http` service would then look like:
|
||||
|
||||
```ts
|
||||
import { HttpStart } from 'kibana/public';
|
||||
|
||||
interface ResponseType {
|
||||
result: string;
|
||||
};
|
||||
|
||||
async function fetchData(http: HttpStart) {
|
||||
return await http.get<ResponseType>(`/api/my_plugin/get_object`);
|
||||
}
|
||||
```
|
||||
|
||||
### Using and validating path parameters
|
||||
|
||||
It is possible to specify dynamic parameters in the `path` of the endpoint using the `{name}` syntax.
|
||||
When doing so, the associated validation schema must be defined via the `validate.params` option
|
||||
of the route definition.
|
||||
|
||||
```ts
|
||||
import { schema } from '@kbn/config-schema';
|
||||
|
@ -29,59 +110,329 @@ import type { CoreSetup, Plugin } from 'kibana/server';
|
|||
export class MyPlugin implements Plugin {
|
||||
public setup(core: CoreSetup) {
|
||||
const router = core.http.createRouter();
|
||||
|
||||
const validate = {
|
||||
params: schema.object({
|
||||
id: schema.string(),
|
||||
}),
|
||||
};
|
||||
|
||||
router.get({
|
||||
path: '/api/my_plugin/{id}',
|
||||
validate
|
||||
},
|
||||
async (context, request, response) => {
|
||||
const data = await findObject(request.params.id);
|
||||
if (!data) return response.notFound();
|
||||
return response.ok({
|
||||
body: data,
|
||||
headers: {
|
||||
'content-type': 'application/json'
|
||||
router.get(
|
||||
{
|
||||
path: '/api/my_plugin/get_object/{id}',
|
||||
validate: {
|
||||
params: schema.object({
|
||||
id: schema.string(),
|
||||
})
|
||||
},
|
||||
},
|
||||
async (context, request, response) => {
|
||||
const { id } = request.params;
|
||||
const data = await findObject(id);
|
||||
if (!data) {
|
||||
return response.notFound();
|
||||
}
|
||||
});
|
||||
});
|
||||
return response.ok({ body: data });
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
<DocCallOut>
|
||||
See [the routing example plugin](https://github.com/elastic/kibana/blob/main/examples/routing_example) for more route registration examples.
|
||||
</DocCallOut>
|
||||
consuming the endpoint from the client-side using core's `http` service would then look like:
|
||||
|
||||
## Consuming the endpoint from the client-side
|
||||
```ts
|
||||
import { HttpStart } from 'kibana/public';
|
||||
import { MyObjectType } from '../common/types';
|
||||
|
||||
The client-side HTTP service provides an API to communicate with the Kibana server via HTTP interface.
|
||||
The client-side `HttpService` is a preconfigured wrapper around `window.fetch` that includes some default behavior and automatically handles common errors (such as session expiration).
|
||||
async function fetchData(http: HttpStart, id: string) {
|
||||
return await http.get<MyObjectType>(`/api/my_plugin/get_object/${id}`);
|
||||
}
|
||||
```
|
||||
|
||||
**The service should only be used for access to backend endpoints registered by the same plugin.** Feel free to use another HTTP client library to request 3rd party services.
|
||||
### Registering a POST endpoint and validating the payload
|
||||
|
||||
Similar to the validation we performed against the path parameters in the previous example, the `body` validation schema
|
||||
must be provided when registering a `post` handler that will access the payload.
|
||||
|
||||
```ts
|
||||
import { schema } from '@kbn/config-schema';
|
||||
import type { CoreSetup, Plugin } from 'kibana/server';
|
||||
|
||||
export class MyPlugin implements Plugin {
|
||||
public setup(core: CoreSetup) {
|
||||
const router = core.http.createRouter();
|
||||
router.post(
|
||||
{
|
||||
path: '/api/my_plugin/objects/{id}/update',
|
||||
validate: {
|
||||
params: schema.object({
|
||||
id: schema.string(),
|
||||
}),
|
||||
body: schema.object({
|
||||
title: schema.string(),
|
||||
description: schema.string()
|
||||
}),
|
||||
},
|
||||
},
|
||||
async (context, request, response) => {
|
||||
const { id } = request.params;
|
||||
const { title, description } = request.body;
|
||||
await updateObject(id, { title, description });
|
||||
return response.ok({ body: { updated: true }});
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
consuming the endpoint from the client-side using core's `http` service would then look like:
|
||||
|
||||
```ts
|
||||
import { HttpStart } from 'kibana/public';
|
||||
|
||||
interface ResponseType {…};
|
||||
interface ResponseType {
|
||||
updated: boolean;
|
||||
};
|
||||
|
||||
async function fetchData(http: HttpStart, id: string) {
|
||||
return await http.get<ResponseType>(
|
||||
`/api/my_plugin/${id}`,
|
||||
{ query: … },
|
||||
);
|
||||
interface UpdateOptions {
|
||||
title?: string;
|
||||
description?: string;
|
||||
}
|
||||
|
||||
async function fetchData(http: HttpStart, id: string, { title, description }: UpdateOptions) {
|
||||
return await http.post<ResponseType>(`/api/my_plugin/objects/${id}/update`, {
|
||||
body: JSON.stringify({ title, description })
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
<DocCallOut>
|
||||
See [the client-side HTTP service API docs](https://github.com/elastic/kibana/blob/main/docs/development/core/public/kibana-plugin-core-public.httpsetup.md)
|
||||
</DocCallOut>
|
||||
### Using the query parameters
|
||||
|
||||
Similar to the `body` validation, the query parameters schema has to be defined using the `validate.query`
|
||||
option of the route definition to be accessible from the handler.
|
||||
|
||||
```ts
|
||||
import { schema } from '@kbn/config-schema';
|
||||
import type { CoreSetup, Plugin } from 'kibana/server';
|
||||
|
||||
export class MyPlugin implements Plugin {
|
||||
public setup(core: CoreSetup) {
|
||||
const router = core.http.createRouter();
|
||||
router.get(
|
||||
{
|
||||
path: '/api/my_plugin/objects/find',
|
||||
validate: {
|
||||
query: schema.object({
|
||||
term: schema.maybe(schema.string()),
|
||||
page: schema.number({ min: 1, defaultValue: 1 }),
|
||||
perPage: schema.number({ min: 5, max: 50, defaultValue: 10 }),
|
||||
}),
|
||||
},
|
||||
},
|
||||
async (context, request, response) => {
|
||||
const { term, page, perPage } = request.query;
|
||||
const results = await findObjects(term, { page, perPage });
|
||||
return response.ok({ body: { results } });
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
consuming the endpoint from the client-side using core's `http` service would then look like:
|
||||
|
||||
```ts
|
||||
import { HttpStart } from 'kibana/public';
|
||||
import { MyObjectType } from '../common/types';
|
||||
|
||||
interface ResponseType {
|
||||
results: MyObjectType[];
|
||||
};
|
||||
|
||||
interface UpdateOptions {
|
||||
term?: string;
|
||||
page?: number;
|
||||
perPage?: number;
|
||||
}
|
||||
|
||||
async function fetchData(http: HttpStart, { term, page, perPage }: UpdateOptions) {
|
||||
return await http.get<ResponseType>(`/api/my_plugin/objects/find`, {
|
||||
query: { term, page, perPage },
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
## Customizing the response
|
||||
|
||||
### Attaching headers to the response
|
||||
|
||||
All APIs of the `response` parameter of the handler accept a `headers` property that can be used
|
||||
to define headers to attach to the response.
|
||||
|
||||
```ts
|
||||
import type { CoreSetup, Plugin } from 'kibana/server';
|
||||
|
||||
export class MyPlugin implements Plugin {
|
||||
public setup(core: CoreSetup) {
|
||||
const router = core.http.createRouter();
|
||||
router.get(
|
||||
{
|
||||
path: '/api/my_plugin/some_text_response',
|
||||
validate: false,
|
||||
},
|
||||
async (context, request, response) => {
|
||||
return response.ok({
|
||||
body: 'some plain text response',
|
||||
headers: {
|
||||
'content-type': 'text/plain',
|
||||
'cache-control': 'must-revalidate',
|
||||
},
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Defining a specific HTTP status code for the response
|
||||
|
||||
The `response` parameter of the handler already provides APIs for the most common HTTP response codes, such as
|
||||
- `response.ok` for `200`
|
||||
- `response.notFound` for `404`
|
||||
- `response.badRequest` for `400`
|
||||
- and so on
|
||||
|
||||
However, some of the less commonly used return codes don't have such helpers. In that case, the `response.custom`
|
||||
and/or `response.customError` APIs should be used.
|
||||
|
||||
```ts
|
||||
import type { CoreSetup, Plugin } from 'kibana/server';
|
||||
|
||||
export class MyPlugin implements Plugin {
|
||||
public setup(core: CoreSetup) {
|
||||
const router = core.http.createRouter();
|
||||
router.get(
|
||||
{
|
||||
path: '/api/my_plugin/some_text_response',
|
||||
validate: false,
|
||||
},
|
||||
async (context, request, response) => {
|
||||
return response.custom({
|
||||
body: `Kibana is a teapot`,
|
||||
statusCode: 418,
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Some advanced usages
|
||||
|
||||
### Handling request cancellation
|
||||
|
||||
Some request lifecycle events are exposed to the handler via `request.events` in the form of `rxjs`
|
||||
observables, such as `request.events.aborted$` that emits if/when the request is canceled by the client.
|
||||
|
||||
These observables can either be used directly, or be used to control an `AbortController`.
|
||||
|
||||
```ts
|
||||
import { schema } from '@kbn/config-schema';
|
||||
import type { CoreSetup, Plugin } from 'kibana/server';
|
||||
|
||||
export class MyPlugin implements Plugin {
|
||||
public setup(core: CoreSetup) {
|
||||
const router = core.http.createRouter();
|
||||
router.get(
|
||||
{
|
||||
path: '/api/my_plugin/objects/find',
|
||||
validate: {
|
||||
query: schema.object({
|
||||
term: schema.maybe(schema.string()),
|
||||
}),
|
||||
},
|
||||
},
|
||||
async (context, request, response) => {
|
||||
const { term } = request.query;
|
||||
const { aborted$ } = request.events;
|
||||
|
||||
const abortController = new AbortController();
|
||||
aborted$.subscribe(() => {
|
||||
abortController.abort();
|
||||
});
|
||||
|
||||
const results = await findObjects(term, { abortController });
|
||||
return response.ok({ body: results });
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Disabling authentication for an endpoint
|
||||
|
||||
By default, when security is enabled, endpoints require the user to be authenticated to be accessed,
|
||||
and will return a `401 - Unauthorized` otherwise.
|
||||
|
||||
It is possible to disable this requirement using the `authRequired` option of the route.
|
||||
|
||||
```ts
|
||||
import type { CoreSetup, Plugin } from 'kibana/server';
|
||||
|
||||
export class MyPlugin implements Plugin {
|
||||
public setup(core: CoreSetup) {
|
||||
const router = core.http.createRouter();
|
||||
router.get(
|
||||
{
|
||||
path: '/api/my_plugin/get_object',
|
||||
validate: false,
|
||||
options: {
|
||||
authRequired: false,
|
||||
},
|
||||
},
|
||||
async (context, request, response) => {
|
||||
return response.ok({
|
||||
body: { authenticated: request.auth.isAuthenticated },
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Note that in addition to `true` and `false`, `authRequired` accepts a third value, `'optional'`. When used,
|
||||
Kibana will try to authenticate the user but will allow access to the endpoint regardless of the result. In that
|
||||
case, the developer needs to manually checks if the user is authenticated via `request.auth.isAuthenticated`.
|
||||
|
||||
### Accessing the url or route configuration from the handler
|
||||
|
||||
In some advanced use cases, such as generic handlers being used for multiple routes, it can be useful to know,
|
||||
from within the handler, the route configuration and the actual url that was requested by the user. This can
|
||||
be achieved by using the `url` and `route` properties of the `request` parameter of the handler.
|
||||
|
||||
request.url / request.route
|
||||
|
||||
```ts
|
||||
import type { CoreSetup, Plugin } from 'kibana/server';
|
||||
|
||||
export class MyPlugin implements Plugin {
|
||||
public setup(core: CoreSetup) {
|
||||
const router = core.http.createRouter();
|
||||
router.get(
|
||||
{
|
||||
path: '/api/my_plugin/get_object',
|
||||
validate: false,
|
||||
options: {
|
||||
authRequired: false,
|
||||
},
|
||||
},
|
||||
async (context, request, response) => {
|
||||
const { url, route } = request;
|
||||
return response.ok({
|
||||
body: `You requested ${route.method} ${url}`,
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## More examples
|
||||
|
||||
<DocCallOut>
|
||||
See [the routing example plugin](https://github.com/elastic/kibana/blob/main/examples/routing_example) for more endpoint consumption examples.
|
||||
See [the routing example plugin](https://github.com/elastic/kibana/blob/main/examples/routing_example) for more route registration examples.
|
||||
</DocCallOut>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue