Merge branch '2.0'

This commit is contained in:
Henning Morud
2016-04-02 12:21:20 +02:00
14 changed files with 247 additions and 80 deletions
+3
View File
@@ -0,0 +1,3 @@
{
presets: ["es2015"]
}
+12
View File
@@ -0,0 +1,12 @@
root = true
[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.md]
indent_size = 4
+7
View File
@@ -0,0 +1,7 @@
{
"extends": "airbnb/base",
"rules": {
"comma-dangle": 0,
"no-param-reassign": 0
}
}
+1
View File
@@ -1,2 +1,3 @@
node_modules/
lib/
*.sw[po]
+1
View File
@@ -0,0 +1 @@
src/
+7
View File
@@ -0,0 +1,7 @@
### v2.0
* __[BREAKING]__ Now supports Express Routers as well. A side-effect of this is that the Router prototype is modified to add a `.ws` method - in rare cases, this may interfere with existing code that relies on the Router prototype either remaining unchanged, or adding its own `.ws` method to it.
A `leaveRouterUntouched` option has been added to prevent this behaviour; see the API documentation for more information.
* [minor] You can now add `.ws` functionality to any custom Router object that follows the Express Router API.
+68 -5
View File
@@ -1,16 +1,25 @@
# express-ws [![Dependency Status](https://www.versioneye.com/nodejs/express-ws/badge?style=flat)](https://www.versioneye.com/nodejs/express-ws)
WebSocket endpoints for express applications. Gives WebSocket connections access to functionality from express middlewares.
[WebSocket](https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API) endpoints for [Express](http://expressjs.com/) applications. Lets you define WebSocket endpoints like any other type of route, and applies regular Express midddleware like for anything else.
Version 2.0 of this library contains a breaking change. Please make sure to read the CHANGELOG.md before upgrading.
## Installation
`npm install express-ws`
`npm install --save express-ws`
## Usage
Add this line to your express application:
__Full documentation can be found in the API section below. This section only shows a brief example.__
Add this line to your Express application:
```javascript
var expressWs = require('express-ws')(app); //app = express app
var expressWs = require('express-ws')(app);
```
Now you will be able to add WebSocket routes (almost) the same way you add other routes. The following snippet sets up a simple echo server at `/echo`.
```javascript
app.ws('/echo', function(ws, req) {
ws.on('message', function(msg) {
@@ -19,7 +28,22 @@ app.ws('/echo', function(ws, req) {
});
```
## Example
It works with routers, too, this time at `/ws-stuff/echo`:
```javascript
var router = express.Router();
router.ws('/echo', function(ws, req) {
ws.on('message', function(msg) {
ws.send(msg);
});
});
app.use("/ws-stuff", router);
```
## Full example
```javascript
var express = require('express');
var app = express();
@@ -45,3 +69,42 @@ app.ws('/', function(ws, req) {
app.listen(3000);
```
## API
### expressWs(app, *server*, *options*)
Sets up `express-ws` on the specified `app`. This will modify the global Router prototype for Express as well - see the `leaveRouterUntouched` option for more information on disabling this.
* __app__: The Express application to set up `express-ws` on.
* __server__: *Optional.* When using a custom `http.Server`, you should pass it in here, so that `express-ws` can use it to set up the WebSocket upgrade handlers. If you don't specify a `server`, you will only be able to use it with the server that is created automatically when you call `app.listen`.
* __options__: *Optional.* An object containing further options.
* __leaveRouterUntouched:__ Set this to `true` to keep `express-ws` from modifying the Router prototype. You will have to manually `applyTo` every Router that you wish to make `.ws` available on, when this is enabled.
This function will return a new `express-ws` API object, which will be referred to as `wsInstance` in the rest of the documentation.
### wsInstance.app
This property contains the `app` that `express-ws` was set up on.
### wsInstance.getWss()
Returns the underlying WebSocket server/handler. You can use `wsInstance.getWss().clients` to obtain a list of all the connected WebSocket clients for this server.
Note that this list will include *all* clients, not just those for a specific route - this means that it's often *not* a good idea to use this for broadcasts, for example.
### wsInstance.applyTo(router)
Sets up `express-ws` on the given `router` (or other Router-like object). You will only need this in two scenarios:
1. You have enabled `options.leaveRouterUntouched`, or
2. You are using a custom router that is not based on the express.Router prototype.
In most cases, you won't need this at all.
## Development
This module is written in ES6, and uses Babel for compilation. What this means in practice:
* The source code lives in the `src/` directory.
* After changing this code, make sure to run `npm run build` to compile it.
+1 -69
View File
@@ -1,69 +1 @@
var url = require('url');
var http = require('http');
var ServerResponse = http.ServerResponse;
var WebSocketServer = require('ws').Server;
var wsServer;
var wrapWsHandler = function(handler) {
return function(req, res, next) {
if (req.ws) {
req.wsHandled = true;
handler(req.ws, req, next);
} else {
next();
}
};
};
/**
* @param {express.Application} app
* @param {http.Server} [server]
*/
module.exports = function (app, server) {
if(!server) {
server = http.createServer(app);
app.listen = function()
{
return server.listen.apply(server, arguments)
}
}
wsServer = new WebSocketServer({ server: server });
wsServer.on('connection', function(ws) {
var response = new ServerResponse(ws.upgradeReq);
response.writeHead = function (statusCode) {
if (statusCode > 200) ws.close();
};
ws.upgradeReq.ws = ws;
ws.upgradeReq.url = '/.websocket/' + ws.upgradeReq.url;
app.handle(ws.upgradeReq, response, function() {
if (!ws.upgradeReq.wsHandled) {
ws.close();
}
});
});
function addSocketRoute(route, middleware) {
var args = [].splice.call(arguments, 0);
route = '/.websocket/' + route;
var middlewares = args.slice(1).map(wrapWsHandler);
var routeArgs = [route].concat(middlewares);
app.get.apply(app, routeArgs);
return app;
};
app.ws = addSocketRoute;
return {
app: app,
getWss: function (route) {
return wsServer;
}
};
};
module.exports = require("./lib").expressWs;
+12 -6
View File
@@ -1,23 +1,23 @@
{
"name": "express-ws",
"version": "1.0.0",
"description": "WebSocket endpoints for express applications",
"description": "WebSocket endpoints for Express applications",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
"prepublish": "npm run build",
"build": "babel src/ -d lib/",
"lint": "eslint src/"
},
"author": "Henning Morud <henning@morud.org>",
"contributors": [
"Jesús Leganés Combarro <piranna@gmail.com>",
"Sven Slootweg <admin@cryto.net>",
"Andrew Phillips <theasp@gmail.com>"
],
"license": "BSD-2-Clause",
"dependencies": {
"ws": "^1.0.0"
},
"devDependencies": {
"express": "^4.0.0"
},
"peerDependencies": {
"express": "^4.0.0"
},
@@ -36,5 +36,11 @@
"bugs": {
"url": "https://github.com/HenningM/express-ws/issues"
},
"homepage": "https://github.com/HenningM/express-ws"
"homepage": "https://github.com/HenningM/express-ws",
"devDependencies": {
"babel-cli": "^6.5.1",
"babel-preset-es2015": "^6.5.0",
"eslint": "^1.10.3",
"eslint-config-airbnb": "^5.0.0"
}
}
+25
View File
@@ -0,0 +1,25 @@
import wrapMiddleware from './wrap-middleware';
import websocketUrl from './websocket-url';
export default function addWsMethod(target) {
/* This prevents conflict with other things setting `.ws`. */
if (target.ws === null || target.ws === undefined) {
target.ws = function addWsRoute(route, ...middlewares) {
const wrappedMiddlewares = middlewares.map(wrapMiddleware);
/* We append `/.websocket` to the route path here. Why? To prevent conflicts when
* a non-WebSocket request is made to the same GET route - after all, we are only
* interested in handling WebSocket requests.
*
* Whereas the original `express-ws` prefixed this path segment, we suffix it -
* this makes it possible to let requests propagate through Routers like normal,
* which allows us to specify WebSocket routes on Routers as well \o/! */
const wsRoute = websocketUrl(route);
/* Here we configure our new GET route. It will never get called by a client
* directly, it's just to let our request propagate internally, so that we can
* leave the regular middleware execution and error handling to Express. */
this.get.apply(this, [wsRoute].concat(wrappedMiddlewares));
};
}
}
+80
View File
@@ -0,0 +1,80 @@
/* This module does a lot of monkeypatching, but unfortunately that appears to be the only way to
* accomplish this kind of stuff in Express.
*
* Here be dragons. */
import http from 'http';
import express from 'express';
import ws from 'ws';
import websocketUrl from './websocket-url';
import addWsMethod from './add-ws-method';
export function expressWs(app, httpServer, options = {}) {
let server = httpServer;
if (server === null || server === undefined) {
/* No HTTP server was explicitly provided, create one for our Express application. */
server = http.createServer(app);
app.listen = function serverListen() {
server.listen.apply(server, arguments);
};
}
/* Make our custom `.ws` method available directly on the Express application. You should
* really be using Routers, though. */
addWsMethod(app);
/* Monkeypatch our custom `.ws` method into Express' Router prototype. This makes it possible,
* when using the standard Express Router, to use the `.ws` method without any further calls
* to `makeRouter`. When using a custom router, the use of `makeRouter` may still be necessary.
*
* This approach works, because Express does a strange mixin hack - the Router factory
* function is simultaneously the prototype that gets assigned to the resulting Router
* object. */
if (!options.leaveRouterUntouched) {
addWsMethod(express.Router);
}
const wsServer = new ws.Server({ server });
wsServer.on('connection', (socket) => {
const request = socket.upgradeReq;
request.ws = socket;
request.wsHandled = false;
/* By setting this fake `.url` on the request, we ensure that it will end up in the fake
* `.get` handler that we defined above - where the wrapper will then unpack the `.ws`
* property, indicate that the WebSocket has been handled, and call the actual handler. */
request.url = websocketUrl(request.url);
const dummyResponse = new http.ServerResponse(request);
dummyResponse.writeHead = function writeHead(statusCode) {
if (statusCode > 200) {
/* Something in the middleware chain signalled an error. */
socket.close();
}
};
app.handle(request, dummyResponse, () => {
if (!request.wsHandled) {
/* There was no matching WebSocket-specific route for this request. We'll close
* the connection, as no endpoint was able to handle the request anyway... */
socket.close();
}
});
});
return {
app,
getWss: function getWss() {
return wsServer;
},
applyTo: function applyTo(router) {
addWsMethod(router);
}
};
}
+7
View File
@@ -0,0 +1,7 @@
export default function addTrailingSlash(string) {
let suffixed = string;
if (suffixed.charAt(suffixed.length - 1) !== '/') {
suffixed = `${suffixed}/`;
}
return suffixed;
}
+11
View File
@@ -0,0 +1,11 @@
import trailingSlash from './trailing-slash';
/* The following fixes HenningM/express-ws#17, correctly. */
export default function websocketUrl(url) {
if (url.indexOf('?') !== -1) {
const [baseUrl, query] = url.split('?');
return `${trailingSlash(baseUrl)}.websocket?${query}`;
}
return `${trailingSlash(url)}.websocket`;
}
+12
View File
@@ -0,0 +1,12 @@
export default function wrapMiddleware(middleware) {
return (req, res, next) => {
if (req.ws !== null && req.ws !== undefined) {
req.wsHandled = true;
/* Unpack the `.ws` property and call the actual handler. */
middleware(req.ws, req, next);
} else {
/* This wasn't a WebSocket request, so skip this middleware. */
next();
}
};
}