express and ws combined
npm install websocket-expressExtends express with WebSocket capabilities from
ws. This allows an easy syntax for handling
WebSockets as well as allowing a single server to handle both HTTP and WebSocket
requests.
This project is similar to (and takes some inspiration from
express-ws, but chooses to provide a
separate API rather than monkeypatching the express objects, and supports
asynchronous operations in all locations.
``bash`
npm install --save websocket-express ws express @types/ws @types/express
(ws and express are required as peer dependencies of websocket-express.express
You can use version 4 or 5. @types/ws and @types/express must be
added even if you are not using TypeScript in your project)
`javascript
import { WebSocketExpress, Router } from 'websocket-express';
const app = new WebSocketExpress();
const router = new Router();
// Simple usage:
router.ws('/path/foo', async (req, res) => {
const ws = await res.accept();
ws.on('message', (msg) => {
ws.send(echo ${msg});
});
ws.send('hello');
});
router.ws('/path/bar', async (req, res) => {
const ws = await res.accept();
ws.send('who are you?');
const message = await ws.nextMessage({ timeout: 1000 });
ws.send(hello, ${message.data});
ws.close();
});
// Asynchronous accept/reject:
router.ws('/path/ws-async', async (req, res, next) => {
const acceptable = await myAsynchronousOperation();
if (acceptable) {
const ws = await res.accept();
ws.send('hello');
} else {
next();
}
});
// Transactions:
router.ws('/path/transactional', async (req, res) => {
const ws = await res.accept();
try {
res.beginTransaction();
ws.send('hello');
ws.send('this is a long series of messages');
ws.send('and all messages will be sent');
ws.send('even if the server is asked to close()');
ws.send('although they may be stopped if the server crashes');
await myAsynchronousOperation();
ws.send('still included');
} finally {
res.endTransaction();
}
ws.send('this message might not be included');
ws.send('because the transaction has finished');
});
// Full Router API of express is supported too:
router.get('/path/foo', (req, res) => {
res.end('response to a normal HTTP GET request');
});
// use sends both HTTP and WS requests to the middleware / router:
app.use(router);
// useHTTP allows attaching middleware only to HTTP requests:
app.useHTTP(middleware);
// the setting 'shutdown timeout' can be set to automatically close
// WebSocket connections after a delay, even if they are in a
// transaction
app.set('shutdown timeout', 1000);
// create and run a server:
const server = app.createServer();
server.listen(8080);
// or attach to an existing server:
const server = http.createServer();
app.attach(server);
server.listen(8080);
`
If you have a vanilla express Router or middleware (e.g. from an externaluseHTTP
library), it is recommended to use rather than use to attach it, to
ensure it is not confused by WebSocket requests.
The static, json and urlencoded middleware is bundled by default anduse
ignores WebSocket requests, so is fine:
`javascript
import { WebSocketExpress } from 'websocket-express';
const app = new WebSocketExpress();
app.use(WebSocketExpress.static(myDirectory));
`
Example integration with an external websocket server library (socket.io):
`javascript
import { Server } from 'socket.io';
import { WebSocketExpress } from 'websocket-express';
const app = new WebSocketExpress();
app.ws('/socket.io/*', (req, res) => res.abandon()); // /socket.io requests are handled by socketioServer
// register other websocket and non-websocket endpoints as normal
const server = app.createServer();
const socketioServer = new Server(server);
// configure socketioServer as normal
server.listen(8080);
`
Note that if you abandon a request which is _not_ handled by another library,websocket-express
the connection will hang until the client eventually times out. This may be
particularly problematic when shutting down the server: willserver.close
normally close all remaining websocket connections automatically when is called, but it will not close abandoned connections. This may
delay the process termination, as NodeJS will remain active as long as any
connections are open.
The main method is Router.ws. This accepts a (possibly asynchronous) functionnext
with 3 parameters: the request, the response, and a callback to be
invoked if the request is rejected for any reason.
If the request is accepted, the function should call accept to get amessage
WebSocket, attach and close event listeners and can continue to
handle the WebSocket as normal.
If the request is rejected, next should be called (possibly with an error
description), and the next possible handler, or the error handler, will be
called (according to the standard express logic).
If no handlers are able to accept a WebSocket request, it will be closed (with
code 4404 by default). If you want another library to handle the request (e.g.
socket.io), you can call abandon to stop any further handling of the request.
If an exception is thrown, the socket will be closed with code 1011.
As with get / post / etc. it is possible to register a WebSocket handler
under the same URL as a non-websocket handler.
- Router.useHTTP / App.useHTTP behaves like Router.use in express. It will
invoke the middleware or router for all non-WebSocket requests.
- Router.all is similarly updated to apply only to non-WebSocket requests.
- Router.use / App.use will invoke the middleware or router for _all_
requests.
- Router.ws will invoke the middleware only for WebSocket requests.
- App.createServer is a convenience method which creates a server and callsattach
(see below).
- App.attach will attach the necessary event listeners to the given server
(e.g. if you want to use a https server, or a long-lived server with hot
reloading).
- App.detach will remove all attached event listeners from the given server.
- App.set('shutdown timeout', millis) configures a timeout (in milliseconds)close()
used by , after which connections will be forced to close even if
they are in a transaction.
The response parameter passed to websocket handlers has additional methods:
- res.accept() accepts the protocol switch, establishing the websocket
connection. This returns a promise which resolves to the newly established
websocket.
- res.reject([httpStatus[, message]]) returns a HTTP error instead of
accepting the websocket connection. Defaults to HTTP 500.
- res.abandon() stops all further processing of the connection. This should beupgrade
used if you want to delegate to another library which has also registered an
listener on the server. Note that this is provided as an escapeaccept
hatch; in general you should try to the connection and pass the
websocket to other libraries, as this will handle lifecycle events (such as
closing the server) more gracefully.
- res.sendError(httpStatus[, websocketErrorCode[, message]]) sends a HTTP or4000 + httpStatus
websocket error and closes the connection. If no explicit websocket error code
is provided and the websocket connection has already been established, this
will default to (e.g. HTTP 404 becomes websocket error
4404).
- res.send(message) shorthand for accepting the connection, sending a message,express
then closing the connection. Provided for compatibility with the
API.
- res.beginTransaction() / res.endTransaction() mark (nestable) transactionswebsocket-express
on the websocket connection. While a transaction is active,
will avoid closing the connection (e.g. during serverendTransaction
shutdown). These should be used to wrap short-lived sequences of messages
which need to be sent together. Do not use these for long-lived operations, as
it will delay server shutdown. To ensure is called, it is
recommended that you use the pattern:
`javascript`
try {
res.beginTransaction();
// transaction code
} finally {
res.endTransaction();
}
The WebSocket returned by accept has some additional helper methods:
- ws.nextMessage will return a promise which resolves with the next messagenextMessage({ timeout: millis })
received by the websocket. If the socket closes before a message arrives, the
promise will be rejected. You can also specify a timeout (with
) to reject if no message is received within
the requested time.
The object returned by nextMessage has a data element (a String if usingws
7.x and the message is text, or a Buffer if the message is binary, orws
if using 8.x), and an isBinary boolean. You can useString(message.data)
to convert the Buffer to a string using UTF8ws
encoding, matching 7.x's behaviour.
- Version 4 fixes an issue with routing where websocket handlers would
incorrectly match sub-paths. To keep the previous behaviour, add an explicit
/*rest` to the end of your websocket route.