Connection.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505
  1. var Crypto = require('crypto');
  2. var Events = require('events');
  3. var Net = require('net');
  4. var tls = require('tls');
  5. var ConnectionConfig = require('./ConnectionConfig');
  6. var Protocol = require('./protocol/Protocol');
  7. var SqlString = require('./protocol/SqlString');
  8. var Query = require('./protocol/sequences/Query');
  9. var Util = require('util');
  10. module.exports = Connection;
  11. Util.inherits(Connection, Events.EventEmitter);
  12. function Connection(options) {
  13. Events.EventEmitter.call(this);
  14. this.config = options.config;
  15. this._socket = options.socket;
  16. this._protocol = new Protocol({config: this.config, connection: this});
  17. this._connectCalled = false;
  18. this.state = 'disconnected';
  19. this.threadId = null;
  20. }
  21. Connection.createQuery = function createQuery(sql, values, callback) {
  22. if (sql instanceof Query) {
  23. return sql;
  24. }
  25. var cb = wrapCallbackInDomain(null, callback);
  26. var options = {};
  27. if (typeof sql === 'function') {
  28. cb = wrapCallbackInDomain(null, sql);
  29. return new Query(options, cb);
  30. }
  31. if (typeof sql === 'object') {
  32. for (var prop in sql) {
  33. options[prop] = sql[prop];
  34. }
  35. if (typeof values === 'function') {
  36. cb = wrapCallbackInDomain(null, values);
  37. } else if (values !== undefined) {
  38. options.values = values;
  39. }
  40. return new Query(options, cb);
  41. }
  42. options.sql = sql;
  43. options.values = values;
  44. if (typeof values === 'function') {
  45. cb = wrapCallbackInDomain(null, values);
  46. options.values = undefined;
  47. }
  48. if (cb === undefined && callback !== undefined) {
  49. throw new TypeError('argument callback must be a function when provided');
  50. }
  51. return new Query(options, cb);
  52. };
  53. Connection.prototype.connect = function connect(options, callback) {
  54. if (!callback && typeof options === 'function') {
  55. callback = options;
  56. options = {};
  57. }
  58. if (!this._connectCalled) {
  59. this._connectCalled = true;
  60. // Connect either via a UNIX domain socket or a TCP socket.
  61. this._socket = (this.config.socketPath)
  62. ? Net.createConnection(this.config.socketPath)
  63. : Net.createConnection(this.config.port, this.config.host);
  64. // Connect socket to connection domain
  65. if (Events.usingDomains) {
  66. this._socket.domain = this.domain;
  67. }
  68. var connection = this;
  69. this._protocol.on('data', function(data) {
  70. connection._socket.write(data);
  71. });
  72. this._socket.on('data', wrapToDomain(connection, function (data) {
  73. connection._protocol.write(data);
  74. }));
  75. this._protocol.on('end', function() {
  76. connection._socket.end();
  77. });
  78. this._socket.on('end', wrapToDomain(connection, function () {
  79. connection._protocol.end();
  80. }));
  81. this._socket.on('error', this._handleNetworkError.bind(this));
  82. this._socket.on('connect', this._handleProtocolConnect.bind(this));
  83. this._protocol.on('handshake', this._handleProtocolHandshake.bind(this));
  84. this._protocol.on('unhandledError', this._handleProtocolError.bind(this));
  85. this._protocol.on('drain', this._handleProtocolDrain.bind(this));
  86. this._protocol.on('end', this._handleProtocolEnd.bind(this));
  87. this._protocol.on('enqueue', this._handleProtocolEnqueue.bind(this));
  88. if (this.config.connectTimeout) {
  89. var handleConnectTimeout = this._handleConnectTimeout.bind(this);
  90. this._socket.setTimeout(this.config.connectTimeout, handleConnectTimeout);
  91. this._socket.once('connect', function() {
  92. this.setTimeout(0, handleConnectTimeout);
  93. });
  94. }
  95. }
  96. this._protocol.handshake(options, wrapCallbackInDomain(this, callback));
  97. };
  98. Connection.prototype.changeUser = function changeUser(options, callback) {
  99. if (!callback && typeof options === 'function') {
  100. callback = options;
  101. options = {};
  102. }
  103. this._implyConnect();
  104. var charsetNumber = (options.charset)
  105. ? ConnectionConfig.getCharsetNumber(options.charset)
  106. : this.config.charsetNumber;
  107. return this._protocol.changeUser({
  108. user : options.user || this.config.user,
  109. password : options.password || this.config.password,
  110. database : options.database || this.config.database,
  111. timeout : options.timeout,
  112. charsetNumber : charsetNumber,
  113. currentConfig : this.config
  114. }, wrapCallbackInDomain(this, callback));
  115. };
  116. Connection.prototype.beginTransaction = function beginTransaction(options, callback) {
  117. if (!callback && typeof options === 'function') {
  118. callback = options;
  119. options = {};
  120. }
  121. options = options || {};
  122. options.sql = 'START TRANSACTION';
  123. options.values = null;
  124. return this.query(options, callback);
  125. };
  126. Connection.prototype.commit = function commit(options, callback) {
  127. if (!callback && typeof options === 'function') {
  128. callback = options;
  129. options = {};
  130. }
  131. options = options || {};
  132. options.sql = 'COMMIT';
  133. options.values = null;
  134. return this.query(options, callback);
  135. };
  136. Connection.prototype.rollback = function rollback(options, callback) {
  137. if (!callback && typeof options === 'function') {
  138. callback = options;
  139. options = {};
  140. }
  141. options = options || {};
  142. options.sql = 'ROLLBACK';
  143. options.values = null;
  144. return this.query(options, callback);
  145. };
  146. Connection.prototype.query = function query(sql, values, cb) {
  147. var query = Connection.createQuery(sql, values, cb);
  148. query._connection = this;
  149. if (!(typeof sql === 'object' && 'typeCast' in sql)) {
  150. query.typeCast = this.config.typeCast;
  151. }
  152. if (query.sql) {
  153. query.sql = this.format(query.sql, query.values);
  154. }
  155. if (query._callback) {
  156. query._callback = wrapCallbackInDomain(this, query._callback);
  157. }
  158. this._implyConnect();
  159. return this._protocol._enqueue(query);
  160. };
  161. Connection.prototype.ping = function ping(options, callback) {
  162. if (!callback && typeof options === 'function') {
  163. callback = options;
  164. options = {};
  165. }
  166. this._implyConnect();
  167. this._protocol.ping(options, wrapCallbackInDomain(this, callback));
  168. };
  169. Connection.prototype.statistics = function statistics(options, callback) {
  170. if (!callback && typeof options === 'function') {
  171. callback = options;
  172. options = {};
  173. }
  174. this._implyConnect();
  175. this._protocol.stats(options, wrapCallbackInDomain(this, callback));
  176. };
  177. Connection.prototype.end = function end(options, callback) {
  178. var cb = callback;
  179. var opts = options;
  180. if (!callback && typeof options === 'function') {
  181. cb = options;
  182. opts = null;
  183. }
  184. // create custom options reference
  185. opts = Object.create(opts || null);
  186. if (opts.timeout === undefined) {
  187. // default timeout of 30 seconds
  188. opts.timeout = 30000;
  189. }
  190. this._implyConnect();
  191. this._protocol.quit(opts, wrapCallbackInDomain(this, cb));
  192. };
  193. Connection.prototype.destroy = function() {
  194. this.state = 'disconnected';
  195. this._implyConnect();
  196. this._socket.destroy();
  197. this._protocol.destroy();
  198. };
  199. Connection.prototype.pause = function() {
  200. this._socket.pause();
  201. this._protocol.pause();
  202. };
  203. Connection.prototype.resume = function() {
  204. this._socket.resume();
  205. this._protocol.resume();
  206. };
  207. Connection.prototype.escape = function(value) {
  208. return SqlString.escape(value, false, this.config.timezone);
  209. };
  210. Connection.prototype.escapeId = function escapeId(value) {
  211. return SqlString.escapeId(value, false);
  212. };
  213. Connection.prototype.format = function(sql, values) {
  214. if (typeof this.config.queryFormat === 'function') {
  215. return this.config.queryFormat.call(this, sql, values, this.config.timezone);
  216. }
  217. return SqlString.format(sql, values, this.config.stringifyObjects, this.config.timezone);
  218. };
  219. if (tls.TLSSocket) {
  220. // 0.11+ environment
  221. Connection.prototype._startTLS = function _startTLS(onSecure) {
  222. var connection = this;
  223. var secureContext = tls.createSecureContext({
  224. ca : this.config.ssl.ca,
  225. cert : this.config.ssl.cert,
  226. ciphers : this.config.ssl.ciphers,
  227. key : this.config.ssl.key,
  228. passphrase : this.config.ssl.passphrase
  229. });
  230. // "unpipe"
  231. this._socket.removeAllListeners('data');
  232. this._protocol.removeAllListeners('data');
  233. // socket <-> encrypted
  234. var rejectUnauthorized = this.config.ssl.rejectUnauthorized;
  235. var secureEstablished = false;
  236. var secureSocket = new tls.TLSSocket(this._socket, {
  237. rejectUnauthorized : rejectUnauthorized,
  238. requestCert : true,
  239. secureContext : secureContext,
  240. isServer : false
  241. });
  242. // error handler for secure socket
  243. secureSocket.on('_tlsError', function(err) {
  244. if (secureEstablished) {
  245. connection._handleNetworkError(err);
  246. } else {
  247. onSecure(err);
  248. }
  249. });
  250. // cleartext <-> protocol
  251. secureSocket.pipe(this._protocol);
  252. this._protocol.on('data', function(data) {
  253. secureSocket.write(data);
  254. });
  255. secureSocket.on('secure', function() {
  256. secureEstablished = true;
  257. onSecure(rejectUnauthorized ? this.ssl.verifyError() : null);
  258. });
  259. // start TLS communications
  260. secureSocket._start();
  261. };
  262. } else {
  263. // pre-0.11 environment
  264. Connection.prototype._startTLS = function _startTLS(onSecure) {
  265. // before TLS:
  266. // _socket <-> _protocol
  267. // after:
  268. // _socket <-> securePair.encrypted <-> securePair.cleartext <-> _protocol
  269. var connection = this;
  270. var credentials = Crypto.createCredentials({
  271. ca : this.config.ssl.ca,
  272. cert : this.config.ssl.cert,
  273. ciphers : this.config.ssl.ciphers,
  274. key : this.config.ssl.key,
  275. passphrase : this.config.ssl.passphrase
  276. });
  277. var rejectUnauthorized = this.config.ssl.rejectUnauthorized;
  278. var secureEstablished = false;
  279. var securePair = tls.createSecurePair(credentials, false, true, rejectUnauthorized);
  280. // error handler for secure pair
  281. securePair.on('error', function(err) {
  282. if (secureEstablished) {
  283. connection._handleNetworkError(err);
  284. } else {
  285. onSecure(err);
  286. }
  287. });
  288. // "unpipe"
  289. this._socket.removeAllListeners('data');
  290. this._protocol.removeAllListeners('data');
  291. // socket <-> encrypted
  292. securePair.encrypted.pipe(this._socket);
  293. this._socket.on('data', function(data) {
  294. securePair.encrypted.write(data);
  295. });
  296. // cleartext <-> protocol
  297. securePair.cleartext.pipe(this._protocol);
  298. this._protocol.on('data', function(data) {
  299. securePair.cleartext.write(data);
  300. });
  301. // secure established
  302. securePair.on('secure', function() {
  303. secureEstablished = true;
  304. if (!rejectUnauthorized) {
  305. onSecure();
  306. return;
  307. }
  308. var verifyError = this.ssl.verifyError();
  309. var err = verifyError;
  310. // node.js 0.6 support
  311. if (typeof err === 'string') {
  312. err = new Error(verifyError);
  313. err.code = verifyError;
  314. }
  315. onSecure(err);
  316. });
  317. // node.js 0.8 bug
  318. securePair._cycle = securePair.cycle;
  319. securePair.cycle = function cycle() {
  320. if (this.ssl && this.ssl.error) {
  321. this.error();
  322. }
  323. return this._cycle.apply(this, arguments);
  324. };
  325. };
  326. }
  327. Connection.prototype._handleConnectTimeout = function() {
  328. if (this._socket) {
  329. this._socket.setTimeout(0);
  330. this._socket.destroy();
  331. }
  332. var err = new Error('connect ETIMEDOUT');
  333. err.errorno = 'ETIMEDOUT';
  334. err.code = 'ETIMEDOUT';
  335. err.syscall = 'connect';
  336. this._handleNetworkError(err);
  337. };
  338. Connection.prototype._handleNetworkError = function(err) {
  339. this._protocol.handleNetworkError(err);
  340. };
  341. Connection.prototype._handleProtocolError = function(err) {
  342. this.state = 'protocol_error';
  343. this.emit('error', err);
  344. };
  345. Connection.prototype._handleProtocolDrain = function() {
  346. this.emit('drain');
  347. };
  348. Connection.prototype._handleProtocolConnect = function() {
  349. this.state = 'connected';
  350. this.emit('connect');
  351. };
  352. Connection.prototype._handleProtocolHandshake = function _handleProtocolHandshake(packet) {
  353. this.state = 'authenticated';
  354. this.threadId = packet.threadId;
  355. };
  356. Connection.prototype._handleProtocolEnd = function(err) {
  357. this.state = 'disconnected';
  358. this.emit('end', err);
  359. };
  360. Connection.prototype._handleProtocolEnqueue = function _handleProtocolEnqueue(sequence) {
  361. this.emit('enqueue', sequence);
  362. };
  363. Connection.prototype._implyConnect = function() {
  364. if (!this._connectCalled) {
  365. this.connect();
  366. }
  367. };
  368. function unwrapFromDomain(fn) {
  369. return function () {
  370. var domains = [];
  371. var ret;
  372. while (process.domain) {
  373. domains.shift(process.domain);
  374. process.domain.exit();
  375. }
  376. try {
  377. ret = fn.apply(this, arguments);
  378. } finally {
  379. for (var i = 0; i < domains.length; i++) {
  380. domains[i].enter();
  381. }
  382. }
  383. return ret;
  384. };
  385. }
  386. function wrapCallbackInDomain(ee, fn) {
  387. if (typeof fn !== 'function' || fn.domain) {
  388. return fn;
  389. }
  390. var domain = process.domain;
  391. if (domain) {
  392. return domain.bind(fn);
  393. } else if (ee) {
  394. return unwrapFromDomain(wrapToDomain(ee, fn));
  395. } else {
  396. return fn;
  397. }
  398. }
  399. function wrapToDomain(ee, fn) {
  400. return function () {
  401. if (Events.usingDomains && ee.domain) {
  402. ee.domain.enter();
  403. fn.apply(this, arguments);
  404. ee.domain.exit();
  405. } else {
  406. fn.apply(this, arguments);
  407. }
  408. };
  409. }