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; |