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.

193 lines
4.5 KiB

4 years ago
  1. 'use strict';
  2. var Buffer = require('safe-buffer').Buffer,
  3. Emitter = require('events').EventEmitter,
  4. util = require('util'),
  5. streams = require('../streams'),
  6. Headers = require('./headers'),
  7. Reader = require('./stream_reader');
  8. var Base = function(request, url, options) {
  9. Emitter.call(this);
  10. Base.validateOptions(options || {}, ['maxLength', 'masking', 'requireMasking', 'protocols']);
  11. this._request = request;
  12. this._reader = new Reader();
  13. this._options = options || {};
  14. this._maxLength = this._options.maxLength || this.MAX_LENGTH;
  15. this._headers = new Headers();
  16. this.__queue = [];
  17. this.readyState = 0;
  18. this.url = url;
  19. this.io = new streams.IO(this);
  20. this.messages = new streams.Messages(this);
  21. this._bindEventListeners();
  22. };
  23. util.inherits(Base, Emitter);
  24. Base.isWebSocket = function(request) {
  25. var connection = request.headers.connection || '',
  26. upgrade = request.headers.upgrade || '';
  27. return request.method === 'GET' &&
  28. connection.toLowerCase().split(/ *, */).indexOf('upgrade') >= 0 &&
  29. upgrade.toLowerCase() === 'websocket';
  30. };
  31. Base.validateOptions = function(options, validKeys) {
  32. for (var key in options) {
  33. if (validKeys.indexOf(key) < 0)
  34. throw new Error('Unrecognized option: ' + key);
  35. }
  36. };
  37. var instance = {
  38. // This is 64MB, small enough for an average VPS to handle without
  39. // crashing from process out of memory
  40. MAX_LENGTH: 0x3ffffff,
  41. STATES: ['connecting', 'open', 'closing', 'closed'],
  42. _bindEventListeners: function() {
  43. var self = this;
  44. // Protocol errors are informational and do not have to be handled
  45. this.messages.on('error', function() {});
  46. this.on('message', function(event) {
  47. var messages = self.messages;
  48. if (messages.readable) messages.emit('data', event.data);
  49. });
  50. this.on('error', function(error) {
  51. var messages = self.messages;
  52. if (messages.readable) messages.emit('error', error);
  53. });
  54. this.on('close', function() {
  55. var messages = self.messages;
  56. if (!messages.readable) return;
  57. messages.readable = messages.writable = false;
  58. messages.emit('end');
  59. });
  60. },
  61. getState: function() {
  62. return this.STATES[this.readyState] || null;
  63. },
  64. addExtension: function(extension) {
  65. return false;
  66. },
  67. setHeader: function(name, value) {
  68. if (this.readyState > 0) return false;
  69. this._headers.set(name, value);
  70. return true;
  71. },
  72. start: function() {
  73. if (this.readyState !== 0) return false;
  74. if (!Base.isWebSocket(this._request))
  75. return this._failHandshake(new Error('Not a WebSocket request'));
  76. var response;
  77. try {
  78. response = this._handshakeResponse();
  79. } catch (error) {
  80. return this._failHandshake(error);
  81. }
  82. this._write(response);
  83. if (this._stage !== -1) this._open();
  84. return true;
  85. },
  86. _failHandshake: function(error) {
  87. var headers = new Headers();
  88. headers.set('Content-Type', 'text/plain');
  89. headers.set('Content-Length', Buffer.byteLength(error.message, 'utf8'));
  90. headers = ['HTTP/1.1 400 Bad Request', headers.toString(), error.message];
  91. this._write(Buffer.from(headers.join('\r\n'), 'utf8'));
  92. this._fail('protocol_error', error.message);
  93. return false;
  94. },
  95. text: function(message) {
  96. return this.frame(message);
  97. },
  98. binary: function(message) {
  99. return false;
  100. },
  101. ping: function() {
  102. return false;
  103. },
  104. pong: function() {
  105. return false;
  106. },
  107. close: function(reason, code) {
  108. if (this.readyState !== 1) return false;
  109. this.readyState = 3;
  110. this.emit('close', new Base.CloseEvent(null, null));
  111. return true;
  112. },
  113. _open: function() {
  114. this.readyState = 1;
  115. this.__queue.forEach(function(args) { this.frame.apply(this, args) }, this);
  116. this.__queue = [];
  117. this.emit('open', new Base.OpenEvent());
  118. },
  119. _queue: function(message) {
  120. this.__queue.push(message);
  121. return true;
  122. },
  123. _write: function(chunk) {
  124. var io = this.io;
  125. if (io.readable) io.emit('data', chunk);
  126. },
  127. _fail: function(type, message) {
  128. this.readyState = 2;
  129. this.emit('error', new Error(message));
  130. this.close();
  131. }
  132. };
  133. for (var key in instance)
  134. Base.prototype[key] = instance[key];
  135. Base.ConnectEvent = function() {};
  136. Base.OpenEvent = function() {};
  137. Base.CloseEvent = function(code, reason) {
  138. this.code = code;
  139. this.reason = reason;
  140. };
  141. Base.MessageEvent = function(data) {
  142. this.data = data;
  143. };
  144. Base.PingEvent = function(data) {
  145. this.data = data;
  146. };
  147. Base.PongEvent = function(data) {
  148. this.data = data;
  149. };
  150. module.exports = Base;