프로젝트

일반

사용자정보

통계
| 개정판:

root / HServer / 00.Server / 00.Program / node_modules / mongod / Mongod.js

이력 | 보기 | 이력해설 | 다운로드 (10.6 KB)

1
'use strict';
2

    
3
/**
4
 * Configuration options for {@link Mongod}.
5
 * @typedef {Object} Mongod~Config
6
 * @property {String} [bin=mongod]
7
 * @property {String} [config]
8
 * @property {(Number|String)} [port=27017]
9
 * @property {String} [dbpath]
10
 * @property {String} [storageEngine]
11
 * @property {Boolean} [nojournal=false]
12
 */
13

    
14
/**
15
 * Invoked when an operation (i.e. {@link Mongod#open}) completes.
16
 * @callback Mongod~callback
17
 * @argument {Error} err
18
 */
19

    
20
const childprocess = require('child_process');
21
const events = require('events');
22
const PromiseQueue = require('promise-queue');
23

    
24
/**
25
 * A collection of regualar expressions used by {@link Mongod.parseData} to
26
 * parse stdout and stderr messages.
27
 * @see Mongod.parseData
28
 * @readonly
29
 * @private
30
 * @type {Object.<String,RegExp>}
31
 */
32
const regExp = {
33
  terminalMessage: /waiting\s+for\s+connections|already\s+in\s+use|denied|error|exception|badvalue/im,
34
  whiteSpace: /\s/g,
35
  newline: /\r?\n/
36
};
37

    
38
/**
39
 * Start and stop a local MongoDB server like a boss.
40
 * @class
41
 */
42
class Mongod extends events.EventEmitter {
43

    
44
  /**
45
   * Get a function that takes chunks of stdin data, aggregates it, and passes
46
   * it in complete lines, one by one, to a given {@link Mongod~callback}.
47
   * @argument {Mongod~callback} callback
48
   * @return {Function}
49
   */
50
  static getTextLineAggregator(callback) {
51
    let buffer = '';
52

    
53
    return (data) => {
54
      const fragments = data.toString().split(regExp.newline);
55
      const lines = fragments.slice(0, fragments.length - 1);
56

    
57
      // If there was an unended line in the previous dump, complete it by
58
      // the first section.
59
      lines[0] = buffer + lines[0];
60

    
61
      // If there is an unended line in this dump, store it to be completed by
62
      // the next. This assumes there will be a terminating newline character
63
      // at some point. Generally, this is a safe assumption.
64
      buffer = fragments[fragments.length - 1];
65

    
66
      for (let line of lines) {
67
        callback(line);
68
      }
69
    };
70
  }
71

    
72
  /**
73
   * Populate a given {@link Mongod~Config} with values from a
74
   * given {@link Mongod~Config}.
75
   * @protected
76
   * @argument {Mongod~Config} source
77
   * @argument {Mongod~Config} target
78
   * @return {Mongod~Config}
79
   */
80
  static parseConfig(source, target) {
81
    if (target == null) {
82
      target = Object.create(null);
83
    }
84

    
85
    if (source == null) {
86
      return target;
87
    }
88

    
89
    if (typeof source === 'number' || typeof source === 'string') {
90
      target.port = source;
91

    
92
      return target;
93
    }
94

    
95
    if (typeof source !== 'object') {
96
      return target;
97
    }
98

    
99
    if (source.bin != null) {
100
      target.bin = source.bin;
101
    }
102

    
103
    if (source.conf != null) {
104
      target.conf = source.conf;
105

    
106
      return target;
107
    }
108

    
109
    if (source.nojournal === true) {
110
      target.nojournal = true;
111
    }
112

    
113
    if (source.storageEngine != null) {
114
      target.storageEngine = source.storageEngine;
115
    }
116

    
117
    if (source.dbpath != null) {
118
      target.dbpath = source.dbpath;
119
    }
120

    
121
    if (source.port != null) {
122
      target.port = source.port;
123
    }
124

    
125
    return target;
126
  }
127

    
128
  /**
129
   * Parse process flags for MongoDB from a given {@link Mongod~Config}.
130
   * @protected
131
   * @argument {Mongod~Config} config
132
   * @return {Array.<String>}
133
   */
134
  static parseFlags(config) {
135
    if (config.conf != null) {
136
      return ['--config', config.conf];
137
    }
138

    
139
    const flags = [];
140

    
141
    if (config.nojournal) {
142
      flags.push('--nojournal');
143
    }
144

    
145
    if (config.storageEngine != null) {
146
      flags.push('--storageEngine', config.storageEngine);
147
    }
148

    
149
    if (config.dbpath != null) {
150
      flags.push('--dbpath', config.dbpath);
151
    }
152

    
153
    if (config.port != null) {
154
      flags.push('--port', config.port);
155
    }
156

    
157
    return flags;
158
  }
159

    
160
  /**
161
   * Parse MongoDB server output for terminal messages.
162
   * @protected
163
   * @argument {String} string
164
   * @return {Object}
165
   */
166
  static parseData(string) {
167
    const matches = regExp.terminalMessage.exec(string);
168

    
169
    if (matches === null) {
170
      return null;
171
    }
172

    
173
    const result = {
174
      err: null,
175
      key: matches
176
      .pop()
177
      .replace(regExp.whiteSpace, '')
178
      .toLowerCase()
179
    };
180

    
181
    switch (result.key) {
182
      case 'waitingforconnections':
183
        break;
184

    
185
      case 'alreadyinuse':
186
        result.err = new Error('Address already in use');
187
        result.err.code = -1;
188

    
189
        break;
190

    
191
      case 'denied':
192
        result.err = new Error('Permission denied');
193
        result.err.code = -2;
194

    
195
        break;
196

    
197
      case 'error':
198
      case 'exception':
199
      case 'badvalue':
200
        result.err = new Error(string.trim());
201
        result.err.code = -3;
202

    
203
        break;
204
    }
205

    
206
    return result;
207
  }
208

    
209
  /**
210
   * Start a given {@link Mongod}.
211
   * @protected
212
   * @argument {Mongod} server
213
   * @return {Promise}
214
   */
215
  static open(server) {
216
    if (server.isOpening) {
217
      return server.openPromise;
218
    }
219

    
220
    server.isOpening = true;
221
    server.isClosing = false;
222
    server.openPromise = server.promiseQueue.add(() => {
223
      if (server.isClosing || server.isRunning) {
224
        server.isOpening = false;
225

    
226
        return Promise.resolve(null);
227
      }
228

    
229
      return new Promise((resolve, reject) => {
230
        /**
231
         * A listener for the current server process' stdout/stderr that
232
         * resolves or rejects the current {@link Promise} when done.
233
         * @see Mongod.getTextLineAggregator
234
         * @see Mongod.parseData
235
         * @argument {Buffer} buffer
236
         * @return {undefined}
237
         */
238
        const dataListener = Mongod.getTextLineAggregator((value) => {
239
          const result = Mongod.parseData(value);
240

    
241
          if (result === null) {
242
            return;
243
          }
244

    
245
          server.process.stdout.removeListener('data', dataListener);
246

    
247
          server.isOpening = false;
248

    
249
          if (result.err === null) {
250
            server.isRunning = true;
251

    
252
            server.emit('open');
253
            resolve(null);
254
          }
255
          else {
256
            server.isClosing = true;
257

    
258
            server.emit('closing');
259
            server.process.once('close', () => reject(result.err));
260
          }
261
        });
262

    
263
        /**
264
         * A listener to close the server when the current process exits.
265
         * @return {undefined}
266
         */
267
        const exitListener = () => {
268
          // istanbul ignore next
269
          server.close();
270
        };
271

    
272
        /**
273
         * Get a text line aggregator that emits a given {@linkcode event}
274
         * for the current server.
275
         * @see Mongod.getTextLineAggregator
276
         * @argument {String} event
277
         * @return {Function}
278
         */
279
        const getDataPropagator = (event) =>
280
          Mongod.getTextLineAggregator((line) => server.emit(event, line));
281

    
282
        server.emit('opening');
283

    
284
        server.process = childprocess.spawn(
285
          server.config.bin,
286
          Mongod.parseFlags(server.config)
287
        );
288

    
289
        server.process.stderr.on('data', dataListener);
290
        server.process.stderr.on('data', getDataPropagator('stdout'));
291
        server.process.stdout.on('data', dataListener);
292
        server.process.stdout.on('data', getDataPropagator('stdout'));
293
        server.process.on('close', () => {
294
          server.process = null;
295
          server.isRunning = false;
296
          server.isClosing = false;
297

    
298
          process.removeListener('exit', exitListener);
299
          server.emit('close');
300
        });
301
        process.on('exit', exitListener);
302
      });
303
    });
304

    
305
    return server.openPromise;
306
  }
307

    
308
  /**
309
   * Stop a given {@link Mongod}.
310
   * @protected
311
   * @argument {Mongod} server
312
   * @return {Promise}
313
   */
314
  static close(server) {
315
    if (server.isClosing) {
316
      return server.closePromise;
317
    }
318

    
319
    server.isClosing = true;
320
    server.isOpening = false;
321
    server.closePromise = server.promiseQueue.add(() => {
322
      if (server.isOpening || !server.isRunning) {
323
        server.isClosing = false;
324

    
325
        return Promise.resolve(null);
326
      }
327

    
328
      return new Promise((resolve) => {
329
        server.emit('closing');
330
        server.process.once('close', () => resolve(null));
331
        server.process.kill();
332
      });
333
    });
334

    
335
    return server.closePromise;
336
  }
337

    
338
  /**
339
   * Construct a new {@link Mongod}.
340
   * @argument {(Number|String|Mongod~Config)} [configOrPort]
341
   * A number or string that is a port or an object for configuration.
342
   */
343
  constructor(configOrPort) {
344
    super();
345

    
346
    /**
347
     * Configuration options.
348
     * @protected
349
     * @type {Mongod~Config}
350
     */
351
    this.config = Mongod.parseConfig(configOrPort, {
352
      bin: 'mongod',
353
      conf: null,
354
      port: 27017,
355
      dbpath: null,
356
      storageEngine: null,
357
      nojournal: false
358
    });
359

    
360
    /**
361
     * The current process.
362
     * @protected
363
     * @type {ChildProcess}
364
     */
365
    this.process = null;
366

    
367
    /**
368
     * The last {@link Promise} returned by {@link Mongod#open}.
369
     * @protected
370
     * @type {Promise}
371
     */
372
    this.openPromise = Promise.resolve(null);
373

    
374
    /**
375
     * The last {@link Promise} returned by {@link Mongod#close}.
376
     * @protected
377
     * @type {Promise}
378
     */
379
    this.closePromise = Promise.resolve(null);
380

    
381
    /**
382
     * A serial queue of open and close promises.
383
     * @protected
384
     * @type {PromiseQueue}
385
     */
386
    this.promiseQueue = new PromiseQueue(1);
387

    
388
    /**
389
     * Determine if the instance is closing a MongoDB server; {@linkcode true}
390
     * while a process is being, or about to be, killed until the
391
     * contained MongoDB server either closes or errs.
392
     * @readonly
393
     * @type {Boolean}
394
     */
395
    this.isClosing = false;
396

    
397
    /**
398
     * Determine if the instance is starting a MongoDB server; {@linkcode true}
399
     * while a process is spawning, or about tobe spawned, until the
400
     * contained MongoDB server either starts or errs.
401
     * @readonly
402
     * @type {Boolean}
403
     */
404
    this.isRunning = false;
405

    
406
    /**
407
     * Determine if the instance is running a MongoDB server; {@linkcode true}
408
     * once a process has spawned and the contained MongoDB server is ready
409
     * to service requests.
410
     * @readonly
411
     * @type {Boolean}
412
     */
413
    this.isOpening = false;
414
  }
415

    
416
  /**
417
   * Open the server.
418
   * @argument {Mongod~callback} [callback]
419
   * @return {Promise}
420
   */
421
  open(callback) {
422
    const promise = Mongod.open(this);
423

    
424
    return typeof callback === 'function'
425
    ? promise
426
      .then((v) => callback(null, v))
427
      .catch((e) => callback(e, null))
428
    : promise;
429
  }
430

    
431
  /**
432
   * Close the server.
433
   * @argument {Mongod~callback} [callback]
434
   * @return {Promise}
435
   */
436
  close(callback) {
437
    const promise = Mongod.close(this);
438

    
439
    return typeof callback === 'function'
440
    ? promise
441
      .then((v) => callback(null, v))
442
      .catch((e) => callback(e, null))
443
    : promise;
444
  }
445
}
446

    
447
module.exports = exports = Mongod;