The Kernel provides a structured process for converting a Request into a Response by making use of the AsyncEventDispatcher.
- Decorators
- Metadata storage
- Request object
- Response object
- Events
- Request Event
- Response Event
- Exception Event
- Http/Websocket definitions
There are two decorators @Http and @Websocket which helps you to register controller methods into the kernel.
import {Request, Response, Http, WebSocket} from '@rxstack/core';
export class IndexController {
@Http('GET', '/index', 'index')
@WebSocket('index')
async indexAction(request: Request): Promise<Response> {
return new Response('Hello world');
}
}@Http decorator takes up to three arguments:
- Http method:
'HEAD' | 'GET' | 'PUT' | 'PATCH' | 'POST' | 'DELETE' - Http path: used by underlying framework to match the route.
- Route name: unique identifier
@Websocket decorator takes only one argument:
- Route name: unique identifier used by socket framework as an event name
The controller method itself accepts one argument Request and must return a promise of Response object.
Once decorators are defined in the controller then metadata is extracted from the methods and added to the metadata storage. You can register methods without using decorators:
import {httpMetadataStorage, webSocketMetadataStorage} from '@rxstack/core';
httpMetadataStorage.add({
'target': IndexController,
'name': 'index',
'path': '/index',
'httpMethod': 'GET',
'propertyKey': 'indexAction',
'transport': 'HTTP'
});
webSocketMetadataStorage.add({
'target': IndexController,
'name': 'index',
'propertyKey': 'indexAction',
'transport': 'SOCKET'
});A Request object holds information about the client request. This information can be accessed via several public properties:
headers: server request headersparams: merged query string and post parametersbody: raw request bodyattributes: extra datapath: http path (available only in http servers)methodhttp method (available only in http servers)controller: Instance of the controller objectrouteName: name of the routetoken: security token (optional)connection: socket connection (available only in socket servers)
A Response object holds all the information that needs to be sent back to the client from a given request.
The constructor takes up to two arguments: the response content and the status code.
Additionally response headers could be set via Response.headers.
content: any type of data, defaults tonullstatusCode: number, defaults to200
Simple response:
@Http('GET', '/get-something', 'app_get_something')
async getAction(request: Request): Promise<Response> {
return new Response('something', 200);
}Streaming file:
@Http('GET', '/download', 'app_download')
async downloadAction(request: Request): Promise<Response> {
const path = 'path_to_file';
const st = fs.createReadStream(path);
const response = new Response(st);
response.headers.set('Content-Disposition', `attachment; filename="my_file.txt"`);
response.headers.set('Content-Type', 'text/plain');
return response;
}Currently streaming is not supported by websockets!
The kernel works internally by dispatching events.
There are three types of events:
The event is used to initialize parts of the system,
or return a Response if possible (e.g. a security layer that denies access).
If a Response is returned at this stage, the process skips directly to the kernel.response event.
When setting a response for the
kernel.requestevent, the propagation is stopped. This means listeners with lower priority won't be executed.
Example:
export class MyListener {
@Observe(KernelEvents.KERNEL_REQUEST)
async onRequest(event: RequestEvent): Promise<void> {
// hit cache
const response = new Response('cached response');
event.setResponse(response);
}
}The event is used to modify the Response object just before it is sent. The end goal of the kernel is to transform
a Request into a Response.
The Response might be created during the kernel.request event, returned from the controller, or returned by one of the listeners.
Example:
export class MyListener {
@Observe(KernelEvents.KERNEL_RESPONSE)
async onResponse(event: ResponseEvent): Promise<void> {
const response = event.getResponse();
// ... modify the response object
}
}The event is used to handle exceptions and create an appropriate Response to return for the exception.
If an exception is thrown at any point inside the Kernel, another event - kernel.exception is dispatched.
the kernel.exception event is dispatched so that your system can somehow respond to the exception.
Each listener to this event is passed an ExceptionEvent object, which you can use to access the original exception via
the getException() method. A typical listener on this event will check for a certain type of exception
and create an appropriate error Response.
When setting a response for the
kernel.exceptionevent, the propagation is stopped. This means listeners with lower priority won't be executed.
Example:
export class MyListener {
@Observe(KernelEvents.KERNEL_EXCEPTION)
async onException(event: ExceptionEvent): Promise<void> {
const exception = event.getException();
const response = new Response();
// setup the Response object based on the caught exception
event.setResponse(response);
// you can alternatively set a new Exception
// const exception = new Exception('Some special exception');
// event.setException(exception);
}
}Internally Kernel converts controller method and all listeners into an http or websocket definition.
It might be useful to write functional tests.
Http definition:
- name: route name
- handler: function which accepts
requestand returnsresponse - path: http path
- method: http method
const def = kernel.httpDefinitions.find((item) => item.name === 'my_route_name');
const request = new Request('HTTP');
const response: Response = await def.handler(request);Websocket definition:
- name: socket event name
- handler: function which accepts
requestand returnsresponse
const def = kernel.webSocketDefinitions.find((item) => item.name === 'my_event');
const request = new Request('SOCKET');
const response: Response = await def.handler(request);