mongo_client.js 15 KB

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