You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

370 lines
13 KiB

4 years ago
  1. # websocket-driver [![Build Status](https://travis-ci.org/faye/websocket-driver-node.svg)](https://travis-ci.org/faye/websocket-driver-node)
  2. This module provides a complete implementation of the WebSocket protocols that
  3. can be hooked up to any I/O stream. It aims to simplify things by decoupling the
  4. protocol details from the I/O layer, such that users only need to implement code
  5. to stream data in and out of it without needing to know anything about how the
  6. protocol actually works. Think of it as a complete WebSocket system with
  7. pluggable I/O.
  8. Due to this design, you get a lot of things for free. In particular, if you hook
  9. this module up to some I/O object, it will do all of this for you:
  10. - Select the correct server-side driver to talk to the client
  11. - Generate and send both server- and client-side handshakes
  12. - Recognize when the handshake phase completes and the WS protocol begins
  13. - Negotiate subprotocol selection based on `Sec-WebSocket-Protocol`
  14. - Negotiate and use extensions via the
  15. [websocket-extensions](https://github.com/faye/websocket-extensions-node)
  16. module
  17. - Buffer sent messages until the handshake process is finished
  18. - Deal with proxies that defer delivery of the draft-76 handshake body
  19. - Notify you when the socket is open and closed and when messages arrive
  20. - Recombine fragmented messages
  21. - Dispatch text, binary, ping, pong and close frames
  22. - Manage the socket-closing handshake process
  23. - Automatically reply to ping frames with a matching pong
  24. - Apply masking to messages sent by the client
  25. This library was originally extracted from the [Faye](http://faye.jcoglan.com)
  26. project but now aims to provide simple WebSocket support for any Node-based
  27. project.
  28. ## Installation
  29. ```
  30. $ npm install websocket-driver
  31. ```
  32. ## Usage
  33. This module provides protocol drivers that have the same interface on the server
  34. and on the client. A WebSocket driver is an object with two duplex streams
  35. attached; one for incoming/outgoing messages and one for managing the wire
  36. protocol over an I/O stream. The full API is described below.
  37. ### Server-side with HTTP
  38. A Node webserver emits a special event for 'upgrade' requests, and this is where
  39. you should handle WebSockets. You first check whether the request is a
  40. WebSocket, and if so you can create a driver and attach the request's I/O stream
  41. to it.
  42. ```js
  43. var http = require('http'),
  44. websocket = require('websocket-driver');
  45. var server = http.createServer();
  46. server.on('upgrade', function(request, socket, body) {
  47. if (!websocket.isWebSocket(request)) return;
  48. var driver = websocket.http(request);
  49. driver.io.write(body);
  50. socket.pipe(driver.io).pipe(socket);
  51. driver.messages.on('data', function(message) {
  52. console.log('Got a message', message);
  53. });
  54. driver.start();
  55. });
  56. ```
  57. Note the line `driver.io.write(body)` - you must pass the `body` buffer to the
  58. socket driver in order to make certain versions of the protocol work.
  59. ### Server-side with TCP
  60. You can also handle WebSocket connections in a bare TCP server, if you're not
  61. using an HTTP server and don't want to implement HTTP parsing yourself.
  62. The driver will emit a `connect` event when a request is received, and at this
  63. point you can detect whether it's a WebSocket and handle it as such. Here's an
  64. example using the Node `net` module:
  65. ```js
  66. var net = require('net'),
  67. websocket = require('websocket-driver');
  68. var server = net.createServer(function(connection) {
  69. var driver = websocket.server();
  70. driver.on('connect', function() {
  71. if (websocket.isWebSocket(driver)) {
  72. driver.start();
  73. } else {
  74. // handle other HTTP requests
  75. }
  76. });
  77. driver.on('close', function() { connection.end() });
  78. connection.on('error', function() {});
  79. connection.pipe(driver.io).pipe(connection);
  80. driver.messages.pipe(driver.messages);
  81. });
  82. server.listen(4180);
  83. ```
  84. In the `connect` event, the driver gains several properties to describe the
  85. request, similar to a Node request object, such as `method`, `url` and
  86. `headers`. However you should remember it's not a real request object; you
  87. cannot write data to it, it only tells you what request data we parsed from the
  88. input.
  89. If the request has a body, it will be in the `driver.body` buffer, but only as
  90. much of the body as has been piped into the driver when the `connect` event
  91. fires.
  92. ### Client-side
  93. Similarly, to implement a WebSocket client you just need to make a driver by
  94. passing in a URL. After this you use the driver API as described below to
  95. process incoming data and send outgoing data.
  96. ```js
  97. var net = require('net'),
  98. websocket = require('websocket-driver');
  99. var driver = websocket.client('ws://www.example.com/socket'),
  100. tcp = net.connect(80, 'www.example.com');
  101. tcp.pipe(driver.io).pipe(tcp);
  102. tcp.on('connect', function() {
  103. driver.start();
  104. });
  105. driver.messages.on('data', function(message) {
  106. console.log('Got a message', message);
  107. });
  108. ```
  109. Client drivers have two additional properties for reading the HTTP data that was
  110. sent back by the server:
  111. - `driver.statusCode` - the integer value of the HTTP status code
  112. - `driver.headers` - an object containing the response headers
  113. ### HTTP Proxies
  114. The client driver supports connections via HTTP proxies using the `CONNECT`
  115. method. Instead of sending the WebSocket handshake immediately, it will send a
  116. `CONNECT` request, wait for a `200` response, and then proceed as normal.
  117. To use this feature, call `driver.proxy(url)` where `url` is the origin of the
  118. proxy, including a username and password if required. This produces a duplex
  119. stream that you should pipe in and out of your TCP connection to the proxy
  120. server. When the proxy emits `connect`, you can then pipe `driver.io` to your
  121. TCP stream and call `driver.start()`.
  122. ```js
  123. var net = require('net'),
  124. websocket = require('websocket-driver');
  125. var driver = websocket.client('ws://www.example.com/socket'),
  126. proxy = driver.proxy('http://username:password@proxy.example.com'),
  127. tcp = net.connect(80, 'proxy.example.com');
  128. tcp.pipe(proxy).pipe(tcp, { end: false });
  129. tcp.on('connect', function() {
  130. proxy.start();
  131. });
  132. proxy.on('connect', function() {
  133. driver.io.pipe(tcp).pipe(driver.io);
  134. driver.start();
  135. });
  136. driver.messages.on('data', function(message) {
  137. console.log('Got a message', message);
  138. });
  139. ```
  140. The proxy's `connect` event is also where you should perform a TLS handshake on
  141. your TCP stream, if you are connecting to a `wss:` endpoint.
  142. In the event that proxy connection fails, `proxy` will emit an `error`. You can
  143. inspect the proxy's response via `proxy.statusCode` and `proxy.headers`.
  144. ```js
  145. proxy.on('error', function(error) {
  146. console.error(error.message);
  147. console.log(proxy.statusCode);
  148. console.log(proxy.headers);
  149. });
  150. ```
  151. Before calling `proxy.start()` you can set custom headers using
  152. `proxy.setHeader()`:
  153. ```js
  154. proxy.setHeader('User-Agent', 'node');
  155. proxy.start();
  156. ```
  157. ### Driver API
  158. Drivers are created using one of the following methods:
  159. ```js
  160. driver = websocket.http(request, options)
  161. driver = websocket.server(options)
  162. driver = websocket.client(url, options)
  163. ```
  164. The `http` method returns a driver chosen using the headers from a Node HTTP
  165. request object. The `server` method returns a driver that will parse an HTTP
  166. request and then decide which driver to use for it using the `http` method. The
  167. `client` method always returns a driver for the RFC version of the protocol with
  168. masking enabled on outgoing frames.
  169. The `options` argument is optional, and is an object. It may contain the
  170. following fields:
  171. - `maxLength` - the maximum allowed size of incoming message frames, in bytes.
  172. The default value is `2^26 - 1`, or 1 byte short of 64 MiB.
  173. - `protocols` - an array of strings representing acceptable subprotocols for use
  174. over the socket. The driver will negotiate one of these to use via the
  175. `Sec-WebSocket-Protocol` header if supported by the other peer.
  176. A driver has two duplex streams attached to it:
  177. - **`driver.io`** - this stream should be attached to an I/O socket like a TCP
  178. stream. Pipe incoming TCP chunks to this stream for them to be parsed, and
  179. pipe this stream back into TCP to send outgoing frames.
  180. - **`driver.messages`** - this stream emits messages received over the
  181. WebSocket. Writing to it sends messages to the other peer by emitting frames
  182. via the `driver.io` stream.
  183. All drivers respond to the following API methods, but some of them are no-ops
  184. depending on whether the client supports the behaviour.
  185. Note that most of these methods are commands: if they produce data that should
  186. be sent over the socket, they will give this to you by emitting `data` events on
  187. the `driver.io` stream.
  188. #### `driver.on('open', function(event) {})`
  189. Adds a callback to execute when the socket becomes open.
  190. #### `driver.on('message', function(event) {})`
  191. Adds a callback to execute when a message is received. `event` will have a
  192. `data` attribute containing either a string in the case of a text message or a
  193. `Buffer` in the case of a binary message.
  194. You can also listen for messages using the `driver.messages.on('data')` event,
  195. which emits strings for text messages and buffers for binary messages.
  196. #### `driver.on('error', function(event) {})`
  197. Adds a callback to execute when a protocol error occurs due to the other peer
  198. sending an invalid byte sequence. `event` will have a `message` attribute
  199. describing the error.
  200. #### `driver.on('close', function(event) {})`
  201. Adds a callback to execute when the socket becomes closed. The `event` object
  202. has `code` and `reason` attributes.
  203. #### `driver.on('ping', function(event) {})`
  204. Adds a callback block to execute when a ping is received. You do not need to
  205. handle this by sending a pong frame yourself; the driver handles this for you.
  206. #### `driver.on('pong', function(event) {})`
  207. Adds a callback block to execute when a pong is received. If this was in
  208. response to a ping you sent, you can also handle this event via the
  209. `driver.ping(message, function() { ... })` callback.
  210. #### `driver.addExtension(extension)`
  211. Registers a protocol extension whose operation will be negotiated via the
  212. `Sec-WebSocket-Extensions` header. `extension` is any extension compatible with
  213. the [websocket-extensions](https://github.com/faye/websocket-extensions-node)
  214. framework.
  215. #### `driver.setHeader(name, value)`
  216. Sets a custom header to be sent as part of the handshake response, either from
  217. the server or from the client. Must be called before `start()`, since this is
  218. when the headers are serialized and sent.
  219. #### `driver.start()`
  220. Initiates the protocol by sending the handshake - either the response for a
  221. server-side driver or the request for a client-side one. This should be the
  222. first method you invoke. Returns `true` if and only if a handshake was sent.
  223. #### `driver.parse(string)`
  224. Takes a string and parses it, potentially resulting in message events being
  225. emitted (see `on('message')` above) or in data being sent to `driver.io`. You
  226. should send all data you receive via I/O to this method by piping a stream into
  227. `driver.io`.
  228. #### `driver.text(string)`
  229. Sends a text message over the socket. If the socket handshake is not yet
  230. complete, the message will be queued until it is. Returns `true` if the message
  231. was sent or queued, and `false` if the socket can no longer send messages.
  232. This method is equivalent to `driver.messages.write(string)`.
  233. #### `driver.binary(buffer)`
  234. Takes a `Buffer` and sends it as a binary message. Will queue and return `true`
  235. or `false` the same way as the `text` method. It will also return `false` if the
  236. driver does not support binary messages.
  237. This method is equivalent to `driver.messages.write(buffer)`.
  238. #### `driver.ping(string = '', function() {})`
  239. Sends a ping frame over the socket, queueing it if necessary. `string` and the
  240. callback are both optional. If a callback is given, it will be invoked when the
  241. socket receives a pong frame whose content matches `string`. Returns `false` if
  242. frames can no longer be sent, or if the driver does not support ping/pong.
  243. #### `driver.pong(string = '')`
  244. Sends a pong frame over the socket, queueing it if necessary. `string` is
  245. optional. Returns `false` if frames can no longer be sent, or if the driver does
  246. not support ping/pong.
  247. You don't need to call this when a ping frame is received; pings are replied to
  248. automatically by the driver. This method is for sending unsolicited pongs.
  249. #### `driver.close()`
  250. Initiates the closing handshake if the socket is still open. For drivers with no
  251. closing handshake, this will result in the immediate execution of the
  252. `on('close')` driver. For drivers with a closing handshake, this sends a closing
  253. frame and `emit('close')` will execute when a response is received or a protocol
  254. error occurs.
  255. #### `driver.version`
  256. Returns the WebSocket version in use as a string. Will either be `hixie-75`,
  257. `hixie-76` or `hybi-$version`.
  258. #### `driver.protocol`
  259. Returns a string containing the selected subprotocol, if any was agreed upon
  260. using the `Sec-WebSocket-Protocol` mechanism. This value becomes available after
  261. `emit('open')` has fired.