Events and Requests
cds. context
This property provides seemingly static access to the current cds.EventContext
, that is, the current tenant
, user
, locale
, and so on, from wherever you are in your code. For example:
let { tenant, user } = cds.context
Usually that context is set by inbound middleware.
The property is realized as a so-called continuation-local variable, implemented using Node.js' async local storage technique, and a getter/setter pair: The getter is a shortcut forgetStore()
. The setter coerces values into valid instances of cds.EventContext
. For example:
[dev] cds repl
> cds.context = { tenant:'t1', user:'u2' }
> let ctx = cds.context
> ctx instanceof cds.EventContext //> true
> ctx.user instanceof cds.User //> true
> ctx.tenant === 't1' //> true
> ctx.user.id === 'u2' //> true
If a transaction object is assigned, its tx.context
is used, hence cds.context = tx
acts as a convenience shortcut for cds.context = tx.context
:
let tx = cds.context = cds.tx({ ... })
cds.context === tx.context //> true
TIP
Prefer local req
objects in your handlers for accessing event context properties, as each access to cds.context
happens through AsyncLocalStorage.getStore()
, which induces some minor overhead.
cds.EventContext
Instances of this class represent the invocation context of incoming requests and event messages, such as tenant
, user
, and locale
. Classes cds.Event
and cds.Request
inherit from it and hence provide access to the event context properties:
this.on ('*', req => {
let { tenant, user } = req
...
})
In addition, you can access the current event context from wherever you are in your code via the continuation-local variable cds.context
:
let { tenant, user } = cds.context
. http
If the inbound process came from an HTTP channel, you can now access express's common req
and res
objects through this property. It is propagated from cds.context
to all child requests, so Request.http
is accessible in all handlers including your database service ones like so:
this.on ('*', req => {
let { res } = req.http
res.set('Content-Type', 'text/plain')
res.send('Hello!')
})
Keep in mind that multiple requests (that is, instances of cds.Request
) may share the same incoming HTTP request and outgoing HTTP response (for example, in case of an OData batch request).
. id
A unique string used for request correlation.
For inbound HTTP requests the implementation fills it from these sources in order of precedence:
x-correlation-id
headerx-correlationid
headerx-request-id
headerx-vcap-request-id
header- a newly created UUID
On outgoing HTTP messages, it's propagated as x-correlation-id
header.
. locale
The current user's preferred locale, taken from the HTTP Accept-Language header of incoming requests and resolved to normalized.
. tenant
A unique string identifying the current tenant, or undefined
if not in multitenancy mode. In the case of multitenant operation, this string is used for tenant isolation, for example as keys in the database connection pools.
. timestamp
A constant timestamp for the current request being processed, as an instance of Date
. The CAP framework uses that to fill in values for the CDS pseudo variable $now
, with the guaranteed same value.
Learn more in the Managed Data guide.
. user
The current user, an instance of cds.User
as identified and verified by the authentication strategy. If no user is authenticated, cds.User.anonymous
is returned.
See reference docs for cds.User
.
cds.Event
Class cds.Event
represents event messages in asynchronous messaging, providing access to the event name, payload data, and optional headers. It also serves as the base class for cds.Request
and hence for all synchronous interactions.
. event
The name of the incoming event, which can be one of:
- The name of an incoming CRUD request like
CREATE
,READ
,UPDATE
,DELETE
- The name of a custom action or function like
submitOrder
- The name of a custom event like
OrderedBook
. data
Contains the event data. For example, the HTTP body for CREATE
or UPDATE
requests, or the payload of an asynchronous event message.
Use req.data
for modifications as shown in the following:
this.before ('UPDATE',Books, req => {
req.data.author = 'Schmidt'
req.query.UPDATE.data.author = 'Schmidt'
})
. headers
Provides access to headers of the event message or request. In the case of asynchronous event messages, it's the headers information sent by the event source. For HTTP requests it's the standard Node.js request headers.
eve. before 'commit'
eve. on 'succeeded'
eve. on 'failed'
eve. on 'done'
Register handlers to these events on a per event / request basis. The events are executed when the whole top-level request handling is finished
Use this method to register handlers, executed when the whole request is finished.
req.before('commit', () => {...}) // immediately before calling commit
req.on('succeeded', () => {...}) // request succeeded, after commit
req.on('failed', () => {...}) // request failed, after rollback
req.on('done', () => {...}) // request succeeded/failed, after all
DANGER
The events succeeded
, failed
, and done
are emitted after the current transaction ended. Hence, they run outside framework-managed transactions, and handlers can't veto the commit anymore.
To veto requests, either use the req.before('commit')
hook, or service-level before
COMMIT
handlers.
To do something that requires databases in succeeded
/failed
handlers, use cds.spawn()
, or one of the other options of manual transactions. Preferably use a variant with automatic commit/ rollback.
Example:
req.on('done', async () => {
await cds.tx(async () => {
await UPDATE `Stats` .set `views = views + 1` .where `book_ID = ${book.ID}`
})
})
Additional note about OData: For requests that are part of a changeset, the events are emitted once the entire changeset was completed. If at least one of the requests in the changeset fails, following the atomicity property ("all or nothing"), all requests fail.
cds.Request
Class cds.Request
extends cds.Event
with additional features to represent and deal with synchronous requests to services in event handlers, such as the query, additional request parameters, the authenticated user, and methods to send responses.
. method
The HTTP method of the incoming request:
msg.event | → | msg.method |
---|---|---|
CREATE | → | POST |
READ | → | GET |
UPDATE | → | PATCH |
DELETE | → | DELETE |
. target
Refers to the current request's target entity definition, if any; undefined
for unbound actions/functions and events. The returned definition is a linked definition as reflected from the CSN model.
For OData navigation requests along associations, msg.target
refers to the last target. For example:
OData Request | req.target |
---|---|
Books | AdminService.Books |
Books/201/author | AdminService.Authors |
Books(201)/author | AdminService.Authors |
See also req.path
to learn how to access full navigation paths.See Entity Definitions in the CSN reference.Learn more about linked models and definitions.
. path
Captures the full canonicalized path information of incoming requests with navigation. For requests without navigation, req.path
is identical to req.target.name
(or req.entity
, which is a shortcut for that).
Examples based on cap/samples/bookshop AdminService:
OData Request | req.path | req.target.name |
---|---|---|
Books | AdminService.Books | AdminService.Books |
Books/201/author | AdminService.Books/author | AdminService.Authors |
Books(201)/author | AdminService.Books/author | AdminService.Authors |
. entity
This is a convenience shortcut to msg.target.name
.
. params
Provides access to parameters in URL paths as an iterable with the contents matching the positional occurrence of parameters in the url path. In the case of compound parameters, the respective entry is the key value pairs as given in the URL.
For example, the parameters in an HTTP request like that:
GET /catalog/Authors(101)/books(title='Eleonora',edition=2) HTTP/1.1
The provided parameters can be accessed as follows:
const [ author, book ] = req.params
// > author === 101
// > book === { title: 'Eleonora', edition: 2 }
. query
Captures the incoming request as a CQN query. For example, an HTTP request like GET http://.../Books
is captured as follows:
req.query = {SELECT:{from:{ref:['Books']}}}
If bound custom operations req.query
contains the query to the entity, on which the bound custom operation is called. For unbound custom operations, req.query
contains an empty object.
. subject
Acts as a pointer to one or more instances targeted by the request. It can be used as input for cds.ql as follows:
SELECT.one.from(req.subject) //> returns single object
SELECT.from(req.subject) //> returns one or many in array
UPDATE(req.subject) //> updates one or many
DELETE(req.subject) //> deletes one or many
It's available for CRUD events and bound actions.
req. reply()
Stores the given results
in req.results
, which is then sent back to the client, rendered in a protocol-specific way.
req. reject()
Rejects the request with the given HTTP response code and single message. Additionally, req.reject
throws an error based on the passed arguments. Hence, no additional code and handlers is executed once req.reject
has been invoked.
Arguments are the same as for req.error
req. error()
req. warn()
req. info()
req. notify()
Use these methods to collect messages or errors and return them in the request response to the caller. The method variants reflect different severity levels. Use them as follows:
Variants
Method | Collected in | Typical UI | Severity |
---|---|---|---|
req.notify() | req.messages | Toasters | 1 |
req.info() | req.messages | Dialog | 2 |
req.warn() | req.messages | Dialog | 3 |
req.error() | req.errors | Dialog | 4 |
Note: messages with a severity less than 4 are collected and accessible in property req.messages
, while error messages are collected in property req.errors
. The latter allows to easily check, whether errors occurred with:
if (req.errors) //> get out somehow...
Arguments
code
Number (Optional) - Represents the error code associated with the message. If the number is in the range of HTTP status codes and the error has a severity of 4, this argument sets the HTTP response status code.message
String | Object | Error - See below for details on the non-string version.target
String (Optional) - The name of an input field/element a message is related to.args
Array (Optional) - Array of placeholder values. See Localized Messages for details.
target
property for UI5 OData model
The target
property is evaluated by the UI5 OData model and needs to be set according to Server Messages in the OData V4 Model.
Using an Object as Argument
You can also pass an object as the sole argument, which then contains the properties code
, message
, target
, and args
. Additional properties are preserved until the error or message is sanitized for the client. In case of an error, the additional property status
can be used to specify the HTTP status code of the response.
req.error ({
code: 'Some-Custom-Code',
message: 'Some Custom Error Message',
target: 'some_field',
status: 418
})
Additional properties can be added as well, for example to be used in custom error handlers.
In OData responses, notifications get collected and put into HTTP response header
sap-messages
as a stringified array, while the others are collected in the respective response body properties (→ see OData Error Responses).
Error Sanitization
In production, errors should never disclose any internal information that could be used by malicious actors. Hence, we sanitize all server-side errors thrown by the CAP framework. That is, all errors with a 5xx status code (the default status code is 500) are returned to the client with only the respective generic message (example: 500 Internal Server Error
). Errors defined by app developers aren't sanitized and returned to the client unchanged.
Additionally, the OData protocol specifies which properties an error object may have. If a custom property shall reach the client, it must be prefixed with @
to not be purged.
req. diff() beta
Use this asynchronous method to calculate the difference between the data on the database and the passed data (defaults to req.data
, if not passed). Note that the usage of req.diff
only makes sense in before handlers as they are run before the actual change was persisted on the database.
This triggers database requests.
const diff = await req.diff()