yawl
v1.0.2
Published
yet another web sockets library
Downloads
24
Maintainers
Readme
node-yawl
Yet Another WebSocket Library - WebSocket server and client for Node.js
Features
- Almost RFC 6455 compliant. Exceptions:
- Uses Node.js's built-in UTF-8 decoding which ignores errors. The spec says to close the connection when invalid UTF-8 is encountered. Instead this module will silently ignore decoding errors just like the rest of your Node.js code.
- "payload length" field is limited to
2^52
instead of2^64
. JavaScript numbers are all 64-bit double precision floating point which have a 52-bit significand precision.
- Uses streams and handles backpressure correctly.
- Low level without sacrificing clean abstractions.
- Secure by default, secure by design
- JavaScript implementation. No compiler required.
- As performant as a pure JavaScript implementation is going to get. See the performance section below for details.
- Built for Node.js only. No hacky code to make it also work in the browser.
Server Usage
var yawl = require('yawl');
var http = require('http');
var server = http.createServer();
var wss = yawl.createServer({
server: server,
origin: null,
allowTextMessages: true,
});
wss.on('connection', function(ws) {
ws.sendText('message');
ws.on('textMessage', function(message) {
console.log(message);
});
});
server.listen(port, host, function() {
log.info("Listening at " + protocol + "://" + host + ":" + port + "/");
});
Client Usage
var yawl = require('yawl');
var url = require('url');
var options = url.parse("wss://example.com/path?query=1");
options.extraHeaders = {
'User-Agent': "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:33.0) Gecko/20100101 Firefox/33.0"
};
options.allowTextMessages = true;
// any options allowed in https.request also allowed here.
var ws = yawl.createClient(options);
ws.on('open', function() {
ws.sendText("hi");
fs.createReadStream("foo.txt").pipe(ws.sendStream());
});
ws.on('textMessage', function(message) {
console.log(message);
});
API Documentation
yawl.createServer(options)
Creates a WebSocketServer
instance.
options
:
server
- an instance ofhttps.Server
orhttp.Server
. Required.origin
- seesetOrigin
belownegotiate
(optional) - seesetNegotiate
below.allowTextMessages
(optional) - seesetAllowTextMessages
below.allowBinaryMessages
(optional) - seesetAllowBinaryMessages
below.allowFragmentedMessages
(optional) - seesetAllowFragmentedMessages
below.allowUnfragmentedMessages
(optional) - seesetAllowUnfragmentedMessages
below.maxFrameSize
(optional) - SeesetMaxFrameSize
below.
yawl.createClient(options)
Creates a WebSocketClient
instance.
options
:
- everything that https.request accepts. This allows you to do things such as connect to UNIX domain sockets rather than ports, use SSL, etc.
extraHeaders
(optional) -Object
of extra headers to include in the upgrade request.origin
(optional) - Sets theOrigin
header. Same as including anOrigin
property inextraHeaders
.allowTextMessages
(optional) - SeesetAllowTextMessages
below.allowBinaryMessages
(optional) - SeesetAllowBinaryMessages
below.allowFragmentedMessages
(optional) - SeesetAllowFragmentedMessages
below.allowUnfragmentedMessages
(optional) - seesetAllowUnfragmentedMessages
below.maxFrameSize
(optional) - SeesetMaxFrameSize
below.
Consider using code like this with createClient
:
var url = require('url');
// use url.parse to create the options object
var options = url.parse("ws://example.com/path?query=1");
// now set more options
options.allowTextMessages = true;
// now create the client
var ws = yawl.createClient(options);
// ...
yawl.parseSubProtocolList(request)
Parses request.headers['sec-websocket-protocol']
and returns an array of
lowercase strings.
Example:
...
Sec-WebSocket-Protocol: chat, SuperChat
...
Yields:
['chat', 'superchat']
yawl.parseExtensionList(request)
Parses request.headers['sec-websocket-extensions']
and returns an array of
objects. If the header is invalid according to
RFC6455 9.1 then an error
is thrown.
Example:
...
Sec-WebSocket-Extensions: foo,bar; baz=2;extra, third; arg="quoted"
...
Yields:
[
{
name: 'foo',
params: [],
},
{
name: 'bar',
params: [
{
name: 'baz',
value: '2',
},
{
name: 'extra',
value: null,
},
],
},
{
name: 'third',
params: [
{
name: 'arg',
value: 'quoted',
},
],
},
]
yawl.WebSocketServer
wss.setOrigin(value)
String
or null
. Set to null
to disable origin validation.
To activate origin validation, set to a string such as:
https://example.com
or http://example.com:1234
wss.setNegotiate(value)
Boolean
. Set to true
to enable upgrade header negotiation with clients.
Defaults to false
. If you set this to true
, you must listen to the
negotiate
event (see below).
wss.setAllowTextMessages(value)
Boolean
. Set to true
to allow UTF-8 encoded text messages. Defaults to
false
.
wss.setAllowBinaryMessages(value)
Boolean
. Set to true
to allow binary messages. Defaults to false
.
wss.setAllowFragmentedMessages(value)
Boolean
. Set to true
to allow fragmented messages, that is, messages for
which you do not know the total size until the message is completely sent.
Defaults to false
.
If you set this to true
be sure to handle the streamMessage
event. Even
if you are not interested in a particular message you must consume the stream.
wss.setAllowUnfragmentedMessages(value)
Boolean
. Set to false
to disallow unfragmented messages. Defaults to true
.
If you set this to true
this will prevent textMessage
and binaryMessage
events from firing.
You might consider instead of this, setting maxFrameSize
to Infinity
. This
will have the effect of causing fragmented messages emit as streamMessage
,
with the length
parameter set.
wss.setMaxFrameSize(value)
Number
. Maximum number of bytes acceptable for non-fragmented messages.
Defaults to 8MB.
If a client attempts to transmit a larger message, the connection is closed
according to the specification. Valid messages are buffered. Text messages
arrive with the textMessage
event and binary messages arrive with the
binaryMessage
event.
If this number is set to Infinity
, then all messages are streaming messages
and arrive with the streamMessage
event. If you do this, be sure to handle
the streamMessage
event. Even if you are not interested in a particular
message you must consume the stream.
Event: 'negotiate'
function (request, socket, callback) { }
request
- the client request getting upgradedsocket
-WritableStream
with which you can talk to the clientcallback (extraHeaders)
- call this if you want to succeed or fail the websocket connection. To fail it, passnull
forextraHeaders
. To succeed it, pass{}
forextraHeaders
. You may also include extra headers in this object which will be sent with the reply. If you wish, you may take control of processing the request directly by writing to socket and managing that connection. In this case, do not callcallback
.
This event only fires if you set negotiate
to true
on the
WebSocketServer
.
See also yawl.parseSubProtocolList
and yawl.parseExtensionList
.
Event: 'connection'
function (ws, request) { }
ws
-WebSocketClient
.request
- http.IncomingMessage the HTTP upgrade request.
Fires when a websocket connection is successfully negotiated. ws
is in the
OPEN
state.
Event: 'error'
function (error) { }
If an error occurs during the upgrade process before the connection
event,
this event will fire. For example, if writing 400 Bad Request
due to an
invalid websocket handshake raised an error, it would be emitted here.
yawl.WebSocketClient
ws.sendText(text)
text
is a String
.
Sends an unfragmented UTF-8 encoded text message.
This method throws an error if you are in the middle of sending a stream.
ws.sendBinary(buffer, [isUtf8])
buffer
is a Buffer
.
Sends an unfragmented binary message.
If this websocket client does not represent a client connected to a server,
buffer
will be modified in place. Make a copy of the buffer if you do not
want this to happen.
If isUtf8
is true
, the message will be sent as an unfragmented text
message.
This method throws an error if you are in the middle of sending a stream.
ws.sendStream([isUtf8], [options])
Sends a fragmented message.
isUtf8
(optional) -Boolean
. Iftrue
this message will be sent as UTF-8 encoded text message. Otherwise, this message will be sent as a binary message.options
(optional):highWaterMark
-Number
- Buffer level whenwrite()
starts returningfalse
. Default 16KB.
Returns a Writable
stream which is sent over the websocket connection. Be
sure to handle the error
event of this stream.
You may not send other text, binary, or stream messages while streaming. This method throws an error if you are in the middle of sending another stream.
When you call write()
on the Writable
stream that is returned, if this
websocket client does not represent a client connected to a server, the buffer
you pass to write()
will be modified in place. Make a copy of the buffer if
you do not want this to happen.
ws.sendFragment(finBit, opcode, buffer)
This is a low level method that you will only need if you are writing tests or using yawl to test other code.
finBit
- Eitheryawl.FIN_BIT_1
oryawl.FIN_BIT_0
.opcode
- One of:yawl.OPCODE_CONTINUATION_FRAME
yawl.OPCODE_TEXT_FRAME
yawl.OPCODE_BINARY_FRAME
yawl.OPCODE_CLOSE
yawl.OPCODE_PING
yawl.OPCODE_PONG
buffer
-Buffer
. If you want no fragment body, useyawl.EMPTY_BUFFER
.
ws.close([statusCode], [message])
statusCode
(optional) -Number
- See RFC6455 Section 11.7message
(optional) -String
. Must be no greater than 123 bytes when UTF-8 encoded.
Sends a close message to the other endpoint. The state of the client becomes
CLOSING
.
If the WebSocketClient
represents a client connected to a server, the server
closes the connection to the client without waiting for a corresonding close
message from the client.
Otherwise, the client waits for the server to close the connection.
ws.isOpen()
Returns true
if the state is OPEN
. Calling any of the send functions
while the state is not OPEN
throws an error.
ws.sendPingBinary(buffer)
Sends a ping message. buffer.length
must be no greater than 125 bytes.
If this websocket client does not represent a client connected to a server,
buffer
will be modified in place. Make a copy of the buffer if you do not
want this to happen.
ws.sendPingText(string)
Sends a ping message. string
must be no greater than 125 bytes when UTF-8
encoded.
ws.sendPongBuffer(buffer)
Sends a pong message. buffer.length
must be no greater than 125 bytes.
Pong messages are automatically sent as a response to ping messages.
If this websocket client does not represent a client connected to a server,
buffer
will be modified in place. Make a copy of the buffer if you do not
want this to happen.
ws.sendPongText(string)
Sends a pong message. string
must be no greater than 125 bytes when UTF-8
encoded.
Pong messages are automatically sent as a response to ping messages.
ws.socket
The underlying socket for this connection.
Event: 'open'
function (response) { }
response
-
http.IncomingMessage
- the HTTP response from the upgrade request.
Emitted when the upgrade request succeeds and the client is in the OPEN
state.
This event is not fired when the WebSocketClient
represents a client
connected to a server. In that situation, the WebSocketClient
parameter of
the connection
event is already in the OPEN
state.
Event: 'textMessage'
function (string) { }
This event will not fire if maxFrameSize
is set to Infinity
.
This event will not fire unless allowTextMessages
is set to true
.
Fragmented messages never arrive in this event.
Event: 'binaryMessage'
function (buffer) { }
This event will not fire if maxFrameSize
is set to Infinity
.
This event will not fire unless allowBinaryMessages
is set to true
.
Fragmented messages never arrive in this event.
Event: 'streamMessage'
function (stream, isUtf8, length) { }
stream
-ReadableStream
. You must consume this stream. If you are not interested in this message, callstream.resume()
to trash the data. Be sure to handle theerror
event of this stream.isUtf8
-Boolean
. Tells whether stream was sent as a UTF-8 text message.length
-Number
. Ifnull
, this is a fragmented message. Otherwise, the total size of the stream is known beforehand.
If isUtf8
is true
, you might want to do this: stream.setEncoding('utf8')
.
See readable.setEncoding(encoding)
Unfragmented messages do not arrive in this event if maxFrameSize
is not
Infinity
.
Fragmented messages do not arrive in this event unless allowFragmentedMessages
is set to true
.
isUtf8
will not be true
if allowTextMessages
is false
.
isUtf8
will not be false
if allowBinaryMessages
is false
.
Event: 'closeMessage'
function (statusCode, message) { }
statusCode
-Number
- See RFC6455 Section 11.7. Can benull
.message
-String
. Can benull
. Guaranteed to be no greater than 123 bytes when UTF-8 encoded.
This event is fired when the other endpoint sends a close frame.
yawl handles this message by closing the socket, so this message is shortly
followed by the close
event.
Event: 'pingMessage'
function (buffer) { }
buffer
-Buffer
. Must be no greater than 125 bytes.
Event: 'pongMessage'
function (buffer) { }
buffer
-Buffer
. Must be no greater than 125 bytes.
Event: 'close'
This event fires when the underlying socket connection is closed. It is
guaranteed to fire even if an error occurs, unlike closeMessage
.
When this event fires the state of the websocket is now CLOSED
.
Event: 'error'
function (error) { }
error
- Error
. If this error is due to a problem with the websocket
protocol, error.statusCode
is set. See
RFC6455 Section 11.7 for
a list of status codes and their meanings.
When an error occurs a WebSocketClient
closes itself, so this event is
shortly followed by the close
event.
Performance
$ node -v
v0.10.35
$ date
Wed Jan 21 09:24:13 MST 2015
$ node test/perf.js
big buffer echo (yawl): 0.52s 191MB/s
big buffer echo (ws): 0.26s 388MB/s
big buffer echo (faye): 0.54s 186MB/s
many small buffers (yawl): 0.41s 12MB/s
many small buffers (ws): 0.33s 15MB/s
many small buffers (faye): 0.58s 8MB/s
permessage-deflate big buffer echo (ws): 8.57s 12MB/s
permessage-deflate many small buffers (ws): 1.76s 3MB/s
permessage-deflate big buffer echo (faye): 4.59s 22MB/s
permessage-deflate many small buffers (faye): 2.31s 2MB/s
done
The bottleneck is in the masking code:
function maskMangleBuf(buffer, mask) {
for (var i = 0; i < buffer.length; i += 1) {
buffer[i] = buffer[i] ^ mask[i % 4];
}
}
This is as fast as it's going to get in JavaScript. Making this module faster requires a native add-on.
How to Run the Autobahn Tests
Note that yawl has its own tests which you can run using npm test
as usual.
Test the Client
- In one terminal,
wstest --mode=fuzzingserver --wsuri=ws://localhost:9001
- In another terminal,
node test/autobahn-client.js
- Open reports/clients/index.html in a web browser.
Test the Server
- In one terminal,
node test/autobahn-server.js
- In another terminal,
wstest --mode=fuzzingclient --wsuri=ws://localhost:9001
- Open reports/servers/index.html in a web browser.