Source: lib/server.js

  1. "use strict";
  2. var EventEmitter = require('events').EventEmitter
  3. , inherits = require('util').inherits
  4. , CServer = require('mongodb-core').Server
  5. , Cursor = require('./cursor')
  6. , AggregationCursor = require('./aggregation_cursor')
  7. , CommandCursor = require('./command_cursor')
  8. , f = require('util').format
  9. , ServerCapabilities = require('./topology_base').ServerCapabilities
  10. , Store = require('./topology_base').Store
  11. , Define = require('./metadata')
  12. , MongoError = require('mongodb-core').MongoError
  13. , MAX_JS_INT = require('./utils').MAX_JS_INT
  14. , translateOptions = require('./utils').translateOptions
  15. , filterOptions = require('./utils').filterOptions
  16. , mergeOptions = require('./utils').mergeOptions
  17. , getReadPreference = require('./utils').getReadPreference
  18. , os = require('os');
  19. // Get package.json variable
  20. var driverVersion = require('../package.json').version;
  21. var nodejsversion = f('Node.js %s, %s', process.version, os.endianness());
  22. var type = os.type();
  23. var name = process.platform;
  24. var architecture = process.arch;
  25. var release = os.release();
  26. /**
  27. * @fileOverview The **Server** class is a class that represents a single server topology and is
  28. * used to construct connections.
  29. *
  30. * **Server Should not be used, use MongoClient.connect**
  31. * @example
  32. * var Db = require('mongodb').Db,
  33. * Server = require('mongodb').Server,
  34. * test = require('assert');
  35. * // Connect using single Server
  36. * var db = new Db('test', new Server('localhost', 27017););
  37. * db.open(function(err, db) {
  38. * // Get an additional db
  39. * db.close();
  40. * });
  41. */
  42. // Allowed parameters
  43. var legalOptionNames = ['ha', 'haInterval', 'acceptableLatencyMS'
  44. , 'poolSize', 'ssl', 'checkServerIdentity', 'sslValidate', 'ciphers', 'ecdhCurve'
  45. , 'sslCA', 'sslCRL', 'sslCert', 'sslKey', 'sslPass', 'socketOptions', 'bufferMaxEntries'
  46. , 'store', 'auto_reconnect', 'autoReconnect', 'emitError'
  47. , 'keepAlive', 'noDelay', 'connectTimeoutMS', 'socketTimeoutMS', 'family'
  48. , 'loggerLevel', 'logger', 'reconnectTries', 'reconnectInterval', 'monitoring'
  49. , 'appname', 'domainsEnabled'
  50. , 'servername', 'promoteLongs', 'promoteValues', 'promoteBuffers'];
  51. /**
  52. * Creates a new Server instance
  53. * @class
  54. * @deprecated
  55. * @param {string} host The host for the server, can be either an IP4, IP6 or domain socket style host.
  56. * @param {number} [port] The server port if IP4.
  57. * @param {object} [options=null] Optional settings.
  58. * @param {number} [options.poolSize=5] Number of connections in the connection pool for each server instance, set to 5 as default for legacy reasons.
  59. * @param {boolean} [options.ssl=false] Use ssl connection (needs to have a mongod server with ssl support)
  60. * @param {object} [options.sslValidate=true] Validate mongod server certificate against ca (needs to have a mongod server with ssl support, 2.4 or higher)
  61. * @param {boolean|function} [options.checkServerIdentity=true] Ensure we check server identify during SSL, set to false to disable checking. Only works for Node 0.12.x or higher. You can pass in a boolean or your own checkServerIdentity override function.
  62. * @param {array} [options.sslCA=null] Array of valid certificates either as Buffers or Strings (needs to have a mongod server with ssl support, 2.4 or higher)
  63. * @param {array} [options.sslCRL=null] Array of revocation certificates either as Buffers or Strings (needs to have a mongod server with ssl support, 2.4 or higher)
  64. * @param {(Buffer|string)} [options.sslCert=null] String or buffer containing the certificate we wish to present (needs to have a mongod server with ssl support, 2.4 or higher)
  65. * @param {(Buffer|string)} [options.sslKey=null] String or buffer containing the certificate private key we wish to present (needs to have a mongod server with ssl support, 2.4 or higher)
  66. * @param {(Buffer|string)} [options.sslPass=null] String or buffer containing the certificate password (needs to have a mongod server with ssl support, 2.4 or higher)
  67. * @param {string} [options.servername=null] String containing the server name requested via TLS SNI.
  68. * @param {boolean} [options.autoReconnect=true] Reconnect on error or timeout.
  69. * @param {object} [options.socketOptions=null] Socket options
  70. * @param {boolean} [options.socketOptions.noDelay=true] TCP Socket NoDelay option.
  71. * @param {number} [options.socketOptions.keepAlive=0] TCP KeepAlive on the socket with a X ms delay before start.
  72. * @param {number} [options.socketOptions.connectTimeoutMS=0] TCP Connection timeout setting
  73. * @param {number} [options.socketOptions.socketTimeoutMS=0] TCP Socket timeout setting
  74. * @param {number} [options.reconnectTries=30] Server attempt to reconnect #times
  75. * @param {number} [options.reconnectInterval=1000] Server will wait # milliseconds between retries
  76. * @param {number} [options.monitoring=true] Triggers the server instance to call ismaster
  77. * @param {number} [options.haInterval=10000] The interval of calling ismaster when monitoring is enabled.
  78. * @param {boolean} [options.domainsEnabled=false] Enable the wrapping of the callback in the current domain, disabled by default to avoid perf hit.
  79. * @fires Server#connect
  80. * @fires Server#close
  81. * @fires Server#error
  82. * @fires Server#timeout
  83. * @fires Server#parseError
  84. * @fires Server#reconnect
  85. * @property {string} parserType the parser type used (c++ or js).
  86. * @return {Server} a Server instance.
  87. */
  88. var Server = function(host, port, options) {
  89. options = options || {};
  90. if(!(this instanceof Server)) return new Server(host, port, options);
  91. EventEmitter.call(this);
  92. var self = this;
  93. // Filter the options
  94. options = filterOptions(options, legalOptionNames);
  95. // Stored options
  96. var storeOptions = {
  97. force: false
  98. , bufferMaxEntries: typeof options.bufferMaxEntries == 'number' ? options.bufferMaxEntries : MAX_JS_INT
  99. }
  100. // Shared global store
  101. var store = options.store || new Store(self, storeOptions);
  102. // Detect if we have a socket connection
  103. if(host.indexOf('\/') != -1) {
  104. if(port != null && typeof port == 'object') {
  105. options = port;
  106. port = null;
  107. }
  108. } else if(port == null) {
  109. throw MongoError.create({message: 'port must be specified', driver:true});
  110. }
  111. // Get the reconnect option
  112. var reconnect = typeof options.auto_reconnect == 'boolean' ? options.auto_reconnect : true;
  113. reconnect = typeof options.autoReconnect == 'boolean' ? options.autoReconnect : reconnect;
  114. // Clone options
  115. var clonedOptions = mergeOptions({}, {
  116. host: host, port: port, disconnectHandler: store,
  117. cursorFactory: Cursor,
  118. reconnect: reconnect,
  119. emitError: typeof options.emitError == 'boolean' ? options.emitError : true,
  120. size: typeof options.poolSize == 'number' ? options.poolSize : 5
  121. });
  122. // Translate any SSL options and other connectivity options
  123. clonedOptions = translateOptions(clonedOptions, options);
  124. // Socket options
  125. var socketOptions = options.socketOptions && Object.keys(options.socketOptions).length > 0
  126. ? options.socketOptions : options;
  127. // Translate all the options to the mongodb-core ones
  128. clonedOptions = translateOptions(clonedOptions, socketOptions);
  129. if(typeof clonedOptions.keepAlive == 'number') {
  130. clonedOptions.keepAliveInitialDelay = clonedOptions.keepAlive;
  131. clonedOptions.keepAlive = clonedOptions.keepAlive > 0;
  132. }
  133. // Build default client information
  134. this.clientInfo = {
  135. driver: {
  136. name: "nodejs",
  137. version: driverVersion
  138. },
  139. os: {
  140. type: type,
  141. name: name,
  142. architecture: architecture,
  143. version: release
  144. },
  145. platform: nodejsversion
  146. }
  147. // Build default client information
  148. clonedOptions.clientInfo = this.clientInfo;
  149. // Do we have an application specific string
  150. if(options.appname) {
  151. clonedOptions.clientInfo.application = { name: options.appname };
  152. }
  153. // Create an instance of a server instance from mongodb-core
  154. var server = new CServer(clonedOptions);
  155. // Define the internal properties
  156. this.s = {
  157. // Create an instance of a server instance from mongodb-core
  158. server: server
  159. // Server capabilities
  160. , sCapabilities: null
  161. // Cloned options
  162. , clonedOptions: clonedOptions
  163. // Reconnect
  164. , reconnect: clonedOptions.reconnect
  165. // Emit error
  166. , emitError: clonedOptions.emitError
  167. // Pool size
  168. , poolSize: clonedOptions.size
  169. // Store Options
  170. , storeOptions: storeOptions
  171. // Store
  172. , store: store
  173. // Host
  174. , host: host
  175. // Port
  176. , port: port
  177. // Options
  178. , options: options
  179. }
  180. }
  181. inherits(Server, EventEmitter);
  182. var define = Server.define = new Define('Server', Server, false);
  183. // BSON property
  184. Object.defineProperty(Server.prototype, 'bson', {
  185. enumerable: true, get: function() {
  186. return this.s.server.s.bson;
  187. }
  188. });
  189. // Last ismaster
  190. Object.defineProperty(Server.prototype, 'isMasterDoc', {
  191. enumerable:true, get: function() {
  192. return this.s.server.lastIsMaster();
  193. }
  194. });
  195. Object.defineProperty(Server.prototype, 'parserType', {
  196. enumerable:true, get: function() {
  197. return this.s.server.parserType;
  198. }
  199. });
  200. // Last ismaster
  201. Object.defineProperty(Server.prototype, 'poolSize', {
  202. enumerable:true, get: function() { return this.s.server.connections().length; }
  203. });
  204. Object.defineProperty(Server.prototype, 'autoReconnect', {
  205. enumerable:true, get: function() { return this.s.reconnect; }
  206. });
  207. Object.defineProperty(Server.prototype, 'host', {
  208. enumerable:true, get: function() { return this.s.host; }
  209. });
  210. Object.defineProperty(Server.prototype, 'port', {
  211. enumerable:true, get: function() { return this.s.port; }
  212. });
  213. // Connect
  214. Server.prototype.connect = function(db, _options, callback) {
  215. var self = this;
  216. if('function' === typeof _options) callback = _options, _options = {};
  217. if(_options == null) _options = {};
  218. if(!('function' === typeof callback)) callback = null;
  219. self.s.options = _options;
  220. // Update bufferMaxEntries
  221. self.s.storeOptions.bufferMaxEntries = db.bufferMaxEntries;
  222. // Error handler
  223. var connectErrorHandler = function() {
  224. return function(err) {
  225. // Remove all event handlers
  226. var events = ['timeout', 'error', 'close'];
  227. events.forEach(function(e) {
  228. self.s.server.removeListener(e, connectHandlers[e]);
  229. });
  230. self.s.server.removeListener('connect', connectErrorHandler);
  231. // Try to callback
  232. try {
  233. callback(err);
  234. } catch(err) {
  235. process.nextTick(function() { throw err; })
  236. }
  237. }
  238. }
  239. // Actual handler
  240. var errorHandler = function(event) {
  241. return function(err) {
  242. if(event != 'error') {
  243. self.emit(event, err);
  244. }
  245. }
  246. }
  247. // Error handler
  248. var reconnectHandler = function() {
  249. self.emit('reconnect', self);
  250. self.s.store.execute();
  251. }
  252. // Reconnect failed
  253. var reconnectFailedHandler = function(err) {
  254. self.emit('reconnectFailed', err);
  255. self.s.store.flush(err);
  256. }
  257. // Destroy called on topology, perform cleanup
  258. var destroyHandler = function() {
  259. self.s.store.flush();
  260. }
  261. // relay the event
  262. var relay = function(event) {
  263. return function(t, server) {
  264. self.emit(event, t, server);
  265. }
  266. }
  267. // Connect handler
  268. var connectHandler = function() {
  269. // Clear out all the current handlers left over
  270. ["timeout", "error", "close", 'destroy'].forEach(function(e) {
  271. self.s.server.removeAllListeners(e);
  272. });
  273. // Set up listeners
  274. self.s.server.on('timeout', errorHandler('timeout'));
  275. self.s.server.once('error', errorHandler('error'));
  276. self.s.server.on('close', errorHandler('close'));
  277. // Only called on destroy
  278. self.s.server.on('destroy', destroyHandler);
  279. // Emit open event
  280. self.emit('open', null, self);
  281. // Return correctly
  282. try {
  283. callback(null, self);
  284. } catch(err) {
  285. console.log(err.stack)
  286. process.nextTick(function() { throw err; })
  287. }
  288. }
  289. // Set up listeners
  290. var connectHandlers = {
  291. timeout: connectErrorHandler('timeout'),
  292. error: connectErrorHandler('error'),
  293. close: connectErrorHandler('close')
  294. };
  295. // Clear out all the current handlers left over
  296. ["timeout", "error", "close", 'serverOpening', 'serverDescriptionChanged', 'serverHeartbeatStarted',
  297. 'serverHeartbeatSucceeded', 'serverHeartbeatFailed', 'serverClosed', 'topologyOpening',
  298. 'topologyClosed', 'topologyDescriptionChanged'].forEach(function(e) {
  299. self.s.server.removeAllListeners(e);
  300. });
  301. // Add the event handlers
  302. self.s.server.once('timeout', connectHandlers.timeout);
  303. self.s.server.once('error', connectHandlers.error);
  304. self.s.server.once('close', connectHandlers.close);
  305. self.s.server.once('connect', connectHandler);
  306. // Reconnect server
  307. self.s.server.on('reconnect', reconnectHandler);
  308. self.s.server.on('reconnectFailed', reconnectFailedHandler);
  309. // Set up SDAM listeners
  310. self.s.server.on('serverDescriptionChanged', relay('serverDescriptionChanged'));
  311. self.s.server.on('serverHeartbeatStarted', relay('serverHeartbeatStarted'));
  312. self.s.server.on('serverHeartbeatSucceeded', relay('serverHeartbeatSucceeded'));
  313. self.s.server.on('serverHeartbeatFailed', relay('serverHeartbeatFailed'));
  314. self.s.server.on('serverOpening', relay('serverOpening'));
  315. self.s.server.on('serverClosed', relay('serverClosed'));
  316. self.s.server.on('topologyOpening', relay('topologyOpening'));
  317. self.s.server.on('topologyClosed', relay('topologyClosed'));
  318. self.s.server.on('topologyDescriptionChanged', relay('topologyDescriptionChanged'));
  319. self.s.server.on('attemptReconnect', relay('attemptReconnect'));
  320. self.s.server.on('monitoring', relay('monitoring'));
  321. // Start connection
  322. self.s.server.connect(_options);
  323. }
  324. // Server capabilities
  325. Server.prototype.capabilities = function() {
  326. if(this.s.sCapabilities) return this.s.sCapabilities;
  327. if(this.s.server.lastIsMaster() == null) return null;
  328. this.s.sCapabilities = new ServerCapabilities(this.s.server.lastIsMaster());
  329. return this.s.sCapabilities;
  330. }
  331. define.classMethod('capabilities', {callback: false, promise:false, returns: [ServerCapabilities]});
  332. // Command
  333. Server.prototype.command = function(ns, cmd, options, callback) {
  334. this.s.server.command(ns, cmd, getReadPreference(options), callback);
  335. }
  336. define.classMethod('command', {callback: true, promise:false});
  337. // Insert
  338. Server.prototype.insert = function(ns, ops, options, callback) {
  339. this.s.server.insert(ns, ops, options, callback);
  340. }
  341. define.classMethod('insert', {callback: true, promise:false});
  342. // Update
  343. Server.prototype.update = function(ns, ops, options, callback) {
  344. this.s.server.update(ns, ops, options, callback);
  345. }
  346. define.classMethod('update', {callback: true, promise:false});
  347. // Remove
  348. Server.prototype.remove = function(ns, ops, options, callback) {
  349. this.s.server.remove(ns, ops, options, callback);
  350. }
  351. define.classMethod('remove', {callback: true, promise:false});
  352. // IsConnected
  353. Server.prototype.isConnected = function() {
  354. return this.s.server.isConnected();
  355. }
  356. Server.prototype.isDestroyed = function() {
  357. return this.s.server.isDestroyed();
  358. }
  359. define.classMethod('isConnected', {callback: false, promise:false, returns: [Boolean]});
  360. // Insert
  361. Server.prototype.cursor = function(ns, cmd, options) {
  362. options.disconnectHandler = this.s.store;
  363. return this.s.server.cursor(ns, cmd, options);
  364. }
  365. define.classMethod('cursor', {callback: false, promise:false, returns: [Cursor, AggregationCursor, CommandCursor]});
  366. Server.prototype.lastIsMaster = function() {
  367. return this.s.server.lastIsMaster();
  368. }
  369. /**
  370. * Unref all sockets
  371. * @method
  372. */
  373. Server.prototype.unref = function() {
  374. this.s.server.unref();
  375. }
  376. Server.prototype.close = function(forceClosed) {
  377. this.s.server.destroy();
  378. // We need to wash out all stored processes
  379. if(forceClosed == true) {
  380. this.s.storeOptions.force = forceClosed;
  381. this.s.store.flush();
  382. }
  383. }
  384. define.classMethod('close', {callback: false, promise:false});
  385. Server.prototype.auth = function() {
  386. var args = Array.prototype.slice.call(arguments, 0);
  387. this.s.server.auth.apply(this.s.server, args);
  388. }
  389. define.classMethod('auth', {callback: true, promise:false});
  390. Server.prototype.logout = function() {
  391. var args = Array.prototype.slice.call(arguments, 0);
  392. this.s.server.logout.apply(this.s.server, args);
  393. }
  394. define.classMethod('logout', {callback: true, promise:false});
  395. /**
  396. * All raw connections
  397. * @method
  398. * @return {array}
  399. */
  400. Server.prototype.connections = function() {
  401. return this.s.server.connections();
  402. }
  403. define.classMethod('connections', {callback: false, promise:false, returns:[Array]});
  404. /**
  405. * Server connect event
  406. *
  407. * @event Server#connect
  408. * @type {object}
  409. */
  410. /**
  411. * Server close event
  412. *
  413. * @event Server#close
  414. * @type {object}
  415. */
  416. /**
  417. * Server reconnect event
  418. *
  419. * @event Server#reconnect
  420. * @type {object}
  421. */
  422. /**
  423. * Server error event
  424. *
  425. * @event Server#error
  426. * @type {MongoError}
  427. */
  428. /**
  429. * Server timeout event
  430. *
  431. * @event Server#timeout
  432. * @type {object}
  433. */
  434. /**
  435. * Server parseError event
  436. *
  437. * @event Server#parseError
  438. * @type {object}
  439. */
  440. module.exports = Server;