123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784 |
- 'use strict';
- const deprecate = require('util').deprecate;
- const Logger = require('../core').Logger;
- const MongoCredentials = require('../core').MongoCredentials;
- const MongoError = require('../core').MongoError;
- const Mongos = require('../topologies/mongos');
- const NativeTopology = require('../topologies/native_topology');
- const parse = require('../core').parseConnectionString;
- const ReadConcern = require('../read_concern');
- const ReadPreference = require('../core').ReadPreference;
- const ReplSet = require('../topologies/replset');
- const Server = require('../topologies/server');
- const ServerSessionPool = require('../core').Sessions.ServerSessionPool;
- const emitDeprecationWarning = require('../utils').emitDeprecationWarning;
- const emitWarningOnce = require('../utils').emitWarningOnce;
- const fs = require('fs');
- const WriteConcern = require('../write_concern');
- const CMAP_EVENT_NAMES = require('../cmap/events').CMAP_EVENT_NAMES;
- let client;
- function loadClient() {
- if (!client) {
- client = require('../mongo_client');
- }
- return client;
- }
- const legacyParse = deprecate(
- require('../url_parser'),
- 'current URL string parser is deprecated, and will be removed in a future version. ' +
- 'To use the new parser, pass option { useNewUrlParser: true } to MongoClient.connect.'
- );
- const AUTH_MECHANISM_INTERNAL_MAP = {
- DEFAULT: 'default',
- PLAIN: 'plain',
- GSSAPI: 'gssapi',
- 'MONGODB-CR': 'mongocr',
- 'MONGODB-X509': 'x509',
- 'MONGODB-AWS': 'mongodb-aws',
- 'SCRAM-SHA-1': 'scram-sha-1',
- 'SCRAM-SHA-256': 'scram-sha-256'
- };
- const monitoringEvents = [
- 'timeout',
- 'close',
- 'serverOpening',
- 'serverDescriptionChanged',
- 'serverHeartbeatStarted',
- 'serverHeartbeatSucceeded',
- 'serverHeartbeatFailed',
- 'serverClosed',
- 'topologyOpening',
- 'topologyClosed',
- 'topologyDescriptionChanged',
- 'commandStarted',
- 'commandSucceeded',
- 'commandFailed',
- 'joined',
- 'left',
- 'ping',
- 'ha',
- 'all',
- 'fullsetup',
- 'open'
- ];
- const VALID_AUTH_MECHANISMS = new Set([
- 'DEFAULT',
- 'PLAIN',
- 'GSSAPI',
- 'MONGODB-CR',
- 'MONGODB-X509',
- 'MONGODB-AWS',
- 'SCRAM-SHA-1',
- 'SCRAM-SHA-256'
- ]);
- const validOptionNames = [
- 'poolSize',
- 'ssl',
- 'sslValidate',
- 'sslCA',
- 'sslCert',
- 'sslKey',
- 'sslPass',
- 'sslCRL',
- 'autoReconnect',
- 'noDelay',
- 'keepAlive',
- 'keepAliveInitialDelay',
- 'connectTimeoutMS',
- 'family',
- 'socketTimeoutMS',
- 'reconnectTries',
- 'reconnectInterval',
- 'ha',
- 'haInterval',
- 'replicaSet',
- 'secondaryAcceptableLatencyMS',
- 'acceptableLatencyMS',
- 'connectWithNoPrimary',
- 'authSource',
- 'w',
- 'wtimeout',
- 'j',
- 'writeConcern',
- 'forceServerObjectId',
- 'serializeFunctions',
- 'ignoreUndefined',
- 'raw',
- 'bufferMaxEntries',
- 'readPreference',
- 'pkFactory',
- 'promiseLibrary',
- 'readConcern',
- 'maxStalenessSeconds',
- 'loggerLevel',
- 'logger',
- 'promoteValues',
- 'promoteBuffers',
- 'promoteLongs',
- 'bsonRegExp',
- 'domainsEnabled',
- 'checkServerIdentity',
- 'validateOptions',
- 'appname',
- 'auth',
- 'user',
- 'password',
- 'authMechanism',
- 'compression',
- 'fsync',
- 'readPreferenceTags',
- 'numberOfRetries',
- 'auto_reconnect',
- 'minSize',
- 'monitorCommands',
- 'serverApi',
- 'retryWrites',
- 'retryReads',
- 'useNewUrlParser',
- 'useUnifiedTopology',
- 'serverSelectionTimeoutMS',
- 'useRecoveryToken',
- 'autoEncryption',
- 'driverInfo',
- 'tls',
- 'tlsInsecure',
- 'tlsinsecure',
- 'tlsAllowInvalidCertificates',
- 'tlsAllowInvalidHostnames',
- 'tlsCAFile',
- 'tlsCertificateFile',
- 'tlsCertificateKeyFile',
- 'tlsCertificateKeyFilePassword',
- 'minHeartbeatFrequencyMS',
- 'heartbeatFrequencyMS',
- 'directConnection',
- 'appName',
- // CMAP options
- 'maxPoolSize',
- 'minPoolSize',
- 'maxIdleTimeMS',
- 'waitQueueTimeoutMS'
- ];
- const ignoreOptionNames = ['native_parser'];
- const legacyOptionNames = ['server', 'replset', 'replSet', 'mongos', 'db'];
- // Validate options object
- function validOptions(options) {
- const _validOptions = validOptionNames.concat(legacyOptionNames);
- for (const name in options) {
- if (ignoreOptionNames.indexOf(name) !== -1) {
- continue;
- }
- if (_validOptions.indexOf(name) === -1) {
- if (options.validateOptions) {
- return new MongoError(`option ${name} is not supported`);
- } else {
- emitWarningOnce(`the options [${name}] is not supported`);
- }
- }
- if (legacyOptionNames.indexOf(name) !== -1) {
- emitWarningOnce(
- `the server/replset/mongos/db options are deprecated, ` +
- `all their options are supported at the top level of the options object [${validOptionNames}]`
- );
- }
- }
- }
- const LEGACY_OPTIONS_MAP = validOptionNames.reduce((obj, name) => {
- obj[name.toLowerCase()] = name;
- return obj;
- }, {});
- function addListeners(mongoClient, topology) {
- topology.on('authenticated', createListener(mongoClient, 'authenticated'));
- topology.on('error', createListener(mongoClient, 'error'));
- topology.on('timeout', createListener(mongoClient, 'timeout'));
- topology.on('close', createListener(mongoClient, 'close'));
- topology.on('parseError', createListener(mongoClient, 'parseError'));
- topology.once('open', createListener(mongoClient, 'open'));
- topology.once('fullsetup', createListener(mongoClient, 'fullsetup'));
- topology.once('all', createListener(mongoClient, 'all'));
- topology.on('reconnect', createListener(mongoClient, 'reconnect'));
- }
- function assignTopology(client, topology) {
- client.topology = topology;
- if (!(topology instanceof NativeTopology)) {
- topology.s.sessionPool = new ServerSessionPool(topology.s.coreTopology);
- }
- }
- // Clear out all events
- function clearAllEvents(topology) {
- monitoringEvents.forEach(event => topology.removeAllListeners(event));
- }
- // Collect all events in order from SDAM
- function collectEvents(mongoClient, topology) {
- let MongoClient = loadClient();
- const collectedEvents = [];
- if (mongoClient instanceof MongoClient) {
- monitoringEvents.forEach(event => {
- topology.on(event, (object1, object2) => {
- if (event === 'open') {
- collectedEvents.push({ event: event, object1: mongoClient });
- } else {
- collectedEvents.push({ event: event, object1: object1, object2: object2 });
- }
- });
- });
- }
- return collectedEvents;
- }
- function resolveTLSOptions(options) {
- if (options.tls == null) {
- return;
- }
- ['sslCA', 'sslKey', 'sslCert'].forEach(optionName => {
- if (options[optionName]) {
- options[optionName] = fs.readFileSync(options[optionName]);
- }
- });
- }
- function connect(mongoClient, url, options, callback) {
- options = Object.assign({}, options);
- // If callback is null throw an exception
- if (callback == null) {
- throw new Error('no callback function provided');
- }
- let didRequestAuthentication = false;
- const logger = Logger('MongoClient', options);
- // Did we pass in a Server/ReplSet/Mongos
- if (url instanceof Server || url instanceof ReplSet || url instanceof Mongos) {
- return connectWithUrl(mongoClient, url, options, connectCallback);
- }
- const useNewUrlParser = options.useNewUrlParser !== false;
- const parseFn = useNewUrlParser ? parse : legacyParse;
- const transform = useNewUrlParser ? transformUrlOptions : legacyTransformUrlOptions;
- parseFn(url, options, (err, _object) => {
- // Do not attempt to connect if parsing error
- if (err) return callback(err);
- // Flatten
- const object = transform(_object);
- // Parse the string
- const _finalOptions = createUnifiedOptions(object, options);
- // Check if we have connection and socket timeout set
- if (_finalOptions.socketTimeoutMS == null) _finalOptions.socketTimeoutMS = 0;
- if (_finalOptions.connectTimeoutMS == null) _finalOptions.connectTimeoutMS = 10000;
- if (_finalOptions.retryWrites == null) _finalOptions.retryWrites = true;
- if (_finalOptions.useRecoveryToken == null) _finalOptions.useRecoveryToken = true;
- if (_finalOptions.readPreference == null) _finalOptions.readPreference = 'primary';
- if (_finalOptions.db_options && _finalOptions.db_options.auth) {
- delete _finalOptions.db_options.auth;
- }
- // resolve tls options if needed
- resolveTLSOptions(_finalOptions);
- // Store the merged options object
- mongoClient.s.options = _finalOptions;
- // Apply read and write concern from parsed url
- mongoClient.s.readPreference = ReadPreference.fromOptions(_finalOptions);
- mongoClient.s.writeConcern = WriteConcern.fromOptions(_finalOptions);
- // Failure modes
- if (object.servers.length === 0) {
- return callback(new Error('connection string must contain at least one seed host'));
- }
- if (_finalOptions.auth && !_finalOptions.credentials) {
- try {
- didRequestAuthentication = true;
- _finalOptions.credentials = generateCredentials(
- mongoClient,
- _finalOptions.auth.user,
- _finalOptions.auth.password,
- _finalOptions
- );
- } catch (err) {
- return callback(err);
- }
- }
- if (_finalOptions.useUnifiedTopology) {
- return createTopology(mongoClient, 'unified', _finalOptions, connectCallback);
- }
- emitWarningOnce(
- 'Current Server Discovery and Monitoring engine is deprecated, and will be removed in a future version. To use the new Server Discover and Monitoring engine, pass option { useUnifiedTopology: true } to the MongoClient constructor.'
- );
- // Do we have a replicaset then skip discovery and go straight to connectivity
- if (_finalOptions.replicaSet || _finalOptions.rs_name) {
- return createTopology(mongoClient, 'replicaset', _finalOptions, connectCallback);
- } else if (object.servers.length > 1) {
- return createTopology(mongoClient, 'mongos', _finalOptions, connectCallback);
- } else {
- return createServer(mongoClient, _finalOptions, connectCallback);
- }
- });
- function connectCallback(err, topology) {
- const warningMessage = `seed list contains no mongos proxies, replicaset connections requires the parameter replicaSet to be supplied in the URI or options object, mongodb://server:port/db?replicaSet=name`;
- if (err && err.message === 'no mongos proxies found in seed list') {
- if (logger.isWarn()) {
- logger.warn(warningMessage);
- }
- // Return a more specific error message for MongoClient.connect
- return callback(new MongoError(warningMessage));
- }
- if (didRequestAuthentication) {
- mongoClient.emit('authenticated', null, true);
- }
- // Return the error and db instance
- callback(err, topology);
- }
- }
- function connectWithUrl(mongoClient, url, options, connectCallback) {
- // Set the topology
- assignTopology(mongoClient, url);
- // Add listeners
- addListeners(mongoClient, url);
- // Propagate the events to the client
- relayEvents(mongoClient, url);
- let finalOptions = Object.assign({}, options);
- // If we have a readPreference passed in by the db options, convert it from a string
- if (typeof options.readPreference === 'string' || typeof options.read_preference === 'string') {
- finalOptions.readPreference = new ReadPreference(
- options.readPreference || options.read_preference
- );
- }
- const isDoingAuth = finalOptions.user || finalOptions.password || finalOptions.authMechanism;
- if (isDoingAuth && !finalOptions.credentials) {
- try {
- finalOptions.credentials = generateCredentials(
- mongoClient,
- finalOptions.user,
- finalOptions.password,
- finalOptions
- );
- } catch (err) {
- return connectCallback(err, url);
- }
- }
- return url.connect(finalOptions, connectCallback);
- }
- function createListener(mongoClient, event) {
- const eventSet = new Set(['all', 'fullsetup', 'open', 'reconnect']);
- return (v1, v2) => {
- if (eventSet.has(event)) {
- return mongoClient.emit(event, mongoClient);
- }
- mongoClient.emit(event, v1, v2);
- };
- }
- function createServer(mongoClient, options, callback) {
- // Pass in the promise library
- options.promiseLibrary = mongoClient.s.promiseLibrary;
- // Set default options
- const servers = translateOptions(options);
- const server = servers[0];
- // Propagate the events to the client
- const collectedEvents = collectEvents(mongoClient, server);
- // Connect to topology
- server.connect(options, (err, topology) => {
- if (err) {
- server.close(true);
- return callback(err);
- }
- // Clear out all the collected event listeners
- clearAllEvents(server);
- // Relay all the events
- relayEvents(mongoClient, server);
- // Add listeners
- addListeners(mongoClient, server);
- // Check if we are really speaking to a mongos
- const ismaster = topology.lastIsMaster();
- // Set the topology
- assignTopology(mongoClient, topology);
- // Do we actually have a mongos
- if (ismaster && ismaster.msg === 'isdbgrid') {
- // Destroy the current connection
- topology.close();
- // Create mongos connection instead
- return createTopology(mongoClient, 'mongos', options, callback);
- }
- // Fire all the events
- replayEvents(mongoClient, collectedEvents);
- // Otherwise callback
- callback(err, topology);
- });
- }
- const DEPRECATED_UNIFIED_EVENTS = new Set([
- 'reconnect',
- 'reconnectFailed',
- 'attemptReconnect',
- 'joined',
- 'left',
- 'ping',
- 'ha',
- 'all',
- 'fullsetup',
- 'open'
- ]);
- function registerDeprecatedEventNotifiers(client) {
- client.on('newListener', eventName => {
- if (DEPRECATED_UNIFIED_EVENTS.has(eventName)) {
- emitDeprecationWarning(
- `The \`${eventName}\` event is no longer supported by the unified topology, please read more by visiting http://bit.ly/2D8WfT6`,
- 'DeprecationWarning'
- );
- }
- });
- }
- function createTopology(mongoClient, topologyType, options, callback) {
- // Pass in the promise library
- options.promiseLibrary = mongoClient.s.promiseLibrary;
- const translationOptions = {};
- if (topologyType === 'unified') translationOptions.createServers = false;
- // Set default options
- const servers = translateOptions(options, translationOptions);
- // determine CSFLE support
- if (options.autoEncryption != null) {
- const Encrypter = require('../encrypter').Encrypter;
- options.encrypter = new Encrypter(mongoClient, options);
- options.autoEncrypter = options.encrypter.autoEncrypter;
- }
- // Create the topology
- let topology;
- if (topologyType === 'mongos') {
- topology = new Mongos(servers, options);
- } else if (topologyType === 'replicaset') {
- topology = new ReplSet(servers, options);
- } else if (topologyType === 'unified') {
- topology = new NativeTopology(options.servers, options);
- registerDeprecatedEventNotifiers(mongoClient);
- }
- // Add listeners
- addListeners(mongoClient, topology);
- // Propagate the events to the client
- relayEvents(mongoClient, topology);
- // Open the connection
- assignTopology(mongoClient, topology);
- // initialize CSFLE if requested
- if (options.autoEncrypter) {
- options.autoEncrypter.init(err => {
- if (err) {
- callback(err);
- return;
- }
- topology.connect(options, err => {
- if (err) {
- topology.close(true);
- callback(err);
- return;
- }
- options.encrypter.connectInternalClient(error => {
- if (error) return callback(error);
- callback(undefined, topology);
- });
- });
- });
- return;
- }
- // otherwise connect normally
- topology.connect(options, err => {
- if (err) {
- topology.close(true);
- return callback(err);
- }
- callback(undefined, topology);
- return;
- });
- }
- function createUnifiedOptions(finalOptions, options) {
- const childOptions = [
- 'mongos',
- 'server',
- 'db',
- 'replset',
- 'db_options',
- 'server_options',
- 'rs_options',
- 'mongos_options'
- ];
- const noMerge = ['readconcern', 'compression', 'autoencryption'];
- const skip = ['w', 'wtimeout', 'j', 'journal', 'fsync', 'writeconcern'];
- for (const name in options) {
- if (skip.indexOf(name.toLowerCase()) !== -1) {
- continue;
- } else if (noMerge.indexOf(name.toLowerCase()) !== -1) {
- finalOptions[name] = options[name];
- } else if (childOptions.indexOf(name.toLowerCase()) !== -1) {
- finalOptions = mergeOptions(finalOptions, options[name], false);
- } else {
- if (
- options[name] &&
- typeof options[name] === 'object' &&
- !Buffer.isBuffer(options[name]) &&
- !Array.isArray(options[name])
- ) {
- finalOptions = mergeOptions(finalOptions, options[name], true);
- } else {
- finalOptions[name] = options[name];
- }
- }
- }
- // Handle write concern keys separately, since `options` may have the keys at the top level or
- // under `options.writeConcern`. The final merged keys will be under `finalOptions.writeConcern`.
- // This way, `fromOptions` will warn once if `options` is using deprecated write concern options
- const optionsWriteConcern = WriteConcern.fromOptions(options);
- if (optionsWriteConcern) {
- finalOptions.writeConcern = Object.assign({}, finalOptions.writeConcern, optionsWriteConcern);
- }
- return finalOptions;
- }
- function generateCredentials(client, username, password, options) {
- options = Object.assign({}, options);
- // the default db to authenticate against is 'self'
- // if authententicate is called from a retry context, it may be another one, like admin
- const source = options.authSource || options.authdb || options.dbName;
- // authMechanism
- const authMechanismRaw = options.authMechanism || 'DEFAULT';
- const authMechanism = authMechanismRaw.toUpperCase();
- const mechanismProperties = options.authMechanismProperties;
- if (!VALID_AUTH_MECHANISMS.has(authMechanism)) {
- throw MongoError.create({
- message: `authentication mechanism ${authMechanismRaw} not supported', options.authMechanism`,
- driver: true
- });
- }
- return new MongoCredentials({
- mechanism: AUTH_MECHANISM_INTERNAL_MAP[authMechanism],
- mechanismProperties,
- source,
- username,
- password
- });
- }
- function legacyTransformUrlOptions(object) {
- return mergeOptions(createUnifiedOptions({}, object), object, false);
- }
- function mergeOptions(target, source, flatten) {
- for (const name in source) {
- if (source[name] && typeof source[name] === 'object' && flatten) {
- target = mergeOptions(target, source[name], flatten);
- } else {
- target[name] = source[name];
- }
- }
- return target;
- }
- function relayEvents(mongoClient, topology) {
- const serverOrCommandEvents = [
- // APM
- 'commandStarted',
- 'commandSucceeded',
- 'commandFailed',
- // SDAM
- 'serverOpening',
- 'serverClosed',
- 'serverDescriptionChanged',
- 'serverHeartbeatStarted',
- 'serverHeartbeatSucceeded',
- 'serverHeartbeatFailed',
- 'topologyOpening',
- 'topologyClosed',
- 'topologyDescriptionChanged',
- // Legacy
- 'joined',
- 'left',
- 'ping',
- 'ha'
- ].concat(CMAP_EVENT_NAMES);
- serverOrCommandEvents.forEach(event => {
- topology.on(event, (object1, object2) => {
- mongoClient.emit(event, object1, object2);
- });
- });
- }
- //
- // Replay any events due to single server connection switching to Mongos
- //
- function replayEvents(mongoClient, events) {
- for (let i = 0; i < events.length; i++) {
- mongoClient.emit(events[i].event, events[i].object1, events[i].object2);
- }
- }
- function transformUrlOptions(_object) {
- let object = Object.assign({ servers: _object.hosts }, _object.options);
- for (let name in object) {
- const camelCaseName = LEGACY_OPTIONS_MAP[name];
- if (camelCaseName) {
- object[camelCaseName] = object[name];
- }
- }
- const hasUsername = _object.auth && _object.auth.username;
- const hasAuthMechanism = _object.options && _object.options.authMechanism;
- if (hasUsername || hasAuthMechanism) {
- object.auth = Object.assign({}, _object.auth);
- if (object.auth.db) {
- object.authSource = object.authSource || object.auth.db;
- }
- if (object.auth.username) {
- object.auth.user = object.auth.username;
- }
- }
- if (_object.defaultDatabase) {
- object.dbName = _object.defaultDatabase;
- }
- if (object.maxPoolSize) {
- object.poolSize = object.maxPoolSize;
- }
- if (object.readConcernLevel) {
- object.readConcern = new ReadConcern(object.readConcernLevel);
- }
- if (object.wTimeoutMS) {
- object.wtimeout = object.wTimeoutMS;
- object.wTimeoutMS = undefined;
- }
- if (_object.srvHost) {
- object.srvHost = _object.srvHost;
- }
- // Any write concern options from the URL will be top-level, so we manually
- // move them options under `object.writeConcern` to avoid warnings later
- const wcKeys = ['w', 'wtimeout', 'j', 'journal', 'fsync'];
- for (const key of wcKeys) {
- if (object[key] !== undefined) {
- if (object.writeConcern === undefined) object.writeConcern = {};
- object.writeConcern[key] = object[key];
- object[key] = undefined;
- }
- }
- return object;
- }
- function translateOptions(options, translationOptions) {
- translationOptions = Object.assign({}, { createServers: true }, translationOptions);
- // If we have a readPreference passed in by the db options
- if (typeof options.readPreference === 'string' || typeof options.read_preference === 'string') {
- options.readPreference = new ReadPreference(options.readPreference || options.read_preference);
- }
- // Do we have readPreference tags, add them
- if (options.readPreference && (options.readPreferenceTags || options.read_preference_tags)) {
- options.readPreference.tags = options.readPreferenceTags || options.read_preference_tags;
- }
- // Do we have maxStalenessSeconds
- if (options.maxStalenessSeconds) {
- options.readPreference.maxStalenessSeconds = options.maxStalenessSeconds;
- }
- // Set the socket and connection timeouts
- if (options.socketTimeoutMS == null) options.socketTimeoutMS = 0;
- if (options.connectTimeoutMS == null) options.connectTimeoutMS = 10000;
- if (!translationOptions.createServers) {
- return;
- }
- // Create server instances
- return options.servers.map(serverObj => {
- return serverObj.domain_socket
- ? new Server(serverObj.domain_socket, 27017, options)
- : new Server(serverObj.host, serverObj.port, options);
- });
- }
- module.exports = { validOptions, connect };
|