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