Websockets make it possible to add support for a bi-directional communication channel between clients and servers. Connection channels are kept alive and are re-used to exchange messages back-and-forth.
The Serverless Framework makes it possible to setup an API Gateway powered Websocket backend with the help of the websocket
event.
The following code will setup a websocket with a $connect
route key:
functions:
connectHandler:
handler: handler.connectHandler
events:
- websocket: $connect
This code will setup a websocket with a $disconnect
route key:
functions:
disconnectHandler:
handler: handler.disconnectHandler
events:
- websocket:
route: $disconnect
This code will setup a RouteResponse, enabling you to respond to websocket messages by using the body
parameter of your handler's callback response:
functions:
helloHandler:
handler: handler.helloHandler
events:
- websocket:
route: hello
routeResponseSelectionExpression: $default
The API-Gateway provides 4 types of routes which relate to the lifecycle of a ws-client:
$connect
called on connect of a ws-client$disconnect
called on disconnect of a ws-client (may not be called in some situations)$default
called if there is no handler to use for the event- custom routes - called if the route name is specified for a handler
This Serverless yaml will specify handlers for the $connect
, $disconnect
, $default
and the custom foo
event.
service: serverless-ws-test
provider:
name: aws
runtime: nodejs14.x
websocketsApiName: custom-websockets-api-name
websocketsApiRouteSelectionExpression: $request.body.action # custom routes are selected by the value of the action property in the body
websocketsDescription: Custom Serverless Websockets
functions:
connectionHandler:
handler: handler.connectionHandler
events:
- websocket:
route: $connect
- websocket:
route: $disconnect
defaultHandler:
handler: handler.defaultHandler
events:
- websocket: $default #simple event definition without extra route property
customFooHandler:
handler: handler.fooHandler
events:
- websocket:
route: foo # will trigger if $request.body.action === "foo"
You can enable an authorizer for your connect route by specifying the authorizer
key in the websocket event definition.
Note: AWS only supports authorizers for the $connect
route.
functions:
connectHandler:
handler: handler.connectHandler
events:
- websocket:
route: $connect
authorizer: auth # references the auth function below
auth:
handler: handler.auth
Or, if your authorizer function is not managed by this service, you can provide an arn instead:
functions:
connectHandler:
handler: handler.connectHandler
events:
- websocket:
route: $connect
authorizer: arn:aws:lambda:us-east-1:1234567890:function:auth
By default, the identitySource
property is set to route.request.header.Auth
, meaning that your request must include the auth token in the Auth
header of the request. You can overwrite this by specifying your own identitySource
configuration:
functions:
connectHandler:
handler: handler.connectHandler
events:
- websocket:
route: $connect
authorizer:
name: auth
identitySource:
- 'route.request.header.Auth'
- 'route.request.querystring.Auth'
auth:
handler: handler.auth
With the above configuration, you can now must pass the auth token in both the Auth
query string as well as the Auth
header.
You can also supply an ARN instead of the name when using the object syntax for the authorizer:
functions:
connectHandler:
handler: handler.connectHandler
events:
- websocket:
route: $connect
authorizer:
arn: arn:aws:lambda:us-east-1:1234567890:function:auth
identitySource:
- 'route.request.header.Auth'
- 'route.request.querystring.Auth'
auth:
handler: handler.auth
To send a message to a ws-client the @connection command is used.
It uses the URL of the websocket API and most importantly the connectionId
of the ws-client's connection. If you want to send a message to a ws-client from another function, you need this connectionId
to address the ws-client.
Example on how to respond with the complete event
to the same ws-client:
const sendMessageToClient = (url, connectionId, payload) =>
new Promise((resolve, reject) => {
const apigatewaymanagementapi = new AWS.ApiGatewayManagementApi({
apiVersion: '2018-11-29',
endpoint: url,
});
apigatewaymanagementapi.postToConnection(
{
ConnectionId: connectionId, // connectionId of the receiving ws-client
Data: JSON.stringify(payload),
},
(err, data) => {
if (err) {
console.log('err is', err);
reject(err);
}
resolve(data);
}
);
});
module.exports.defaultHandler = async (event, context) => {
const domain = event.requestContext.domainName;
const stage = event.requestContext.stage;
const connectionId = event.requestContext.connectionId;
const callbackUrlForAWS = util.format(util.format('https://%s/%s', domain, stage)); //construct the needed url
await sendMessageToClient(callbackUrlForAWS, connectionId, event);
return {
statusCode: 200,
};
};
To respond to a websocket message from your handler function, Route Responses can be used. Set the routeResponseSelectionExpression
option to enable this. This option allows you to respond to a websocket message using the body
parameter.
functions:
sayHelloHandler:
handler: handler.sayHello
events:
- websocket:
route: hello
routeResponseSelectionExpression: $default
module.exports.helloHandler = async (event, context) => {
const body = JSON.parse(event.body);
return {
statusCode: 200,
body: `Hello, ${body.name}`,
};
};
Use the following configuration to enable Websocket logs:
# serverless.yml
provider:
name: aws
logs:
websocket: true
The log streams will be generated in a dedicated log group which follows the naming schema /aws/websocket/{service}-{stage}
.
The default log level will be INFO. You can change this to error with the following:
# serverless.yml
provider:
name: aws
logs:
websocket:
level: ERROR
Valid values are INFO, ERROR.
You can specify your own format for API Gateway Access Logs by including your preferred string in the format
property:
# serverless.yml
provider:
name: aws
logs:
websocket:
format: '{ "requestId":"$context.requestId", "ip": "$context.identity.sourceIp" }'
The existence of the logs
property enables both access and execution logging. If you want to disable one or both of them, you can do so with the following:
# serverless.yml
provider:
name: aws
logs:
websocket:
accessLogging: false
executionLogging: false
By default, the full requests and responses data will be logged. If you want to disable like so:
# serverless.yml
provider:
name: aws
logs:
websocket:
fullExecutionData: false
When using Websocket API, it is possible to tag the corresponding API Gateway resources. By setting provider.websocket.useProviderTags
to true
, all tags defined on provider.tags
will be applied to API Gateway and API Gateway Stage.
provider:
tags:
project: myProject
websocket:
useProviderTags: true
In the above example, the tag project: myProject will be applied to API Gateway and API Gateway Stage.