Skip to content

Commit

Permalink
Merge pull request #711 from dherault/websocket-fixes
Browse files Browse the repository at this point in the history
Websocket
  • Loading branch information
dnalborczyk committed Jul 4, 2019
2 parents 364b4c0 + 09d299a commit fdbde8f
Show file tree
Hide file tree
Showing 44 changed files with 4,292 additions and 1,297 deletions.
5 changes: 1 addition & 4 deletions .eslintignore
@@ -1,4 +1 @@
manual_test_nodejs
manual_test_python
manual_test_ruby
manual_test_websocket
**/node_modules
5 changes: 5 additions & 0 deletions .eslintrc.js
Expand Up @@ -8,6 +8,7 @@ const rules = {
'key-spacing': 'off',
'no-restricted-syntax': 'off',
'prefer-destructuring': 'off',
'one-var-declaration-per-line': ['error', 'always'],
semi: ['error', 'always'],
strict: 'off',
};
Expand All @@ -21,4 +22,8 @@ if (env.TRAVIS && platform === 'win32') {
module.exports = {
extends: 'dherault',
rules,
env: {
node: true,
mocha: true,
},
};
7 changes: 4 additions & 3 deletions .gitignore
Expand Up @@ -104,6 +104,7 @@ fabric.properties
# auto-generated tag files
tags
=======

.idea/
manual_test/.serverless

.idea/
.serverless
.dynamodb
30 changes: 30 additions & 0 deletions README.md
Expand Up @@ -28,6 +28,7 @@ This plugin is updated by its users, I just do maintenance and ensure that PRs a
* [Custom headers](#custom-headers)
* [Environment variables](#environment-variables)
* [AWS API Gateway features](#aws-api-gateway-features)
* [WebSocket](#websocket)
* [Usage with Webpack](#usage-with-webpack)
* [Velocity nuances](#velocity-nuances)
* [Debug process](#debug-process)
Expand Down Expand Up @@ -75,6 +76,7 @@ All CLI options are optional:
--location -l The root location of the handlers' files. Defaults to the current directory
--host -o Host name to listen on. Default: localhost
--port -P Port to listen on. Default: 3000
--websocketPort WebSocket port to listen on. Default: 3001
--stage -s The stage used to populate your templates. Default: the first stage found in your project.
--region -r The region used to populate your templates. Default: the first region for the first stage found.
--noTimeout -t Disables the timeout feature.
Expand Down Expand Up @@ -359,6 +361,34 @@ resources:

To disable the model validation you can use `--disableModelValidation`.

## WebSocket

:warning: *This is an experimental functionality. Please report any bugs or missing features. PRs are welcome.*

Usage in order to send messages back to clients:

`POST http://localhost:{websocketPort}/@connections/{connectionId}`

Or,

```js
const apiGatewayManagementApi = new AWS.ApiGatewayManagementApi({
apiVersion: '2018-11-29',
endpoint: event.apiGatewayUrl || `${event.requestContext.domainName}/${event.requestContext.stage}`,
});

apiGatewayManagementApi.postToConnection({
ConnectionId: ...,
Data: ...,
});
```

Where the `event` is received in the lambda handler function.

There's support for [websocketsApiRouteSelectionExpression](https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-websocket-api-selection-expressions.html) in it's basic form: `$request.body.x.y.z`, where the default value is `$request.body.action`.

Authorizers and wss:// are currectly not supported in this feature.

## Usage with Webpack

Use [serverless-webpack](https://github.com/serverless-heaven/serverless-webpack) to compile and bundle your ES-next code
Expand Down
7 changes: 3 additions & 4 deletions manual_test_nodejs/handler.js
@@ -1,4 +1,3 @@
'use strict';

module.exports.hello = (event, context, callback) => {
const response = {
Expand Down Expand Up @@ -28,7 +27,7 @@ module.exports.rejectedPromise = (event, context, callback) => {
callback(null, response);
};

module.exports.authFunction = (event, context, callback) => {
module.exports.authFunction = (event, context) => {
context.succeed({
principalId: 'xxxxxxx', // the principal user identification associated with the token send by the client
policyDocument: {
Expand Down Expand Up @@ -98,6 +97,6 @@ module.exports.pathParams = (event, context, cb) => {
cb(null, response);
};

module.exports.failure = (event, context, cb) => {
throw new Error('Unexpected error!')
module.exports.failure = () => {
throw new Error('Unexpected error!');
};
1 change: 0 additions & 1 deletion manual_test_nodejs/subprocess.js
@@ -1,4 +1,3 @@
'use strict';

const { exec } = require('child_process');

Expand Down
1 change: 1 addition & 0 deletions manual_test_websocket/.gitignore
@@ -0,0 +1 @@
/**/serverless.yml
25 changes: 11 additions & 14 deletions manual_test_websocket/README.md
Expand Up @@ -8,6 +8,7 @@ Set AWS credentials, e.g.: `export AWS_PROFILE=...`

To start AWS DynamoDB locally (can run only after first deploying locally): `sls dynamodb install` `sls dynamodb start`

### In either main or RouteSelection folder the following:

## Deploying locally

Expand All @@ -26,28 +27,24 @@ To start AWS DynamoDB locally (can run only after first deploying locally): `sls

## Testing on AWS

`npm --endpoint={WebSocket endpoint URL on AWS} run test`
`npm --endpoint={WebSocket endpoint URL on AWS} --timeout={timeout in ms} run test`


## Usage Assumption - In order to send messages back to clients
`const newAWSApiGatewayManagementApi=(event, context)=>{`
## Usage in order to send messages back to clients

`POST http://localhost:3001/@connections/{connectionId}`

` const endpoint=event.requestContext.domainName+'/'+event.requestContext.stage;`
Or,

` const apiVersion='2018-11-29';`
`let endpoint=event.apiGatewayUrl;`

` let API=context.API;`
`if (!endpoint) endpoint = event.requestContext.domainName+'/'+event.requestContext.stage;`

` if (!process.env.IS_OFFLINE) {`
`const apiVersion='2018-11-29';`

` API = require('aws-sdk');`
`const apiGM=new API.ApiGatewayManagementApi({ apiVersion, endpoint });`

` require('aws-sdk/clients/apigatewaymanagementapi');`
`apiGM.postToConnection({ConnectionId, Data});`

` }`

` return new API.ApiGatewayManagementApi({ apiVersion, endpoint });`

`};`


31 changes: 31 additions & 0 deletions manual_test_websocket/RouteSelection/handler.js
@@ -0,0 +1,31 @@
const AWS = require('aws-sdk');

const successfullResponse = {
statusCode: 200,
body: 'Request is OK.',
};

module.exports.echo = async (event, context) => {
const action = JSON.parse(event.body);

await sendToClient(action.message, event.requestContext.connectionId, newAWSApiGatewayManagementApi(event, context));

return successfullResponse;
};

const newAWSApiGatewayManagementApi = event => {
let endpoint = event.apiGatewayUrl;

if (!endpoint) endpoint = `${event.requestContext.domainName}/${event.requestContext.stage}`;
const apiVersion = '2018-11-29';

return new AWS.ApiGatewayManagementApi({ apiVersion, endpoint });
};

const sendToClient = (data, connectionId, apigwManagementApi) => {
// console.log(`sendToClient:${connectionId}`);
let sendee = data;
if (typeof data === 'object') sendee = JSON.stringify(data);

return apigwManagementApi.postToConnection({ ConnectionId: connectionId, Data: sendee }).promise();
};

0 comments on commit fdbde8f

Please sign in to comment.