Events and Requests
Class cds.EventContext
Class cds.EventContext
represents the invocation context of incoming request and event messages, mostly tenant
. It also serves as a base class for cds.Event
and cds.Request
.
ctx.id → string
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 is propagated as x-correlation-id
header.
For inbound CloudEvents messages it taken from the id
context property and propagated to the same on ougoing CloudEvents messages.
ctx.user → cds.User
The current user as identified and verified by the authentication strategy.
See reference docs for cds.User
.
ctx.tenant → string
A unique string identifying the current tenant, or undefined
if not run in multitenancy mode. In case of multitenant operation, this string is used for tenant isolation, for example as keys in the database connection pools.
ctx.locale → string
The current user’s preferred locale, usually taken from HTTP Accept-Language header of incoming requests and resolve to normalized
ctx.timestamp → Date
Returns a stable timestamp for the current request being processed.
The first invocation on a request or any nested request calls and returns the response of new Date()
. Subsequent invocations return the formerly determined and pinned value.
The CAP framework uses that to fill in values for the CDS pseudo variable $now
, with the guaranteed same timestamp value.
Learn more in the Managed Data guide.
Learn more on Date
in the MDN docs.
ctx.on (event, handler)
Use this method to register handlers, executed when the whole request is finished.
req.on('succeeded', () => {...}) // request succeeded
req.on('failed', () => {...}) // request failed
req.on('done', () => {...}) // request succeeded/failed
Additionally, you can register a handler for req.before('commit')
to perform some final actions, such as validity checks, bookkeeping, etc.
Example:
srv.before('CREATE', Order, function(req) {
req.before('commit', async function() {
const { creditscore } = await SELECT.one.from(Customers)
.where({ ID: req.data.customer_ID })
if (creditscore < 42) throw new Error("We shouldn't make this sale")
})
})
A request has succeeded
or failed
only once the respective transaction was finally committed or rolled back. Hence, succeeded
handlers can’t veto the commit anymore. Even more, as the final commit
or rollback
already happened, they run outside framework-managed transaction boundaries.
To veto requests, either use the req.before('commit')
hook described above, or service-level event handlers as shown in the following example:
const srv = await cds.connect.to('AdminService')
srv.after('UPDATE', 'Orders', function(data, req) {
if ([...]) req.reject('Veto UPDATE Orders!')
})
const db = await cds.connect.to('db')
db.before('COMMIT', function(req) {
if ([...]) req.reject('Veto entire transaction!')
})
To do something which requires databases in succeeded
/failed
handlers, use cds.spawn()
, or one of the other options of manually-managed transactions.
Errors thrown by the registered handlers are treated the same as any other error thrown during request processing. Hence, if you are doing something that should not result in an error being returned to the client, make sure to either start an asynchronous workflow via cds.spawn()
or to wrap your code in a try...catch
block.
Additional note about OData: For requests that are part of a changeset, the events are emitted once the entire changeset was completed. Following the atomicity property (“all or nothing”), if at least one of the requests in the changeset fails, all requests fail.
Class cds.Event
Class cds.Event
represents event messages in asynchronous messaging, providing access to the event name, target, payload data, and headers. It also serves as the base class for cds.Request
and hence for all synchronous interaction.
msg.event → string
The name of the incoming event, which can be one of:
- The name of an incoming CRUD request like
CREATE
- The name of an emitted CRUD event like
UPDATED
- The name of a custom operation like
cancelOrder
. - The name of a custom event like
cancelledOrder
msg.data → {…} or […]
Contains the request payload if CREATE
and UPDATE
requests, which can either be a single object or an array of objects in case of bulk operations (example: await CatalogService.create('Books').entries([...])
).
Contains the keys of the entity if DELETE
and READ
requests on a single entity through OData or REST.
Contains parameters for functions and payload for actions.
msg.headers → {…}
Provides access to headers of the event message or request. In case of asynchronous event messages, it’s the headers information sent by the event source. If HTTP requests, it’s the standard Node.js headers of class IncomingMessage.
Class 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.
req._ → {…}
Provides access to original inbound protocol-specific request objects. For events triggered by an HTTP request, it contains the original req
and res
objects as obtained from express.js.
Please refrain from using internal properties of that object, i.e. the ones starting with ‘_’. They might be removed in any future release without notice.
req.method → string
The HTTP method of the incoming request:
msg.event |
→ | msg.method |
---|---|---|
CREATE | → | POST |
READ | → | GET |
UPDATE | → | PATCH |
DELETE | → | DELETE |
req.target → [def]
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.
In case of 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.
req.path → string
Captures the full canonicalized path information of incoming requests with navigation.
If 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 |
req.entity → string
This is a convenience shortcut to msg.target.name
.
req.params → iterable
Provides access to parameters in URL paths as an iterable with the contents matching the positional occurrence of parameters in the url path. In 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 }
req.query → cqn
Captures the incoming request as a CQN query object. For example, an HTTP request like GET http://.../Books
would be 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.
req.reply (results)
Stores the given results
in req.results
, which will then be sent back to the client, rendered in a protocol-specific way.
req.reject (code?, msg, target?, args?)
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 will be executed once req.reject
has been invoked.
Arguments are the same as for req.error
req.error, notify, info, warn (code?, msg, target?, args?)
Use these methods to collect messages or error 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 severity < 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, that means, HTTP status codesmsg
String | Object | Error → see below for details on the non-string version.target
the name of an input field/element a message is related to.args
array of placeholder values → see Localized Messages for details.
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
(or statusCode
) 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 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 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 are not 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 @
in order to not be purged.