@depup/hapi 18.1.0-depup.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.npmignore +4 -0
- package/LICENSE +26 -0
- package/README.md +47 -0
- package/lib/auth.js +545 -0
- package/lib/compression.js +107 -0
- package/lib/config.js +436 -0
- package/lib/core.js +680 -0
- package/lib/cors.js +207 -0
- package/lib/ext.js +94 -0
- package/lib/handler.js +165 -0
- package/lib/headers.js +200 -0
- package/lib/index.js +11 -0
- package/lib/methods.js +123 -0
- package/lib/request.js +623 -0
- package/lib/response.js +730 -0
- package/lib/route.js +519 -0
- package/lib/security.js +84 -0
- package/lib/server.js +557 -0
- package/lib/streams.js +37 -0
- package/lib/toolkit.js +201 -0
- package/lib/transmit.js +369 -0
- package/lib/validation.js +198 -0
- package/npm-shrinkwrap.json +2456 -0
- package/package.json +132 -0
package/lib/core.js
ADDED
|
@@ -0,0 +1,680 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const Http = require('http');
|
|
4
|
+
const Https = require('https');
|
|
5
|
+
const Os = require('os');
|
|
6
|
+
const Path = require('path');
|
|
7
|
+
|
|
8
|
+
const Boom = require('boom');
|
|
9
|
+
const Bounce = require('bounce');
|
|
10
|
+
const Call = require('call');
|
|
11
|
+
const Catbox = require('catbox');
|
|
12
|
+
const CatboxMemory = require('catbox-memory');
|
|
13
|
+
const Heavy = require('heavy');
|
|
14
|
+
const Hoek = require('hoek');
|
|
15
|
+
const Mimos = require('mimos');
|
|
16
|
+
const Podium = require('podium');
|
|
17
|
+
const Somever = require('somever');
|
|
18
|
+
const Statehood = require('statehood');
|
|
19
|
+
|
|
20
|
+
const Auth = require('./auth');
|
|
21
|
+
const Compression = require('./compression');
|
|
22
|
+
const Config = require('./config');
|
|
23
|
+
const Cors = require('./cors');
|
|
24
|
+
const Ext = require('./ext');
|
|
25
|
+
const Methods = require('./methods');
|
|
26
|
+
const Request = require('./request');
|
|
27
|
+
const Route = require('./route');
|
|
28
|
+
const Toolkit = require('./toolkit');
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
const internals = {
|
|
32
|
+
counter: {
|
|
33
|
+
min: 10000,
|
|
34
|
+
max: 99999
|
|
35
|
+
},
|
|
36
|
+
events: [
|
|
37
|
+
{ name: 'log', channels: ['app', 'internal'], tags: true },
|
|
38
|
+
{ name: 'request', channels: ['app', 'internal', 'error'], tags: true, spread: true },
|
|
39
|
+
'response',
|
|
40
|
+
'route',
|
|
41
|
+
'start',
|
|
42
|
+
'stop'
|
|
43
|
+
],
|
|
44
|
+
badRequestResponse: Buffer.from('HTTP/1.1 400 Bad Request\r\n\r\n', 'ascii')
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
exports = module.exports = internals.Core = class {
|
|
49
|
+
|
|
50
|
+
constructor(options) {
|
|
51
|
+
|
|
52
|
+
this.root = null; // Dispatch reference of the root server
|
|
53
|
+
|
|
54
|
+
const { settings, type } = internals.setup(options);
|
|
55
|
+
|
|
56
|
+
this.settings = settings;
|
|
57
|
+
this.type = type;
|
|
58
|
+
|
|
59
|
+
this.app = {};
|
|
60
|
+
this.auth = new Auth(this);
|
|
61
|
+
this.caches = new Map(); // Cache clients
|
|
62
|
+
this.compression = new Compression();
|
|
63
|
+
this.controlled = null; // Other servers linked to the phases of this server
|
|
64
|
+
this.decorations = { handler: [], request: [], server: [], toolkit: [] }; // Public decoration names
|
|
65
|
+
this.dependencies = []; // Plugin dependencies
|
|
66
|
+
this.events = new Podium(internals.events);
|
|
67
|
+
this.heavy = new Heavy(this.settings.load);
|
|
68
|
+
this.instances = new Set();
|
|
69
|
+
this.methods = new Methods(this); // Server methods
|
|
70
|
+
this.mime = new Mimos(this.settings.mime);
|
|
71
|
+
this.onConnection = null; // Used to remove event listener on stop
|
|
72
|
+
this.plugins = {}; // Exposed plugin properties by name
|
|
73
|
+
this.queue = new internals.Queue(this.settings.load);
|
|
74
|
+
this.registrations = {}; // Tracks plugin for dependency validation { name -> { version } }
|
|
75
|
+
this.registring = 0; // > 0 while register() is waiting for plugin callbacks
|
|
76
|
+
this.requestCounter = { value: internals.counter.min, min: internals.counter.min, max: internals.counter.max };
|
|
77
|
+
this.router = new Call.Router(this.settings.router);
|
|
78
|
+
this.phase = 'stopped'; // 'stopped', 'initializing', 'initialized', 'starting', 'started', 'stopping', 'invalid'
|
|
79
|
+
this.sockets = null; // Track open sockets for graceful shutdown
|
|
80
|
+
this.started = false;
|
|
81
|
+
this.states = new Statehood.Definitions(this.settings.state);
|
|
82
|
+
this.toolkit = new Toolkit();
|
|
83
|
+
|
|
84
|
+
this.extensionsSeq = 0; // Used to keep absolute order of extensions based on the order added across locations
|
|
85
|
+
this.extensions = {
|
|
86
|
+
server: {
|
|
87
|
+
onPreStart: new Ext('onPreStart', this),
|
|
88
|
+
onPostStart: new Ext('onPostStart', this),
|
|
89
|
+
onPreStop: new Ext('onPreStop', this),
|
|
90
|
+
onPostStop: new Ext('onPostStop', this)
|
|
91
|
+
},
|
|
92
|
+
route: {
|
|
93
|
+
onRequest: new Ext('onRequest', this),
|
|
94
|
+
onPreAuth: new Ext('onPreAuth', this),
|
|
95
|
+
onCredentials: new Ext('onCredentials', this),
|
|
96
|
+
onPostAuth: new Ext('onPostAuth', this),
|
|
97
|
+
onPreHandler: new Ext('onPreHandler', this),
|
|
98
|
+
onPostHandler: new Ext('onPostHandler', this),
|
|
99
|
+
onPreResponse: new Ext('onPreResponse', this)
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
this.Request = class extends Request { };
|
|
104
|
+
|
|
105
|
+
this._debug();
|
|
106
|
+
this._decorations = { handler: {}, request: {}, server: {}, toolkit: {}, requestApply: null };
|
|
107
|
+
this._initializeCache();
|
|
108
|
+
|
|
109
|
+
this.listener = this._createListener();
|
|
110
|
+
this._initializeListener();
|
|
111
|
+
this.info = this._info();
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
_debug() {
|
|
115
|
+
|
|
116
|
+
// Subscribe to server log events
|
|
117
|
+
|
|
118
|
+
if (this.settings.debug) {
|
|
119
|
+
const debug = (request, event) => {
|
|
120
|
+
|
|
121
|
+
const data = event.error || event.data;
|
|
122
|
+
console.error('Debug:', event.tags.join(', '), (data ? '\n ' + (data.stack || (typeof data === 'object' ? Hoek.stringify(data) : data)) : ''));
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
if (this.settings.debug.log) {
|
|
126
|
+
const filter = this.settings.debug.log.some((tag) => tag === '*') ? undefined : this.settings.debug.log;
|
|
127
|
+
this.events.on({ name: 'log', filter }, (event) => debug(null, event));
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (this.settings.debug.request) {
|
|
131
|
+
const filter = this.settings.debug.request.some((tag) => tag === '*') ? undefined : this.settings.debug.request;
|
|
132
|
+
this.events.on({ name: 'request', filter }, debug);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
_initializeCache() {
|
|
138
|
+
|
|
139
|
+
if (this.settings.cache) {
|
|
140
|
+
this._createCache(this.settings.cache);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (!this.caches.has('_default')) {
|
|
144
|
+
this._createCache([{ provider: CatboxMemory }]); // Defaults to memory-based
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
_info() {
|
|
149
|
+
|
|
150
|
+
const now = Date.now();
|
|
151
|
+
const protocol = this.type === 'tcp' ? (this.settings.tls ? 'https' : 'http') : this.type;
|
|
152
|
+
const host = this.settings.host || Os.hostname() || 'localhost';
|
|
153
|
+
const port = this.settings.port;
|
|
154
|
+
|
|
155
|
+
const info = {
|
|
156
|
+
created: now,
|
|
157
|
+
started: 0,
|
|
158
|
+
host,
|
|
159
|
+
port,
|
|
160
|
+
protocol,
|
|
161
|
+
id: Os.hostname() + ':' + process.pid + ':' + now.toString(36),
|
|
162
|
+
uri: this.settings.uri || (protocol + ':' + (this.type === 'tcp' ? '//' + host + (port ? ':' + port : '') : port))
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
return info;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
_createCache(configs) {
|
|
169
|
+
|
|
170
|
+
Hoek.assert(this.phase !== 'initializing', 'Cannot provision server cache while server is initializing');
|
|
171
|
+
|
|
172
|
+
configs = Config.apply('cache', configs);
|
|
173
|
+
|
|
174
|
+
const added = [];
|
|
175
|
+
for (let config of configs) {
|
|
176
|
+
|
|
177
|
+
// <function>
|
|
178
|
+
// { provider: <function> }
|
|
179
|
+
// { provider: { constructor: <function>, options } }
|
|
180
|
+
// { engine }
|
|
181
|
+
|
|
182
|
+
if (typeof config === 'function') {
|
|
183
|
+
config = { provider: { constructor: config } };
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const name = config.name || '_default';
|
|
187
|
+
Hoek.assert(!this.caches.has(name), 'Cannot configure the same cache more than once: ', name === '_default' ? 'default cache' : name);
|
|
188
|
+
|
|
189
|
+
let client = null;
|
|
190
|
+
|
|
191
|
+
if (config.provider) {
|
|
192
|
+
let provider = config.provider;
|
|
193
|
+
if (typeof provider === 'function') {
|
|
194
|
+
provider = { constructor: provider };
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
client = new Catbox.Client(provider.constructor, provider.options || { partition: 'hapi-cache' });
|
|
198
|
+
}
|
|
199
|
+
else {
|
|
200
|
+
client = new Catbox.Client(config.engine);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
this.caches.set(name, { client, segments: {}, shared: config.shared || false });
|
|
204
|
+
added.push(client);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
return added;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
registerServer(server) {
|
|
211
|
+
|
|
212
|
+
if (!this.root) {
|
|
213
|
+
this.root = server;
|
|
214
|
+
this._defaultRoutes();
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
this.instances.add(server);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
async _start() {
|
|
221
|
+
|
|
222
|
+
if (this.phase === 'initialized' ||
|
|
223
|
+
this.phase === 'started') {
|
|
224
|
+
|
|
225
|
+
this._validateDeps();
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
if (this.phase === 'started') {
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
if (this.phase !== 'stopped' &&
|
|
233
|
+
this.phase !== 'initialized') {
|
|
234
|
+
|
|
235
|
+
throw new Error('Cannot start server while it is in ' + this.phase + ' phase');
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
if (this.phase !== 'initialized') {
|
|
239
|
+
await this._initialize();
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
this.phase = 'starting';
|
|
243
|
+
this.started = true;
|
|
244
|
+
this.info.started = Date.now();
|
|
245
|
+
|
|
246
|
+
try {
|
|
247
|
+
await this._listen();
|
|
248
|
+
}
|
|
249
|
+
catch (err) {
|
|
250
|
+
this.started = false;
|
|
251
|
+
this.phase = 'invalid';
|
|
252
|
+
throw err;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
this.phase = 'started';
|
|
256
|
+
await this.events.emit('start');
|
|
257
|
+
|
|
258
|
+
try {
|
|
259
|
+
if (this.controlled) {
|
|
260
|
+
await Promise.all(this.controlled.map((control) => control.start()));
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
await this._invoke('onPostStart');
|
|
264
|
+
}
|
|
265
|
+
catch (err) {
|
|
266
|
+
this.phase = 'invalid';
|
|
267
|
+
throw err;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
_listen() {
|
|
272
|
+
|
|
273
|
+
return new Promise((resolve, reject) => {
|
|
274
|
+
|
|
275
|
+
if (!this.settings.autoListen) {
|
|
276
|
+
resolve();
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
const onError = (err) => {
|
|
281
|
+
|
|
282
|
+
reject(err);
|
|
283
|
+
return;
|
|
284
|
+
};
|
|
285
|
+
|
|
286
|
+
this.listener.once('error', onError);
|
|
287
|
+
|
|
288
|
+
const finalize = () => {
|
|
289
|
+
|
|
290
|
+
this.listener.removeListener('error', onError);
|
|
291
|
+
resolve();
|
|
292
|
+
return;
|
|
293
|
+
};
|
|
294
|
+
|
|
295
|
+
if (this.type !== 'tcp') {
|
|
296
|
+
this.listener.listen(this.settings.port, finalize);
|
|
297
|
+
}
|
|
298
|
+
else {
|
|
299
|
+
const address = this.settings.address || this.settings.host || '0.0.0.0';
|
|
300
|
+
this.listener.listen(this.settings.port, address, finalize);
|
|
301
|
+
}
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
async _initialize() {
|
|
306
|
+
|
|
307
|
+
if (this.registring) {
|
|
308
|
+
throw new Error('Cannot start server before plugins finished registration');
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
if (this.phase === 'initialized') {
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
if (this.phase !== 'stopped') {
|
|
316
|
+
throw new Error('Cannot initialize server while it is in ' + this.phase + ' phase');
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
this._validateDeps();
|
|
320
|
+
this.phase = 'initializing';
|
|
321
|
+
|
|
322
|
+
// Start cache
|
|
323
|
+
|
|
324
|
+
try {
|
|
325
|
+
const caches = [];
|
|
326
|
+
this.caches.forEach((cache) => caches.push(cache.client.start()));
|
|
327
|
+
await Promise.all(caches);
|
|
328
|
+
await this._invoke('onPreStart');
|
|
329
|
+
this.heavy.start();
|
|
330
|
+
this.phase = 'initialized';
|
|
331
|
+
|
|
332
|
+
if (this.controlled) {
|
|
333
|
+
await Promise.all(this.controlled.map((control) => control.initialize()));
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
catch (err) {
|
|
337
|
+
this.phase = 'invalid';
|
|
338
|
+
throw err;
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
_validateDeps() {
|
|
343
|
+
|
|
344
|
+
for (const { deps, plugin } of this.dependencies) {
|
|
345
|
+
for (const dep in deps) {
|
|
346
|
+
const version = deps[dep];
|
|
347
|
+
Hoek.assert(this.registrations[dep], 'Plugin', plugin, 'missing dependency', dep);
|
|
348
|
+
Hoek.assert(version === '*' || Somever.match(this.registrations[dep].version, version), 'Plugin', plugin, 'requires', dep, 'version', version, 'but found', this.registrations[dep].version);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
async _stop(options = {}) {
|
|
354
|
+
|
|
355
|
+
options.timeout = options.timeout || 5000; // Default timeout to 5 seconds
|
|
356
|
+
|
|
357
|
+
if (['stopped', 'initialized', 'started', 'invalid'].indexOf(this.phase) === -1) {
|
|
358
|
+
throw new Error('Cannot stop server while in ' + this.phase + ' phase');
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
this.phase = 'stopping';
|
|
362
|
+
|
|
363
|
+
try {
|
|
364
|
+
await this._invoke('onPreStop');
|
|
365
|
+
|
|
366
|
+
if (this.started) {
|
|
367
|
+
this.started = false;
|
|
368
|
+
this.info.started = 0;
|
|
369
|
+
|
|
370
|
+
await this._unlisten(options);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
const caches = [];
|
|
374
|
+
this.caches.forEach((cache) => caches.push(cache.client.stop()));
|
|
375
|
+
await Promise.all(caches);
|
|
376
|
+
|
|
377
|
+
await this.events.emit('stop');
|
|
378
|
+
this.heavy.stop();
|
|
379
|
+
|
|
380
|
+
if (this.controlled) {
|
|
381
|
+
await Promise.all(this.controlled.map((control) => control.stop(options)));
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
await this._invoke('onPostStop');
|
|
385
|
+
this.phase = 'stopped';
|
|
386
|
+
}
|
|
387
|
+
catch (err) {
|
|
388
|
+
this.phase = 'invalid';
|
|
389
|
+
throw err;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
_unlisten(options) {
|
|
394
|
+
|
|
395
|
+
let timeoutId = null;
|
|
396
|
+
if (this.settings.operations.cleanStop) {
|
|
397
|
+
|
|
398
|
+
// Set connections timeout
|
|
399
|
+
|
|
400
|
+
const timeout = () => {
|
|
401
|
+
|
|
402
|
+
this.sockets.forEach((connection) => connection.destroy());
|
|
403
|
+
this.sockets.clear();
|
|
404
|
+
};
|
|
405
|
+
|
|
406
|
+
timeoutId = setTimeout(timeout, options.timeout);
|
|
407
|
+
|
|
408
|
+
// Tell idle keep-alive connections to close
|
|
409
|
+
|
|
410
|
+
this.sockets.forEach((connection) => {
|
|
411
|
+
|
|
412
|
+
if (!connection._isHapiProcessing) {
|
|
413
|
+
connection.end();
|
|
414
|
+
}
|
|
415
|
+
});
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
// Close connection
|
|
419
|
+
|
|
420
|
+
return new Promise((resolve) => {
|
|
421
|
+
|
|
422
|
+
this.listener.close(() => {
|
|
423
|
+
|
|
424
|
+
if (this.settings.operations.cleanStop) {
|
|
425
|
+
this.listener.removeListener(this.settings.tls ? 'secureConnection' : 'connection', this.onConnection);
|
|
426
|
+
clearTimeout(timeoutId);
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
this._initializeListener();
|
|
430
|
+
resolve();
|
|
431
|
+
});
|
|
432
|
+
});
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
async _invoke(type) {
|
|
436
|
+
|
|
437
|
+
const exts = this.extensions.server[type];
|
|
438
|
+
if (!exts.nodes) {
|
|
439
|
+
return;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
for (const ext of exts.nodes) {
|
|
443
|
+
const bind = (ext.bind || ext.realm.settings.bind);
|
|
444
|
+
await ext.func.call(bind, ext.server, bind);
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
_defaultRoutes() {
|
|
449
|
+
|
|
450
|
+
this.router.special('notFound', new Route({ method: '_special', path: '/{p*}', handler: internals.notFound }, this.root, { special: true }));
|
|
451
|
+
this.router.special('badRequest', new Route({ method: '_special', path: '/{p*}', handler: internals.badRequest }, this.root, { special: true }));
|
|
452
|
+
|
|
453
|
+
if (this.settings.routes.cors) {
|
|
454
|
+
Cors.handler(this.root);
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
_dispatch(options = {}) {
|
|
459
|
+
|
|
460
|
+
return (req, res) => {
|
|
461
|
+
|
|
462
|
+
// Track socket request processing state
|
|
463
|
+
|
|
464
|
+
if (req.socket) {
|
|
465
|
+
req.socket._isHapiProcessing = true;
|
|
466
|
+
const env = { core: this, req };
|
|
467
|
+
res.on('finish', internals.onFinish.bind(res, env));
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
// Create request
|
|
471
|
+
|
|
472
|
+
const request = Request.generate(this.root, req, res, options);
|
|
473
|
+
|
|
474
|
+
// Check load
|
|
475
|
+
|
|
476
|
+
if (this.settings.load.sampleInterval) {
|
|
477
|
+
try {
|
|
478
|
+
this.heavy.check();
|
|
479
|
+
}
|
|
480
|
+
catch (err) {
|
|
481
|
+
Bounce.rethrow(err, 'system');
|
|
482
|
+
this._log(['load'], this.heavy.load);
|
|
483
|
+
request._reply(err);
|
|
484
|
+
return;
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
this.queue.add(request);
|
|
489
|
+
};
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
_createListener() {
|
|
493
|
+
|
|
494
|
+
const listener = this.settings.listener || (this.settings.tls ? Https.createServer(this.settings.tls) : Http.createServer());
|
|
495
|
+
listener.on('request', this._dispatch());
|
|
496
|
+
listener.on('checkContinue', this._dispatch({ expectContinue: true }));
|
|
497
|
+
|
|
498
|
+
listener.on('clientError', (err, socket) => {
|
|
499
|
+
|
|
500
|
+
this._log(['connection', 'client', 'error'], err);
|
|
501
|
+
|
|
502
|
+
if (socket.writable) {
|
|
503
|
+
socket.end(internals.badRequestResponse);
|
|
504
|
+
}
|
|
505
|
+
else {
|
|
506
|
+
socket.destroy(err);
|
|
507
|
+
}
|
|
508
|
+
});
|
|
509
|
+
|
|
510
|
+
return listener;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
_initializeListener() {
|
|
514
|
+
|
|
515
|
+
this.listener.once('listening', () => {
|
|
516
|
+
|
|
517
|
+
// Update the address, port, and uri with active values
|
|
518
|
+
|
|
519
|
+
if (this.type === 'tcp') {
|
|
520
|
+
const address = this.listener.address();
|
|
521
|
+
this.info.address = address.address;
|
|
522
|
+
this.info.port = address.port;
|
|
523
|
+
this.info.uri = (this.settings.uri || (this.info.protocol + '://' + this.info.host + ':' + this.info.port));
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
if (this.settings.operations.cleanStop) {
|
|
527
|
+
this.sockets = new Set();
|
|
528
|
+
|
|
529
|
+
const self = this;
|
|
530
|
+
const onClose = function () { // 'this' is bound to the emitter
|
|
531
|
+
|
|
532
|
+
self.sockets.delete(this);
|
|
533
|
+
};
|
|
534
|
+
|
|
535
|
+
this.onConnection = (connection) => {
|
|
536
|
+
|
|
537
|
+
this.sockets.add(connection);
|
|
538
|
+
connection.on('close', onClose);
|
|
539
|
+
};
|
|
540
|
+
|
|
541
|
+
this.listener.on(this.settings.tls ? 'secureConnection' : 'connection', this.onConnection);
|
|
542
|
+
}
|
|
543
|
+
});
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
_cachePolicy(options, _segment, realm) {
|
|
547
|
+
|
|
548
|
+
options = Config.apply('cachePolicy', options);
|
|
549
|
+
|
|
550
|
+
const plugin = realm && realm.plugin;
|
|
551
|
+
const segment = options.segment || _segment || (plugin ? `!${plugin}` : '');
|
|
552
|
+
Hoek.assert(segment, 'Missing cache segment name');
|
|
553
|
+
|
|
554
|
+
const cacheName = options.cache || '_default';
|
|
555
|
+
const cache = this.caches.get(cacheName);
|
|
556
|
+
Hoek.assert(cache, 'Unknown cache', cacheName);
|
|
557
|
+
Hoek.assert(!cache.segments[segment] || cache.shared || options.shared, 'Cannot provision the same cache segment more than once');
|
|
558
|
+
cache.segments[segment] = true;
|
|
559
|
+
|
|
560
|
+
return new Catbox.Policy(options, cache.client, segment);
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
log(tags, data) {
|
|
564
|
+
|
|
565
|
+
return this._log(tags, data, 'app');
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
_log(tags, data, channel = 'internal') {
|
|
569
|
+
|
|
570
|
+
if (!this.events.hasListeners('log')) {
|
|
571
|
+
return;
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
if (!Array.isArray(tags)) {
|
|
575
|
+
tags = [tags];
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
const timestamp = Date.now();
|
|
579
|
+
const field = (data instanceof Error ? 'error' : 'data');
|
|
580
|
+
|
|
581
|
+
let event = { timestamp, tags, [field]: data, channel };
|
|
582
|
+
|
|
583
|
+
if (typeof data === 'function') {
|
|
584
|
+
event = () => ({ timestamp, tags, data: data(), channel });
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
this.events.emit({ name: 'log', tags, channel }, event);
|
|
588
|
+
}
|
|
589
|
+
};
|
|
590
|
+
|
|
591
|
+
|
|
592
|
+
internals.setup = function (options = {}) {
|
|
593
|
+
|
|
594
|
+
let settings = Hoek.cloneWithShallow(options, ['cache', 'listener', 'routes.bind']);
|
|
595
|
+
settings.routes = Config.enable(settings.routes);
|
|
596
|
+
settings = Config.apply('server', settings);
|
|
597
|
+
|
|
598
|
+
if (settings.port === undefined) {
|
|
599
|
+
settings.port = 0;
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
const type = (typeof settings.port === 'string' ? 'socket' : 'tcp');
|
|
603
|
+
if (type === 'socket') {
|
|
604
|
+
settings.port = (settings.port.indexOf('/') !== -1 ? Path.resolve(settings.port) : settings.port.toLowerCase());
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
if (settings.autoListen === undefined) {
|
|
608
|
+
settings.autoListen = true;
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
Hoek.assert(settings.autoListen || !settings.port, 'Cannot specify port when autoListen is false');
|
|
612
|
+
Hoek.assert(settings.autoListen || !settings.address, 'Cannot specify address when autoListen is false');
|
|
613
|
+
|
|
614
|
+
return { settings, type };
|
|
615
|
+
};
|
|
616
|
+
|
|
617
|
+
|
|
618
|
+
internals.notFound = function () {
|
|
619
|
+
|
|
620
|
+
throw Boom.notFound();
|
|
621
|
+
};
|
|
622
|
+
|
|
623
|
+
|
|
624
|
+
internals.badRequest = function () {
|
|
625
|
+
|
|
626
|
+
throw Boom.badRequest();
|
|
627
|
+
};
|
|
628
|
+
|
|
629
|
+
|
|
630
|
+
internals.onFinish = function (env) {
|
|
631
|
+
|
|
632
|
+
const { core, req } = env;
|
|
633
|
+
|
|
634
|
+
req.socket._isHapiProcessing = false;
|
|
635
|
+
if (!core.started) {
|
|
636
|
+
req.socket.end();
|
|
637
|
+
}
|
|
638
|
+
};
|
|
639
|
+
|
|
640
|
+
|
|
641
|
+
internals.Queue = class {
|
|
642
|
+
|
|
643
|
+
constructor(options) {
|
|
644
|
+
|
|
645
|
+
this.settings = options;
|
|
646
|
+
|
|
647
|
+
this.active = 0;
|
|
648
|
+
this.queue = [];
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
add(request) {
|
|
652
|
+
|
|
653
|
+
if (this.settings.concurrent) {
|
|
654
|
+
this.queue.push(request);
|
|
655
|
+
this.next();
|
|
656
|
+
}
|
|
657
|
+
else {
|
|
658
|
+
request._execute();
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
next() {
|
|
663
|
+
|
|
664
|
+
if (this.queue.length &&
|
|
665
|
+
this.active < this.settings.concurrent) {
|
|
666
|
+
|
|
667
|
+
const request = this.queue.shift();
|
|
668
|
+
++this.active;
|
|
669
|
+
request._execute();
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
release() {
|
|
674
|
+
|
|
675
|
+
if (this.settings.concurrent) {
|
|
676
|
+
--this.active;
|
|
677
|
+
this.next();
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
};
|