AWS API Gateway WebSocket is very cool.
I have to say it, and I really like it. If you were reading — setting up — fixing a NodeJS WebSocket server, probably you will understand how complex it is, when you need to scale up your server, when you need to monitor your server, and also when you need to optimize your server. API Gateway does all the jobs for you, so you can focus on coding, without worry about almost of DevOps stuffs and reasonable price.
However, the way API Gateway WebSocket works is a bit different from the way you implement your NodeJS WebSocket by yourself. But when you get used to, I’m pretty sure you will like it.
But hey, this article is not a tutorial for the beginner with AWS API Gateway (Probably I will have another article for it), this article is about a problem I faced many times: Force disconnecting a client from server-side.
Giving an example: We’re running a Websocket server which pushes messages to connected & authenticated clients. Sometimes, a user changes his password, therefore, his connected sessions should be invalidated.
Straightforward, I think the most standard way is just sending an SNS notification to our Lambda function which runs the API Gateway — the same way we did to push message to clients. Then this Lambda function will just close the connection with given connectionId.
Things were working fine, until I found that there is no way to disconnect a client from Lambda function. API Gateway documentation just describes the way to post data to connected clients. I hit the wall so fast.
After trying to dig more into Amazon documentations, StackOverflow, Github,…, I’ve found a way, well, at least a way to solve:
https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-how-to-call-websocket-api-connections.html
But it will require authorization with signature — the thing I tried to avoid to keep my Lambda NodeJS function as lite as I could, also relying on ApiGatewayManagementApi to make sure things are flexible.
So, based on the documentation, I’ve tried to “patch” the ApiGatewayManagementApi to have more functionality
I create a patch.js file
require('aws-sdk/lib/node_loader');
var AWS = require('aws-sdk/lib/core');
var Service = AWS.Service;
var apiLoader = AWS.apiLoader;
apiLoader.services['apigatewaymanagementapi'] = {};
AWS.ApiGatewayManagementApi = Service.defineService('apigatewaymanagementapi', ['2018–11–29']);
Object.defineProperty(apiLoader.services['apigatewaymanagementapi'], '2018–11–29', {
get: function get() {
var model = {
"metadata": {
"apiVersion": "2018–11–29",
"endpointPrefix": "execute-api",
"signingName": "execute-api",
"serviceFullName": "AmazonApiGatewayManagementApi",
"serviceId": "ApiGatewayManagementApi",
"protocol": "rest-json",
"jsonVersion": "1.1",
"uid": "apigatewaymanagementapi-2018–11–29",
"signatureVersion": "v4"
},
"operations": {
"PostToConnection": {
"http": {
"requestUri": "/@connections/{connectionId}",
"responseCode": 200
},
"input": {
"type": "structure",
"members": {
"Data": {
"type": "blob"
},
"ConnectionId": {
"location": "uri",
"locationName": "connectionId"
}
},
"required": ["ConnectionId", "Data"],
"payload": "Data"
}
},
"DeleteConnection": {
"http": {
"requestUri": "/@connections/{connectionId}",
"responseCode": 200,
"method": "DELETE"
},
"input": {
"type": "structure",
"members": {
"ConnectionId": {
"location": "uri",
"locationName": "connectionId"
}
},
"required": [
"ConnectionId"
]
}
}
},
"shapes": {}
}
model.paginators = {
"pagination": {}
}
return model;
},
enumerable: true,
configurable: true
});
module.exports = AWS.ApiGatewayManagementApi;
Inside the patch, I’ve added a method into ApiGatewayManagementApi class, called “deleteConnection”. So when I call it, ApiGatewayManagementApi will issue a DELETE request to “/@connections/{connectionId}”, to close the connection.
Let’s try it
const AWS = require('aws-sdk');
require('./patch.js'); // apply the patch
const apigwManagementApi = new AWS.ApiGatewayManagementApi({
apiVersion: '2018–11–29',
endpoint: 'YOUR ENDPOINT'
});
apigwManagementApi.deleteConnection({
ConnectionId: 'CONNECTION ID'
});
And it disconnects the client successfully!
Here is my solution to disconnect the client from server-side in API Gateway. Hope it helps you if you face the same problem.