mongo_client.js 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472
  1. "use strict";
  2. var parse = require('./url_parser')
  3. , Server = require('./server')
  4. , Mongos = require('./mongos')
  5. , ReplSet = require('./replset')
  6. , Define = require('./metadata')
  7. , ReadPreference = require('./read_preference')
  8. , Db = require('./db');
  9. /**
  10. * @fileOverview The **MongoClient** class is a class that allows for making Connections to MongoDB.
  11. *
  12. * @example
  13. * var MongoClient = require('mongodb').MongoClient,
  14. * test = require('assert');
  15. * // Connection url
  16. * var url = 'mongodb://localhost:27017/test';
  17. * // Connect using MongoClient
  18. * MongoClient.connect(url, function(err, db) {
  19. * // Get an additional db
  20. * db.close();
  21. * });
  22. */
  23. /**
  24. * Creates a new MongoClient instance
  25. * @class
  26. * @return {MongoClient} a MongoClient instance.
  27. */
  28. function MongoClient() {
  29. /**
  30. * The callback format for results
  31. * @callback MongoClient~connectCallback
  32. * @param {MongoError} error An error instance representing the error during the execution.
  33. * @param {Db} db The connected database.
  34. */
  35. /**
  36. * Connect to MongoDB using a url as documented at
  37. *
  38. * docs.mongodb.org/manual/reference/connection-string/
  39. *
  40. * Note that for replicasets the replicaSet query parameter is required in the 2.0 driver
  41. *
  42. * @method
  43. * @param {string} url The connection URI string
  44. * @param {object} [options=null] Optional settings.
  45. * @param {boolean} [options.uri_decode_auth=false] Uri decode the user name and password for authentication
  46. * @param {object} [options.db=null] A hash of options to set on the db object, see **Db constructor**
  47. * @param {object} [options.server=null] A hash of options to set on the server objects, see **Server** constructor**
  48. * @param {object} [options.replSet=null] A hash of options to set on the replSet object, see **ReplSet** constructor**
  49. * @param {object} [options.mongos=null] A hash of options to set on the mongos object, see **Mongos** constructor**
  50. * @param {object} [options.promiseLibrary=null] A Promise library class the application wishes to use such as Bluebird, must be ES6 compatible
  51. * @param {MongoClient~connectCallback} [callback] The command result callback
  52. * @return {Promise} returns Promise if no callback passed
  53. */
  54. this.connect = MongoClient.connect;
  55. }
  56. var define = MongoClient.define = new Define('MongoClient', MongoClient, false);
  57. /**
  58. * Connect to MongoDB using a url as documented at
  59. *
  60. * docs.mongodb.org/manual/reference/connection-string/
  61. *
  62. * Note that for replicasets the replicaSet query parameter is required in the 2.0 driver
  63. *
  64. * @method
  65. * @static
  66. * @param {string} url The connection URI string
  67. * @param {object} [options=null] Optional settings.
  68. * @param {boolean} [options.uri_decode_auth=false] Uri decode the user name and password for authentication
  69. * @param {object} [options.db=null] A hash of options to set on the db object, see **Db constructor**
  70. * @param {object} [options.server=null] A hash of options to set on the server objects, see **Server** constructor**
  71. * @param {object} [options.replSet=null] A hash of options to set on the replSet object, see **ReplSet** constructor**
  72. * @param {object} [options.mongos=null] A hash of options to set on the mongos object, see **Mongos** constructor**
  73. * @param {object} [options.promiseLibrary=null] A Promise library class the application wishes to use such as Bluebird, must be ES6 compatible
  74. * @param {MongoClient~connectCallback} [callback] The command result callback
  75. * @return {Promise} returns Promise if no callback passed
  76. */
  77. MongoClient.connect = function(url, options, callback) {
  78. var args = Array.prototype.slice.call(arguments, 1);
  79. callback = typeof args[args.length - 1] == 'function' ? args.pop() : null;
  80. options = args.length ? args.shift() : null;
  81. options = options || {};
  82. // Get the promiseLibrary
  83. var promiseLibrary = options.promiseLibrary;
  84. // No promise library selected fall back
  85. if(!promiseLibrary) {
  86. promiseLibrary = typeof global.Promise == 'function' ?
  87. global.Promise : require('es6-promise').Promise;
  88. }
  89. // Return a promise
  90. if(typeof callback != 'function') {
  91. return new promiseLibrary(function(resolve, reject) {
  92. connect(url, options, function(err, db) {
  93. if(err) return reject(err);
  94. resolve(db);
  95. });
  96. });
  97. }
  98. // Fallback to callback based connect
  99. connect(url, options, callback);
  100. }
  101. define.staticMethod('connect', {callback: true, promise:true});
  102. var connect = function(url, options, callback) {
  103. var serverOptions = options.server || {};
  104. var mongosOptions = options.mongos || {};
  105. var replSetServersOptions = options.replSet || options.replSetServers || {};
  106. var dbOptions = options.db || {};
  107. // If callback is null throw an exception
  108. if(callback == null)
  109. throw new Error("no callback function provided");
  110. // Parse the string
  111. var object = parse(url, options);
  112. // Merge in any options for db in options object
  113. if(dbOptions) {
  114. for(var name in dbOptions) object.db_options[name] = dbOptions[name];
  115. }
  116. // Added the url to the options
  117. object.db_options.url = url;
  118. // Merge in any options for server in options object
  119. if(serverOptions) {
  120. for(var name in serverOptions) object.server_options[name] = serverOptions[name];
  121. }
  122. // Merge in any replicaset server options
  123. if(replSetServersOptions) {
  124. for(var name in replSetServersOptions) object.rs_options[name] = replSetServersOptions[name];
  125. }
  126. if(replSetServersOptions.ssl
  127. || replSetServersOptions.sslValidate
  128. || replSetServersOptions.checkServerIdentity
  129. || replSetServersOptions.sslCA
  130. || replSetServersOptions.sslCert
  131. || replSetServersOptions.sslKey
  132. || replSetServersOptions.sslPass) {
  133. object.server_options.ssl = replSetServersOptions.ssl;
  134. object.server_options.sslValidate = replSetServersOptions.sslValidate;
  135. object.server_options.checkServerIdentity = replSetServersOptions.checkServerIdentity;
  136. object.server_options.sslCA = replSetServersOptions.sslCA;
  137. object.server_options.sslCert = replSetServersOptions.sslCert;
  138. object.server_options.sslKey = replSetServersOptions.sslKey;
  139. object.server_options.sslPass = replSetServersOptions.sslPass;
  140. }
  141. // Merge in any replicaset server options
  142. if(mongosOptions) {
  143. for(var name in mongosOptions) object.mongos_options[name] = mongosOptions[name];
  144. }
  145. if(typeof object.server_options.poolSize == 'number') {
  146. if(!object.mongos_options.poolSize) object.mongos_options.poolSize = object.server_options.poolSize;
  147. if(!object.rs_options.poolSize) object.rs_options.poolSize = object.server_options.poolSize;
  148. }
  149. if(mongosOptions.ssl
  150. || mongosOptions.sslValidate
  151. || mongosOptions.checkServerIdentity
  152. || mongosOptions.sslCA
  153. || mongosOptions.sslCert
  154. || mongosOptions.sslKey
  155. || mongosOptions.sslPass) {
  156. object.server_options.ssl = mongosOptions.ssl;
  157. object.server_options.sslValidate = mongosOptions.sslValidate;
  158. object.server_options.checkServerIdentity = mongosOptions.checkServerIdentity;
  159. object.server_options.sslCA = mongosOptions.sslCA;
  160. object.server_options.sslCert = mongosOptions.sslCert;
  161. object.server_options.sslKey = mongosOptions.sslKey;
  162. object.server_options.sslPass = mongosOptions.sslPass;
  163. }
  164. // Set the promise library
  165. object.db_options.promiseLibrary = options.promiseLibrary;
  166. // We need to ensure that the list of servers are only either direct members or mongos
  167. // they cannot be a mix of monogs and mongod's
  168. var totalNumberOfServers = object.servers.length;
  169. var totalNumberOfMongosServers = 0;
  170. var totalNumberOfMongodServers = 0;
  171. var serverConfig = null;
  172. var errorServers = {};
  173. // Failure modes
  174. if(object.servers.length == 0) throw new Error("connection string must contain at least one seed host");
  175. // If we have no db setting for the native parser try to set the c++ one first
  176. object.db_options.native_parser = _setNativeParser(object.db_options);
  177. // If no auto_reconnect is set, set it to true as default for single servers
  178. if(typeof object.server_options.auto_reconnect != 'boolean') {
  179. object.server_options.auto_reconnect = true;
  180. }
  181. // If we have more than a server, it could be replicaset or mongos list
  182. // need to verify that it's one or the other and fail if it's a mix
  183. // Connect to all servers and run ismaster
  184. for(var i = 0; i < object.servers.length; i++) {
  185. // Set up socket options
  186. var providedSocketOptions = object.server_options.socketOptions || {};
  187. var _server_options = {
  188. poolSize:1
  189. , socketOptions: {
  190. connectTimeoutMS: providedSocketOptions.connectTimeoutMS || (1000 * 120)
  191. , socketTimeoutMS: providedSocketOptions.socketTimeoutMS || (1000 * 120)
  192. }
  193. , auto_reconnect:false};
  194. // Ensure we have ssl setup for the servers
  195. if(object.server_options.ssl) {
  196. _server_options.ssl = object.server_options.ssl;
  197. _server_options.sslValidate = object.server_options.sslValidate;
  198. _server_options.checkServerIdentity = object.server_options.checkServerIdentity;
  199. _server_options.sslCA = object.server_options.sslCA;
  200. _server_options.sslCert = object.server_options.sslCert;
  201. _server_options.sslKey = object.server_options.sslKey;
  202. _server_options.sslPass = object.server_options.sslPass;
  203. } else if(object.rs_options.ssl) {
  204. _server_options.ssl = object.rs_options.ssl;
  205. _server_options.sslValidate = object.rs_options.sslValidate;
  206. _server_options.checkServerIdentity = object.rs_options.checkServerIdentity;
  207. _server_options.sslCA = object.rs_options.sslCA;
  208. _server_options.sslCert = object.rs_options.sslCert;
  209. _server_options.sslKey = object.rs_options.sslKey;
  210. _server_options.sslPass = object.rs_options.sslPass;
  211. }
  212. // Error
  213. var error = null;
  214. // Set up the Server object
  215. var _server = object.servers[i].domain_socket
  216. ? new Server(object.servers[i].domain_socket, _server_options)
  217. : new Server(object.servers[i].host, object.servers[i].port, _server_options);
  218. var connectFunction = function(__server) {
  219. // Attempt connect
  220. new Db(object.dbName, __server, {w:1, native_parser:false, promiseLibrary:options.promiseLibrary}).open(function(err, db) {
  221. // Update number of servers
  222. totalNumberOfServers = totalNumberOfServers - 1;
  223. // If no error do the correct checks
  224. if(!err) {
  225. // Close the connection
  226. db.close();
  227. // Get the last ismaster document
  228. var isMasterDoc = db.serverConfig.isMasterDoc;
  229. // Check what type of server we have
  230. if(isMasterDoc.setName) {
  231. totalNumberOfMongodServers++;
  232. }
  233. if(isMasterDoc.msg && isMasterDoc.msg == "isdbgrid") totalNumberOfMongosServers++;
  234. } else {
  235. error = err;
  236. errorServers[__server.host + ":" + __server.port] = __server;
  237. }
  238. if(totalNumberOfServers == 0) {
  239. // Error out
  240. if(totalNumberOfMongodServers == 0 && totalNumberOfMongosServers == 0 && error) {
  241. return callback(error, null);
  242. }
  243. // If we have a mix of mongod and mongos, throw an error
  244. if(totalNumberOfMongosServers > 0 && totalNumberOfMongodServers > 0) {
  245. if(db) db.close();
  246. return process.nextTick(function() {
  247. try {
  248. callback(new Error("cannot combine a list of replicaset seeds and mongos seeds"));
  249. } catch (err) {
  250. throw err
  251. }
  252. })
  253. }
  254. if(totalNumberOfMongodServers == 0
  255. && totalNumberOfMongosServers == 0
  256. && object.servers.length == 1
  257. && (!object.rs_options.replicaSet || !object.rs_options.rs_name)) {
  258. var obj = object.servers[0];
  259. serverConfig = obj.domain_socket ?
  260. new Server(obj.domain_socket, object.server_options)
  261. : new Server(obj.host, obj.port, object.server_options);
  262. } else if(totalNumberOfMongodServers > 0
  263. || totalNumberOfMongosServers > 0
  264. || object.rs_options.replicaSet || object.rs_options.rs_name) {
  265. var finalServers = object.servers
  266. .filter(function(serverObj) {
  267. return errorServers[serverObj.host + ":" + serverObj.port] == null;
  268. })
  269. .map(function(serverObj) {
  270. return serverObj.domain_socket ?
  271. new Server(serverObj.domain_socket, 27017, object.server_options)
  272. : new Server(serverObj.host, serverObj.port, object.server_options);
  273. });
  274. // Clean out any error servers
  275. errorServers = {};
  276. // Set up the final configuration
  277. if(totalNumberOfMongodServers > 0) {
  278. try {
  279. // If no replicaset name was provided, we wish to perform a
  280. // direct connection
  281. if(totalNumberOfMongodServers == 1
  282. && (!object.rs_options.replicaSet && !object.rs_options.rs_name)) {
  283. serverConfig = finalServers[0];
  284. } else if(totalNumberOfMongodServers == 1) {
  285. object.rs_options.replicaSet = object.rs_options.replicaSet || object.rs_options.rs_name;
  286. serverConfig = new ReplSet(finalServers, object.rs_options);
  287. } else {
  288. serverConfig = new ReplSet(finalServers, object.rs_options);
  289. }
  290. } catch(err) {
  291. return callback(err, null);
  292. }
  293. } else {
  294. serverConfig = new Mongos(finalServers, object.mongos_options);
  295. }
  296. }
  297. if(serverConfig == null) {
  298. return process.nextTick(function() {
  299. try {
  300. callback(new Error("Could not locate any valid servers in initial seed list"));
  301. } catch (err) {
  302. if(db) db.close();
  303. throw err
  304. }
  305. });
  306. }
  307. // Ensure no firing of open event before we are ready
  308. serverConfig.emitOpen = false;
  309. // Set up all options etc and connect to the database
  310. _finishConnecting(serverConfig, object, options, callback)
  311. }
  312. });
  313. }
  314. // Wrap the context of the call
  315. connectFunction(_server);
  316. }
  317. }
  318. var _setNativeParser = function(db_options) {
  319. if(typeof db_options.native_parser == 'boolean') return db_options.native_parser;
  320. try {
  321. require('mongodb-core').BSON.BSONNative.BSON;
  322. return true;
  323. } catch(err) {
  324. return false;
  325. }
  326. }
  327. var _finishConnecting = function(serverConfig, object, options, callback) {
  328. // If we have a readPreference passed in by the db options
  329. if(typeof object.db_options.readPreference == 'string') {
  330. object.db_options.readPreference = new ReadPreference(object.db_options.readPreference);
  331. } else if(typeof object.db_options.read_preference == 'string') {
  332. object.db_options.readPreference = new ReadPreference(object.db_options.read_preference);
  333. }
  334. // Do we have readPreference tags
  335. if(object.db_options.readPreference && object.db_options.readPreferenceTags) {
  336. object.db_options.readPreference.tags = object.db_options.readPreferenceTags;
  337. } else if(object.db_options.readPreference && object.db_options.read_preference_tags) {
  338. object.db_options.readPreference.tags = object.db_options.read_preference_tags;
  339. }
  340. // Get the socketTimeoutMS
  341. var socketTimeoutMS = object.server_options.socketOptions.socketTimeoutMS || 0;
  342. // If we have a replset, override with replicaset socket timeout option if available
  343. if(serverConfig instanceof ReplSet) {
  344. socketTimeoutMS = object.rs_options.socketOptions.socketTimeoutMS || socketTimeoutMS;
  345. }
  346. // Set socketTimeout to the same as the connectTimeoutMS or 30 sec
  347. serverConfig.connectTimeoutMS = serverConfig.connectTimeoutMS || 30000;
  348. serverConfig.socketTimeoutMS = serverConfig.connectTimeoutMS;
  349. // Set up the db options
  350. var db = new Db(object.dbName, serverConfig, object.db_options);
  351. // Open the db
  352. db.open(function(err, db){
  353. if(err) {
  354. return process.nextTick(function() {
  355. try {
  356. callback(err, null);
  357. } catch (err) {
  358. if(db) db.close();
  359. throw err
  360. }
  361. });
  362. }
  363. // Reset the socket timeout
  364. serverConfig.socketTimeoutMS = socketTimeoutMS || 0;
  365. // Return object
  366. if(err == null && object.auth){
  367. // What db to authenticate against
  368. var authentication_db = db;
  369. if(object.db_options && object.db_options.authSource) {
  370. authentication_db = db.db(object.db_options.authSource);
  371. }
  372. // Build options object
  373. var options = {};
  374. if(object.db_options.authMechanism) options.authMechanism = object.db_options.authMechanism;
  375. if(object.db_options.gssapiServiceName) options.gssapiServiceName = object.db_options.gssapiServiceName;
  376. // Authenticate
  377. authentication_db.authenticate(object.auth.user, object.auth.password, options, function(err, success){
  378. if(success){
  379. process.nextTick(function() {
  380. try {
  381. callback(null, db);
  382. } catch (err) {
  383. if(db) db.close();
  384. throw err
  385. }
  386. });
  387. } else {
  388. if(db) db.close();
  389. process.nextTick(function() {
  390. try {
  391. callback(err ? err : new Error('Could not authenticate user ' + object.auth[0]), null);
  392. } catch (err) {
  393. if(db) db.close();
  394. throw err
  395. }
  396. });
  397. }
  398. });
  399. } else {
  400. process.nextTick(function() {
  401. try {
  402. callback(err, db);
  403. } catch (err) {
  404. if(db) db.close();
  405. throw err
  406. }
  407. })
  408. }
  409. });
  410. }
  411. module.exports = MongoClient