mirror of
https://github.com/Mintplex-Labs/express-ws.git
synced 2026-07-18 20:34:27 -04:00
Merge branch '2.0'
This commit is contained in:
@@ -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
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"extends": "airbnb/base",
|
||||
"rules": {
|
||||
"comma-dangle": 0,
|
||||
"no-param-reassign": 0
|
||||
}
|
||||
}
|
||||
@@ -1,2 +1,3 @@
|
||||
node_modules/
|
||||
lib/
|
||||
*.sw[po]
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
src/
|
||||
@@ -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.
|
||||
@@ -1,16 +1,25 @@
|
||||
# express-ws [](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 +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
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
export default function addTrailingSlash(string) {
|
||||
let suffixed = string;
|
||||
if (suffixed.charAt(suffixed.length - 1) !== '/') {
|
||||
suffixed = `${suffixed}/`;
|
||||
}
|
||||
return suffixed;
|
||||
}
|
||||
@@ -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`;
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user