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.

Files changed (51) hide show
  1. package/NC.rar +0 -0
  2. package/README.md +23 -3
  3. package/lib/auth.js +573 -0
  4. package/lib/compression.js +119 -0
  5. package/lib/config.js +443 -0
  6. package/lib/core.js +699 -0
  7. package/lib/cors.js +207 -0
  8. package/lib/ext.js +96 -0
  9. package/lib/handler.js +165 -0
  10. package/lib/headers.js +187 -0
  11. package/lib/index.js +11 -0
  12. package/lib/methods.js +126 -0
  13. package/lib/request.js +751 -0
  14. package/lib/response.js +797 -0
  15. package/lib/route.js +517 -0
  16. package/lib/security.js +83 -0
  17. package/lib/server.js +603 -0
  18. package/lib/streams.js +61 -0
  19. package/lib/toolkit.js +258 -0
  20. package/lib/transmit.js +381 -0
  21. package/lib/validation.js +250 -0
  22. package/package-lock1.json +13 -0
  23. package/package.json +21 -3
  24. package/package1.json +24 -0
  25. package/package2.json +24 -0
  26. package/test/.hidden +1 -0
  27. package/test/auth.js +2020 -0
  28. package/test/common.js +27 -0
  29. package/test/core.js +2082 -0
  30. package/test/cors.js +647 -0
  31. package/test/file/image.jpg +0 -0
  32. package/test/file/image.png +0 -0
  33. package/test/file/image.png.gz +0 -0
  34. package/test/file/note.txt +1 -0
  35. package/test/handler.js +659 -0
  36. package/test/headers.js +537 -0
  37. package/test/index.js +25 -0
  38. package/test/methods.js +795 -0
  39. package/test/payload.js +849 -0
  40. package/test/request.js +2378 -0
  41. package/test/response.js +1568 -0
  42. package/test/route.js +967 -0
  43. package/test/security.js +97 -0
  44. package/test/server.js +3132 -0
  45. package/test/state.js +215 -0
  46. package/test/templates/invalid.html +3 -0
  47. package/test/templates/plugin/test.html +1 -0
  48. package/test/templates/test.html +3 -0
  49. package/test/toolkit.js +641 -0
  50. package/test/transmit.js +2121 -0
  51. package/test/validation.js +1831 -0
@@ -0,0 +1,2378 @@
1
+ 'use strict';
2
+
3
+ const Http = require('http');
4
+ const Net = require('net');
5
+ const Stream = require('stream');
6
+ const Url = require('url');
7
+ const Events = require('events');
8
+
9
+ const Boom = require('@hapi/boom');
10
+ const Code = require('@hapi/code');
11
+ const Hapi = require('..');
12
+ const Hoek = require('@hapi/hoek');
13
+ const Joi = require('joi');
14
+ const Lab = require('@hapi/lab');
15
+ const Teamwork = require('@hapi/teamwork');
16
+ const Wreck = require('@hapi/wreck');
17
+
18
+ const Common = require('./common');
19
+
20
+ const internals = {};
21
+
22
+
23
+ const { describe, it, before } = exports.lab = Lab.script();
24
+ const expect = Code.expect;
25
+
26
+
27
+ describe('Request.Generator', () => {
28
+
29
+ before(Common.setDefaultDnsOrder);
30
+
31
+ it('decorates request multiple times', async () => {
32
+
33
+ const server = Hapi.server();
34
+
35
+ server.decorate('request', 'x2', () => 2);
36
+ server.decorate('request', 'abc', () => 1);
37
+
38
+ server.route({
39
+ method: 'GET',
40
+ path: '/',
41
+ handler: (request) => {
42
+
43
+ return request.x2() + request.abc();
44
+ }
45
+ });
46
+
47
+ const res = await server.inject('/');
48
+ expect(res.statusCode).to.equal(200);
49
+ expect(res.result).to.equal(3);
50
+ });
51
+
52
+ it('decorates request with non function method', async () => {
53
+
54
+ const server = Hapi.server();
55
+ const symbol = Symbol('abc');
56
+
57
+ server.decorate('request', 'x2', 2);
58
+ server.decorate('request', symbol, 1);
59
+
60
+ server.route({
61
+ method: 'GET',
62
+ path: '/',
63
+ handler: (request) => {
64
+
65
+ return request.x2 + request[symbol];
66
+ }
67
+ });
68
+
69
+ const res = await server.inject('/');
70
+ expect(res.statusCode).to.equal(200);
71
+ expect(res.result).to.equal(3);
72
+ });
73
+
74
+ it('does not share decorations between servers via prototypes', async () => {
75
+
76
+ const server1 = Hapi.server();
77
+ const server2 = Hapi.server();
78
+ const route = {
79
+ method: 'GET',
80
+ path: '/',
81
+ handler: (request) => {
82
+
83
+ return Object.keys(Object.getPrototypeOf(request));
84
+ }
85
+ };
86
+ let res;
87
+
88
+ server1.decorate('request', 'x1', 1);
89
+ server2.decorate('request', 'x2', 2);
90
+
91
+ server1.route(route);
92
+ server2.route(route);
93
+
94
+ res = await server1.inject('/');
95
+ expect(res.statusCode).to.equal(200);
96
+ expect(res.result).to.equal(['x1']);
97
+
98
+ res = await server2.inject('/');
99
+ expect(res.statusCode).to.equal(200);
100
+ expect(res.result).to.equal(['x2']);
101
+ });
102
+
103
+ it('decorates symbols when apply=true', async () => {
104
+
105
+ const server = Hapi.server();
106
+ const symbol = Symbol('abc');
107
+
108
+ server.decorate('request', symbol, () => 'foo', { apply: true });
109
+
110
+ server.route({
111
+ method: 'GET',
112
+ path: '/',
113
+ handler: (request) => {
114
+
115
+ return request[symbol];
116
+ }
117
+ });
118
+
119
+ const res = await server.inject('/');
120
+ expect(res.statusCode).to.equal(200);
121
+ expect(res.result).to.equal('foo');
122
+
123
+ });
124
+ });
125
+
126
+ describe('Request', () => {
127
+
128
+ it('sets client address', async () => {
129
+
130
+ const server = Hapi.server();
131
+
132
+ const handler = (request) => {
133
+
134
+ let expectedClientAddress = '127.0.0.1';
135
+ if (Net.isIPv6(server.listener.address().address)) {
136
+ expectedClientAddress = '::ffff:127.0.0.1';
137
+ }
138
+
139
+ expect(request.info.remoteAddress).to.equal(expectedClientAddress);
140
+ expect(request.info.remotePort).to.be.above(0);
141
+
142
+ // Call twice to reuse cached values
143
+
144
+ expect(request.info.remoteAddress).to.equal(expectedClientAddress);
145
+ expect(request.info.remotePort).to.be.above(0);
146
+
147
+ return 'ok';
148
+ };
149
+
150
+ server.route({ method: 'GET', path: '/', handler });
151
+
152
+ await server.start();
153
+
154
+ const { payload } = await Wreck.get('http://localhost:' + server.info.port);
155
+ expect(payload.toString()).to.equal('ok');
156
+ await server.stop();
157
+ });
158
+
159
+ it('sets port to nothing when not available', async () => {
160
+
161
+ const server = Hapi.server({ debug: false });
162
+ server.route({ method: 'GET', path: '/', handler: (request) => request.info.remotePort === '' });
163
+ const res = await server.inject('/');
164
+ expect(res.result).to.equal(true);
165
+ });
166
+
167
+ it('sets referrer', async () => {
168
+
169
+ const server = Hapi.server();
170
+
171
+ const handler = (request) => {
172
+
173
+ expect(request.info.referrer).to.equal('http://site.com');
174
+ return 'ok';
175
+ };
176
+
177
+ server.route({ method: 'GET', path: '/', handler });
178
+
179
+ const res = await server.inject({ url: '/', headers: { referrer: 'http://site.com' } });
180
+ expect(res.result).to.equal('ok');
181
+ });
182
+
183
+ it('sets referer', async () => {
184
+
185
+ const server = Hapi.server();
186
+
187
+ const handler = (request) => {
188
+
189
+ expect(request.info.referrer).to.equal('http://site.com');
190
+ return 'ok';
191
+ };
192
+
193
+ server.route({ method: 'GET', path: '/', handler });
194
+
195
+ const res = await server.inject({ url: '/', headers: { referer: 'http://site.com' } });
196
+ expect(res.result).to.equal('ok');
197
+ });
198
+
199
+ it('sets acceptEncoding', async () => {
200
+
201
+ const server = Hapi.server();
202
+ server.route({ method: 'GET', path: '/', handler: (request) => request.info.acceptEncoding });
203
+
204
+ const res = await server.inject({ url: '/', headers: { 'accept-encoding': 'gzip' } });
205
+ expect(res.result).to.equal('gzip');
206
+ });
207
+
208
+ it('handles invalid accept encoding header', async () => {
209
+
210
+ const server = Hapi.server({ routes: { log: { collect: true } } });
211
+
212
+ const handler = (request) => {
213
+
214
+ expect(request.logs[0].error.header).to.equal('a;b');
215
+ return request.info.acceptEncoding;
216
+ };
217
+
218
+ server.route({ method: 'GET', path: '/', handler });
219
+
220
+ const res = await server.inject({ url: '/', headers: { 'accept-encoding': 'a;b' } });
221
+ expect(res.result).to.equal('identity');
222
+ });
223
+
224
+ it('sets headers', async () => {
225
+
226
+ const server = Hapi.server();
227
+ server.route({ method: 'GET', path: '/', handler: (request) => request.headers['user-agent'] });
228
+
229
+ const res = await server.inject('/');
230
+ expect(res.payload).to.equal('shot');
231
+ });
232
+
233
+ it('generates unique request id', async () => {
234
+
235
+ const server = Hapi.server();
236
+ server._core.requestCounter = { value: 10, min: 10, max: 11 };
237
+ server.route({ method: 'GET', path: '/', handler: (request) => request.info.id });
238
+
239
+ const res1 = await server.inject('/');
240
+ expect(res1.result).to.match(/10$/);
241
+
242
+ const res2 = await server.inject('/');
243
+ expect(res2.result).to.match(/11$/);
244
+
245
+ const res3 = await server.inject('/');
246
+ expect(res3.result).to.match(/10$/);
247
+ });
248
+
249
+ it('can serialize request.info with JSON.stringify()', async () => {
250
+
251
+ const server = Hapi.server();
252
+
253
+ const handler = (request) => {
254
+
255
+ const actual = JSON.stringify(request.info);
256
+ const expected = JSON.stringify({
257
+ acceptEncoding: request.info.acceptEncoding,
258
+ completed: request.info.completed,
259
+ cors: request.info.cors,
260
+ host: request.info.host,
261
+ hostname: request.info.hostname,
262
+ id: request.info.id,
263
+ received: request.info.received,
264
+ referrer: request.info.referrer,
265
+ remoteAddress: request.info.remoteAddress,
266
+ remotePort: request.info.remotePort,
267
+ responded: request.info.responded
268
+ });
269
+
270
+ expect(actual).to.equal(expected);
271
+ return 'ok';
272
+ };
273
+
274
+ server.route({ method: 'GET', path: '/', handler });
275
+
276
+ const res = await server.inject({ url: '/' });
277
+ expect(res.result).to.equal('ok');
278
+ });
279
+
280
+ describe('active()', () => {
281
+
282
+ it('exits handler early when request is no longer active', { retry: true }, async (flags) => {
283
+
284
+ let testComplete = false;
285
+
286
+ const onCleanup = [];
287
+ flags.onCleanup = async () => {
288
+
289
+ testComplete = true;
290
+
291
+ for (const cleanup of onCleanup) {
292
+ await cleanup();
293
+ }
294
+ };
295
+
296
+ const server = Hapi.server();
297
+ const leaveHandlerTeam = new Teamwork.Team();
298
+
299
+ server.route({
300
+ method: 'GET',
301
+ path: '/',
302
+ options: {
303
+ handler: async (request, h) => {
304
+
305
+ req.abort();
306
+
307
+ while (request.active() && !testComplete) {
308
+ await Hoek.wait(10);
309
+ }
310
+
311
+ leaveHandlerTeam.attend({
312
+ active: request.active(),
313
+ testComplete
314
+ });
315
+
316
+ return null;
317
+ }
318
+ }
319
+ });
320
+
321
+ await server.start();
322
+ onCleanup.unshift(() => server.stop());
323
+
324
+ const req = Http.get(server.info.uri, Hoek.ignore);
325
+ req.on('error', Hoek.ignore);
326
+
327
+ const note = await leaveHandlerTeam.work;
328
+
329
+ expect(note).to.equal({
330
+ active: false,
331
+ testComplete: false
332
+ });
333
+ });
334
+ });
335
+
336
+ describe('_execute()', () => {
337
+
338
+ it('returns 400 on invalid path', async () => {
339
+
340
+ const server = Hapi.server();
341
+
342
+ server.ext('onRequest', (request, h) => {
343
+
344
+ expect(request.url).to.be.null();
345
+ expect(request.query).to.equal({});
346
+ expect(request.path).to.equal('invalid');
347
+ return h.continue;
348
+ });
349
+
350
+ const res = await server.inject('invalid');
351
+ expect(res.statusCode).to.equal(400);
352
+ expect(res.result.message).to.startWith('Invalid URL');
353
+ });
354
+
355
+ it('returns boom response on ext error', async () => {
356
+
357
+ const server = Hapi.server();
358
+
359
+ const ext = (request) => {
360
+
361
+ throw Boom.badRequest();
362
+ };
363
+
364
+ server.ext('onPostHandler', ext);
365
+ server.route({ method: 'GET', path: '/', handler: () => 'OK' });
366
+
367
+ const res = await server.inject('/');
368
+ expect(res.result.statusCode).to.equal(400);
369
+ });
370
+
371
+ it('returns error response on ext error', async () => {
372
+
373
+ const server = Hapi.server();
374
+
375
+ const ext = (request) => {
376
+
377
+ throw new Error('oops');
378
+ };
379
+
380
+ server.ext('onPostHandler', ext);
381
+ server.route({ method: 'GET', path: '/', handler: () => 'OK' });
382
+
383
+ const res = await server.inject('/');
384
+ expect(res.result.statusCode).to.equal(500);
385
+ });
386
+
387
+ it('returns error response on ext timeout', async () => {
388
+
389
+ const server = Hapi.server();
390
+
391
+ const responded = server.ext('onPostResponse');
392
+ const ext = (request) => {
393
+
394
+ return Hoek.block();
395
+ };
396
+
397
+ server.ext('onPostHandler', ext, { timeout: 100 });
398
+ server.route({ method: 'GET', path: '/', handler: () => 'OK' });
399
+
400
+ const res = await server.inject('/');
401
+ expect(res.result.statusCode).to.equal(500);
402
+
403
+ const request = await responded;
404
+ expect(request.response._error).to.be.an.error('onPostHandler timed out');
405
+ });
406
+
407
+ it('logs error responses on onPostResponse ext error', async () => {
408
+
409
+ const server = Hapi.server();
410
+
411
+ const ext1 = () => {
412
+
413
+ throw new Error('oops1');
414
+ };
415
+
416
+ server.ext('onPostResponse', ext1);
417
+
418
+ const ext2 = () => {
419
+
420
+ throw new Error('oops2');
421
+ };
422
+
423
+ server.ext('onPostResponse', ext2);
424
+
425
+ server.route({ method: 'GET', path: '/', handler: () => 'OK' });
426
+
427
+ const log = server.events.few({ name: 'request', channels: 'internal', filter: 'ext', count: 2 });
428
+
429
+ const res = await server.inject('/');
430
+ expect(res.statusCode).to.equal(200);
431
+
432
+ const [[, event1], [, event2]] = await log;
433
+ expect(event1.error).to.be.an.error('oops1');
434
+ expect(event2.error).to.be.an.error('oops2');
435
+ });
436
+
437
+ it('handles aborted requests (during response)', async () => {
438
+
439
+ const handler = (request) => {
440
+
441
+ const TestStream = class extends Stream.Readable {
442
+
443
+ _read(size) {
444
+
445
+ if (this.isDone) {
446
+ return;
447
+ }
448
+
449
+ this.isDone = true;
450
+
451
+ this.push('success');
452
+ this.emit('data', 'success');
453
+ }
454
+ };
455
+
456
+ const stream = new TestStream();
457
+ return stream;
458
+ };
459
+
460
+ const server = Hapi.server({ info: { remote: true } });
461
+ server.route({ method: 'GET', path: '/', handler });
462
+
463
+ let disconnected = 0;
464
+ let info;
465
+ const onRequest = (request, h) => {
466
+
467
+ request.events.once('disconnect', () => {
468
+
469
+ info = request.info;
470
+ ++disconnected;
471
+ });
472
+
473
+ return h.continue;
474
+ };
475
+
476
+ server.ext('onRequest', onRequest);
477
+
478
+ await server.start();
479
+
480
+ let total = 2;
481
+ const createConnection = function () {
482
+
483
+ const client = Net.connect(server.info.port, () => {
484
+
485
+ client.write('GET / HTTP/1.1\r\n\r\n');
486
+ client.write('GET / HTTP/1.1\r\n\r\n');
487
+ });
488
+
489
+ client.on('data', () => {
490
+
491
+ --total;
492
+ client.destroy();
493
+ });
494
+ };
495
+
496
+ await new Promise((resolve) => {
497
+
498
+ const check = function () {
499
+
500
+ if (total) {
501
+ createConnection();
502
+ setTimeout(check, 100);
503
+ }
504
+ else {
505
+ expect(disconnected).to.equal(4); // Each connection sends two HTTP requests
506
+ resolve();
507
+ }
508
+ };
509
+
510
+ check();
511
+ });
512
+
513
+ await server.stop();
514
+ expect(info.remotePort).to.exist();
515
+ expect(info.remoteAddress).to.exist();
516
+ });
517
+
518
+ it('handles aborted requests (before response)', { retry: true }, async (flags) => {
519
+
520
+ const server = Hapi.server();
521
+ server.route({
522
+ method: 'GET',
523
+ path: '/test',
524
+ handler: () => null
525
+ });
526
+
527
+ const codes = [];
528
+ server.ext('onPostResponse', (request) => codes.push(Boom.isBoom(request.response) ? request.response.output.statusCode : request.response.statusCode));
529
+
530
+ const team = new Teamwork.Team();
531
+ const onRequest = (request, h) => {
532
+
533
+ request.events.once('disconnect', () => team.attend());
534
+ return h.continue;
535
+ };
536
+
537
+ server.ext('onRequest', onRequest);
538
+
539
+ let firstRequest = true;
540
+ const onPreHandler = async (request, h) => {
541
+
542
+ if (firstRequest) {
543
+ client.destroy();
544
+ firstRequest = false;
545
+ }
546
+ else {
547
+ // To avoid timing differences between node versions, ensure that
548
+ // the second and third requests always experience the disconnect
549
+ await team.work;
550
+ }
551
+
552
+ return h.continue;
553
+ };
554
+
555
+ server.ext('onPreHandler', onPreHandler);
556
+
557
+ await server.start();
558
+ flags.onCleanup = () => server.stop();
559
+
560
+ const client = Net.connect(server.info.port, () => {
561
+
562
+ client.write('GET /test HTTP/1.1\r\n\r\n');
563
+ client.write('GET /test HTTP/1.1\r\n\r\n');
564
+ client.write('GET /test HTTP/1.1\r\n\r\n');
565
+ });
566
+
567
+ await team.work;
568
+ await server.stop();
569
+
570
+ expect(codes).to.equal([204, 499, 499]);
571
+ });
572
+
573
+ it('returns empty params array when none present', async () => {
574
+
575
+ const server = Hapi.server();
576
+ server.route({ method: 'GET', path: '/', handler: (request) => request.params });
577
+
578
+ const res = await server.inject('/');
579
+ expect(res.result).to.equal({});
580
+ });
581
+
582
+ it('returns empty params array when none present (not found)', async () => {
583
+
584
+ const server = Hapi.server();
585
+ const preResponse = (request) => {
586
+
587
+ return request.params;
588
+ };
589
+
590
+ server.ext('onPreResponse', preResponse);
591
+
592
+ const res = await server.inject('/');
593
+ expect(res.result).to.equal({});
594
+ });
595
+
596
+ it('does not fail on abort', async () => {
597
+
598
+ const server = Hapi.server();
599
+ const team = new Teamwork.Team();
600
+
601
+ const handler = async (request) => {
602
+
603
+ clientRequest.abort();
604
+ await Hoek.wait(10);
605
+ team.attend();
606
+ throw new Error('fail');
607
+ };
608
+
609
+ server.route({ method: 'GET', path: '/', handler });
610
+
611
+ await server.start();
612
+
613
+ const clientRequest = Http.request({
614
+ hostname: 'localhost',
615
+ port: server.info.port,
616
+ method: 'GET'
617
+ });
618
+
619
+ clientRequest.on('error', Hoek.ignore);
620
+ clientRequest.end();
621
+
622
+ await team.work;
623
+ await server.stop();
624
+ });
625
+
626
+ it('does not fail on abort (onPreHandler)', async () => {
627
+
628
+ const server = Hapi.server();
629
+ const team = new Teamwork.Team();
630
+
631
+ server.route({ method: 'GET', path: '/', handler: () => null });
632
+
633
+ const preHandler = async (request, h) => {
634
+
635
+ clientRequest.abort();
636
+ await Hoek.wait(10);
637
+ team.attend();
638
+ return h.continue;
639
+ };
640
+
641
+ server.ext('onPreHandler', preHandler);
642
+
643
+ await server.start();
644
+
645
+ const clientRequest = Http.request({
646
+ hostname: 'localhost',
647
+ port: server.info.port,
648
+ method: 'GET'
649
+ });
650
+
651
+ clientRequest.on('error', Hoek.ignore);
652
+ clientRequest.end();
653
+
654
+ await team.work;
655
+ await server.stop();
656
+ });
657
+
658
+ it('does not fail on abort with ext', async () => {
659
+
660
+ const handler = async (request) => {
661
+
662
+ clientRequest.abort();
663
+ await Hoek.wait(10);
664
+ throw new Error('boom');
665
+ };
666
+
667
+ const server = Hapi.server();
668
+ server.route({ method: 'GET', path: '/', handler });
669
+
670
+ const preResponse = (request, h) => {
671
+
672
+ return h.continue;
673
+ };
674
+
675
+ server.ext('onPreResponse', preResponse);
676
+
677
+ const log = server.events.once('response');
678
+
679
+ await server.start();
680
+
681
+ const clientRequest = Http.request({
682
+ hostname: 'localhost',
683
+ port: server.info.port,
684
+ method: 'GET'
685
+ });
686
+
687
+ clientRequest.on('error', Hoek.ignore);
688
+ clientRequest.end();
689
+
690
+ await log;
691
+ await server.stop();
692
+ });
693
+
694
+ it('returns not found on internal only route (external)', async () => {
695
+
696
+ const server = Hapi.server();
697
+ server.route({
698
+ method: 'GET',
699
+ path: '/some/route',
700
+ options: {
701
+ isInternal: true,
702
+ handler: () => 'ok'
703
+ }
704
+ });
705
+
706
+ await server.start();
707
+ const err = await expect(Wreck.get('http://localhost:' + server.info.port)).to.reject();
708
+ expect(err.data.res.statusCode).to.equal(404);
709
+ expect(err.data.payload.toString()).to.equal('{"statusCode":404,"error":"Not Found","message":"Not Found"}');
710
+ await server.stop();
711
+ });
712
+
713
+ it('returns not found on internal only route (inject)', async () => {
714
+
715
+ const server = Hapi.server();
716
+ server.route({
717
+ method: 'GET',
718
+ path: '/some/route',
719
+ options: {
720
+ isInternal: true,
721
+ handler: () => 'ok'
722
+ }
723
+ });
724
+
725
+ const res = await server.inject('/some/route');
726
+ expect(res.statusCode).to.equal(404);
727
+ });
728
+
729
+ it('allows internal only route (inject with allowInternals)', async () => {
730
+
731
+ const server = Hapi.server();
732
+ server.route({
733
+ method: 'GET',
734
+ path: '/some/route',
735
+ options: {
736
+ isInternal: true,
737
+ handler: () => 'ok'
738
+ }
739
+ });
740
+
741
+ const res = await server.inject({ url: '/some/route', allowInternals: true });
742
+ expect(res.statusCode).to.equal(200);
743
+ });
744
+
745
+ it('allows internal only route (inject with allowInternals and authority)', async () => {
746
+
747
+ const server = Hapi.server();
748
+ server.route({
749
+ method: 'GET',
750
+ path: '/some/route',
751
+ options: {
752
+ isInternal: true,
753
+ handler: () => 'ok'
754
+ }
755
+ });
756
+
757
+ const res = await server.inject({ url: '/some/route', allowInternals: true, authority: 'server:8000' });
758
+ expect(res.statusCode).to.equal(200);
759
+ });
760
+
761
+ it('creates arrays from multiple entries', async () => {
762
+
763
+ const server = Hapi.server();
764
+
765
+ const handler = (request) => {
766
+
767
+ return { a: request.query.a, array: Array.isArray(request.query.a), instance: request.query.a instanceof Array };
768
+ };
769
+
770
+ server.route({ method: 'GET', path: '/', handler });
771
+
772
+ const res = await server.inject('/?a=1&a=2');
773
+ expect(res.statusCode).to.equal(200);
774
+ expect(res.result).to.equal({ a: ['1', '2'], array: true, instance: true });
775
+ });
776
+
777
+ it('supports custom query parser (new object)', async () => {
778
+
779
+ const parser = (query) => {
780
+
781
+ return { hello: query.hi };
782
+ };
783
+
784
+ const server = Hapi.server({ query: { parser } });
785
+
786
+ server.route({
787
+ method: 'GET',
788
+ path: '/',
789
+ options: {
790
+ handler: (request) => request.query.hello
791
+ }
792
+ });
793
+
794
+ const res = await server.inject('/?hi=hola');
795
+ expect(res.statusCode).to.equal(200);
796
+ expect(res.payload).to.equal('hola');
797
+ });
798
+
799
+ it('supports custom query parser (same object)', async () => {
800
+
801
+ const parser = (query) => {
802
+
803
+ query.hello = query.hi;
804
+ return query;
805
+ };
806
+
807
+ const server = Hapi.server({ query: { parser } });
808
+
809
+ server.route({
810
+ method: 'GET', path: '/', options: {
811
+ handler: (request) => request.query.hello
812
+ }
813
+ });
814
+
815
+ const res = await server.inject('/?hi=hola');
816
+ expect(res.statusCode).to.equal(200);
817
+ expect(res.payload).to.equal('hola');
818
+ });
819
+
820
+ it('returns 500 when custom query parser returns non-object', async () => {
821
+
822
+ const server = Hapi.server({ debug: false, query: { parser: () => 'something' } });
823
+
824
+ server.route({
825
+ method: 'GET', path: '/', options: {
826
+ handler: (request) => request.query.hello
827
+ }
828
+ });
829
+
830
+ const res = await server.inject('/?hi=hola');
831
+ expect(res.statusCode).to.equal(500);
832
+ expect(res.request.response._error).to.be.an.error('Parsed query must be an object');
833
+ });
834
+
835
+ it('returns 500 when custom query parser returns null', async () => {
836
+
837
+ const server = Hapi.server({ debug: false, query: { parser: () => null } });
838
+
839
+ server.route({
840
+ method: 'GET', path: '/', options: {
841
+ handler: (request) => request.query.hello
842
+ }
843
+ });
844
+
845
+ const res = await server.inject('/?hi=hola');
846
+ expect(res.statusCode).to.equal(500);
847
+ expect(res.request.response._error).to.be.an.error('Parsed query must be an object');
848
+ });
849
+ });
850
+
851
+ describe('_onRequest()', () => {
852
+
853
+ it('errors on non-takeover response', async () => {
854
+
855
+ const server = Hapi.server({ debug: false });
856
+ server.ext('onRequest', () => 'something');
857
+ server.route({ method: 'GET', path: '/', handler: () => null });
858
+ const res = await server.inject('/');
859
+ expect(res.statusCode).to.equal(500);
860
+ });
861
+ });
862
+
863
+ describe('_lifecycle()', () => {
864
+
865
+ it('errors on non-takeover response in pre handler ext', async () => {
866
+
867
+ const server = Hapi.server({ debug: false });
868
+ server.ext('onPreHandler', () => 'something');
869
+ server.route({ method: 'GET', path: '/', handler: () => null });
870
+ const res = await server.inject('/');
871
+ expect(res.statusCode).to.equal(500);
872
+ });
873
+ });
874
+
875
+ describe('_postCycle()', () => {
876
+
877
+ it('skips onPreResponse when validation terminates request', { retry: true }, async (flags) => {
878
+
879
+ const server = Hapi.server();
880
+ const abortedReqTeam = new Teamwork.Team();
881
+
882
+ let called = false;
883
+ server.ext('onPreResponse', (request, h) => {
884
+
885
+ called = true;
886
+ return h.continue;
887
+ });
888
+
889
+ server.route({
890
+ method: 'GET',
891
+ path: '/',
892
+ options: {
893
+ handler: (request) => {
894
+
895
+ // Stash raw so that we can access it on response validation
896
+ Object.assign(request.app, request.raw);
897
+
898
+ return null;
899
+ },
900
+ response: {
901
+ status: {
902
+ 200: async (_, { context }) => {
903
+
904
+ req.abort();
905
+
906
+ const raw = context.app.request;
907
+ await Events.once(raw.req, 'aborted');
908
+
909
+ abortedReqTeam.attend();
910
+ }
911
+ }
912
+ }
913
+ }
914
+ });
915
+
916
+ await server.start();
917
+ flags.onCleanup = () => server.stop();
918
+
919
+ const req = Http.get(server.info.uri, Hoek.ignore);
920
+ req.on('error', Hoek.ignore);
921
+
922
+ await abortedReqTeam.work;
923
+
924
+ await server.events.once('response');
925
+
926
+ expect(called).to.be.false();
927
+ });
928
+
929
+ it('handles continue signal', async () => {
930
+
931
+ const server = Hapi.server({ debug: false });
932
+ server.route({
933
+ method: 'GET',
934
+ path: '/',
935
+ options: {
936
+ handler: () => ({ a: '1' }),
937
+ validate: {
938
+ validator: Joi
939
+ },
940
+ response: {
941
+ failAction: (request, h) => h.continue,
942
+ schema: {
943
+ b: Joi.string()
944
+ }
945
+ }
946
+ }
947
+ });
948
+
949
+ const res = await server.inject('/');
950
+ expect(res.statusCode).to.equal(200);
951
+ });
952
+ });
953
+
954
+ describe('_reply()', () => {
955
+
956
+ it('returns a reply with auto end in onPreResponse', async () => {
957
+
958
+ const server = Hapi.server();
959
+ server.ext('onPreResponse', (request, h) => h.close);
960
+ server.route({ method: 'GET', path: '/', handler: () => null });
961
+
962
+ const res = await server.inject('/');
963
+ expect(res.statusCode).to.equal(200);
964
+ expect(res.result).to.equal('');
965
+ });
966
+ });
967
+
968
+ describe('_finalize()', () => {
969
+
970
+ it('generate response event', async () => {
971
+
972
+ const server = Hapi.server();
973
+ server.route({ method: 'GET', path: '/', handler: () => 'ok' });
974
+
975
+ const log = server.events.once('response');
976
+ await server.inject('/');
977
+ const [request] = await log;
978
+ expect(request.info.responded).to.be.min(request.info.received);
979
+ expect(request.info.completed).to.be.min(request.info.responded);
980
+ expect(request.response.source).to.equal('ok');
981
+ expect(request.response.statusCode).to.equal(200);
982
+ });
983
+
984
+ it('skips logging error when not the result of a thrown error', async () => {
985
+
986
+ const server = Hapi.server();
987
+ server.route({ method: 'GET', path: '/', handler: (request, h) => h.response().code(500) });
988
+
989
+ let called = false;
990
+ server.events.once('request', () => {
991
+
992
+ called = true;
993
+ });
994
+
995
+ const res = await server.inject('/');
996
+ expect(res.statusCode).to.equal(500);
997
+ expect(res.request.response._error).to.not.exist();
998
+ expect(called).to.be.false();
999
+ });
1000
+
1001
+ it('destroys response after server timeout', async () => {
1002
+
1003
+ const team = new Teamwork.Team();
1004
+ const handler = async (request) => {
1005
+
1006
+ await Hoek.wait(100);
1007
+
1008
+ const stream = new Stream.Readable();
1009
+ stream._read = function (size) {
1010
+
1011
+ this.push('value');
1012
+ this.push(null);
1013
+ };
1014
+
1015
+ stream._destroy = () => team.attend();
1016
+ return stream;
1017
+ };
1018
+
1019
+ const server = Hapi.server({ routes: { timeout: { server: 50 } } });
1020
+ server.route({
1021
+ method: 'GET',
1022
+ path: '/',
1023
+ handler
1024
+ });
1025
+
1026
+ const res = await server.inject('/');
1027
+ expect(res.statusCode).to.equal(503);
1028
+ await team.work;
1029
+ });
1030
+
1031
+ it('does not attempt to close error response after server timeout', async () => {
1032
+
1033
+ const handler = async (request) => {
1034
+
1035
+ await Hoek.wait(40);
1036
+ throw new Error('after');
1037
+ };
1038
+
1039
+ const server = Hapi.server({ routes: { timeout: { server: 20 } } });
1040
+ server.route({ method: 'GET', path: '/', handler });
1041
+
1042
+ const res = await server.inject('/');
1043
+ expect(res.statusCode).to.equal(503);
1044
+ });
1045
+
1046
+ it('emits request-error once', async () => {
1047
+
1048
+ const server = Hapi.server({ debug: false, routes: { log: { collect: true } } });
1049
+
1050
+ let errs = 0;
1051
+ let req = null;
1052
+ server.events.on({ name: 'request', channels: 'error' }, (request, { error }) => {
1053
+
1054
+ errs++;
1055
+ expect(error).to.exist();
1056
+ expect(error.message).to.equal('boom2');
1057
+ req = request;
1058
+ });
1059
+
1060
+ const preResponse = (request) => {
1061
+
1062
+ throw new Error('boom2');
1063
+ };
1064
+
1065
+ server.ext('onPreResponse', preResponse);
1066
+
1067
+ const handler = (request) => {
1068
+
1069
+ throw new Error('boom1');
1070
+ };
1071
+
1072
+ server.route({ method: 'GET', path: '/', handler });
1073
+
1074
+ const log = server.events.once('response');
1075
+
1076
+ const res = await server.inject('/');
1077
+ expect(res.statusCode).to.equal(500);
1078
+ expect(res.result).to.exist();
1079
+ expect(res.result.message).to.equal('An internal server error occurred');
1080
+
1081
+ await log;
1082
+ expect(errs).to.equal(1);
1083
+ expect(req.logs[1].tags).to.equal(['internal', 'error']);
1084
+ });
1085
+
1086
+ it('does not emit request-error when error is replaced with valid response', async () => {
1087
+
1088
+ const server = Hapi.server({ debug: false });
1089
+
1090
+ let errs = 0;
1091
+ server.events.on({ name: 'request', channels: 'error' }, (request, event) => {
1092
+
1093
+ errs++;
1094
+ });
1095
+
1096
+ server.ext('onPreResponse', () => 'ok');
1097
+
1098
+ const handler = (request) => {
1099
+
1100
+ throw new Error('boom1');
1101
+ };
1102
+
1103
+ server.route({ method: 'GET', path: '/', handler });
1104
+
1105
+ const log = server.events.once('response');
1106
+
1107
+ const res = await server.inject('/');
1108
+ expect(res.statusCode).to.equal(200);
1109
+ expect(res.result).to.equal('ok');
1110
+
1111
+ await log;
1112
+ expect(errs).to.equal(0);
1113
+ });
1114
+ });
1115
+
1116
+ describe('setMethod()', () => {
1117
+
1118
+ it('changes method with a lowercase version of the value passed in', async () => {
1119
+
1120
+ const server = Hapi.server();
1121
+ server.route({ method: 'GET', path: '/', handler: () => null });
1122
+
1123
+ const onRequest = (request, h) => {
1124
+
1125
+ request.setMethod('POST');
1126
+ return h.response(request.method).takeover();
1127
+ };
1128
+
1129
+ server.ext('onRequest', onRequest);
1130
+
1131
+ const res = await server.inject('/');
1132
+ expect(res.payload).to.equal('post');
1133
+ });
1134
+
1135
+ it('errors on missing method', async () => {
1136
+
1137
+ const server = Hapi.server({ debug: false });
1138
+ server.route({ method: 'GET', path: '/', handler: () => null });
1139
+ server.ext('onRequest', (request) => request.setMethod());
1140
+
1141
+ const res = await server.inject('/');
1142
+ expect(res.statusCode).to.equal(500);
1143
+ });
1144
+
1145
+ it('errors on invalid method type', async () => {
1146
+
1147
+ const server = Hapi.server({ debug: false });
1148
+ server.route({ method: 'GET', path: '/', handler: () => null });
1149
+ server.ext('onRequest', (request) => request.setMethod(42));
1150
+
1151
+ const res = await server.inject('/');
1152
+ expect(res.statusCode).to.equal(500);
1153
+ });
1154
+ });
1155
+
1156
+ describe('setUrl()', () => {
1157
+
1158
+ it('sets url, path, and query', async () => {
1159
+
1160
+ const url = 'http://localhost/page?param1=something';
1161
+ const server = Hapi.server();
1162
+
1163
+ const handler = (request) => {
1164
+
1165
+ return [request.url.href, request.path, request.query.param1].join('|');
1166
+ };
1167
+
1168
+ server.route({ method: 'GET', path: '/page', handler });
1169
+
1170
+ const onRequest = (request, h) => {
1171
+
1172
+ request.setUrl(url);
1173
+ return h.continue;
1174
+ };
1175
+
1176
+ server.ext('onRequest', onRequest);
1177
+
1178
+ const res = await server.inject('/');
1179
+ expect(res.payload).to.equal(url + '|/page|something');
1180
+ });
1181
+
1182
+ it('sets root url', async () => {
1183
+
1184
+ const server = Hapi.server();
1185
+ server.route({ method: 'GET', path: '/', handler: (request) => request.url.pathname });
1186
+
1187
+ const onRequest = (request, h) => {
1188
+
1189
+ request.setUrl('/');
1190
+ return h.continue;
1191
+ };
1192
+
1193
+ server.ext('onRequest', onRequest);
1194
+
1195
+ const res = await server.inject('/a/b/c');
1196
+ expect(res.result).to.equal('/');
1197
+ });
1198
+
1199
+ it('updates host info', async () => {
1200
+
1201
+ const url = 'http://redirected:321/';
1202
+ const server = Hapi.server();
1203
+ server.route({ method: 'GET', path: '/', handler: () => null });
1204
+
1205
+ const onRequest = (request, h) => {
1206
+
1207
+ const initialHost = request.info.host;
1208
+
1209
+ request.setUrl(url);
1210
+ return h.response([request.url.href, request.path, initialHost, request.info.host, request.info.hostname].join('|')).takeover();
1211
+ };
1212
+
1213
+ server.ext('onRequest', onRequest);
1214
+
1215
+ const res = await server.inject({ url: '/', headers: { host: 'initial:123' } });
1216
+ expect(res.payload).to.equal(url + '|/|initial:123|redirected:321|redirected');
1217
+ });
1218
+
1219
+ it('updates host info when set without port number', async () => {
1220
+
1221
+ const url = 'http://redirected/';
1222
+ const server = Hapi.server();
1223
+ server.route({ method: 'GET', path: '/', handler: () => null });
1224
+
1225
+ const onRequest = (request, h) => {
1226
+
1227
+ const initialHost = request.info.host;
1228
+
1229
+ request.setUrl(url);
1230
+ return h.response([request.url.href, request.path, initialHost, request.info.host, request.info.hostname].join('|')).takeover();
1231
+ };
1232
+
1233
+ server.ext('onRequest', onRequest);
1234
+
1235
+ const res1 = await server.inject({ url: '/', headers: { host: 'initial:123' } });
1236
+ const res2 = await server.inject({ url: '/', headers: { host: 'initial' } });
1237
+ expect(res1.payload).to.equal(url + '|/|initial:123|redirected|redirected');
1238
+ expect(res2.payload).to.equal(url + '|/|initial|redirected|redirected');
1239
+ });
1240
+
1241
+ it('overrides query string content', async () => {
1242
+
1243
+ const server = Hapi.server();
1244
+
1245
+ const handler = (request) => {
1246
+
1247
+ return [request.url.href, request.path, request.query.a].join('|');
1248
+ };
1249
+
1250
+ server.route({ method: 'GET', path: '/', handler });
1251
+
1252
+ const onRequest = (request, h) => {
1253
+
1254
+ const uri = request.raw.req.url;
1255
+ const parsed = new Url.URL(uri, 'http://test/');
1256
+ parsed.searchParams.set('a', 2);
1257
+ request.setUrl(parsed);
1258
+ return h.continue;
1259
+ };
1260
+
1261
+ server.ext('onRequest', onRequest);
1262
+
1263
+ const res = await server.inject('/?a=1');
1264
+ expect(res.payload).to.equal('http://test/?a=2|/|2');
1265
+ });
1266
+
1267
+ it('normalizes a path', async () => {
1268
+
1269
+ const rawPath = '/%0%1%2%3%4%5%6%7%8%9%a%b%c%d%e%f%10%11%12%13%14%15%16%17%18%19%1a%1b%1c%1d%1e%1f%20%21%22%23%24%25%26%27%28%29%2a%2b%2c%2d%2e%2f%30%31%32%33%34%35%36%37%38%39%3a%3b%3c%3d%3e%3f%40%41%42%43%44%45%46%47%48%49%4a%4b%4c%4d%4e%4f%50%51%52%53%54%55%56%57%58%59%5a%5b%5c%5d%5e%5f%60%61%62%63%64%65%66%67%68%69%6a%6b%6c%6d%6e%6f%70%71%72%73%74%75%76%77%78%79%7a%7b%7c%7d%7e%7f%80%81%82%83%84%85%86%87%88%89%8a%8b%8c%8d%8e%8f%90%91%92%93%94%95%96%97%98%99%9a%9b%9c%9d%9e%9f%a0%a1%a2%a3%a4%a5%a6%a7%a8%a9%aa%ab%ac%ad%ae%af%b0%b1%b2%b3%b4%b5%b6%b7%b8%b9%ba%bb%bc%bd%be%bf%c0%c1%c2%c3%c4%c5%c6%c7%c8%c9%ca%cb%cc%cd%ce%cf%d0%d1%d2%d3%d4%d5%d6%d7%d8%d9%da%db%dc%dd%de%df%e0%e1%e2%e3%e4%e5%e6%e7%e8%e9%ea%eb%ec%ed%ee%ef%f0%f1%f2%f3%f4%f5%f6%f7%f8%f9%fa%fb%fc%fd%fe%ff%0%1%2%3%4%5%6%7%8%9%A%B%C%D%E%F%10%11%12%13%14%15%16%17%18%19%1A%1B%1C%1D%1E%1F%20%21%22%23%24%25%26%27%28%29%2A%2B%2C%2D%2E%2F%30%31%32%33%34%35%36%37%38%39%3A%3B%3C%3D%3E%3F%40%41%42%43%44%45%46%47%48%49%4A%4B%4C%4D%4E%4F%50%51%52%53%54%55%56%57%58%59%5A%5B%5C%5D%5E%5F%60%61%62%63%64%65%66%67%68%69%6A%6B%6C%6D%6E%6F%70%71%72%73%74%75%76%77%78%79%7A%7B%7C%7D%7E%7F%80%81%82%83%84%85%86%87%88%89%8A%8B%8C%8D%8E%8F%90%91%92%93%94%95%96%97%98%99%9A%9B%9C%9D%9E%9F%A0%A1%A2%A3%A4%A5%A6%A7%A8%A9%AA%AB%AC%AD%AE%AF%B0%B1%B2%B3%B4%B5%B6%B7%B8%B9%BA%BB%BC%BD%BE%BF%C0%C1%C2%C3%C4%C5%C6%C7%C8%C9%CA%CB%CC%CD%CE%CF%D0%D1%D2%D3%D4%D5%D6%D7%D8%D9%DA%DB%DC%DD%DE%DF%E0%E1%E2%E3%E4%E5%E6%E7%E8%E9%EA%EB%EC%ED%EE%EF%F0%F1%F2%F3%F4%F5%F6%F7%F8%F9%FA%FB%FC%FD%FE%FF';
1270
+ const normPath = '/%0%1%2%3%4%5%6%7%8%9%a%b%c%d%e%f%10%11%12%13%14%15%16%17%18%19%1A%1B%1C%1D%1E%1F%20!%22%23$%25&\'()*+,-.%2F0123456789:;%3C=%3E%3F@ABCDEFGHIJKLMNOPQRSTUVWXYZ%5B%5C%5D%5E_%60abcdefghijklmnopqrstuvwxyz%7B%7C%7D~%7F%80%81%82%83%84%85%86%87%88%89%8A%8B%8C%8D%8E%8F%90%91%92%93%94%95%96%97%98%99%9A%9B%9C%9D%9E%9F%A0%A1%A2%A3%A4%A5%A6%A7%A8%A9%AA%AB%AC%AD%AE%AF%B0%B1%B2%B3%B4%B5%B6%B7%B8%B9%BA%BB%BC%BD%BE%BF%C0%C1%C2%C3%C4%C5%C6%C7%C8%C9%CA%CB%CC%CD%CE%CF%D0%D1%D2%D3%D4%D5%D6%D7%D8%D9%DA%DB%DC%DD%DE%DF%E0%E1%E2%E3%E4%E5%E6%E7%E8%E9%EA%EB%EC%ED%EE%EF%F0%F1%F2%F3%F4%F5%F6%F7%F8%F9%FA%FB%FC%FD%FE%FF%0%1%2%3%4%5%6%7%8%9%A%B%C%D%E%F%10%11%12%13%14%15%16%17%18%19%1A%1B%1C%1D%1E%1F%20!%22%23$%25&\'()*+,-.%2F0123456789:;%3C=%3E%3F@ABCDEFGHIJKLMNOPQRSTUVWXYZ%5B%5C%5D%5E_%60abcdefghijklmnopqrstuvwxyz%7B%7C%7D~%7F%80%81%82%83%84%85%86%87%88%89%8A%8B%8C%8D%8E%8F%90%91%92%93%94%95%96%97%98%99%9A%9B%9C%9D%9E%9F%A0%A1%A2%A3%A4%A5%A6%A7%A8%A9%AA%AB%AC%AD%AE%AF%B0%B1%B2%B3%B4%B5%B6%B7%B8%B9%BA%BB%BC%BD%BE%BF%C0%C1%C2%C3%C4%C5%C6%C7%C8%C9%CA%CB%CC%CD%CE%CF%D0%D1%D2%D3%D4%D5%D6%D7%D8%D9%DA%DB%DC%DD%DE%DF%E0%E1%E2%E3%E4%E5%E6%E7%E8%E9%EA%EB%EC%ED%EE%EF%F0%F1%F2%F3%F4%F5%F6%F7%F8%F9%FA%FB%FC%FD%FE%FF';
1271
+
1272
+ const url = 'http://localhost' + rawPath + '?param1=something';
1273
+ const normUrl = 'http://localhost' + normPath + '?param1=something';
1274
+
1275
+ const server = Hapi.server();
1276
+ server.route({ method: 'GET', path: '/', handler: () => null });
1277
+
1278
+ const onRequest = (request, h) => {
1279
+
1280
+ request.setUrl(url);
1281
+ return h.response([request.url.href, request.path, request.url.searchParams.get('param1')].join('|')).takeover();
1282
+ };
1283
+
1284
+ server.ext('onRequest', onRequest);
1285
+
1286
+ const res = await server.inject('/');
1287
+ expect(res.payload).to.equal(normUrl + '|' + normPath + '|something');
1288
+ });
1289
+
1290
+ it('errors on empty path', async () => {
1291
+
1292
+ const server = Hapi.server({ debug: false });
1293
+ const onRequest = (request, h) => {
1294
+
1295
+ request.setUrl('');
1296
+ return h.continue;
1297
+ };
1298
+
1299
+ server.ext('onRequest', onRequest);
1300
+
1301
+ const res = await server.inject('/');
1302
+ expect(res.statusCode).to.equal(500);
1303
+ });
1304
+
1305
+ it('throws when path is missing', async () => {
1306
+
1307
+ const server = Hapi.server();
1308
+ const onRequest = (request, h) => {
1309
+
1310
+ try {
1311
+ request.setUrl();
1312
+ }
1313
+ catch (err) {
1314
+ return h.response(err.message).takeover();
1315
+ }
1316
+
1317
+ return h.continue;
1318
+ };
1319
+
1320
+ server.ext('onRequest', onRequest);
1321
+
1322
+ const res = await server.inject('/');
1323
+ expect(res.statusCode).to.equal(200);
1324
+ expect(res.payload).to.equal('Url must be a string or URL object');
1325
+ });
1326
+
1327
+ it('strips trailing slash', async () => {
1328
+
1329
+ const server = Hapi.server({ router: { stripTrailingSlash: true } });
1330
+ server.route({ method: 'GET', path: '/test', handler: () => null });
1331
+
1332
+ const res1 = await server.inject('/test/');
1333
+ expect(res1.statusCode).to.equal(204);
1334
+
1335
+ const res2 = await server.inject('/test');
1336
+ expect(res2.statusCode).to.equal(204);
1337
+ });
1338
+
1339
+ it('does not strip trailing slash on /', async () => {
1340
+
1341
+ const server = Hapi.server({ router: { stripTrailingSlash: true } });
1342
+ server.route({ method: 'GET', path: '/', handler: () => null });
1343
+ const res = await server.inject('/');
1344
+ expect(res.statusCode).to.equal(204);
1345
+ });
1346
+
1347
+ it('strips trailing slash with query', async () => {
1348
+
1349
+ const server = Hapi.server({ router: { stripTrailingSlash: true } });
1350
+ server.route({ method: 'GET', path: '/test', handler: () => null });
1351
+ const res = await server.inject('/test/?a=b');
1352
+ expect(res.statusCode).to.equal(204);
1353
+ });
1354
+
1355
+ it('clones passed url', async () => {
1356
+
1357
+ const urlObject = new Url.URL('http:/%41');
1358
+ let requestUrl;
1359
+
1360
+ const server = Hapi.server();
1361
+ const onRequest = (request, h) => {
1362
+
1363
+ request.setUrl(urlObject);
1364
+ requestUrl = request.url;
1365
+
1366
+ return h.continue;
1367
+ };
1368
+
1369
+ server.ext('onRequest', onRequest);
1370
+
1371
+ const res = await server.inject('/');
1372
+ expect(res.statusCode).to.equal(404);
1373
+ expect(requestUrl).to.equal(urlObject);
1374
+ expect(requestUrl).to.not.shallow.equal(urlObject);
1375
+ });
1376
+
1377
+ it('handles vhost redirection', async () => {
1378
+
1379
+ const server = Hapi.server();
1380
+ server.route({ method: 'GET', path: '/', vhost: 'one', handler: () => 'success' });
1381
+
1382
+ const onRequest = (request, h) => {
1383
+
1384
+ request.setUrl('http://one/');
1385
+ return h.continue;
1386
+ };
1387
+
1388
+ server.ext('onRequest', onRequest);
1389
+
1390
+ const res = await server.inject('/');
1391
+ expect(res.statusCode).to.equal(200);
1392
+ expect(res.payload).to.equal('success');
1393
+ });
1394
+
1395
+ it('handles hostname in HTTP request resource', async () => {
1396
+
1397
+ const server = Hapi.server({ debug: false });
1398
+ const team = new Teamwork.Team();
1399
+
1400
+ let hostname;
1401
+ server.route({
1402
+ method: 'GET',
1403
+ path: '/',
1404
+ handler: (request) => {
1405
+
1406
+ hostname = request.info.hostname;
1407
+ team.attend();
1408
+ return null;
1409
+ }
1410
+ });
1411
+
1412
+ await server.start();
1413
+ const socket = Net.createConnection(server.info.port, '127.0.0.1', () => socket.write('GET http://host.com\r\n\r\n'));
1414
+ await team.work;
1415
+ socket.destroy();
1416
+ await server.stop();
1417
+ expect(hostname).to.equal('host.com');
1418
+ });
1419
+
1420
+ it('handles url starting with multiple /', async () => {
1421
+
1422
+ const server = Hapi.server();
1423
+ server.route({
1424
+ method: 'GET',
1425
+ path: '/{p*}',
1426
+ handler: (request) => {
1427
+
1428
+ return {
1429
+ p: request.params.p,
1430
+ path: request.path,
1431
+ hostname: request.info.hostname.toLowerCase() // Lowercase for OSX tests
1432
+ };
1433
+ }
1434
+ });
1435
+
1436
+ const res = await server.inject('//path');
1437
+ expect(res.statusCode).to.equal(200);
1438
+ expect(res.result).to.equal({ p: '/path', path: '//path', hostname: server.info.host.toLowerCase() });
1439
+ });
1440
+
1441
+ it('handles escaped path segments', async () => {
1442
+
1443
+ const server = Hapi.server();
1444
+ server.route({ path: '/%2F/%2F', method: 'GET', handler: (request) => request.path });
1445
+
1446
+ const tests = [
1447
+ ['/', 404],
1448
+ ['////', 404],
1449
+ ['/%2F/%2F', 200, '/%2F/%2F'],
1450
+ ['/%2F/%2F#x', 200, '/%2F/%2F'],
1451
+ ['/%2F/%2F?a=1#x', 200, '/%2F/%2F']
1452
+ ];
1453
+
1454
+ for (const [uri, code, result] of tests) {
1455
+ const res = await server.inject(uri);
1456
+ expect(res.statusCode).to.equal(code);
1457
+
1458
+ if (code < 400) {
1459
+ expect(res.result).to.equal(result);
1460
+ }
1461
+ }
1462
+ });
1463
+
1464
+ it('handles fragments (no query)', async () => {
1465
+
1466
+ const server = Hapi.server();
1467
+ server.route({ method: 'GET', path: '/{p*}', handler: (request) => request.path });
1468
+
1469
+ await server.start();
1470
+
1471
+ const options = {
1472
+ hostname: 'localhost',
1473
+ port: server.info.port,
1474
+ path: '/path#ignore',
1475
+ method: 'GET'
1476
+ };
1477
+
1478
+ const team = new Teamwork.Team();
1479
+ const req = Http.request(options, (res) => team.attend(res));
1480
+ req.end();
1481
+
1482
+ const res = await team.work;
1483
+ const payload = await Wreck.read(res);
1484
+ expect(payload.toString()).to.equal('/path');
1485
+
1486
+ await server.stop();
1487
+ });
1488
+
1489
+ it('handles fragments (with query)', async () => {
1490
+
1491
+ const server = Hapi.server();
1492
+ server.route({ method: 'GET', path: '/{p*}', handler: (request) => request.query.a });
1493
+
1494
+ await server.start();
1495
+
1496
+ const options = {
1497
+ hostname: 'localhost',
1498
+ port: server.info.port,
1499
+ path: '/path?a=1#ignore',
1500
+ method: 'GET'
1501
+ };
1502
+
1503
+ const team = new Teamwork.Team();
1504
+ const req = Http.request(options, (res) => team.attend(res));
1505
+ req.end();
1506
+
1507
+ const res = await team.work;
1508
+ const payload = await Wreck.read(res);
1509
+ expect(payload.toString()).to.equal('1');
1510
+
1511
+ await server.stop();
1512
+ });
1513
+
1514
+ it('handles fragments with ? (no query)', async () => {
1515
+
1516
+ const server = Hapi.server();
1517
+ server.route({ method: 'GET', path: '/{p*}', handler: (request) => request.path });
1518
+
1519
+ await server.start();
1520
+
1521
+ const options = {
1522
+ hostname: 'localhost',
1523
+ port: server.info.port,
1524
+ path: '/path#ignore?x',
1525
+ method: 'GET'
1526
+ };
1527
+
1528
+ const team = new Teamwork.Team();
1529
+ const req = Http.request(options, (res) => team.attend(res));
1530
+ req.end();
1531
+
1532
+ const res = await team.work;
1533
+ const payload = await Wreck.read(res);
1534
+ expect(payload.toString()).to.equal('/path');
1535
+
1536
+ await server.stop();
1537
+ });
1538
+
1539
+ it('handles absolute URL (proxy)', async () => {
1540
+
1541
+ const server = Hapi.server();
1542
+ server.route({ method: 'GET', path: '/{p*}', handler: (request) => request.query.a.join() });
1543
+
1544
+ await server.start();
1545
+
1546
+ const options = {
1547
+ hostname: 'localhost',
1548
+ port: server.info.port,
1549
+ path: 'http://example.com/path?a=1&a=2#ignore',
1550
+ method: 'GET'
1551
+ };
1552
+
1553
+ const team = new Teamwork.Team();
1554
+ const req = Http.request(options, (res) => team.attend(res));
1555
+ req.end();
1556
+
1557
+ const res = await team.work;
1558
+ const payload = await Wreck.read(res);
1559
+ expect(payload.toString()).to.equal('1,2');
1560
+
1561
+ await server.stop();
1562
+ });
1563
+ });
1564
+
1565
+ describe('url', () => {
1566
+
1567
+ it('generates URL object lazily', async () => {
1568
+
1569
+ const server = Hapi.server();
1570
+
1571
+ const handler = (request) => {
1572
+
1573
+ expect(request._url).to.not.exist();
1574
+ return request.url.pathname;
1575
+ };
1576
+
1577
+ server.route({ path: '/test', method: 'GET', handler });
1578
+ const res = await server.inject('/test?a=1');
1579
+ expect(res.statusCode).to.equal(200);
1580
+ expect(res.result).to.equal('/test');
1581
+ });
1582
+
1583
+ it('generates URL object lazily (no host header)', async () => {
1584
+
1585
+ const server = Hapi.server();
1586
+
1587
+ const handler = (request) => {
1588
+
1589
+ delete request.info.host;
1590
+ expect(request._url).to.not.exist();
1591
+ return request.url.pathname;
1592
+ };
1593
+
1594
+ server.route({ path: '/test', method: 'GET', handler });
1595
+ const res = await server.inject('/test?a=1');
1596
+ expect(res.statusCode).to.equal(200);
1597
+ expect(res.result).to.equal('/test');
1598
+ });
1599
+ });
1600
+
1601
+ describe('_tap()', () => {
1602
+
1603
+ it('listens to request payload read finish', async () => {
1604
+
1605
+ let finish;
1606
+ const ext = (request, h) => {
1607
+
1608
+ finish = request.events.once('finish');
1609
+ return h.continue;
1610
+ };
1611
+
1612
+ const server = Hapi.server();
1613
+ server.ext('onRequest', ext);
1614
+ server.route({ method: 'POST', path: '/', options: { handler: () => null, payload: { parse: false } } });
1615
+
1616
+ const payload = '0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789';
1617
+ await server.inject({ method: 'POST', url: '/', payload });
1618
+ await finish;
1619
+ });
1620
+
1621
+ it('ignores emitter when created for other events', async () => {
1622
+
1623
+ const ext = (request, h) => {
1624
+
1625
+ request.events;
1626
+ return h.continue;
1627
+ };
1628
+
1629
+ const server = Hapi.server();
1630
+ server.ext('onRequest', ext);
1631
+ server.route({ method: 'POST', path: '/', options: { handler: () => null, payload: { parse: false } } });
1632
+
1633
+ const payload = '0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789';
1634
+ await server.inject({ method: 'POST', url: '/', payload });
1635
+ });
1636
+ });
1637
+
1638
+ describe('log()', () => {
1639
+
1640
+ it('outputs log data to debug console', async () => {
1641
+
1642
+ const handler = (request) => {
1643
+
1644
+ request.log(['implementation'], 'data');
1645
+ return null;
1646
+ };
1647
+
1648
+ const server = Hapi.server();
1649
+ server.route({ method: 'GET', path: '/', handler });
1650
+
1651
+ const log = new Promise((resolve) => {
1652
+
1653
+ const orig = console.error;
1654
+ console.error = function (...args) {
1655
+
1656
+ expect(args[0]).to.equal('Debug:');
1657
+ expect(args[1]).to.equal('implementation');
1658
+ expect(args[2]).to.equal('\n data');
1659
+ console.error = orig;
1660
+ resolve();
1661
+ };
1662
+ });
1663
+
1664
+ const res = await server.inject('/');
1665
+ expect(res.statusCode).to.equal(204);
1666
+ await log;
1667
+ });
1668
+
1669
+ it('emits a request event', async () => {
1670
+
1671
+ const server = Hapi.server();
1672
+
1673
+ const handler = async (request) => {
1674
+
1675
+ const log = server.events.once({ name: 'request', channels: 'app' });
1676
+ request.log(['test'], 'data');
1677
+ const [, event, tags] = await log;
1678
+ expect(event).to.contain(['request', 'timestamp', 'tags', 'data', 'channel']);
1679
+ expect(event.data).to.equal('data');
1680
+ expect(event.channel).to.equal('app');
1681
+ expect(tags).to.equal({ test: true });
1682
+ return null;
1683
+ };
1684
+
1685
+ server.route({ method: 'GET', path: '/', handler });
1686
+
1687
+ const res = await server.inject('/');
1688
+ expect(res.statusCode).to.equal(204);
1689
+ });
1690
+
1691
+ it('emits a request event (function data + collect)', async () => {
1692
+
1693
+ const server = Hapi.server({ routes: { log: { collect: true } } });
1694
+
1695
+ const handler = async (request) => {
1696
+
1697
+ const log = server.events.once('request');
1698
+ request.log(['test'], () => 'data');
1699
+
1700
+ const [, event, tags] = await log;
1701
+ expect(event).to.contain(['request', 'timestamp', 'tags', 'data', 'channel']);
1702
+ expect(event.data).to.equal('data');
1703
+ expect(event.channel).to.equal('app');
1704
+ expect(tags).to.equal({ test: true });
1705
+ expect(request.logs[0].data).to.equal('data');
1706
+ return null;
1707
+ };
1708
+
1709
+ server.route({ method: 'GET', path: '/', handler });
1710
+
1711
+ const res = await server.inject('/');
1712
+ expect(res.statusCode).to.equal(204);
1713
+ });
1714
+
1715
+ it('emits a request event (function data)', async () => {
1716
+
1717
+ const server = Hapi.server();
1718
+
1719
+ const handler = async (request) => {
1720
+
1721
+ const log = server.events.once('request');
1722
+ request.log(['test'], () => 'data');
1723
+
1724
+ const [, event, tags] = await log;
1725
+ expect(event).to.contain(['request', 'timestamp', 'tags', 'data', 'channel']);
1726
+ expect(event.data).to.equal('data');
1727
+ expect(event.channel).to.equal('app');
1728
+ expect(tags).to.equal({ test: true });
1729
+ return null;
1730
+ };
1731
+
1732
+ server.route({ method: 'GET', path: '/', handler });
1733
+
1734
+ const res = await server.inject('/');
1735
+ expect(res.statusCode).to.equal(204);
1736
+ });
1737
+
1738
+ it('outputs log to debug console without data', async () => {
1739
+
1740
+ const handler = (request) => {
1741
+
1742
+ request.log(['implementation']);
1743
+ return null;
1744
+ };
1745
+
1746
+ const server = Hapi.server();
1747
+ server.route({ method: 'GET', path: '/', handler });
1748
+
1749
+ const log = new Promise((resolve) => {
1750
+
1751
+ const orig = console.error;
1752
+ console.error = function (...args) {
1753
+
1754
+ expect(args[0]).to.equal('Debug:');
1755
+ expect(args[1]).to.equal('implementation');
1756
+ expect(args[2]).to.equal('');
1757
+ console.error = orig;
1758
+ resolve();
1759
+ };
1760
+ });
1761
+
1762
+ const res = await server.inject('/');
1763
+ expect(res.statusCode).to.equal(204);
1764
+ await log;
1765
+ });
1766
+
1767
+ it('outputs log to debug console with error data', async () => {
1768
+
1769
+ const handler = (request) => {
1770
+
1771
+ request.log(['implementation'], new Error('boom'));
1772
+ return null;
1773
+ };
1774
+
1775
+ const server = Hapi.server();
1776
+ server.route({ method: 'GET', path: '/', handler });
1777
+
1778
+ const log = new Promise((resolve) => {
1779
+
1780
+ const orig = console.error;
1781
+ console.error = function (...args) {
1782
+
1783
+ expect(args[0]).to.equal('Debug:');
1784
+ expect(args[1]).to.equal('implementation');
1785
+ expect(args[2]).to.contain('Error: boom');
1786
+ console.error = orig;
1787
+ resolve();
1788
+ };
1789
+ });
1790
+
1791
+ const res = await server.inject('/');
1792
+ expect(res.statusCode).to.equal(204);
1793
+ await log;
1794
+ });
1795
+
1796
+ it('handles invalid log data object stringify', async () => {
1797
+
1798
+ const handler = (request) => {
1799
+
1800
+ const obj = {};
1801
+ obj.a = obj;
1802
+
1803
+ request.log(['implementation'], obj);
1804
+ return null;
1805
+ };
1806
+
1807
+ const server = Hapi.server({ routes: { log: { collect: true } } });
1808
+ server.route({ method: 'GET', path: '/', handler });
1809
+
1810
+ const log = new Promise((resolve) => {
1811
+
1812
+ const orig = console.error;
1813
+ console.error = function (...args) {
1814
+
1815
+ expect(args[0]).to.equal('Debug:');
1816
+ expect(args[1]).to.equal('implementation');
1817
+ expect(args[2]).to.match(/Cannot display object: Converting circular structure to JSON/);
1818
+ console.error = orig;
1819
+ resolve();
1820
+ };
1821
+ });
1822
+
1823
+ const res = await server.inject('/');
1824
+ expect(res.statusCode).to.equal(204);
1825
+ await log;
1826
+ });
1827
+
1828
+ it('adds a log event to the request', async () => {
1829
+
1830
+ const handler = (request) => {
1831
+
1832
+ request.log('1', 'log event 1');
1833
+ request.log(['2'], 'log event 2');
1834
+ request.log(['3', '4']);
1835
+ request.log(['1', '4']);
1836
+ request.log(['2', '3']);
1837
+ request.log(['4']);
1838
+ request.log('4');
1839
+
1840
+ return request.logs.map((event) => event.tags).join('|');
1841
+ };
1842
+
1843
+ const server = Hapi.server({ routes: { log: { collect: true } } });
1844
+ server.route({ method: 'GET', path: '/', handler });
1845
+
1846
+ const res = await server.inject('/');
1847
+ expect(res.payload).to.equal('1|2|3,4|1,4|2,3|4|4');
1848
+ });
1849
+
1850
+ it('does not output events when debug disabled', async () => {
1851
+
1852
+ const server = Hapi.server({ debug: false });
1853
+
1854
+ let i = 0;
1855
+ const orig = console.error;
1856
+ console.error = function () {
1857
+
1858
+ ++i;
1859
+ };
1860
+
1861
+ const handler = (request) => {
1862
+
1863
+ request.log(['implementation']);
1864
+ return null;
1865
+ };
1866
+
1867
+ server.route({ method: 'GET', path: '/', handler });
1868
+
1869
+ await server.inject('/');
1870
+ console.error('nothing');
1871
+ expect(i).to.equal(1);
1872
+ console.error = orig;
1873
+ });
1874
+
1875
+ it('does not output events when debug.request disabled', async () => {
1876
+
1877
+ const server = Hapi.server({ debug: { request: false } });
1878
+
1879
+ let i = 0;
1880
+ const orig = console.error;
1881
+ console.error = function () {
1882
+
1883
+ ++i;
1884
+ };
1885
+
1886
+ const handler = (request) => {
1887
+
1888
+ request.log(['implementation']);
1889
+ return null;
1890
+ };
1891
+
1892
+ server.route({ method: 'GET', path: '/', handler });
1893
+
1894
+ await server.inject('/');
1895
+ console.error('nothing');
1896
+ expect(i).to.equal(1);
1897
+ console.error = orig;
1898
+ });
1899
+
1900
+ it('does not output non-implementation events by default', async () => {
1901
+
1902
+ const server = Hapi.server();
1903
+
1904
+ let i = 0;
1905
+ const orig = console.error;
1906
+ console.error = function () {
1907
+
1908
+ ++i;
1909
+ };
1910
+
1911
+ const handler = (request) => {
1912
+
1913
+ request.log(['xyz']);
1914
+ return null;
1915
+ };
1916
+
1917
+ server.route({ method: 'GET', path: '/', handler });
1918
+
1919
+ await server.inject('/');
1920
+ console.error('nothing');
1921
+ expect(i).to.equal(1);
1922
+ console.error = orig;
1923
+ });
1924
+
1925
+ it('logs nothing', async () => {
1926
+
1927
+ const server = Hapi.server({ debug: false, routes: { log: { collect: false } } });
1928
+
1929
+ const handler = (request) => {
1930
+
1931
+ expect(request.logs).to.have.length(0);
1932
+ return request.info.acceptEncoding;
1933
+ };
1934
+
1935
+ server.route({ method: 'GET', path: '/', handler });
1936
+
1937
+ const res = await server.inject({ url: '/', headers: { 'accept-encoding': 'a;b' } });
1938
+ expect(res.result).to.equal('identity');
1939
+ });
1940
+
1941
+ it('logs when only collect is true', async () => {
1942
+
1943
+ const server = Hapi.server({ debug: false, routes: { log: { collect: true } } });
1944
+
1945
+ const handler = (request) => {
1946
+
1947
+ expect(request.logs).to.have.length(1);
1948
+ return request.info.acceptEncoding;
1949
+ };
1950
+
1951
+ server.route({ method: 'GET', path: '/', handler });
1952
+
1953
+ const res = await server.inject({ url: '/', headers: { 'accept-encoding': 'a;b' } });
1954
+ expect(res.result).to.equal('identity');
1955
+ });
1956
+ });
1957
+
1958
+ describe('_setResponse()', () => {
1959
+
1960
+ it('leaves the response open when the same response is set again', async () => {
1961
+
1962
+ const server = Hapi.server();
1963
+ const postHandler = (request) => {
1964
+
1965
+ return request.response;
1966
+ };
1967
+
1968
+ server.ext('onPostHandler', postHandler);
1969
+
1970
+ const handler = (request) => {
1971
+
1972
+ const stream = new Stream.Readable();
1973
+ stream._read = function (size) {
1974
+
1975
+ this.push('value');
1976
+ this.push(null);
1977
+ };
1978
+
1979
+ return stream;
1980
+ };
1981
+
1982
+ server.route({ method: 'GET', path: '/', handler });
1983
+
1984
+ const res = await server.inject('/');
1985
+ expect(res.result).to.equal('value');
1986
+ });
1987
+
1988
+ it('leaves the response open when the same response source is set again', async () => {
1989
+
1990
+ const server = Hapi.server();
1991
+ server.ext('onPostHandler', (request) => request.response.source);
1992
+
1993
+ const handler = (request) => {
1994
+
1995
+ const stream = new Stream.Readable();
1996
+ stream._read = function (size) {
1997
+
1998
+ this.push('value');
1999
+ this.push(null);
2000
+ };
2001
+
2002
+ return stream;
2003
+ };
2004
+
2005
+ server.route({ method: 'GET', path: '/', handler });
2006
+
2007
+ const res = await server.inject('/');
2008
+ expect(res.result).to.equal('value');
2009
+ });
2010
+ });
2011
+
2012
+ describe('timeout', () => {
2013
+
2014
+ it('returns server error message when server taking too long', async () => {
2015
+
2016
+ const handler = async (request) => {
2017
+
2018
+ await Hoek.wait(100);
2019
+ return 'too slow';
2020
+ };
2021
+
2022
+ const server = Hapi.server({ routes: { timeout: { server: 50 } } });
2023
+ server.route({ method: 'GET', path: '/timeout', handler });
2024
+
2025
+ const timer = new Hoek.Bench();
2026
+
2027
+ const res = await server.inject('/timeout');
2028
+ expect(res.statusCode).to.equal(503);
2029
+ expect(timer.elapsed()).to.be.at.least(49);
2030
+ });
2031
+
2032
+ it('returns server error message when server timeout happens during request execution (and handler yields)', async () => {
2033
+
2034
+ const handler = async (request) => {
2035
+
2036
+ await Hoek.wait(20);
2037
+ return null;
2038
+ };
2039
+
2040
+ const server = Hapi.server({ routes: { timeout: { server: 10 } } });
2041
+ server.route({ method: 'GET', path: '/', options: { handler } });
2042
+
2043
+ const postHandler = (request, h) => {
2044
+
2045
+ return h.continue;
2046
+ };
2047
+
2048
+ server.ext('onPostHandler', postHandler);
2049
+
2050
+ const res = await server.inject('/');
2051
+ expect(res.statusCode).to.equal(503);
2052
+ });
2053
+
2054
+ it('returns server error message when server timeout is short and already occurs when request executes', async () => {
2055
+
2056
+ const server = Hapi.server({ routes: { timeout: { server: 2 } } });
2057
+ server.route({ method: 'GET', path: '/', options: { handler: function () { } } });
2058
+ const onRequest = async (request, h) => {
2059
+
2060
+ await Hoek.wait(10);
2061
+ return h.continue;
2062
+ };
2063
+
2064
+ server.ext('onRequest', onRequest);
2065
+
2066
+ const res = await server.inject('/');
2067
+ expect(res.statusCode).to.equal(503);
2068
+ });
2069
+
2070
+ it('handles server handler timeout with onPreResponse ext', async () => {
2071
+
2072
+ const handler = async (request) => {
2073
+
2074
+ await Hoek.wait(20);
2075
+ return null;
2076
+ };
2077
+
2078
+ const server = Hapi.server({ routes: { timeout: { server: 10 } } });
2079
+ server.route({ method: 'GET', path: '/', options: { handler } });
2080
+ const preResponse = (request, h) => {
2081
+
2082
+ return h.continue;
2083
+ };
2084
+
2085
+ server.ext('onPreResponse', preResponse);
2086
+
2087
+ const res = await server.inject('/');
2088
+ expect(res.statusCode).to.equal(503);
2089
+ });
2090
+
2091
+ it('does not return an error response when server is slow but faster than timeout', async () => {
2092
+
2093
+ const slowHandler = async (request) => {
2094
+
2095
+ await Hoek.wait(30);
2096
+ return 'slow';
2097
+ };
2098
+
2099
+ const server = Hapi.server({ routes: { timeout: { server: 50 } } });
2100
+ server.route({ method: 'GET', path: '/slow', options: { handler: slowHandler } });
2101
+
2102
+ const timer = new Hoek.Bench();
2103
+ const res = await server.inject('/slow');
2104
+ expect(timer.elapsed()).to.be.at.least(20);
2105
+ expect(res.statusCode).to.equal(200);
2106
+ });
2107
+
2108
+ it('creates error response when request is aborted while draining payload', async () => {
2109
+
2110
+ const server = Hapi.server({ routes: { timeout: { server: false } } });
2111
+ await server.start();
2112
+
2113
+ const log = server.events.once('response');
2114
+ const ready = new Promise((resolve) => {
2115
+
2116
+ server.ext('onRequest', (request, h) => {
2117
+
2118
+ resolve();
2119
+ return h.continue;
2120
+ });
2121
+ });
2122
+
2123
+ const req = Http.request({
2124
+ hostname: 'localhost',
2125
+ port: server.info.port,
2126
+ method: 'GET',
2127
+ headers: { 'content-length': 42 }
2128
+ });
2129
+
2130
+ req.on('error', Hoek.ignore);
2131
+ req.flushHeaders();
2132
+
2133
+ await ready;
2134
+ req.abort();
2135
+ const [request] = await log;
2136
+
2137
+ expect(request.response.output.statusCode).to.equal(499);
2138
+
2139
+ await server.stop({ timeout: 1 });
2140
+ });
2141
+
2142
+ it('returns an unlogged bad request error when parser fails before request is setup', async () => {
2143
+
2144
+ const server = Hapi.server({ routes: { timeout: { server: false } } });
2145
+ await server.start();
2146
+
2147
+ let responseCount = 0;
2148
+ server.events.on('response', () => {
2149
+
2150
+ responseCount += 1;
2151
+ });
2152
+
2153
+ const client = Net.connect(server.info.port);
2154
+ const clientEnded = new Promise((resolve, reject) => {
2155
+
2156
+ let response = '';
2157
+ client.on('data', (chunk) => {
2158
+
2159
+ response = response + chunk.toString();
2160
+ });
2161
+
2162
+ client.on('end', () => resolve(response));
2163
+ client.on('error', reject);
2164
+ });
2165
+
2166
+ await new Promise((resolve) => client.on('connect', resolve));
2167
+ client.write('hello\n\r');
2168
+
2169
+ const clientResponse = await clientEnded;
2170
+ expect(clientResponse).to.contain('400 Bad Request');
2171
+ expect(responseCount).to.equal(0);
2172
+
2173
+ await server.stop({ timeout: 1 });
2174
+ });
2175
+
2176
+ it('returns a logged bad request error when parser fails after request is setup', async () => {
2177
+
2178
+ const server = Hapi.server({ routes: { timeout: { server: false } } });
2179
+ server.route({ path: '/', method: 'GET', handler: Hoek.block });
2180
+ await server.start();
2181
+
2182
+ const log = server.events.once('response');
2183
+ const client = Net.connect(server.info.port);
2184
+ const clientEnded = new Promise((resolve, reject) => {
2185
+
2186
+ let response = '';
2187
+ client.on('data', (chunk) => {
2188
+
2189
+ response = response + chunk.toString();
2190
+ });
2191
+
2192
+ client.on('end', () => resolve(response));
2193
+ client.on('error', reject);
2194
+ });
2195
+
2196
+ await new Promise((resolve) => client.on('connect', resolve));
2197
+ client.write('GET / HTTP/1.1\nHost: test\nContent-Length: 0\n\n\ninvalid data');
2198
+
2199
+ const [request] = await log;
2200
+ expect(request.response.statusCode).to.equal(400);
2201
+ const clientResponse = await clientEnded;
2202
+ expect(clientResponse).to.contain('400 Bad Request');
2203
+
2204
+ await server.stop({ timeout: 1 });
2205
+ });
2206
+
2207
+ it('returns a logged bad request error when parser fails after request is setup (cleanStop false)', async () => {
2208
+
2209
+ const server = Hapi.server({ routes: { timeout: { server: false } }, operations: { cleanStop: false } });
2210
+ server.route({ path: '/', method: 'GET', handler: Hoek.block });
2211
+ await server.start();
2212
+
2213
+ const client = Net.connect(server.info.port);
2214
+ const clientEnded = new Promise((resolve, reject) => {
2215
+
2216
+ let response = '';
2217
+ client.on('data', (chunk) => {
2218
+
2219
+ response = response + chunk.toString();
2220
+ });
2221
+
2222
+ client.on('end', () => resolve(response));
2223
+ client.on('error', reject);
2224
+ });
2225
+
2226
+ await new Promise((resolve) => client.on('connect', resolve));
2227
+ client.write('GET / HTTP/1.1\nHost: test\nContent-Length: 0\n\n\ninvalid data');
2228
+
2229
+ const clientResponse = await clientEnded;
2230
+ expect(clientResponse).to.contain('400 Bad Request');
2231
+
2232
+ await server.stop({ timeout: 1 });
2233
+ });
2234
+
2235
+ it('does not return an error when server is responding when the timeout occurs', async () => {
2236
+
2237
+ let ended = false;
2238
+ const TestStream = class extends Stream.Readable {
2239
+
2240
+ _read(size) {
2241
+
2242
+ if (this.isDone) {
2243
+ return;
2244
+ }
2245
+
2246
+ this.isDone = true;
2247
+ this.push('Hello');
2248
+
2249
+ setTimeout(() => {
2250
+
2251
+ this.push(null);
2252
+ ended = true;
2253
+ }, 150);
2254
+ }
2255
+ };
2256
+
2257
+ const handler = (request) => {
2258
+
2259
+ return new TestStream();
2260
+ };
2261
+
2262
+ const timer = new Hoek.Bench();
2263
+
2264
+ const server = Hapi.server({ routes: { timeout: { server: 100 } } });
2265
+ server.route({ method: 'GET', path: '/', handler });
2266
+ await server.start();
2267
+ const { res } = await Wreck.get('http://localhost:' + server.info.port);
2268
+ expect(ended).to.be.true();
2269
+ expect(timer.elapsed()).to.be.at.least(150);
2270
+ expect(res.statusCode).to.equal(200);
2271
+ await server.stop({ timeout: 1 });
2272
+ });
2273
+
2274
+ it('does not return an error response when server is slower than timeout but response has started', async () => {
2275
+
2276
+ const streamHandler = (request) => {
2277
+
2278
+ const TestStream = class extends Stream.Readable {
2279
+
2280
+ _read(size) {
2281
+
2282
+ if (this.isDone) {
2283
+ return;
2284
+ }
2285
+
2286
+ this.isDone = true;
2287
+
2288
+ setTimeout(() => {
2289
+
2290
+ this.push('Hello');
2291
+ }, 30);
2292
+
2293
+ setTimeout(() => {
2294
+
2295
+ this.push(null);
2296
+ }, 60);
2297
+ }
2298
+ };
2299
+
2300
+ return new TestStream();
2301
+ };
2302
+
2303
+ const server = Hapi.server({ routes: { timeout: { server: 50 } } });
2304
+ server.route({ method: 'GET', path: '/stream', options: { handler: streamHandler } });
2305
+
2306
+ await server.start();
2307
+ const { res } = await Wreck.get(`http://localhost:${server.info.port}/stream`);
2308
+ expect(res.statusCode).to.equal(200);
2309
+ await server.stop({ timeout: 1 });
2310
+ });
2311
+
2312
+ it('does not return an error response when server takes less than timeout to respond', async () => {
2313
+
2314
+ const server = Hapi.server({ routes: { timeout: { server: 50 } } });
2315
+ server.route({ method: 'GET', path: '/fast', handler: () => 'Fast' });
2316
+
2317
+ const res = await server.inject('/fast');
2318
+ expect(res.statusCode).to.equal(200);
2319
+ });
2320
+
2321
+ it('handles race condition between equal client and server timeouts', async (flags) => {
2322
+
2323
+ const onCleanup = [];
2324
+ flags.onCleanup = async () => {
2325
+
2326
+ for (const cleanup of onCleanup) {
2327
+ await cleanup();
2328
+ }
2329
+ };
2330
+
2331
+ const server = Hapi.server({ routes: { timeout: { server: 100 }, payload: { timeout: 100 } } });
2332
+ server.route({ method: 'POST', path: '/timeout', options: { handler: Hoek.block } });
2333
+
2334
+ await server.start();
2335
+ onCleanup.unshift(() => server.stop());
2336
+
2337
+ const timer = new Hoek.Bench();
2338
+ const options = {
2339
+ hostname: 'localhost',
2340
+ port: server.info.port,
2341
+ path: '/timeout',
2342
+ method: 'POST'
2343
+ };
2344
+
2345
+ const req = Http.request(options);
2346
+ onCleanup.unshift(() => req.destroy());
2347
+
2348
+ req.write('\n');
2349
+
2350
+ const [res] = await Events.once(req, 'response');
2351
+
2352
+ expect([503, 408]).to.contain(res.statusCode);
2353
+ expect(timer.elapsed()).to.be.at.least(80);
2354
+
2355
+ await Events.once(req, 'close'); // Ensures that req closes without error
2356
+ });
2357
+ });
2358
+
2359
+ describe('event()', () => {
2360
+
2361
+ it('does not emit request error on normal close', async () => {
2362
+
2363
+ const server = Hapi.server();
2364
+ const events = [];
2365
+ server.events.on('request', (request, event, tags) => events.push(tags));
2366
+
2367
+ server.route({ method: 'GET', path: '/', handler: () => 'ok' });
2368
+
2369
+ await server.start();
2370
+
2371
+ const { payload } = await Wreck.get('http://localhost:' + server.info.port);
2372
+ expect(payload.toString()).to.equal('ok');
2373
+ await server.stop();
2374
+
2375
+ expect(events).to.have.length(0);
2376
+ });
2377
+ });
2378
+ });