@jaredwray/mockhttp 1.5.0 → 1.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js DELETED
@@ -1,4709 +0,0 @@
1
- var __defProp = Object.defineProperty;
2
- var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
3
-
4
- // src/index.ts
5
- import process2 from "process";
6
-
7
- // src/mock-http.ts
8
- import * as fsPromises2 from "fs/promises";
9
- import path from "path";
10
- import fastifyCookie from "@fastify/cookie";
11
- import fastifyHelmet from "@fastify/helmet";
12
- import fastifyRateLimit from "@fastify/rate-limit";
13
- import fastifyStatic from "@fastify/static";
14
- import { fastifySwagger } from "@fastify/swagger";
15
- import { detect } from "detect-port";
16
- import Fastify from "fastify";
17
- import { Hookified } from "hookified";
18
-
19
- // src/certificate.ts
20
- import * as crypto from "crypto";
21
- import * as fsPromises from "fs/promises";
22
- function encodeLength(length) {
23
- if (length < 128) {
24
- return Buffer.from([
25
- length
26
- ]);
27
- }
28
- const bytes = [];
29
- let temp = length;
30
- while (temp > 0) {
31
- bytes.unshift(temp & 255);
32
- temp >>= 8;
33
- }
34
- return Buffer.from([
35
- 128 | bytes.length,
36
- ...bytes
37
- ]);
38
- }
39
- __name(encodeLength, "encodeLength");
40
- function encodeTlv(tag, value) {
41
- return Buffer.concat([
42
- Buffer.from([
43
- tag
44
- ]),
45
- encodeLength(value.length),
46
- value
47
- ]);
48
- }
49
- __name(encodeTlv, "encodeTlv");
50
- function encodeSequence(...elements) {
51
- const content = Buffer.concat(elements);
52
- return encodeTlv(48, content);
53
- }
54
- __name(encodeSequence, "encodeSequence");
55
- function encodeSet(...elements) {
56
- const content = Buffer.concat(elements);
57
- return encodeTlv(49, content);
58
- }
59
- __name(encodeSet, "encodeSet");
60
- function encodeInteger(value) {
61
- let buf;
62
- if (typeof value === "number") {
63
- if (value === 0) {
64
- buf = Buffer.from([
65
- 0
66
- ]);
67
- } else {
68
- const bytes = [];
69
- let temp = value;
70
- while (temp > 0) {
71
- bytes.unshift(temp & 255);
72
- temp >>= 8;
73
- }
74
- if (bytes[0] & 128) {
75
- bytes.unshift(0);
76
- }
77
- buf = Buffer.from(bytes);
78
- }
79
- } else {
80
- if (value.length > 0 && value[0] & 128) {
81
- buf = Buffer.concat([
82
- Buffer.from([
83
- 0
84
- ]),
85
- value
86
- ]);
87
- } else {
88
- buf = value;
89
- }
90
- }
91
- return encodeTlv(2, buf);
92
- }
93
- __name(encodeInteger, "encodeInteger");
94
- function encodeBitString(value) {
95
- const content = Buffer.concat([
96
- Buffer.from([
97
- 0
98
- ]),
99
- value
100
- ]);
101
- return encodeTlv(3, content);
102
- }
103
- __name(encodeBitString, "encodeBitString");
104
- function encodeOctetString(value) {
105
- return encodeTlv(4, value);
106
- }
107
- __name(encodeOctetString, "encodeOctetString");
108
- function encodeOid(oid) {
109
- const bytes = [];
110
- bytes.push(40 * oid[0] + oid[1]);
111
- for (let i = 2; i < oid.length; i++) {
112
- let component = oid[i];
113
- if (component < 128) {
114
- bytes.push(component);
115
- } else {
116
- const encoded = [];
117
- encoded.push(component & 127);
118
- component >>= 7;
119
- while (component > 0) {
120
- encoded.push(component & 127 | 128);
121
- component >>= 7;
122
- }
123
- encoded.reverse();
124
- bytes.push(...encoded);
125
- }
126
- }
127
- return encodeTlv(6, Buffer.from(bytes));
128
- }
129
- __name(encodeOid, "encodeOid");
130
- function encodeUtf8String(value) {
131
- return encodeTlv(12, Buffer.from(value, "utf8"));
132
- }
133
- __name(encodeUtf8String, "encodeUtf8String");
134
- function encodeTime(date) {
135
- const fullYear = date.getUTCFullYear();
136
- const timePart = `${pad2(date.getUTCMonth() + 1)}${pad2(date.getUTCDate())}${pad2(date.getUTCHours())}${pad2(date.getUTCMinutes())}${pad2(date.getUTCSeconds())}Z`;
137
- if (fullYear >= 2050) {
138
- const str2 = `${fullYear}${timePart}`;
139
- return encodeTlv(24, Buffer.from(str2, "ascii"));
140
- }
141
- const str = `${pad2(fullYear % 100)}${timePart}`;
142
- return encodeTlv(23, Buffer.from(str, "ascii"));
143
- }
144
- __name(encodeTime, "encodeTime");
145
- function encodeContextSpecific(tag, value) {
146
- return encodeTlv(160 | tag, value);
147
- }
148
- __name(encodeContextSpecific, "encodeContextSpecific");
149
- function pad2(n) {
150
- return n.toString().padStart(2, "0");
151
- }
152
- __name(pad2, "pad2");
153
- var OID_SHA256_WITH_RSA = [
154
- 1,
155
- 2,
156
- 840,
157
- 113549,
158
- 1,
159
- 1,
160
- 11
161
- ];
162
- var OID_COMMON_NAME = [
163
- 2,
164
- 5,
165
- 4,
166
- 3
167
- ];
168
- var OID_SUBJECT_ALT_NAME = [
169
- 2,
170
- 5,
171
- 29,
172
- 17
173
- ];
174
- function buildAlgorithmIdentifier() {
175
- return encodeSequence(encodeOid(OID_SHA256_WITH_RSA), encodeTlv(5, Buffer.alloc(0)));
176
- }
177
- __name(buildAlgorithmIdentifier, "buildAlgorithmIdentifier");
178
- function buildName(cn) {
179
- const rdn = encodeSet(encodeSequence(encodeOid(OID_COMMON_NAME), encodeUtf8String(cn)));
180
- return encodeSequence(rdn);
181
- }
182
- __name(buildName, "buildName");
183
- function buildValidity(notBefore, notAfter) {
184
- return encodeSequence(encodeTime(notBefore), encodeTime(notAfter));
185
- }
186
- __name(buildValidity, "buildValidity");
187
- function encodeIpAddress(ip) {
188
- if (ip.includes(".")) {
189
- const parts = ip.split(".").map(Number);
190
- return Buffer.from(parts);
191
- }
192
- const expanded = expandIpv6(ip);
193
- const buf = Buffer.alloc(16);
194
- const groups = expanded.split(":");
195
- for (let i = 0; i < 8; i++) {
196
- const val = Number.parseInt(groups[i], 16);
197
- buf.writeUInt16BE(val, i * 2);
198
- }
199
- return buf;
200
- }
201
- __name(encodeIpAddress, "encodeIpAddress");
202
- function expandIpv6(ip) {
203
- if (ip.includes("::")) {
204
- const [left, right] = ip.split("::");
205
- const leftGroups = left ? left.split(":") : [];
206
- const rightGroups = right ? right.split(":") : [];
207
- const missing = 8 - leftGroups.length - rightGroups.length;
208
- const middle = Array.from({
209
- length: missing
210
- }).fill("0000");
211
- const allGroups = [
212
- ...leftGroups,
213
- ...middle,
214
- ...rightGroups
215
- ];
216
- return allGroups.map((g) => g.padStart(4, "0")).join(":");
217
- }
218
- return ip.split(":").map((g) => g.padStart(4, "0")).join(":");
219
- }
220
- __name(expandIpv6, "expandIpv6");
221
- function buildSubjectAltNameExtension(altNames) {
222
- const names = [];
223
- for (const alt of altNames) {
224
- if (alt.type === "dns") {
225
- names.push(encodeTlv(130, Buffer.from(alt.value, "ascii")));
226
- } else {
227
- names.push(encodeTlv(135, encodeIpAddress(alt.value)));
228
- }
229
- }
230
- const sanValue = encodeSequence(...names);
231
- return encodeSequence(encodeOid(OID_SUBJECT_ALT_NAME), encodeOctetString(sanValue));
232
- }
233
- __name(buildSubjectAltNameExtension, "buildSubjectAltNameExtension");
234
- function buildExtensions(altNames) {
235
- const extensions = encodeSequence(buildSubjectAltNameExtension(altNames));
236
- return encodeContextSpecific(3, extensions);
237
- }
238
- __name(buildExtensions, "buildExtensions");
239
- function buildTbsCertificate(serialNumber, issuerCn, notBefore, notAfter, subjectCn, publicKeyDer, altNames) {
240
- const version = encodeContextSpecific(0, encodeInteger(2));
241
- const serial = encodeInteger(serialNumber);
242
- const signatureAlgorithm = buildAlgorithmIdentifier();
243
- const issuer = buildName(issuerCn);
244
- const validity = buildValidity(notBefore, notAfter);
245
- const subject = buildName(subjectCn);
246
- const extensions = buildExtensions(altNames);
247
- return encodeSequence(version, serial, signatureAlgorithm, issuer, validity, subject, publicKeyDer, extensions);
248
- }
249
- __name(buildTbsCertificate, "buildTbsCertificate");
250
- function derToPem(der, label) {
251
- const base64 = der.toString("base64");
252
- const lines = [];
253
- for (let i = 0; i < base64.length; i += 64) {
254
- lines.push(base64.slice(i, i + 64));
255
- }
256
- return `-----BEGIN ${label}-----
257
- ${lines.join("\n")}
258
- -----END ${label}-----
259
- `;
260
- }
261
- __name(derToPem, "derToPem");
262
- function generateCertificate(options) {
263
- const commonName = options?.commonName ?? "localhost";
264
- const validityDays = options?.validityDays ?? 365;
265
- const keySize = options?.keySize ?? 2048;
266
- const altNames = options?.altNames ?? [
267
- {
268
- type: "dns",
269
- value: "localhost"
270
- },
271
- {
272
- type: "ip",
273
- value: "127.0.0.1"
274
- },
275
- {
276
- type: "ip",
277
- value: "::1"
278
- }
279
- ];
280
- const { publicKey, privateKey } = crypto.generateKeyPairSync("rsa", {
281
- modulusLength: keySize,
282
- publicKeyEncoding: {
283
- type: "spki",
284
- format: "der"
285
- },
286
- privateKeyEncoding: {
287
- type: "pkcs8",
288
- format: "pem"
289
- }
290
- });
291
- const notBefore = /* @__PURE__ */ new Date();
292
- const notAfter = /* @__PURE__ */ new Date();
293
- notAfter.setDate(notAfter.getDate() + validityDays);
294
- const serialBytes = crypto.randomBytes(16);
295
- serialBytes[0] &= 127;
296
- let start2 = 0;
297
- while (start2 < serialBytes.length - 1 && serialBytes[start2] === 0) {
298
- start2++;
299
- }
300
- const serialNumber = serialBytes.subarray(start2);
301
- const tbsCertificate = buildTbsCertificate(serialNumber, commonName, notBefore, notAfter, commonName, publicKey, altNames);
302
- const signer = crypto.createSign("SHA256");
303
- signer.update(tbsCertificate);
304
- const signature = signer.sign(privateKey);
305
- const certificate = encodeSequence(tbsCertificate, buildAlgorithmIdentifier(), encodeBitString(signature));
306
- return {
307
- cert: derToPem(certificate, "CERTIFICATE"),
308
- key: privateKey
309
- };
310
- }
311
- __name(generateCertificate, "generateCertificate");
312
- async function generateCertificateFiles(options) {
313
- const result = generateCertificate(options);
314
- await fsPromises.writeFile(options.certPath, result.cert, "utf8");
315
- await fsPromises.writeFile(options.keyPath, result.key, "utf8");
316
- return result;
317
- }
318
- __name(generateCertificateFiles, "generateCertificateFiles");
319
-
320
- // src/fastify-config.ts
321
- function getFastifyConfig(logging = true) {
322
- return {
323
- logger: logging ? {
324
- transport: {
325
- target: "pino-pretty",
326
- options: {
327
- colorize: true,
328
- translateTime: true,
329
- ignore: "pid,hostname",
330
- singleLine: true
331
- }
332
- }
333
- } : false
334
- };
335
- }
336
- __name(getFastifyConfig, "getFastifyConfig");
337
-
338
- // src/routes/anything/index.ts
339
- var anythingSchema = {
340
- description: "Will return anything that you send it",
341
- tags: [
342
- "Anything"
343
- ],
344
- response: {
345
- 200: {
346
- type: "object",
347
- properties: {
348
- // Echo back all your params under `args`
349
- args: {
350
- type: "object",
351
- description: "Route parameters",
352
- additionalProperties: true
353
- },
354
- // Echo back whatever was in the request.body
355
- data: {
356
- type: "object",
357
- description: "Request body data",
358
- additionalProperties: true
359
- },
360
- // If you’re using fastify-multipart, you’ll get
361
- // files via request.files(); this just allows
362
- // you to mirror them back
363
- files: {
364
- type: "object",
365
- description: "Uploaded files",
366
- additionalProperties: true
367
- },
368
- // If you parse application/x-www-form-urlencoded
369
- // these fields land in request.body too, but you can
370
- // mirror them separately if you like
371
- form: {
372
- type: "object",
373
- description: "Form data",
374
- additionalProperties: true
375
- },
376
- headers: {
377
- type: "object",
378
- description: "Request headers",
379
- additionalProperties: true
380
- },
381
- json: {
382
- type: "object",
383
- description: "Parsed JSON body",
384
- additionalProperties: true
385
- },
386
- method: {
387
- type: "string",
388
- description: "HTTP method used"
389
- },
390
- origin: {
391
- type: "string",
392
- description: "Origin of the request"
393
- },
394
- url: {
395
- type: "string",
396
- description: "Request URL"
397
- }
398
- },
399
- // Don’t allow any other top-level keys in your response
400
- additionalProperties: false
401
- }
402
- }
403
- };
404
- var anythingRoute = /* @__PURE__ */ __name((fastify) => {
405
- const handler = /* @__PURE__ */ __name(async (request, reply) => {
406
- const response = {
407
- args: request.query,
408
- data: request.body ?? {},
409
- // biome-ignore lint/suspicious/noExplicitAny: expected
410
- files: request.raw?.files ?? {},
411
- form: request.body ?? {},
412
- headers: request.headers,
413
- json: request.body ?? {},
414
- method: request.method,
415
- origin: request.headers.origin ?? request.headers.host ?? "",
416
- url: request.url
417
- };
418
- await reply.send(response);
419
- }, "handler");
420
- const noValidation = {
421
- schema: anythingSchema,
422
- // Skip AJV on *this* route
423
- /* v8 ignore next -- @preserve */
424
- validatorCompiler: /* @__PURE__ */ __name(() => () => true, "validatorCompiler")
425
- };
426
- fastify.get("/anything", noValidation, handler);
427
- fastify.post("/anything", noValidation, handler);
428
- fastify.put("/anything", noValidation, handler);
429
- fastify.patch("/anything", noValidation, handler);
430
- fastify.delete("/anything", noValidation, handler);
431
- }, "anythingRoute");
432
-
433
- // src/routes/auth/basic.ts
434
- import { Buffer as Buffer2 } from "buffer";
435
- import { timingSafeEqual } from "crypto";
436
- var parseBasic = /* @__PURE__ */ __name((header) => {
437
- if (!header || typeof header !== "string") {
438
- return void 0;
439
- }
440
- const spaceIndex = header.indexOf(" ");
441
- if (spaceIndex === -1) {
442
- return void 0;
443
- }
444
- const scheme = header.slice(0, spaceIndex);
445
- const value = header.slice(spaceIndex + 1);
446
- if (scheme.toLowerCase() !== "basic" || !value) {
447
- return void 0;
448
- }
449
- try {
450
- const decoded = Buffer2.from(value, "base64").toString("utf8");
451
- const idx = decoded.indexOf(":");
452
- if (idx === -1) {
453
- return void 0;
454
- }
455
- const username = decoded.slice(0, idx);
456
- const password = decoded.slice(idx + 1);
457
- if (username === "") {
458
- return void 0;
459
- }
460
- return {
461
- username,
462
- password
463
- };
464
- } catch {
465
- return void 0;
466
- }
467
- }, "parseBasic");
468
- var safeCompare = /* @__PURE__ */ __name((a, b) => {
469
- if (a.length !== b.length) {
470
- return false;
471
- }
472
- try {
473
- return timingSafeEqual(Buffer2.from(a, "utf8"), Buffer2.from(b, "utf8"));
474
- } catch {
475
- return false;
476
- }
477
- }, "safeCompare");
478
- var basicAuthSchema = {
479
- description: "HTTP Basic authentication. Succeeds only if the user/pass provided in the path matches the Basic Authorization header.",
480
- tags: [
481
- "Auth"
482
- ],
483
- params: {
484
- type: "object",
485
- properties: {
486
- user: {
487
- type: "string"
488
- },
489
- passwd: {
490
- type: "string"
491
- }
492
- },
493
- required: [
494
- "user",
495
- "passwd"
496
- ]
497
- },
498
- response: {
499
- 200: {
500
- type: "object",
501
- properties: {
502
- authenticated: {
503
- type: "boolean"
504
- },
505
- user: {
506
- type: "string"
507
- }
508
- },
509
- required: [
510
- "authenticated",
511
- "user"
512
- ]
513
- },
514
- 401: {
515
- type: "object",
516
- properties: {
517
- message: {
518
- type: "string"
519
- }
520
- },
521
- required: [
522
- "message"
523
- ]
524
- }
525
- }
526
- };
527
- var basicAuthRoute = /* @__PURE__ */ __name((fastify) => {
528
- fastify.get("/basic-auth/:user/:passwd", {
529
- schema: basicAuthSchema
530
- }, async (request, reply) => {
531
- const { user, passwd } = request.params;
532
- if (!user || typeof user !== "string" || typeof passwd !== "string") {
533
- void reply.header("WWW-Authenticate", 'Basic realm="mockhttp"');
534
- return reply.status(401).send({
535
- message: "Unauthorized"
536
- });
537
- }
538
- const parsed = parseBasic(request.headers.authorization);
539
- if (!parsed || !safeCompare(parsed.username, user) || !safeCompare(parsed.password, passwd)) {
540
- void reply.header("WWW-Authenticate", 'Basic realm="mockhttp"');
541
- return reply.status(401).send({
542
- message: "Unauthorized"
543
- });
544
- }
545
- return reply.send({
546
- authenticated: true,
547
- user
548
- });
549
- });
550
- }, "basicAuthRoute");
551
-
552
- // src/routes/auth/bearer.ts
553
- var parseBearer = /* @__PURE__ */ __name((header) => {
554
- if (!header) {
555
- return void 0;
556
- }
557
- const [scheme, token] = header.split(" ");
558
- if (!scheme || scheme.toLowerCase() !== "bearer" || !token) {
559
- return void 0;
560
- }
561
- return token;
562
- }, "parseBearer");
563
- var bearerAuthSchema = {
564
- description: "HTTP Bearer authentication. Succeeds if any Bearer token is present unless ?required=true is provided.",
565
- tags: [
566
- "Auth"
567
- ],
568
- querystring: {
569
- type: "object",
570
- properties: {
571
- required: {
572
- type: "boolean",
573
- default: true
574
- }
575
- }
576
- },
577
- response: {
578
- 200: {
579
- type: "object",
580
- properties: {
581
- authenticated: {
582
- type: "boolean"
583
- },
584
- token: {
585
- type: "string"
586
- }
587
- },
588
- required: [
589
- "authenticated",
590
- "token"
591
- ]
592
- },
593
- 401: {
594
- type: "object",
595
- properties: {
596
- message: {
597
- type: "string"
598
- }
599
- },
600
- required: [
601
- "message"
602
- ]
603
- }
604
- }
605
- };
606
- var bearerAuthRoute = /* @__PURE__ */ __name((fastify) => {
607
- fastify.get("/bearer", {
608
- schema: bearerAuthSchema
609
- }, async (request, reply) => {
610
- const token = parseBearer(request.headers.authorization);
611
- const qs = request.query;
612
- const required = qs?.required ?? true;
613
- if (!token && required) {
614
- void reply.header("WWW-Authenticate", "Bearer");
615
- return reply.status(401).send({
616
- message: "Unauthorized"
617
- });
618
- }
619
- if (!token) {
620
- return reply.send({
621
- authenticated: false,
622
- token: ""
623
- });
624
- }
625
- return reply.send({
626
- authenticated: true,
627
- token
628
- });
629
- });
630
- }, "bearerAuthRoute");
631
-
632
- // src/routes/auth/digest.ts
633
- import crypto2 from "crypto";
634
- var makeNonce = /* @__PURE__ */ __name(() => crypto2.randomBytes(16).toString("hex"), "makeNonce");
635
- var algorithms = /* @__PURE__ */ new Set([
636
- "MD5",
637
- "md5",
638
- "SHA-256",
639
- "sha-256",
640
- "SHA-512",
641
- "sha-512"
642
- ]);
643
- var hashAlg = /* @__PURE__ */ __name((alg) => {
644
- if (!alg) {
645
- return "md5";
646
- }
647
- const a = alg.toLowerCase();
648
- if (a === "md5") {
649
- return "md5";
650
- }
651
- if (a === "sha-256") {
652
- return "sha256";
653
- }
654
- if (a === "sha-512") {
655
- return "sha512";
656
- }
657
- return "md5";
658
- }, "hashAlg");
659
- var h = /* @__PURE__ */ __name((algo, data) => crypto2.createHash(algo).update(data).digest("hex"), "h");
660
- var parseDigest = /* @__PURE__ */ __name((header) => {
661
- if (!header) {
662
- return void 0;
663
- }
664
- const spaceIndex = header.indexOf(" ");
665
- if (spaceIndex === -1) {
666
- return void 0;
667
- }
668
- const scheme = header.slice(0, spaceIndex);
669
- const rest = header.slice(spaceIndex + 1);
670
- if (!scheme || scheme.toLowerCase() !== "digest" || !rest) {
671
- return void 0;
672
- }
673
- const out = {};
674
- const parts = [];
675
- let current = "";
676
- let inQuotes = false;
677
- for (const char of rest) {
678
- if (char === '"') {
679
- inQuotes = !inQuotes;
680
- current += char;
681
- } else if (char === "," && !inQuotes) {
682
- if (current.trim()) {
683
- parts.push(current.trim());
684
- }
685
- current = "";
686
- } else {
687
- current += char;
688
- }
689
- }
690
- if (current.trim()) {
691
- parts.push(current.trim());
692
- }
693
- if (parts.length === 0) {
694
- return out;
695
- }
696
- for (const p of parts) {
697
- const equalIndex = p.indexOf("=");
698
- if (equalIndex === -1) {
699
- continue;
700
- }
701
- const k = p.slice(0, equalIndex).trim();
702
- const v = p.slice(equalIndex + 1).trim();
703
- let value = v;
704
- if (value.startsWith('"') && value.endsWith('"')) {
705
- value = value.slice(1, -1);
706
- }
707
- out[k] = value;
708
- }
709
- return out;
710
- }, "parseDigest");
711
- var digestAuthSchema = {
712
- description: "HTTP Digest authentication. Mirrors httpbin behavior for testing clients.",
713
- tags: [
714
- "Auth"
715
- ],
716
- params: {
717
- type: "object",
718
- properties: {
719
- qop: {
720
- type: "string"
721
- },
722
- user: {
723
- type: "string"
724
- },
725
- passwd: {
726
- type: "string"
727
- },
728
- algorithm: {
729
- type: "string"
730
- },
731
- stale_after: {
732
- type: "string"
733
- }
734
- },
735
- required: [
736
- "qop",
737
- "user",
738
- "passwd"
739
- ]
740
- }
741
- };
742
- var digestAuthRoute = /* @__PURE__ */ __name((fastify) => {
743
- fastify.get("/digest-auth/:qop/:user/:passwd", {
744
- schema: digestAuthSchema
745
- }, async (request, reply) => {
746
- const { user, passwd } = request.params;
747
- const realm = "mockhttp";
748
- const nonce = makeNonce();
749
- const auth = parseDigest(request.headers.authorization);
750
- if (!auth) {
751
- void reply.header("WWW-Authenticate", `Digest realm="${realm}", qop="auth,auth-int", nonce="${nonce}", opaque="${makeNonce()}"`);
752
- return reply.status(401).send({
753
- message: "Unauthorized"
754
- });
755
- }
756
- if (auth.username !== user) {
757
- void reply.header("WWW-Authenticate", `Digest realm="${realm}", qop="auth,auth-int", nonce="${nonce}", opaque="${makeNonce()}"`);
758
- return reply.status(401).send({
759
- message: "Unauthorized"
760
- });
761
- }
762
- const method = "GET";
763
- const uri = auth.uri ?? request.url.replace(/^https?:\/\/[^/]+/, "");
764
- const algo = hashAlg(auth.algorithm);
765
- const ha1 = h(algo, `${user}:${realm}:${passwd}`);
766
- const ha2 = h(algo, `${method}:${uri}`);
767
- let expected = "";
768
- expected = auth.qop ? h(algo, `${ha1}:${auth.nonce}:${auth.nc}:${auth.cnonce}:${auth.qop}:${ha2}`) : h(algo, `${ha1}:${auth.nonce}:${ha2}`);
769
- if (!auth.response || auth.response !== expected) {
770
- void reply.header("WWW-Authenticate", `Digest realm="${realm}", qop="auth,auth-int", nonce="${nonce}", opaque="${makeNonce()}"`);
771
- return reply.status(401).send({
772
- message: "Unauthorized"
773
- });
774
- }
775
- return reply.send({
776
- authenticated: true,
777
- user
778
- });
779
- });
780
- fastify.get("/digest-auth/:qop/:user/:passwd/:algorithm", {
781
- schema: digestAuthSchema
782
- }, async (request, reply) => {
783
- const { user, passwd, algorithm } = request.params;
784
- const realm = "mockhttp";
785
- const algoHeader = algorithms.has(String(algorithm)) ? String(algorithm) : "MD5";
786
- const nonce = makeNonce();
787
- const auth = parseDigest(request.headers.authorization);
788
- if (!auth) {
789
- void reply.header("WWW-Authenticate", `Digest realm="${realm}", qop="auth,auth-int", algorithm=${algoHeader}, nonce="${nonce}", opaque="${makeNonce()}"`);
790
- return reply.status(401).send({
791
- message: "Unauthorized"
792
- });
793
- }
794
- if (auth.username !== user) {
795
- void reply.header("WWW-Authenticate", `Digest realm="${realm}", qop="auth,auth-int", algorithm=${algoHeader}, nonce="${nonce}", opaque="${makeNonce()}"`);
796
- return reply.status(401).send({
797
- message: "Unauthorized"
798
- });
799
- }
800
- const method = "GET";
801
- const uri = auth.uri ?? request.url.replace(/^https?:\/\/[^/]+/, "");
802
- const algo = hashAlg(auth.algorithm ?? algoHeader);
803
- const ha1 = h(algo, `${user}:${realm}:${passwd}`);
804
- const ha2 = h(algo, `${method}:${uri}`);
805
- const expected = auth.qop ? h(algo, `${ha1}:${auth.nonce}:${auth.nc}:${auth.cnonce}:${auth.qop}:${ha2}`) : h(algo, `${ha1}:${auth.nonce}:${ha2}`);
806
- if (!auth.response || auth.response !== expected) {
807
- void reply.header("WWW-Authenticate", `Digest realm="${realm}", qop="auth,auth-int", algorithm=${algoHeader}, nonce="${nonce}", opaque="${makeNonce()}"`);
808
- return reply.status(401).send({
809
- message: "Unauthorized"
810
- });
811
- }
812
- return reply.send({
813
- authenticated: true,
814
- user
815
- });
816
- });
817
- }, "digestAuthRoute");
818
-
819
- // src/routes/auth/hidden-basic.ts
820
- import { Buffer as Buffer3 } from "buffer";
821
- var parseBasic2 = /* @__PURE__ */ __name((header) => {
822
- if (!header) {
823
- return void 0;
824
- }
825
- const [scheme, value] = header.split(" ");
826
- if (!scheme || scheme.toLowerCase() !== "basic" || !value) {
827
- return void 0;
828
- }
829
- try {
830
- const decoded = Buffer3.from(value, "base64").toString("utf8");
831
- const idx = decoded.indexOf(":");
832
- if (idx === -1) {
833
- return void 0;
834
- }
835
- return {
836
- username: decoded.slice(0, idx),
837
- password: decoded.slice(idx + 1)
838
- };
839
- } catch {
840
- return void 0;
841
- }
842
- }, "parseBasic");
843
- var hiddenBasicAuthSchema = {
844
- description: "HTTP Basic authentication with docs hidden. Mirrors /basic-auth but hides from schema.",
845
- tags: [
846
- "Auth"
847
- ],
848
- hide: true,
849
- params: {
850
- type: "object",
851
- properties: {
852
- user: {
853
- type: "string"
854
- },
855
- passwd: {
856
- type: "string"
857
- }
858
- },
859
- required: [
860
- "user",
861
- "passwd"
862
- ]
863
- }
864
- };
865
- var hiddenBasicAuthRoute = /* @__PURE__ */ __name((fastify) => {
866
- fastify.get("/hidden-basic-auth/:user/:passwd", {
867
- schema: hiddenBasicAuthSchema
868
- }, async (request, reply) => {
869
- const { user, passwd } = request.params;
870
- const parsed = parseBasic2(request.headers.authorization);
871
- if (!parsed || parsed.username !== user || parsed.password !== passwd) {
872
- void reply.header("WWW-Authenticate", 'Basic realm="mockhttp"');
873
- return reply.status(401).send({
874
- message: "Unauthorized"
875
- });
876
- }
877
- return reply.send({
878
- authenticated: true,
879
- user
880
- });
881
- });
882
- }, "hiddenBasicAuthRoute");
883
-
884
- // src/routes/cookies/delete.ts
885
- var cookiesDeleteSchema = {
886
- description: "Delete a cookie",
887
- tags: [
888
- "Cookies"
889
- ],
890
- querystring: {
891
- type: "object",
892
- properties: {
893
- name: {
894
- type: "string"
895
- }
896
- },
897
- required: [
898
- "name"
899
- ],
900
- additionalProperties: false
901
- },
902
- response: {
903
- 204: {
904
- type: "null"
905
- }
906
- }
907
- };
908
- var deleteCookieRoute = /* @__PURE__ */ __name((fastify) => {
909
- fastify.delete("/cookies", {
910
- schema: cookiesDeleteSchema
911
- }, async (request, reply) => {
912
- const { name } = request.query;
913
- reply.clearCookie(name, {
914
- path: "/"
915
- });
916
- return reply.status(204).send();
917
- });
918
- }, "deleteCookieRoute");
919
-
920
- // src/routes/cookies/get.ts
921
- var getCookiesRouteSchema = {
922
- description: "Return cookies from the request",
923
- tags: [
924
- "Cookies"
925
- ],
926
- response: {
927
- 200: {
928
- type: "object",
929
- properties: {
930
- cookies: {
931
- type: "object",
932
- additionalProperties: {
933
- type: "string"
934
- }
935
- }
936
- },
937
- required: [
938
- "cookies"
939
- ]
940
- }
941
- }
942
- };
943
- var getCookiesRoute = /* @__PURE__ */ __name((fastify) => {
944
- fastify.get("/cookies", {
945
- schema: getCookiesRouteSchema
946
- }, async (request, reply) => {
947
- reply.type("application/json");
948
- await reply.send({
949
- cookies: request.cookies
950
- });
951
- });
952
- }, "getCookiesRoute");
953
-
954
- // src/routes/cookies/post.ts
955
- var postCookieRouteSchema = {
956
- description: "Set a cookie",
957
- tags: [
958
- "Cookies"
959
- ],
960
- body: {
961
- type: "object",
962
- properties: {
963
- name: {
964
- type: "string"
965
- },
966
- value: {
967
- type: "string"
968
- },
969
- expires: {
970
- type: "string",
971
- format: "date-time"
972
- }
973
- },
974
- required: [
975
- "name",
976
- "value"
977
- ],
978
- additionalProperties: false
979
- },
980
- response: {
981
- 200: {
982
- type: "object",
983
- properties: {
984
- cookies: {
985
- type: "object",
986
- additionalProperties: {
987
- type: "string"
988
- }
989
- }
990
- },
991
- required: [
992
- "cookies"
993
- ]
994
- }
995
- }
996
- };
997
- var postCookieRoute = /* @__PURE__ */ __name((fastify) => {
998
- fastify.post("/cookies", {
999
- schema: postCookieRouteSchema
1000
- }, async (request, reply) => {
1001
- const { name, value, expires } = request.body;
1002
- if (expires) {
1003
- const date = new Date(expires);
1004
- reply.setCookie(name, value, {
1005
- path: "/",
1006
- expires: date
1007
- });
1008
- } else {
1009
- reply.setCookie(name, value, {
1010
- path: "/"
1011
- });
1012
- }
1013
- return reply.status(200).send();
1014
- });
1015
- }, "postCookieRoute");
1016
-
1017
- // src/routes/dynamic-data/base64.ts
1018
- var base64Schema = {
1019
- description: "Decodes base64url-encoded string",
1020
- tags: [
1021
- "Dynamic Data"
1022
- ],
1023
- params: {
1024
- type: "object",
1025
- properties: {
1026
- value: {
1027
- type: "string",
1028
- description: "Base64 encoded value to decode"
1029
- }
1030
- },
1031
- required: [
1032
- "value"
1033
- ]
1034
- },
1035
- response: {
1036
- 200: {
1037
- type: "string",
1038
- description: "Decoded value"
1039
- },
1040
- 400: {
1041
- type: "object",
1042
- properties: {
1043
- error: {
1044
- type: "string"
1045
- }
1046
- }
1047
- }
1048
- }
1049
- };
1050
- function isValidBase64(str) {
1051
- const base64Regex = /^[A-Za-z0-9+/\-_]*={0,2}$/;
1052
- if (!base64Regex.test(str)) {
1053
- return false;
1054
- }
1055
- const strippedLength = str.replace(/=+$/, "").length;
1056
- return strippedLength % 4 !== 1;
1057
- }
1058
- __name(isValidBase64, "isValidBase64");
1059
- var base64Route = /* @__PURE__ */ __name((fastify) => {
1060
- fastify.get("/base64/:value", {
1061
- schema: base64Schema
1062
- }, async (request, reply) => {
1063
- const { value } = request.params;
1064
- if (!isValidBase64(value)) {
1065
- return reply.code(400).send({
1066
- error: "Incorrect Base64 data"
1067
- });
1068
- }
1069
- const normalizedValue = value.replace(/-/g, "+").replace(/_/g, "/");
1070
- const decoded = Buffer.from(normalizedValue, "base64").toString("utf-8");
1071
- return reply.header("Content-Type", "text/html; charset=utf-8").send(decoded);
1072
- });
1073
- }, "base64Route");
1074
-
1075
- // src/routes/dynamic-data/bytes.ts
1076
- import { randomBytes as randomBytes2 } from "crypto";
1077
- var bytesSchema = {
1078
- description: "Returns n random bytes generated with given seed",
1079
- tags: [
1080
- "Dynamic Data"
1081
- ],
1082
- params: {
1083
- type: "object",
1084
- properties: {
1085
- n: {
1086
- type: "string",
1087
- description: "Number of bytes to generate"
1088
- }
1089
- },
1090
- required: [
1091
- "n"
1092
- ]
1093
- },
1094
- querystring: {
1095
- type: "object",
1096
- properties: {
1097
- seed: {
1098
- type: "string",
1099
- description: "Seed for random number generation"
1100
- }
1101
- }
1102
- },
1103
- response: {
1104
- 200: {
1105
- type: "string",
1106
- description: "Random binary data"
1107
- },
1108
- 400: {
1109
- type: "object",
1110
- properties: {
1111
- error: {
1112
- type: "string"
1113
- }
1114
- }
1115
- }
1116
- }
1117
- };
1118
- function seededRandom(seed) {
1119
- let state = seed;
1120
- return () => {
1121
- state = state * 1103515245 + 12345 & 2147483647;
1122
- return state / 2147483647;
1123
- };
1124
- }
1125
- __name(seededRandom, "seededRandom");
1126
- function generateSeededBytes(n, seed) {
1127
- const random = seededRandom(seed);
1128
- const bytes = Buffer.alloc(n);
1129
- for (let i = 0; i < n; i++) {
1130
- bytes[i] = Math.floor(random() * 256);
1131
- }
1132
- return bytes;
1133
- }
1134
- __name(generateSeededBytes, "generateSeededBytes");
1135
- var MAX_BYTES = 100 * 1024;
1136
- var bytesRoute = /* @__PURE__ */ __name((fastify) => {
1137
- fastify.get("/bytes/:n", {
1138
- schema: bytesSchema
1139
- }, async (request, reply) => {
1140
- const n = Number.parseInt(request.params.n, 10);
1141
- if (Number.isNaN(n) || n < 0) {
1142
- return reply.code(400).send({
1143
- error: "n must be a non-negative integer"
1144
- });
1145
- }
1146
- const limitedN = Math.min(n, MAX_BYTES);
1147
- const seedParam = request.query.seed;
1148
- let bytes;
1149
- if (seedParam !== void 0) {
1150
- const seed = Number.parseInt(seedParam, 10);
1151
- if (Number.isNaN(seed)) {
1152
- return reply.code(400).send({
1153
- error: "seed must be an integer"
1154
- });
1155
- }
1156
- bytes = generateSeededBytes(limitedN, seed);
1157
- } else {
1158
- bytes = randomBytes2(limitedN);
1159
- }
1160
- return reply.header("Content-Type", "application/octet-stream").header("Content-Length", bytes.length).send(bytes);
1161
- });
1162
- }, "bytesRoute");
1163
-
1164
- // src/routes/dynamic-data/delay.ts
1165
- var delaySchema = {
1166
- description: "Returns a delayed response (max of 10 seconds)",
1167
- tags: [
1168
- "Dynamic Data"
1169
- ],
1170
- params: {
1171
- type: "object",
1172
- properties: {
1173
- delay: {
1174
- type: "string",
1175
- description: "Delay in seconds (max 10)"
1176
- }
1177
- },
1178
- required: [
1179
- "delay"
1180
- ]
1181
- },
1182
- querystring: {
1183
- type: "object",
1184
- additionalProperties: true
1185
- },
1186
- response: {
1187
- 200: {
1188
- type: "object",
1189
- properties: {
1190
- args: {
1191
- type: "object"
1192
- },
1193
- data: {
1194
- type: "string"
1195
- },
1196
- files: {
1197
- type: "object"
1198
- },
1199
- form: {
1200
- type: "object"
1201
- },
1202
- headers: {
1203
- type: "object"
1204
- },
1205
- origin: {
1206
- type: "string"
1207
- },
1208
- url: {
1209
- type: "string"
1210
- }
1211
- }
1212
- },
1213
- 400: {
1214
- type: "object",
1215
- properties: {
1216
- error: {
1217
- type: "string"
1218
- }
1219
- }
1220
- }
1221
- }
1222
- };
1223
- var MAX_DELAY = 10;
1224
- function sleep(ms) {
1225
- return new Promise((resolve) => setTimeout(resolve, ms));
1226
- }
1227
- __name(sleep, "sleep");
1228
- var delayRoute = /* @__PURE__ */ __name((fastify) => {
1229
- const handler = /* @__PURE__ */ __name(async (request, reply) => {
1230
- const delay = Number.parseFloat(request.params.delay);
1231
- if (Number.isNaN(delay) || delay < 0) {
1232
- return reply.code(400).send({
1233
- error: "delay must be a non-negative number"
1234
- });
1235
- }
1236
- const actualDelay = Math.min(delay, MAX_DELAY);
1237
- await sleep(actualDelay * 1e3);
1238
- const { protocol } = request;
1239
- const host = request.headers.host || "localhost";
1240
- return {
1241
- /* v8 ignore next -- @preserve */
1242
- args: request.query || {},
1243
- data: "",
1244
- files: {},
1245
- form: {},
1246
- headers: request.headers,
1247
- origin: request.ip,
1248
- url: `${protocol}://${host}${request.url}`
1249
- };
1250
- }, "handler");
1251
- fastify.get("/delay/:delay", {
1252
- schema: delaySchema
1253
- }, handler);
1254
- fastify.post("/delay/:delay", {
1255
- schema: delaySchema
1256
- }, handler);
1257
- fastify.put("/delay/:delay", {
1258
- schema: delaySchema
1259
- }, handler);
1260
- fastify.patch("/delay/:delay", {
1261
- schema: delaySchema
1262
- }, handler);
1263
- fastify.delete("/delay/:delay", {
1264
- schema: delaySchema
1265
- }, handler);
1266
- }, "delayRoute");
1267
-
1268
- // src/routes/dynamic-data/drip.ts
1269
- var dripSchema = {
1270
- description: "Drips data over a duration after an optional initial delay, then (optionally) returns with the given status code",
1271
- tags: [
1272
- "Dynamic Data"
1273
- ],
1274
- querystring: {
1275
- type: "object",
1276
- properties: {
1277
- duration: {
1278
- type: "string",
1279
- description: "Duration in seconds over which to drip the data (default: 2)"
1280
- },
1281
- numbytes: {
1282
- type: "string",
1283
- description: "Number of bytes to send (default: 10)"
1284
- },
1285
- code: {
1286
- type: "string",
1287
- description: "HTTP status code to return (default: 200)"
1288
- },
1289
- delay: {
1290
- type: "string",
1291
- description: "Initial delay in seconds before starting (default: 2)"
1292
- }
1293
- }
1294
- },
1295
- response: {
1296
- 200: {
1297
- type: "string",
1298
- description: "Dripped data"
1299
- },
1300
- 400: {
1301
- type: "object",
1302
- properties: {
1303
- error: {
1304
- type: "string"
1305
- }
1306
- }
1307
- }
1308
- }
1309
- };
1310
- function sleep2(ms) {
1311
- return new Promise((resolve) => setTimeout(resolve, ms));
1312
- }
1313
- __name(sleep2, "sleep");
1314
- var DEFAULT_DURATION = 2;
1315
- var DEFAULT_NUMBYTES = 10;
1316
- var DEFAULT_CODE = 200;
1317
- var DEFAULT_DELAY = 2;
1318
- var MAX_DURATION = 10;
1319
- var MAX_DELAY2 = 10;
1320
- var MAX_BYTES2 = 10 * 1024 * 1024;
1321
- var dripRoute = /* @__PURE__ */ __name((fastify) => {
1322
- fastify.get("/drip", {
1323
- schema: dripSchema
1324
- }, async (request, reply) => {
1325
- const duration = request.query.duration ? Number.parseFloat(request.query.duration) : DEFAULT_DURATION;
1326
- const numbytes = request.query.numbytes ? Number.parseInt(request.query.numbytes, 10) : DEFAULT_NUMBYTES;
1327
- const code = request.query.code ? Number.parseInt(request.query.code, 10) : DEFAULT_CODE;
1328
- const delay = request.query.delay ? Number.parseFloat(request.query.delay) : DEFAULT_DELAY;
1329
- if (Number.isNaN(duration) || duration < 0) {
1330
- return reply.code(400).send({
1331
- error: "duration must be a non-negative number"
1332
- });
1333
- }
1334
- if (Number.isNaN(numbytes) || numbytes < 0) {
1335
- return reply.code(400).send({
1336
- error: "numbytes must be a non-negative integer"
1337
- });
1338
- }
1339
- if (Number.isNaN(code) || code < 100 || code > 599) {
1340
- return reply.code(400).send({
1341
- error: "code must be a valid HTTP status code"
1342
- });
1343
- }
1344
- if (Number.isNaN(delay) || delay < 0) {
1345
- return reply.code(400).send({
1346
- error: "delay must be a non-negative number"
1347
- });
1348
- }
1349
- const actualDuration = Math.min(duration, MAX_DURATION);
1350
- const actualDelay = Math.min(delay, MAX_DELAY2);
1351
- const actualBytes = Math.min(numbytes, MAX_BYTES2);
1352
- if (actualDelay > 0) {
1353
- await sleep2(actualDelay * 1e3);
1354
- }
1355
- reply.raw.writeHead(code, {
1356
- "Content-Type": "application/octet-stream",
1357
- "Content-Length": actualBytes.toString()
1358
- });
1359
- if (actualBytes === 0) {
1360
- reply.raw.end();
1361
- return;
1362
- }
1363
- const intervalMs = actualBytes > 1 ? actualDuration * 1e3 / (actualBytes - 1) : 0;
1364
- const byte = Buffer.from("*");
1365
- for (let i = 0; i < actualBytes; i++) {
1366
- reply.raw.write(byte);
1367
- if (i < actualBytes - 1 && intervalMs > 0) {
1368
- await sleep2(intervalMs);
1369
- }
1370
- }
1371
- reply.raw.end();
1372
- });
1373
- }, "dripRoute");
1374
-
1375
- // src/routes/dynamic-data/links.ts
1376
- var linksSchema = {
1377
- description: "Generate a page containing n links to other pages which do the same",
1378
- tags: [
1379
- "Dynamic Data"
1380
- ],
1381
- params: {
1382
- type: "object",
1383
- properties: {
1384
- n: {
1385
- type: "string",
1386
- description: "Number of links to generate"
1387
- },
1388
- offset: {
1389
- type: "string",
1390
- description: "Starting offset for pagination (default: 0)"
1391
- }
1392
- },
1393
- required: [
1394
- "n"
1395
- ]
1396
- },
1397
- response: {
1398
- 200: {
1399
- type: "string",
1400
- description: "HTML page with links"
1401
- },
1402
- 400: {
1403
- type: "object",
1404
- properties: {
1405
- error: {
1406
- type: "string"
1407
- }
1408
- }
1409
- }
1410
- }
1411
- };
1412
- var MAX_LINKS = 200;
1413
- var linksRoute = /* @__PURE__ */ __name((fastify) => {
1414
- fastify.get("/links/:n/:offset?", {
1415
- schema: linksSchema
1416
- }, async (request, reply) => {
1417
- const n = Number.parseInt(request.params.n, 10);
1418
- const offset = request.params.offset ? Number.parseInt(request.params.offset, 10) : 0;
1419
- if (Number.isNaN(n) || n < 0) {
1420
- return reply.code(400).send({
1421
- error: "n must be a non-negative integer"
1422
- });
1423
- }
1424
- if (Number.isNaN(offset) || offset < 0) {
1425
- return reply.code(400).send({
1426
- error: "offset must be a non-negative integer"
1427
- });
1428
- }
1429
- const limitedN = Math.min(n, MAX_LINKS);
1430
- const links = [];
1431
- for (let i = 0; i < limitedN; i++) {
1432
- if (i === offset) {
1433
- links.push(`${i} `);
1434
- } else {
1435
- links.push(`<a href="/links/${limitedN}/${i}">${i}</a> `);
1436
- }
1437
- }
1438
- const html = `<html>
1439
- <head>
1440
- <title>Links</title>
1441
- </head>
1442
- <body>
1443
- ${links.join("")}
1444
- </body>
1445
- </html>`;
1446
- return reply.header("Content-Type", "text/html; charset=utf-8").send(html);
1447
- });
1448
- }, "linksRoute");
1449
-
1450
- // src/routes/dynamic-data/range.ts
1451
- var rangeSchema = {
1452
- description: "Streams n random bytes generated, and supports HTTP Range requests",
1453
- tags: [
1454
- "Dynamic Data"
1455
- ],
1456
- params: {
1457
- type: "object",
1458
- properties: {
1459
- numbytes: {
1460
- type: "string",
1461
- description: "Number of bytes available"
1462
- }
1463
- },
1464
- required: [
1465
- "numbytes"
1466
- ]
1467
- },
1468
- querystring: {
1469
- type: "object",
1470
- properties: {
1471
- duration: {
1472
- type: "string",
1473
- description: "Delay before sending response in seconds"
1474
- }
1475
- }
1476
- },
1477
- response: {
1478
- 200: {
1479
- type: "string",
1480
- description: "Binary data"
1481
- },
1482
- 206: {
1483
- type: "string",
1484
- description: "Partial binary data"
1485
- },
1486
- 400: {
1487
- type: "object",
1488
- properties: {
1489
- error: {
1490
- type: "string"
1491
- }
1492
- }
1493
- },
1494
- 416: {
1495
- type: "object",
1496
- properties: {
1497
- error: {
1498
- type: "string"
1499
- }
1500
- }
1501
- }
1502
- }
1503
- };
1504
- var MAX_BYTES3 = 100 * 1024;
1505
- function sleep3(ms) {
1506
- return new Promise((resolve) => setTimeout(resolve, ms));
1507
- }
1508
- __name(sleep3, "sleep");
1509
- function generateBytes(n) {
1510
- const alphabet = "abcdefghijklmnopqrstuvwxyz";
1511
- const bytes = Buffer.alloc(n);
1512
- for (let i = 0; i < n; i++) {
1513
- bytes[i] = alphabet.charCodeAt(i % alphabet.length);
1514
- }
1515
- return bytes;
1516
- }
1517
- __name(generateBytes, "generateBytes");
1518
- function parseRangeHeader(rangeHeader, totalSize) {
1519
- const match = rangeHeader.match(/^bytes=(.+)$/);
1520
- if (!match) return null;
1521
- const ranges = [];
1522
- const parts = match[1].split(",");
1523
- for (const part of parts) {
1524
- const trimmedPart = part.trim();
1525
- if (trimmedPart.startsWith("-")) {
1526
- const suffix = Number.parseInt(trimmedPart.slice(1), 10);
1527
- if (Number.isNaN(suffix)) return null;
1528
- ranges.push({
1529
- start: Math.max(0, totalSize - suffix),
1530
- end: totalSize - 1
1531
- });
1532
- } else if (trimmedPart.endsWith("-")) {
1533
- const start2 = Number.parseInt(trimmedPart.slice(0, -1), 10);
1534
- if (Number.isNaN(start2)) return null;
1535
- ranges.push({
1536
- start: start2,
1537
- end: totalSize - 1
1538
- });
1539
- } else {
1540
- const [startStr, endStr] = trimmedPart.split("-");
1541
- const start2 = Number.parseInt(startStr, 10);
1542
- const end = Number.parseInt(endStr, 10);
1543
- if (Number.isNaN(start2) || Number.isNaN(end)) return null;
1544
- ranges.push({
1545
- start: start2,
1546
- end: Math.min(end, totalSize - 1)
1547
- });
1548
- }
1549
- }
1550
- for (const range of ranges) {
1551
- if (range.start > range.end || range.start >= totalSize) {
1552
- return null;
1553
- }
1554
- }
1555
- return ranges;
1556
- }
1557
- __name(parseRangeHeader, "parseRangeHeader");
1558
- var rangeRoute = /* @__PURE__ */ __name((fastify) => {
1559
- fastify.get("/range/:numbytes", {
1560
- schema: rangeSchema
1561
- }, async (request, reply) => {
1562
- const numbytes = Number.parseInt(request.params.numbytes, 10);
1563
- if (Number.isNaN(numbytes) || numbytes < 0) {
1564
- return reply.code(400).send({
1565
- error: "numbytes must be a non-negative integer"
1566
- });
1567
- }
1568
- const limitedBytes = Math.min(numbytes, MAX_BYTES3);
1569
- if (request.query.duration) {
1570
- const duration = Number.parseFloat(request.query.duration);
1571
- if (!Number.isNaN(duration) && duration > 0) {
1572
- await sleep3(Math.min(duration, 10) * 1e3);
1573
- }
1574
- }
1575
- const allBytes = generateBytes(limitedBytes);
1576
- const rangeHeader = request.headers.range;
1577
- if (!rangeHeader) {
1578
- return reply.header("Content-Type", "application/octet-stream").header("Content-Length", allBytes.length).header("Accept-Ranges", "bytes").send(allBytes);
1579
- }
1580
- const ranges = parseRangeHeader(rangeHeader, limitedBytes);
1581
- if (!ranges || ranges.length === 0) {
1582
- return reply.code(416).header("Content-Range", `bytes */${limitedBytes}`).send({
1583
- error: "Range Not Satisfiable"
1584
- });
1585
- }
1586
- if (ranges.length === 1) {
1587
- const range = ranges[0];
1588
- const chunk = allBytes.subarray(range.start, range.end + 1);
1589
- return reply.code(206).header("Content-Type", "application/octet-stream").header("Content-Length", chunk.length).header("Accept-Ranges", "bytes").header("Content-Range", `bytes ${range.start}-${range.end}/${limitedBytes}`).send(chunk);
1590
- }
1591
- const boundary = "3d6b6a416f9b5";
1592
- const parts = [];
1593
- for (const range of ranges) {
1594
- const chunk = allBytes.subarray(range.start, range.end + 1);
1595
- const header = `\r
1596
- --${boundary}\r
1597
- Content-Type: application/octet-stream\r
1598
- Content-Range: bytes ${range.start}-${range.end}/${limitedBytes}\r
1599
- \r
1600
- `;
1601
- parts.push(Buffer.from(header));
1602
- parts.push(chunk);
1603
- }
1604
- parts.push(Buffer.from(`\r
1605
- --${boundary}--\r
1606
- `));
1607
- const multipartBody = Buffer.concat(parts);
1608
- return reply.code(206).header("Content-Type", `multipart/byteranges; boundary=${boundary}`).header("Content-Length", multipartBody.length).send(multipartBody);
1609
- });
1610
- }, "rangeRoute");
1611
-
1612
- // src/routes/dynamic-data/stream.ts
1613
- var streamSchema = {
1614
- description: "Stream n JSON responses",
1615
- tags: [
1616
- "Dynamic Data"
1617
- ],
1618
- params: {
1619
- type: "object",
1620
- properties: {
1621
- n: {
1622
- type: "string",
1623
- description: "Number of JSON responses to stream (max 100)"
1624
- }
1625
- },
1626
- required: [
1627
- "n"
1628
- ]
1629
- },
1630
- response: {
1631
- 200: {
1632
- type: "string",
1633
- description: "Newline-delimited JSON responses"
1634
- },
1635
- 400: {
1636
- type: "object",
1637
- properties: {
1638
- error: {
1639
- type: "string"
1640
- }
1641
- }
1642
- }
1643
- }
1644
- };
1645
- var MAX_LINES = 100;
1646
- var streamRoute = /* @__PURE__ */ __name((fastify) => {
1647
- fastify.get("/stream/:n", {
1648
- schema: streamSchema
1649
- }, async (request, reply) => {
1650
- const n = Number.parseInt(request.params.n, 10);
1651
- if (Number.isNaN(n) || n < 0) {
1652
- return reply.code(400).send({
1653
- error: "n must be a non-negative integer"
1654
- });
1655
- }
1656
- const lines = Math.min(n, MAX_LINES);
1657
- const { protocol } = request;
1658
- const host = request.headers.host || "localhost";
1659
- reply.raw.writeHead(200, {
1660
- "Content-Type": "application/json",
1661
- "Transfer-Encoding": "chunked"
1662
- });
1663
- for (let i = 0; i < lines; i++) {
1664
- const data = {
1665
- id: i,
1666
- /* v8 ignore next -- @preserve */
1667
- args: request.query || {},
1668
- headers: request.headers,
1669
- origin: request.ip,
1670
- url: `${protocol}://${host}${request.url}`
1671
- };
1672
- reply.raw.write(`${JSON.stringify(data)}
1673
- `);
1674
- }
1675
- reply.raw.end();
1676
- });
1677
- }, "streamRoute");
1678
-
1679
- // src/routes/dynamic-data/stream-bytes.ts
1680
- import { randomBytes as randomBytes3 } from "crypto";
1681
- var streamBytesSchema = {
1682
- description: "Streams n random bytes generated with given seed, in chunks of chunk_size per packet",
1683
- tags: [
1684
- "Dynamic Data"
1685
- ],
1686
- params: {
1687
- type: "object",
1688
- properties: {
1689
- n: {
1690
- type: "string",
1691
- description: "Number of bytes to stream"
1692
- }
1693
- },
1694
- required: [
1695
- "n"
1696
- ]
1697
- },
1698
- querystring: {
1699
- type: "object",
1700
- properties: {
1701
- seed: {
1702
- type: "string",
1703
- description: "Seed for random number generation"
1704
- },
1705
- chunk_size: {
1706
- type: "string",
1707
- description: "Size of each chunk (default: 10240)"
1708
- }
1709
- }
1710
- },
1711
- response: {
1712
- 200: {
1713
- type: "string",
1714
- description: "Streamed random binary data"
1715
- },
1716
- 400: {
1717
- type: "object",
1718
- properties: {
1719
- error: {
1720
- type: "string"
1721
- }
1722
- }
1723
- }
1724
- }
1725
- };
1726
- function seededRandom2(seed) {
1727
- let state = seed;
1728
- return () => {
1729
- state = state * 1103515245 + 12345 & 2147483647;
1730
- return state / 2147483647;
1731
- };
1732
- }
1733
- __name(seededRandom2, "seededRandom");
1734
- function generateSeededBytes2(n, seed) {
1735
- const random = seededRandom2(seed);
1736
- const bytes = Buffer.alloc(n);
1737
- for (let i = 0; i < n; i++) {
1738
- bytes[i] = Math.floor(random() * 256);
1739
- }
1740
- return bytes;
1741
- }
1742
- __name(generateSeededBytes2, "generateSeededBytes");
1743
- var MAX_BYTES4 = 100 * 1024;
1744
- var DEFAULT_CHUNK_SIZE = 10240;
1745
- var streamBytesRoute = /* @__PURE__ */ __name((fastify) => {
1746
- fastify.get("/stream-bytes/:n", {
1747
- schema: streamBytesSchema
1748
- }, async (request, reply) => {
1749
- const n = Number.parseInt(request.params.n, 10);
1750
- if (Number.isNaN(n) || n < 0) {
1751
- return reply.code(400).send({
1752
- error: "n must be a non-negative integer"
1753
- });
1754
- }
1755
- const limitedN = Math.min(n, MAX_BYTES4);
1756
- const chunkSize = request.query.chunk_size ? Number.parseInt(request.query.chunk_size, 10) : DEFAULT_CHUNK_SIZE;
1757
- if (Number.isNaN(chunkSize) || chunkSize <= 0) {
1758
- return reply.code(400).send({
1759
- error: "chunk_size must be a positive integer"
1760
- });
1761
- }
1762
- const seedParam = request.query.seed;
1763
- let allBytes;
1764
- if (seedParam !== void 0) {
1765
- const seed = Number.parseInt(seedParam, 10);
1766
- if (Number.isNaN(seed)) {
1767
- return reply.code(400).send({
1768
- error: "seed must be an integer"
1769
- });
1770
- }
1771
- allBytes = generateSeededBytes2(limitedN, seed);
1772
- } else {
1773
- allBytes = randomBytes3(limitedN);
1774
- }
1775
- reply.raw.writeHead(200, {
1776
- "Content-Type": "application/octet-stream",
1777
- "Transfer-Encoding": "chunked"
1778
- });
1779
- let offset = 0;
1780
- while (offset < allBytes.length) {
1781
- const chunk = allBytes.subarray(offset, offset + chunkSize);
1782
- reply.raw.write(chunk);
1783
- offset += chunkSize;
1784
- }
1785
- reply.raw.end();
1786
- });
1787
- }, "streamBytesRoute");
1788
-
1789
- // src/routes/dynamic-data/uuid.ts
1790
- import { randomUUID } from "crypto";
1791
- var uuidSchema = {
1792
- description: "Return a UUID4",
1793
- tags: [
1794
- "Dynamic Data"
1795
- ],
1796
- response: {
1797
- 200: {
1798
- type: "object",
1799
- properties: {
1800
- uuid: {
1801
- type: "string",
1802
- format: "uuid"
1803
- }
1804
- },
1805
- required: [
1806
- "uuid"
1807
- ]
1808
- }
1809
- }
1810
- };
1811
- var uuidRoute = /* @__PURE__ */ __name((fastify) => {
1812
- fastify.get("/uuid", {
1813
- schema: uuidSchema
1814
- }, async () => ({
1815
- uuid: randomUUID()
1816
- }));
1817
- }, "uuidRoute");
1818
-
1819
- // src/routes/http-methods/delete.ts
1820
- var deleteSchema = {
1821
- description: "Handles a DELETE request and returns request information",
1822
- tags: [
1823
- "HTTP Methods"
1824
- ],
1825
- response: {
1826
- 200: {
1827
- type: "object",
1828
- properties: {
1829
- method: {
1830
- type: "string"
1831
- },
1832
- headers: {
1833
- type: "object",
1834
- additionalProperties: {
1835
- type: "string"
1836
- }
1837
- },
1838
- body: {
1839
- oneOf: [
1840
- {
1841
- type: "object",
1842
- additionalProperties: true
1843
- },
1844
- {
1845
- type: "string"
1846
- },
1847
- {
1848
- type: "null"
1849
- }
1850
- ]
1851
- }
1852
- },
1853
- required: [
1854
- "method",
1855
- "headers"
1856
- ]
1857
- }
1858
- }
1859
- };
1860
- var deleteRoute = /* @__PURE__ */ __name((fastify) => {
1861
- fastify.delete("/delete", {
1862
- schema: deleteSchema
1863
- }, async (request) => ({
1864
- method: "DELETE",
1865
- headers: request.headers,
1866
- body: request.body
1867
- }));
1868
- }, "deleteRoute");
1869
-
1870
- // src/routes/http-methods/get.ts
1871
- var getSchema = {
1872
- description: "Returns request information for GET requests",
1873
- tags: [
1874
- "HTTP Methods"
1875
- ],
1876
- querystring: {
1877
- type: "object",
1878
- additionalProperties: true,
1879
- properties: {},
1880
- description: "Query parameters sent in the request"
1881
- },
1882
- headers: {
1883
- type: "object",
1884
- additionalProperties: true,
1885
- properties: {},
1886
- description: "Headers sent in the request"
1887
- },
1888
- response: {
1889
- 200: {
1890
- type: "object",
1891
- properties: {
1892
- method: {
1893
- type: "string"
1894
- },
1895
- headers: {
1896
- type: "object",
1897
- additionalProperties: {
1898
- type: "string"
1899
- }
1900
- },
1901
- queryParams: {
1902
- type: "object",
1903
- additionalProperties: {
1904
- type: "string"
1905
- }
1906
- }
1907
- },
1908
- required: [
1909
- "method",
1910
- "headers",
1911
- "queryParams"
1912
- ]
1913
- }
1914
- }
1915
- };
1916
- var getRoute = /* @__PURE__ */ __name((fastify) => {
1917
- fastify.get("/get", {
1918
- schema: getSchema
1919
- }, async (request, _reply) => ({
1920
- method: "GET",
1921
- headers: request.headers,
1922
- queryParams: request.query
1923
- }));
1924
- }, "getRoute");
1925
-
1926
- // src/routes/http-methods/patch.ts
1927
- var patchSchema = {
1928
- description: "Handles a PATCH request and returns request information",
1929
- tags: [
1930
- "HTTP Methods"
1931
- ],
1932
- body: {
1933
- type: "object",
1934
- additionalProperties: true,
1935
- description: "The body of the PATCH request"
1936
- },
1937
- response: {
1938
- 200: {
1939
- type: "object",
1940
- properties: {
1941
- method: {
1942
- type: "string"
1943
- },
1944
- headers: {
1945
- type: "object",
1946
- additionalProperties: {
1947
- type: "string"
1948
- }
1949
- },
1950
- body: {
1951
- type: "object",
1952
- additionalProperties: true
1953
- }
1954
- },
1955
- required: [
1956
- "method",
1957
- "headers",
1958
- "body"
1959
- ]
1960
- }
1961
- }
1962
- };
1963
- var patchRoute = /* @__PURE__ */ __name((fastify) => {
1964
- fastify.patch("/patch", {
1965
- schema: patchSchema
1966
- }, async (request) => ({
1967
- method: "PATCH",
1968
- headers: request.headers,
1969
- body: request.body
1970
- }));
1971
- }, "patchRoute");
1972
-
1973
- // src/routes/http-methods/post.ts
1974
- var postSchema = {
1975
- description: "Handles a POST request and returns request information",
1976
- tags: [
1977
- "HTTP Methods"
1978
- ],
1979
- body: {
1980
- type: "object",
1981
- additionalProperties: true,
1982
- description: "The body of the POST request"
1983
- },
1984
- response: {
1985
- 200: {
1986
- type: "object",
1987
- properties: {
1988
- method: {
1989
- type: "string"
1990
- },
1991
- headers: {
1992
- type: "object",
1993
- additionalProperties: {
1994
- type: "string"
1995
- }
1996
- },
1997
- body: {
1998
- type: "object",
1999
- additionalProperties: true
2000
- }
2001
- },
2002
- required: [
2003
- "method",
2004
- "headers",
2005
- "body"
2006
- ]
2007
- }
2008
- }
2009
- };
2010
- var postRoute = /* @__PURE__ */ __name((fastify) => {
2011
- fastify.post("/post", {
2012
- schema: postSchema
2013
- }, async (request) => ({
2014
- method: "POST",
2015
- headers: request.headers,
2016
- body: request.body
2017
- }));
2018
- }, "postRoute");
2019
-
2020
- // src/routes/http-methods/put.ts
2021
- var putSchema = {
2022
- description: "Handles a PUT request and returns request information",
2023
- tags: [
2024
- "HTTP Methods"
2025
- ],
2026
- body: {
2027
- type: "object",
2028
- additionalProperties: true,
2029
- description: "The body of the PUT request"
2030
- },
2031
- response: {
2032
- 200: {
2033
- type: "object",
2034
- properties: {
2035
- method: {
2036
- type: "string"
2037
- },
2038
- headers: {
2039
- type: "object",
2040
- additionalProperties: {
2041
- type: "string"
2042
- }
2043
- },
2044
- body: {
2045
- type: "object",
2046
- additionalProperties: true
2047
- }
2048
- },
2049
- required: [
2050
- "method",
2051
- "headers",
2052
- "body"
2053
- ]
2054
- }
2055
- }
2056
- };
2057
- var putRoute = /* @__PURE__ */ __name((fastify) => {
2058
- fastify.put("/put", {
2059
- schema: putSchema
2060
- }, async (request) => ({
2061
- method: "PUT",
2062
- headers: request.headers,
2063
- body: request.body
2064
- }));
2065
- }, "putRoute");
2066
-
2067
- // src/routes/images/index.ts
2068
- import { readFileSync } from "fs";
2069
- import { join } from "path";
2070
- var imageSchema = {
2071
- description: "Returns an image based on Accept header (supports jpeg, png, svg, webp)",
2072
- tags: [
2073
- "Images"
2074
- ],
2075
- headers: {
2076
- type: "object",
2077
- properties: {
2078
- accept: {
2079
- type: "string",
2080
- description: "Accept header for content negotiation"
2081
- }
2082
- }
2083
- },
2084
- response: {
2085
- 200: {
2086
- description: "Image file",
2087
- type: "string",
2088
- format: "binary"
2089
- }
2090
- }
2091
- };
2092
- var jpegSchema = {
2093
- description: "Returns a JPEG image",
2094
- tags: [
2095
- "Images"
2096
- ],
2097
- response: {
2098
- 200: {
2099
- description: "JPEG image",
2100
- type: "string",
2101
- format: "binary"
2102
- }
2103
- }
2104
- };
2105
- var pngSchema = {
2106
- description: "Returns a PNG image",
2107
- tags: [
2108
- "Images"
2109
- ],
2110
- response: {
2111
- 200: {
2112
- description: "PNG image",
2113
- type: "string",
2114
- format: "binary"
2115
- }
2116
- }
2117
- };
2118
- var svgSchema = {
2119
- description: "Returns an SVG image",
2120
- tags: [
2121
- "Images"
2122
- ],
2123
- response: {
2124
- 200: {
2125
- description: "SVG image",
2126
- type: "string",
2127
- format: "binary"
2128
- }
2129
- }
2130
- };
2131
- var webpSchema = {
2132
- description: "Returns a WEBP image",
2133
- tags: [
2134
- "Images"
2135
- ],
2136
- response: {
2137
- 200: {
2138
- description: "WEBP image",
2139
- type: "string",
2140
- format: "binary"
2141
- }
2142
- }
2143
- };
2144
- var imageRoutes = /* @__PURE__ */ __name((fastify) => {
2145
- const publicPath = join(process.cwd(), "public");
2146
- fastify.get("/image", {
2147
- schema: imageSchema
2148
- }, async (request, reply) => {
2149
- const accept = request.headers.accept || "";
2150
- let file = "logo.png";
2151
- let contentType = "image/png";
2152
- if (accept.includes("image/jpeg") || accept.includes("image/jpg")) {
2153
- file = "logo.jpg";
2154
- contentType = "image/jpeg";
2155
- } else if (accept.includes("image/svg+xml") || accept.includes("image/svg")) {
2156
- file = "logo.svg";
2157
- contentType = "image/svg+xml";
2158
- } else if (accept.includes("image/webp")) {
2159
- file = "logo.webp";
2160
- contentType = "image/webp";
2161
- } else if (accept.includes("image/png")) {
2162
- file = "logo.png";
2163
- contentType = "image/png";
2164
- }
2165
- try {
2166
- const imagePath = join(publicPath, file);
2167
- const imageBuffer = readFileSync(imagePath);
2168
- reply.type(contentType);
2169
- return imageBuffer;
2170
- } catch (error) {
2171
- reply.code(500);
2172
- return {
2173
- error: `${error}`
2174
- };
2175
- }
2176
- });
2177
- fastify.get("/image/jpeg", {
2178
- schema: jpegSchema
2179
- }, async (_request, reply) => {
2180
- try {
2181
- const imagePath = join(publicPath, "logo.jpg");
2182
- const imageBuffer = readFileSync(imagePath);
2183
- reply.type("image/jpeg");
2184
- return imageBuffer;
2185
- } catch (error) {
2186
- reply.code(500);
2187
- return {
2188
- error: `${error}`
2189
- };
2190
- }
2191
- });
2192
- fastify.get("/image/png", {
2193
- schema: pngSchema
2194
- }, async (_request, reply) => {
2195
- try {
2196
- const imagePath = join(publicPath, "logo.png");
2197
- const imageBuffer = readFileSync(imagePath);
2198
- reply.type("image/png");
2199
- return imageBuffer;
2200
- } catch (error) {
2201
- reply.code(500);
2202
- return {
2203
- error: `${error}`
2204
- };
2205
- }
2206
- });
2207
- fastify.get("/image/svg", {
2208
- schema: svgSchema
2209
- }, async (_request, reply) => {
2210
- try {
2211
- const imagePath = join(publicPath, "logo.svg");
2212
- const imageBuffer = readFileSync(imagePath);
2213
- reply.type("image/svg+xml");
2214
- return imageBuffer;
2215
- } catch (error) {
2216
- reply.code(500);
2217
- return {
2218
- error: `${error}`
2219
- };
2220
- }
2221
- });
2222
- fastify.get("/image/webp", {
2223
- schema: webpSchema
2224
- }, async (_request, reply) => {
2225
- try {
2226
- const imagePath = join(publicPath, "logo.webp");
2227
- const imageBuffer = readFileSync(imagePath);
2228
- reply.type("image/webp");
2229
- return imageBuffer;
2230
- } catch (error) {
2231
- reply.code(500);
2232
- return {
2233
- error: `${error}`
2234
- };
2235
- }
2236
- });
2237
- }, "imageRoutes");
2238
-
2239
- // src/routes/index.ts
2240
- var indexRoute = /* @__PURE__ */ __name((fastify) => {
2241
- fastify.get("/", {
2242
- schema: {
2243
- hide: true
2244
- }
2245
- }, async (_request, reply) => {
2246
- const redocHtml = `
2247
- <!doctype html>
2248
- <html>
2249
- <head>
2250
- <title>{ mockhttp }</title>
2251
- <meta charset="utf-8" />
2252
- <meta
2253
- name="viewport"
2254
- content="width=device-width, initial-scale=1" />
2255
- <meta name="description" content="MockHttp - A powerful HTTP mock server and httpbin replacement for API testing. Built with Fastify, TypeScript, and Docker support." />
2256
- <meta name="keywords" content="mock, http, httpbin, testing, api, fastify, docker, typescript, rest, mock-server, api-testing, http-testing, httpbin-alternative" />
2257
- <meta property="og:title" content="MockHttp - HTTP Mock Server" />
2258
- <meta property="og:description" content="A powerful HTTP mock server and httpbin replacement for API testing. Built with Fastify, TypeScript, and Docker support." />
2259
- <meta property="og:type" content="website" />
2260
- <meta property="og:url" content="https://mockhttp.org" />
2261
- <meta property="og:image" content="https://mockhttp.org/logo.png" />
2262
- <meta property="og:site_name" content="MockHttp" />
2263
- <meta name="twitter:card" content="summary" />
2264
- <meta name="twitter:title" content="MockHttp - HTTP Mock Server" />
2265
- <meta name="twitter:description" content="A powerful HTTP mock server and httpbin replacement for API testing." />
2266
- <meta name="twitter:image" content="https://mockhttp.org/logo.png" />
2267
- <style>
2268
- :root {
2269
- --scalar-custom-header-height: 70px;
2270
- }
2271
- .custom-header {
2272
- height: var(--scalar-custom-header-height);
2273
- background-color: var(--scalar-background-1);
2274
- box-shadow: inset 0 -1px 0 var(--scalar-border-color);
2275
- color: var(--scalar-color-1);
2276
- font-size: var(--scalar-font-size-2);
2277
- padding: 0 18px;
2278
- position: sticky;
2279
- justify-content: space-between;
2280
- top: 0;
2281
- z-index: 100;
2282
- }
2283
- .custom-header,
2284
- .custom-header nav {
2285
- display: flex;
2286
- align-items: center;
2287
- gap: 18px;
2288
- }
2289
-
2290
- .custom-header img {
2291
- height: var(--scalar-custom-header-height);
2292
- object-fit: contain;
2293
- }
2294
-
2295
- .custom-header a:hover {
2296
- color: var(--scalar-color-2);
2297
- }
2298
-
2299
- .github-corner {
2300
- fill: var(--scalar-border-color) !important;
2301
- }
2302
-
2303
- </style>
2304
- </head>
2305
- <body>
2306
- <header class="custom-header scalar-app">
2307
- <a href="/"><img src="/logo.svg" alt="{ mockhttp }" /></a>
2308
- <a href="https://github.com/jaredwray/mockhttp" class="github-corner" aria-label="View source on GitHub">
2309
- <svg width="70" height="70" viewBox="0 0 250 250" style="color:#fff; position: absolute; top: 0; border: 0; right: 0;" aria-hidden="true">
2310
- <path d="M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z"></path>
2311
- <path d="M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2" fill="currentColor" style="transform-origin: 130px 106px;" class="octo-arm"></path>
2312
- <path d="M115.0,115.0 C114.9,115.1 118.7,116.5 119.8,115.4 L133.7,101.6 C136.9,99.2 139.9,98.4 142.2,98.6 C133.8,88.0 127.5,74.4 143.8,58.0 C148.5,53.4 154.0,51.2 159.7,51.0 C160.3,49.4 163.2,43.6 171.4,40.1 C171.4,40.1 176.1,42.5 178.8,56.2 C183.1,58.6 187.2,61.8 190.9,65.4 C194.5,69.0 197.7,73.2 200.1,77.6 C213.8,80.2 216.3,84.9 216.3,84.9 C212.7,93.1 206.9,96.0 205.4,96.6 C205.1,102.4 203.0,107.8 198.3,112.5 C181.9,128.9 168.3,122.5 157.7,114.1 C157.9,116.9 156.7,120.9 152.7,124.9 L141.0,136.5 C139.8,137.7 141.6,141.9 141.8,141.8 Z" fill="currentColor" class="octo-body"></path></svg></a><style>.github-corner:hover .octo-arm{animation:octocat-wave 560ms ease-in-out}@keyframes octocat-wave{0%,100%{transform:rotate(0)}20%,60%{transform:rotate(-25deg)}40%,80%{transform:rotate(10deg)}}@media (max-width:500px){.github-corner:hover .octo-arm{animation:none}.github-corner .octo-arm{animation:octocat-wave 560ms ease-in-out}}</style>
2313
- </header>
2314
-
2315
- <script
2316
- id="api-reference"
2317
- data-url="/docs/json"></script>
2318
-
2319
- <script src="/scalar/browser/standalone.js"></script>
2320
- </body>
2321
- </html>
2322
- `;
2323
- await reply.type("text/html").send(redocHtml);
2324
- });
2325
- }, "indexRoute");
2326
-
2327
- // src/routes/redirects/absolute-redirect.ts
2328
- var absoluteRedirectSchema = {
2329
- tags: [
2330
- "Redirects"
2331
- ],
2332
- description: "Redirects the request to the target URL using an absolute URL",
2333
- params: {
2334
- type: "object",
2335
- properties: {
2336
- value: {
2337
- type: "integer",
2338
- minimum: 1
2339
- }
2340
- },
2341
- required: [
2342
- "value"
2343
- ]
2344
- },
2345
- headers: {
2346
- type: "object",
2347
- properties: {
2348
- host: {
2349
- type: "string"
2350
- }
2351
- },
2352
- required: [
2353
- "host"
2354
- ]
2355
- },
2356
- response: {
2357
- 302: {
2358
- type: "string"
2359
- }
2360
- }
2361
- };
2362
- var absoluteRedirectRoute = /* @__PURE__ */ __name((fastify) => {
2363
- fastify.get("/absolute-redirect/:value", {
2364
- schema: absoluteRedirectSchema
2365
- }, async (request, reply) => {
2366
- const { value } = request.params;
2367
- const { host } = request.headers;
2368
- const { protocol } = request;
2369
- let url = `${protocol}://${host}/get`;
2370
- let html = `
2371
- <title>Redirecting...</title>
2372
- <h1>Redirecting...</h1>
2373
- <p>You should be redirected automatically to target URL: <a href="${url}">${url}</a>. If not click the link.</p>
2374
- `;
2375
- if (value > 1) {
2376
- url = `${protocol}://${host}/absolute-redirect/${value - 1}`;
2377
- html = `
2378
- <title>Redirecting...</title>
2379
- <h1>Redirecting...</h1>
2380
- <p>You should be redirected automatically to target URL: <a href="${url}">${url}</a>. If not click the link.</p>
2381
- `;
2382
- }
2383
- await reply.header("Location", url).status(302).type("text/html").send(html);
2384
- });
2385
- }, "absoluteRedirectRoute");
2386
-
2387
- // src/routes/redirects/redirect-to.ts
2388
- var redirectToSchema = {
2389
- tags: [
2390
- "Redirects"
2391
- ],
2392
- description: "Redirects the request to the target URL",
2393
- querystring: {
2394
- type: "object",
2395
- properties: {
2396
- url: {
2397
- type: "string",
2398
- format: "uri",
2399
- description: "The URL to redirect to"
2400
- },
2401
- status_code: {
2402
- type: "string",
2403
- pattern: "^[1-5][0-9][0-9]$",
2404
- description: "The HTTP status code to use for redirection"
2405
- }
2406
- },
2407
- required: [
2408
- "url"
2409
- ],
2410
- additionalProperties: false
2411
- }
2412
- };
2413
- var redirectToRoute = /* @__PURE__ */ __name((fastify) => {
2414
- const handler = /* @__PURE__ */ __name(async (request, reply) => {
2415
- const { url, status_code } = request.query;
2416
- const statusCode = status_code ? Number(status_code) : 302;
2417
- await reply.redirect(url, statusCode);
2418
- }, "handler");
2419
- fastify.get("/redirect-to", {
2420
- schema: redirectToSchema
2421
- }, handler);
2422
- fastify.post("/redirect-to", {
2423
- schema: redirectToSchema
2424
- }, handler);
2425
- fastify.put("/redirect-to", {
2426
- schema: redirectToSchema
2427
- }, handler);
2428
- fastify.patch("/redirect-to", {
2429
- schema: redirectToSchema
2430
- }, handler);
2431
- fastify.delete("/redirect-to", {
2432
- schema: redirectToSchema
2433
- }, handler);
2434
- }, "redirectToRoute");
2435
-
2436
- // src/routes/redirects/relative-redirect.ts
2437
- var relativeRedirectSchema = {
2438
- tags: [
2439
- "Redirects"
2440
- ],
2441
- description: "Redirects the request to the target URL using an relative URL",
2442
- params: {
2443
- type: "object",
2444
- properties: {
2445
- value: {
2446
- type: "integer",
2447
- minimum: 1
2448
- }
2449
- },
2450
- required: [
2451
- "value"
2452
- ]
2453
- },
2454
- headers: {
2455
- type: "object",
2456
- properties: {
2457
- host: {
2458
- type: "string"
2459
- }
2460
- },
2461
- required: [
2462
- "host"
2463
- ]
2464
- },
2465
- response: {
2466
- 302: {
2467
- type: "string"
2468
- }
2469
- }
2470
- };
2471
- var relativeRedirectRoute = /* @__PURE__ */ __name((fastify) => {
2472
- fastify.get("/relative-redirect/:value", {
2473
- schema: relativeRedirectSchema
2474
- }, async (request, reply) => {
2475
- const { value } = request.params;
2476
- let url = "/get";
2477
- let html = `
2478
- <title>Redirecting...</title>
2479
- <h1>Redirecting...</h1>
2480
- <p>You should be redirected automatically to target URL: <a href="${url}">${url}</a>. If not click the link.</p>
2481
- `;
2482
- if (value > 1) {
2483
- url = `/relative-redirect/${value - 1}`;
2484
- html = `
2485
- <title>Redirecting...</title>
2486
- <h1>Redirecting...</h1>
2487
- <p>You should be redirected automatically to target URL: <a href="${url}">${url}</a>. If not click the link.</p>
2488
- `;
2489
- }
2490
- await reply.header("Location", url).status(302).type("text/html").send(html);
2491
- });
2492
- }, "relativeRedirectRoute");
2493
-
2494
- // src/routes/request-inspection/headers.ts
2495
- var headersSchema = {
2496
- description: "Returns all headers of the client request",
2497
- tags: [
2498
- "Request Inspection"
2499
- ],
2500
- response: {
2501
- 200: {
2502
- type: "object",
2503
- properties: {
2504
- headers: {
2505
- type: "object",
2506
- additionalProperties: {
2507
- type: "string"
2508
- },
2509
- description: "All headers of the client request"
2510
- }
2511
- },
2512
- required: [
2513
- "headers"
2514
- ]
2515
- }
2516
- }
2517
- };
2518
- var headersRoute = /* @__PURE__ */ __name((fastify) => {
2519
- fastify.get("/headers", {
2520
- schema: headersSchema
2521
- }, async (request) => {
2522
- const { headers } = request;
2523
- return {
2524
- headers
2525
- };
2526
- });
2527
- }, "headersRoute");
2528
-
2529
- // src/routes/request-inspection/ip.ts
2530
- var ipSchema = {
2531
- description: "Returns the IP address of the client",
2532
- tags: [
2533
- "Request Inspection"
2534
- ],
2535
- response: {
2536
- 200: {
2537
- type: "object",
2538
- properties: {
2539
- ip: {
2540
- type: "string",
2541
- description: "The IP address of the client"
2542
- }
2543
- },
2544
- required: [
2545
- "ip"
2546
- ]
2547
- }
2548
- }
2549
- };
2550
- var ipRoute = /* @__PURE__ */ __name((fastify) => {
2551
- fastify.get("/ip", {
2552
- schema: ipSchema
2553
- }, async (request) => {
2554
- const cloudflareIp = request.headers["cf-connecting-ip"];
2555
- const forwardedIp = request.headers["x-forwarded-for"];
2556
- const remoteIp = request.ip;
2557
- const ip = cloudflareIp ?? (typeof forwardedIp === "string" ? forwardedIp.split(",")[0].trim() : remoteIp);
2558
- return {
2559
- ip
2560
- };
2561
- });
2562
- }, "ipRoute");
2563
-
2564
- // src/routes/request-inspection/user-agent.ts
2565
- var userAgentSchema = {
2566
- description: "Returns the User-Agent header of the client request",
2567
- tags: [
2568
- "Request Inspection"
2569
- ],
2570
- response: {
2571
- 200: {
2572
- type: "object",
2573
- properties: {
2574
- userAgent: {
2575
- type: "string",
2576
- description: "The User-Agent header of the client request"
2577
- }
2578
- },
2579
- required: [
2580
- "userAgent"
2581
- ]
2582
- }
2583
- }
2584
- };
2585
- var userAgentRoute = /* @__PURE__ */ __name((fastify) => {
2586
- fastify.get("/user-agent", {
2587
- schema: userAgentSchema
2588
- }, async (request) => {
2589
- const userAgent = request.headers["user-agent"];
2590
- return {
2591
- userAgent
2592
- };
2593
- });
2594
- }, "userAgentRoute");
2595
-
2596
- // src/routes/response-formats/index.ts
2597
- import { brotliCompressSync, deflateSync, gzipSync } from "zlib";
2598
- var plainSchema = {
2599
- description: "Returns random plain text content",
2600
- tags: [
2601
- "Response Formats"
2602
- ],
2603
- response: {
2604
- 200: {
2605
- type: "string",
2606
- description: "Random plain text content"
2607
- }
2608
- }
2609
- };
2610
- var textSchema = {
2611
- description: "Returns random text content",
2612
- tags: [
2613
- "Response Formats"
2614
- ],
2615
- response: {
2616
- 200: {
2617
- type: "string",
2618
- description: "Random text content"
2619
- }
2620
- }
2621
- };
2622
- var htmlSchema = {
2623
- description: "Returns random HTML content",
2624
- tags: [
2625
- "Response Formats"
2626
- ],
2627
- response: {
2628
- 200: {
2629
- type: "string",
2630
- description: "Random HTML content"
2631
- }
2632
- }
2633
- };
2634
- var xmlSchema = {
2635
- description: "Returns random XML content",
2636
- tags: [
2637
- "Response Formats"
2638
- ],
2639
- response: {
2640
- 200: {
2641
- type: "string",
2642
- description: "Random XML content"
2643
- }
2644
- }
2645
- };
2646
- var jsonSchema = {
2647
- description: "Returns random JSON content",
2648
- tags: [
2649
- "Response Formats"
2650
- ],
2651
- response: {
2652
- 200: {
2653
- type: "object",
2654
- description: "Random JSON content",
2655
- additionalProperties: true
2656
- }
2657
- }
2658
- };
2659
- var denySchema = {
2660
- description: "Returns page denied by robots.txt rules",
2661
- tags: [
2662
- "Response Formats"
2663
- ],
2664
- response: {
2665
- 403: {
2666
- type: "string",
2667
- description: "Page denied by robots.txt rules"
2668
- }
2669
- }
2670
- };
2671
- var gzipSchema = {
2672
- description: "Returns GZip-encoded data",
2673
- tags: [
2674
- "Response Formats"
2675
- ],
2676
- response: {
2677
- 200: {
2678
- type: "string",
2679
- description: "GZip-encoded content"
2680
- }
2681
- }
2682
- };
2683
- var deflateSchema = {
2684
- description: "Returns Deflate-encoded data",
2685
- tags: [
2686
- "Response Formats"
2687
- ],
2688
- response: {
2689
- 200: {
2690
- type: "string",
2691
- description: "Deflate-encoded content"
2692
- }
2693
- }
2694
- };
2695
- var brotliSchema = {
2696
- description: "Returns Brotli-encoded data",
2697
- tags: [
2698
- "Response Formats"
2699
- ],
2700
- response: {
2701
- 200: {
2702
- type: "string",
2703
- description: "Brotli-encoded content"
2704
- }
2705
- }
2706
- };
2707
- var randomTexts = [
2708
- "The quick brown fox jumps over the lazy dog.",
2709
- "Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
2710
- "To be or not to be, that is the question.",
2711
- "All work and no play makes Jack a dull boy.",
2712
- "The only way to do great work is to love what you do.",
2713
- "In the beginning was the Word, and the Word was with God.",
2714
- "Once upon a time in a galaxy far, far away...",
2715
- "It was the best of times, it was the worst of times.",
2716
- "The journey of a thousand miles begins with a single step.",
2717
- "Knowledge is power. France is bacon."
2718
- ];
2719
- var randomHtmlContent = [
2720
- `<!DOCTYPE html>
2721
- <html lang="en">
2722
- <head>
2723
- <title>Sample Page</title>
2724
- </head>
2725
- <body>
2726
- <h1>Welcome to MockHTTP</h1>
2727
- <p>This is a sample HTML page with random content.</p>
2728
- </body>
2729
- </html>`,
2730
- `<!DOCTYPE html>
2731
- <html>
2732
- <head>
2733
- <meta charset="UTF-8">
2734
- <title>Test Document</title>
2735
- </head>
2736
- <body>
2737
- <header>
2738
- <h1>Test Page</h1>
2739
- <nav>
2740
- <ul>
2741
- <li><a href="#">Home</a></li>
2742
- <li><a href="#">About</a></li>
2743
- <li><a href="#">Contact</a></li>
2744
- </ul>
2745
- </nav>
2746
- </header>
2747
- <main>
2748
- <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</p>
2749
- </main>
2750
- </body>
2751
- </html>`,
2752
- `<!DOCTYPE html>
2753
- <html lang="en">
2754
- <head>
2755
- <title>Article Page</title>
2756
- <style>
2757
- body { font-family: Arial, sans-serif; }
2758
- article { max-width: 800px; margin: 0 auto; }
2759
- </style>
2760
- </head>
2761
- <body>
2762
- <article>
2763
- <h1>The Journey Begins</h1>
2764
- <p>Once upon a time in a digital world...</p>
2765
- <blockquote>
2766
- "The only way to do great work is to love what you do."
2767
- </blockquote>
2768
- </article>
2769
- </body>
2770
- </html>`,
2771
- `<!DOCTYPE html>
2772
- <html>
2773
- <head>
2774
- <title>Form Example</title>
2775
- </head>
2776
- <body>
2777
- <h2>Contact Form</h2>
2778
- <form action="/submit" method="post">
2779
- <label for="name">Name:</label>
2780
- <input type="text" id="name" name="name" required>
2781
- <br>
2782
- <label for="email">Email:</label>
2783
- <input type="email" id="email" name="email" required>
2784
- <br>
2785
- <button type="submit">Submit</button>
2786
- </form>
2787
- </body>
2788
- </html>`,
2789
- `<!DOCTYPE html>
2790
- <html lang="en">
2791
- <head>
2792
- <meta charset="UTF-8">
2793
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
2794
- <title>Responsive Page</title>
2795
- </head>
2796
- <body>
2797
- <div class="container">
2798
- <h1>Responsive Design</h1>
2799
- <p>This page adapts to different screen sizes.</p>
2800
- <img src="placeholder.jpg" alt="Placeholder" style="max-width: 100%; height: auto;">
2801
- </div>
2802
- </body>
2803
- </html>`
2804
- ];
2805
- var randomXmlContent = [
2806
- `<?xml version="1.0" encoding="UTF-8"?>
2807
- <root>
2808
- <message>Hello from MockHTTP</message>
2809
- <timestamp>${(/* @__PURE__ */ new Date()).toISOString()}</timestamp>
2810
- <status>success</status>
2811
- </root>`,
2812
- `<?xml version="1.0" encoding="UTF-8"?>
2813
- <book>
2814
- <title>The Great Gatsby</title>
2815
- <author>F. Scott Fitzgerald</author>
2816
- <year>1925</year>
2817
- <publisher>Charles Scribner's Sons</publisher>
2818
- <isbn>978-0-7432-7356-5</isbn>
2819
- </book>`,
2820
- `<?xml version="1.0" encoding="UTF-8"?>
2821
- <users>
2822
- <user id="1">
2823
- <name>John Doe</name>
2824
- <email>john@example.com</email>
2825
- <role>admin</role>
2826
- </user>
2827
- <user id="2">
2828
- <name>Jane Smith</name>
2829
- <email>jane@example.com</email>
2830
- <role>user</role>
2831
- </user>
2832
- </users>`,
2833
- `<?xml version="1.0" encoding="UTF-8"?>
2834
- <product>
2835
- <id>12345</id>
2836
- <name>Wireless Mouse</name>
2837
- <description>Ergonomic wireless mouse with 3-year battery life</description>
2838
- <price currency="USD">29.99</price>
2839
- <stock>150</stock>
2840
- <category>Electronics</category>
2841
- </product>`,
2842
- `<?xml version="1.0" encoding="UTF-8"?>
2843
- <rss version="2.0">
2844
- <channel>
2845
- <title>Mock RSS Feed</title>
2846
- <link>http://example.com/rss</link>
2847
- <description>Sample RSS feed from MockHTTP</description>
2848
- <item>
2849
- <title>First Article</title>
2850
- <link>http://example.com/article1</link>
2851
- <description>This is the first article in our feed</description>
2852
- <pubDate>${(/* @__PURE__ */ new Date()).toUTCString()}</pubDate>
2853
- </item>
2854
- </channel>
2855
- </rss>`
2856
- ];
2857
- var randomJsonContent = [
2858
- () => ({
2859
- message: "Hello from MockHTTP",
2860
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
2861
- status: "success",
2862
- code: 200
2863
- }),
2864
- () => ({
2865
- user: {
2866
- id: 1,
2867
- name: "John Doe",
2868
- email: "john@example.com",
2869
- role: "admin",
2870
- createdAt: "2024-01-15T10:30:00Z"
2871
- },
2872
- permissions: [
2873
- "read",
2874
- "write",
2875
- "delete"
2876
- ]
2877
- }),
2878
- () => ({
2879
- products: [
2880
- {
2881
- id: 101,
2882
- name: "Laptop",
2883
- price: 999.99,
2884
- stock: 50
2885
- },
2886
- {
2887
- id: 102,
2888
- name: "Mouse",
2889
- price: 29.99,
2890
- stock: 200
2891
- }
2892
- ],
2893
- total: 2,
2894
- page: 1
2895
- }),
2896
- () => ({
2897
- config: {
2898
- theme: "dark",
2899
- language: "en",
2900
- notifications: {
2901
- email: true,
2902
- push: false,
2903
- sms: false
2904
- },
2905
- privacy: {
2906
- profile: "public",
2907
- activity: "friends"
2908
- }
2909
- }
2910
- }),
2911
- () => ({
2912
- metrics: {
2913
- cpu: 45.2,
2914
- memory: 78.5,
2915
- disk: 62.3,
2916
- network: {
2917
- inbound: "1.2MB/s",
2918
- outbound: "0.8MB/s"
2919
- },
2920
- uptime: "15 days",
2921
- services: [
2922
- {
2923
- name: "api",
2924
- status: "healthy"
2925
- },
2926
- {
2927
- name: "database",
2928
- status: "healthy"
2929
- },
2930
- {
2931
- name: "cache",
2932
- status: "warning"
2933
- }
2934
- ]
2935
- }
2936
- })
2937
- ];
2938
- var responseFormatRoutes = /* @__PURE__ */ __name((fastify) => {
2939
- const plainHandler = /* @__PURE__ */ __name(async (_request, reply) => {
2940
- const randomIndex = Math.floor(Math.random() * randomTexts.length);
2941
- const text = randomTexts[randomIndex];
2942
- reply.type("text/plain");
2943
- return text;
2944
- }, "plainHandler");
2945
- fastify.get("/plain", {
2946
- schema: plainSchema
2947
- }, plainHandler);
2948
- fastify.post("/plain", {
2949
- schema: plainSchema
2950
- }, plainHandler);
2951
- fastify.put("/plain", {
2952
- schema: plainSchema
2953
- }, plainHandler);
2954
- fastify.patch("/plain", {
2955
- schema: plainSchema
2956
- }, plainHandler);
2957
- fastify.delete("/plain", {
2958
- schema: plainSchema
2959
- }, plainHandler);
2960
- const textHandler = /* @__PURE__ */ __name(async (_request, reply) => {
2961
- const randomIndex = Math.floor(Math.random() * randomTexts.length);
2962
- const text = randomTexts[randomIndex];
2963
- reply.type("text/plain");
2964
- return text;
2965
- }, "textHandler");
2966
- fastify.get("/text", {
2967
- schema: textSchema
2968
- }, textHandler);
2969
- fastify.post("/text", {
2970
- schema: textSchema
2971
- }, textHandler);
2972
- fastify.put("/text", {
2973
- schema: textSchema
2974
- }, textHandler);
2975
- fastify.patch("/text", {
2976
- schema: textSchema
2977
- }, textHandler);
2978
- fastify.delete("/text", {
2979
- schema: textSchema
2980
- }, textHandler);
2981
- const htmlHandler = /* @__PURE__ */ __name(async (_request, reply) => {
2982
- const randomIndex = Math.floor(Math.random() * randomHtmlContent.length);
2983
- const html = randomHtmlContent[randomIndex];
2984
- reply.type("text/html");
2985
- return html;
2986
- }, "htmlHandler");
2987
- fastify.get("/html", {
2988
- schema: htmlSchema
2989
- }, htmlHandler);
2990
- fastify.post("/html", {
2991
- schema: htmlSchema
2992
- }, htmlHandler);
2993
- fastify.put("/html", {
2994
- schema: htmlSchema
2995
- }, htmlHandler);
2996
- fastify.patch("/html", {
2997
- schema: htmlSchema
2998
- }, htmlHandler);
2999
- fastify.delete("/html", {
3000
- schema: htmlSchema
3001
- }, htmlHandler);
3002
- const xmlHandler = /* @__PURE__ */ __name(async (_request, reply) => {
3003
- const randomIndex = Math.floor(Math.random() * randomXmlContent.length);
3004
- const xml = randomXmlContent[randomIndex];
3005
- reply.type("application/xml");
3006
- return xml;
3007
- }, "xmlHandler");
3008
- fastify.get("/xml", {
3009
- schema: xmlSchema
3010
- }, xmlHandler);
3011
- fastify.post("/xml", {
3012
- schema: xmlSchema
3013
- }, xmlHandler);
3014
- fastify.put("/xml", {
3015
- schema: xmlSchema
3016
- }, xmlHandler);
3017
- fastify.patch("/xml", {
3018
- schema: xmlSchema
3019
- }, xmlHandler);
3020
- fastify.delete("/xml", {
3021
- schema: xmlSchema
3022
- }, xmlHandler);
3023
- const jsonHandler = /* @__PURE__ */ __name(async (_request, reply) => {
3024
- const randomIndex = Math.floor(Math.random() * randomJsonContent.length);
3025
- const jsonGenerator = randomJsonContent[randomIndex];
3026
- const json = jsonGenerator();
3027
- reply.type("application/json");
3028
- return json;
3029
- }, "jsonHandler");
3030
- fastify.get("/json", {
3031
- schema: jsonSchema
3032
- }, jsonHandler);
3033
- fastify.post("/json", {
3034
- schema: jsonSchema
3035
- }, jsonHandler);
3036
- fastify.put("/json", {
3037
- schema: jsonSchema
3038
- }, jsonHandler);
3039
- fastify.patch("/json", {
3040
- schema: jsonSchema
3041
- }, jsonHandler);
3042
- fastify.delete("/json", {
3043
- schema: jsonSchema
3044
- }, jsonHandler);
3045
- const denyHandler = /* @__PURE__ */ __name(async (_request, reply) => {
3046
- const denyMessages = [
3047
- `<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
3048
- <html><head>
3049
- <title>403 Forbidden</title>
3050
- </head><body>
3051
- <h1>Forbidden</h1>
3052
- <p>Access denied by robots.txt rules.</p>
3053
- <p>The requested resource is disallowed by robots.txt.</p>
3054
- </body></html>`,
3055
- `<!DOCTYPE html>
3056
- <html lang="en">
3057
- <head>
3058
- <meta charset="UTF-8">
3059
- <title>403 - Access Denied</title>
3060
- </head>
3061
- <body>
3062
- <h1>403 Forbidden</h1>
3063
- <p>This page is blocked by robots.txt rules.</p>
3064
- <p>User-agent: *<br>Disallow: ${_request.url}</p>
3065
- </body>
3066
- </html>`,
3067
- `<!DOCTYPE html>
3068
- <html>
3069
- <head>
3070
- <title>403 - Access Denied</title>
3071
- <style>body { font-family: Arial, sans-serif; margin: 40px; }</style>
3072
- </head>
3073
- <body>
3074
- <h2>403 - Access Denied by robots.txt</h2>
3075
- <p>The robots.txt file prevents access to this resource.</p>
3076
- <hr>
3077
- <small>MockHTTP Server</small>
3078
- </body>
3079
- </html>`
3080
- ];
3081
- const randomIndex = Math.floor(Math.random() * denyMessages.length);
3082
- const html = denyMessages[randomIndex];
3083
- reply.code(403);
3084
- reply.type("text/html");
3085
- return html;
3086
- }, "denyHandler");
3087
- fastify.get("/deny", {
3088
- schema: denySchema
3089
- }, denyHandler);
3090
- fastify.post("/deny", {
3091
- schema: denySchema
3092
- }, denyHandler);
3093
- fastify.put("/deny", {
3094
- schema: denySchema
3095
- }, denyHandler);
3096
- fastify.patch("/deny", {
3097
- schema: denySchema
3098
- }, denyHandler);
3099
- fastify.delete("/deny", {
3100
- schema: denySchema
3101
- }, denyHandler);
3102
- const gzipHandler = /* @__PURE__ */ __name(async (_request, reply) => {
3103
- const sampleData = [
3104
- {
3105
- message: "This is GZip compressed data from MockHTTP",
3106
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
3107
- compression: "gzip",
3108
- originalSize: 0,
3109
- compressedSize: 0
3110
- },
3111
- {
3112
- title: "Sample Document",
3113
- content: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. ".repeat(50),
3114
- metadata: {
3115
- author: "MockHTTP Server",
3116
- format: "gzip",
3117
- version: "1.0"
3118
- }
3119
- },
3120
- {
3121
- data: Array.from({
3122
- length: 100
3123
- }, (_, i) => ({
3124
- id: i + 1,
3125
- value: `Item ${i + 1}`,
3126
- description: `This is item number ${i + 1} in the dataset`
3127
- })),
3128
- info: "Large dataset compressed with GZip"
3129
- }
3130
- ];
3131
- const randomIndex = Math.floor(Math.random() * sampleData.length);
3132
- const data = sampleData[randomIndex];
3133
- const jsonString = JSON.stringify(data);
3134
- if (data.originalSize !== void 0) {
3135
- data.originalSize = Buffer.byteLength(jsonString);
3136
- }
3137
- const compressed = gzipSync(jsonString);
3138
- if (data.compressedSize !== void 0) {
3139
- const updatedData = {
3140
- ...data,
3141
- compressedSize: compressed.length
3142
- };
3143
- const updatedJsonString = JSON.stringify(updatedData);
3144
- const updatedCompressed = gzipSync(updatedJsonString);
3145
- reply.header("Content-Encoding", "gzip");
3146
- reply.header("Content-Type", "application/json");
3147
- reply.type("application/octet-stream");
3148
- return updatedCompressed;
3149
- }
3150
- reply.header("Content-Encoding", "gzip");
3151
- reply.header("Content-Type", "application/json");
3152
- reply.type("application/octet-stream");
3153
- return compressed;
3154
- }, "gzipHandler");
3155
- fastify.get("/gzip", {
3156
- schema: gzipSchema
3157
- }, gzipHandler);
3158
- fastify.post("/gzip", {
3159
- schema: gzipSchema
3160
- }, gzipHandler);
3161
- fastify.put("/gzip", {
3162
- schema: gzipSchema
3163
- }, gzipHandler);
3164
- fastify.patch("/gzip", {
3165
- schema: gzipSchema
3166
- }, gzipHandler);
3167
- fastify.delete("/gzip", {
3168
- schema: gzipSchema
3169
- }, gzipHandler);
3170
- const deflateHandler = /* @__PURE__ */ __name(async (_request, reply) => {
3171
- const sampleData = [
3172
- {
3173
- message: "This is Deflate compressed data from MockHTTP",
3174
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
3175
- compression: "deflate",
3176
- originalSize: 0,
3177
- compressedSize: 0
3178
- },
3179
- {
3180
- title: "Deflate Compressed Document",
3181
- content: "The deflate algorithm is used for compression. ".repeat(40),
3182
- details: {
3183
- algorithm: "DEFLATE",
3184
- method: "zlib deflate",
3185
- server: "MockHTTP"
3186
- }
3187
- },
3188
- {
3189
- items: Array.from({
3190
- length: 50
3191
- }, (_, i) => ({
3192
- index: i,
3193
- name: `Entry ${i}`,
3194
- data: `This is test data for entry number ${i}`,
3195
- timestamp: new Date(Date.now() - i * 1e6).toISOString()
3196
- })),
3197
- metadata: {
3198
- compression: "deflate",
3199
- count: 50
3200
- }
3201
- }
3202
- ];
3203
- const randomIndex = Math.floor(Math.random() * sampleData.length);
3204
- const data = sampleData[randomIndex];
3205
- const jsonString = JSON.stringify(data);
3206
- if (data.originalSize !== void 0) {
3207
- data.originalSize = Buffer.byteLength(jsonString);
3208
- }
3209
- const compressed = deflateSync(jsonString);
3210
- if (data.compressedSize !== void 0) {
3211
- const updatedData = {
3212
- ...data,
3213
- compressedSize: compressed.length
3214
- };
3215
- const updatedJsonString = JSON.stringify(updatedData);
3216
- const updatedCompressed = deflateSync(updatedJsonString);
3217
- reply.header("Content-Encoding", "deflate");
3218
- reply.header("Content-Type", "application/json");
3219
- reply.type("application/octet-stream");
3220
- return updatedCompressed;
3221
- }
3222
- reply.header("Content-Encoding", "deflate");
3223
- reply.header("Content-Type", "application/json");
3224
- reply.type("application/octet-stream");
3225
- return compressed;
3226
- }, "deflateHandler");
3227
- fastify.get("/deflate", {
3228
- schema: deflateSchema
3229
- }, deflateHandler);
3230
- fastify.post("/deflate", {
3231
- schema: deflateSchema
3232
- }, deflateHandler);
3233
- fastify.put("/deflate", {
3234
- schema: deflateSchema
3235
- }, deflateHandler);
3236
- fastify.patch("/deflate", {
3237
- schema: deflateSchema
3238
- }, deflateHandler);
3239
- fastify.delete("/deflate", {
3240
- schema: deflateSchema
3241
- }, deflateHandler);
3242
- const brotliHandler = /* @__PURE__ */ __name(async (_request, reply) => {
3243
- const sampleData = [
3244
- {
3245
- message: "This is Brotli compressed data from MockHTTP",
3246
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
3247
- compression: "br",
3248
- algorithm: "Brotli",
3249
- originalSize: 0,
3250
- compressedSize: 0
3251
- },
3252
- {
3253
- title: "Brotli Compression Example",
3254
- description: "Brotli is a generic-purpose lossless compression algorithm that compresses data using a combination of a modern variant of the LZ77 algorithm, Huffman coding and 2nd order context modeling.",
3255
- content: "Brotli compression typically achieves 20-26% better compression ratios than gzip. ".repeat(10),
3256
- metadata: {
3257
- encoding: "br",
3258
- quality: 11,
3259
- server: "MockHTTP"
3260
- }
3261
- },
3262
- {
3263
- benchmark: {
3264
- title: "Compression Benchmark Data",
3265
- results: Array.from({
3266
- length: 75
3267
- }, (_, i) => ({
3268
- id: `test-${i}`,
3269
- name: `Benchmark Test ${i}`,
3270
- value: Math.random() * 1e3,
3271
- unit: "ms",
3272
- timestamp: new Date(Date.now() - i * 6e4).toISOString()
3273
- })),
3274
- summary: {
3275
- compression: "brotli",
3276
- totalTests: 75,
3277
- encoding: "br"
3278
- }
3279
- }
3280
- }
3281
- ];
3282
- const randomIndex = Math.floor(Math.random() * sampleData.length);
3283
- const data = sampleData[randomIndex];
3284
- const jsonString = JSON.stringify(data);
3285
- if (data.originalSize !== void 0) {
3286
- data.originalSize = Buffer.byteLength(jsonString);
3287
- }
3288
- const compressed = brotliCompressSync(jsonString);
3289
- if (data.compressedSize !== void 0) {
3290
- const updatedData = {
3291
- ...data,
3292
- compressedSize: compressed.length
3293
- };
3294
- const updatedJsonString = JSON.stringify(updatedData);
3295
- const updatedCompressed = brotliCompressSync(updatedJsonString);
3296
- reply.header("Content-Encoding", "br");
3297
- reply.header("Content-Type", "application/json");
3298
- reply.type("application/octet-stream");
3299
- return updatedCompressed;
3300
- }
3301
- reply.header("Content-Encoding", "br");
3302
- reply.header("Content-Type", "application/json");
3303
- reply.type("application/octet-stream");
3304
- return compressed;
3305
- }, "brotliHandler");
3306
- fastify.get("/brotli", {
3307
- schema: brotliSchema
3308
- }, brotliHandler);
3309
- fastify.post("/brotli", {
3310
- schema: brotliSchema
3311
- }, brotliHandler);
3312
- fastify.put("/brotli", {
3313
- schema: brotliSchema
3314
- }, brotliHandler);
3315
- fastify.patch("/brotli", {
3316
- schema: brotliSchema
3317
- }, brotliHandler);
3318
- fastify.delete("/brotli", {
3319
- schema: brotliSchema
3320
- }, brotliHandler);
3321
- }, "responseFormatRoutes");
3322
-
3323
- // src/routes/response-inspection/cache.ts
3324
- var cacheGetSchema = {
3325
- description: "Handles cache validation and retrieval.",
3326
- tags: [
3327
- "Response Inspection"
3328
- ],
3329
- response: {
3330
- 200: {
3331
- type: "string",
3332
- description: "The cached content or resource."
3333
- },
3334
- 304: {
3335
- type: "null",
3336
- description: "Indicates the resource has not been modified."
3337
- }
3338
- }
3339
- };
3340
- var cacheSetSchema = {
3341
- description: "Sets a Cache-Control header for n seconds.",
3342
- tags: [
3343
- "Response Inspection"
3344
- ],
3345
- params: {
3346
- type: "object",
3347
- properties: {
3348
- value: {
3349
- type: "integer",
3350
- description: "Number of seconds to cache the resource."
3351
- }
3352
- },
3353
- required: [
3354
- "value"
3355
- ]
3356
- },
3357
- response: {
3358
- 200: {
3359
- type: "string",
3360
- description: "Confirmation message for setting cache."
3361
- }
3362
- }
3363
- };
3364
- var cacheRoutes = /* @__PURE__ */ __name((fastify) => {
3365
- fastify.get("/cache", {
3366
- schema: cacheGetSchema
3367
- }, async (request, reply) => {
3368
- const ifModifiedSince = request.headers["if-modified-since"];
3369
- const ifNoneMatch = request.headers["if-none-match"];
3370
- if (ifModifiedSince ?? ifNoneMatch) {
3371
- await reply.status(304).send();
3372
- return;
3373
- }
3374
- await reply.send("Cache content or resource response here.");
3375
- });
3376
- fastify.get("/cache/:value", {
3377
- schema: cacheSetSchema
3378
- }, async (request, reply) => {
3379
- const { value } = request.params;
3380
- await reply.header("Cache-Control", `max-age=${value}`).send(`Cache-Control set for ${value} seconds.`);
3381
- });
3382
- }, "cacheRoutes");
3383
-
3384
- // src/routes/response-inspection/etag.ts
3385
- var etagSchema = {
3386
- description: "Handles ETag-based conditional requests.",
3387
- tags: [
3388
- "Response Inspection"
3389
- ],
3390
- params: {
3391
- type: "object",
3392
- properties: {
3393
- etag: {
3394
- type: "string",
3395
- description: "The ETag value for the resource."
3396
- }
3397
- },
3398
- required: [
3399
- "etag"
3400
- ]
3401
- },
3402
- response: {
3403
- 200: {
3404
- type: "string",
3405
- description: "Resource content for matching ETag."
3406
- },
3407
- 304: {
3408
- type: "null",
3409
- description: "Indicates the resource has not been modified."
3410
- },
3411
- 412: {
3412
- type: "null",
3413
- description: "Indicates a precondition failed due to mismatched ETag."
3414
- }
3415
- }
3416
- };
3417
- var etagRoutes = /* @__PURE__ */ __name((fastify) => {
3418
- fastify.get("/etag/:etag", {
3419
- schema: etagSchema
3420
- }, async (request, reply) => {
3421
- const { etag } = request.params;
3422
- const ifNoneMatch = request.headers["if-none-match"];
3423
- const ifMatch = request.headers["if-match"];
3424
- if (ifNoneMatch === etag) {
3425
- await reply.status(304).send();
3426
- return;
3427
- }
3428
- if (ifMatch && ifMatch !== etag) {
3429
- await reply.status(412).send();
3430
- return;
3431
- }
3432
- await reply.header("ETag", etag).send("Resource content for matching ETag.");
3433
- });
3434
- }, "etagRoutes");
3435
-
3436
- // src/routes/response-inspection/response-headers.ts
3437
- import { escape as escapeHtml } from "html-escaper";
3438
- var responseHeadersSchema = {
3439
- description: "Returns a set of response headers based on the query string.",
3440
- tags: [
3441
- "Response Inspection"
3442
- ],
3443
- querystring: {
3444
- type: "object",
3445
- additionalProperties: true,
3446
- description: "Freeform query string parameters to be added as response headers."
3447
- },
3448
- response: {
3449
- 200: {
3450
- type: "object",
3451
- additionalProperties: true,
3452
- description: "Response headers as per query string parameters."
3453
- }
3454
- }
3455
- };
3456
- var responseHeadersRoutes = /* @__PURE__ */ __name((fastify) => {
3457
- fastify.get("/response-headers", {
3458
- schema: responseHeadersSchema
3459
- }, async (request, reply) => {
3460
- const queryParameters = request.query;
3461
- for (const [key, value] of Object.entries(queryParameters)) {
3462
- reply.header(key, escapeHtml(value));
3463
- }
3464
- const cleanedResponse = Object.fromEntries(Object.entries(queryParameters).map(([key, value]) => [
3465
- key,
3466
- escapeHtml(value)
3467
- ]));
3468
- await reply.send(cleanedResponse);
3469
- });
3470
- fastify.post("/response-headers", {
3471
- schema: responseHeadersSchema
3472
- }, async (request, reply) => {
3473
- const queryParameters = request.query ?? {};
3474
- const body = request.body ?? {};
3475
- for (const [key, value] of Object.entries(queryParameters)) {
3476
- reply.header(key, escapeHtml(value));
3477
- }
3478
- for (const [key, value] of Object.entries(body)) {
3479
- reply.header(key, escapeHtml(value));
3480
- }
3481
- const cleanedResponse = Object.fromEntries(Object.entries({
3482
- ...queryParameters,
3483
- ...body
3484
- }).map(([key, value]) => [
3485
- key,
3486
- escapeHtml(value)
3487
- ]));
3488
- await reply.send(cleanedResponse);
3489
- });
3490
- }, "responseHeadersRoutes");
3491
-
3492
- // src/routes/sitemap.ts
3493
- var sitemapRoute = /* @__PURE__ */ __name((fastify) => {
3494
- fastify.get("/sitemap.xml", {
3495
- schema: {
3496
- hide: true
3497
- }
3498
- }, async (_request, reply) => {
3499
- try {
3500
- const host = _request.headers.host ?? "mockhttp.org";
3501
- const isSecure = _request.headers["x-forwarded-proto"]?.includes("https");
3502
- const protocol = isSecure ? "https" : "http";
3503
- const xml = `<?xml version="1.0" encoding="UTF-8"?>
3504
- <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
3505
- <url>
3506
- <loc>${protocol}://${host}/</loc>
3507
- <changefreq>monthly</changefreq>
3508
- <priority>1.0</priority>
3509
- </url>
3510
- </urlset>`;
3511
- await reply.type("text/xml").send(xml);
3512
- } catch (error) {
3513
- fastify.log.error(error);
3514
- await reply.status(500).send({
3515
- error: "Internal Server Error"
3516
- });
3517
- }
3518
- });
3519
- }, "sitemapRoute");
3520
-
3521
- // src/routes/status-codes/index.ts
3522
- import { randomInt } from "crypto";
3523
- var statusCodes = [
3524
- {
3525
- code: 100,
3526
- message: "Continue"
3527
- },
3528
- {
3529
- code: 101,
3530
- message: "Switching Protocols"
3531
- },
3532
- {
3533
- code: 102,
3534
- message: "Processing"
3535
- },
3536
- {
3537
- code: 103,
3538
- message: "Early Hints"
3539
- },
3540
- {
3541
- code: 200,
3542
- message: "OK"
3543
- },
3544
- {
3545
- code: 201,
3546
- message: "Created"
3547
- },
3548
- {
3549
- code: 202,
3550
- message: "Accepted"
3551
- },
3552
- {
3553
- code: 203,
3554
- message: "Non-Authoritative Information"
3555
- },
3556
- {
3557
- code: 204,
3558
- message: "No Content"
3559
- },
3560
- {
3561
- code: 205,
3562
- message: "Reset Content"
3563
- },
3564
- {
3565
- code: 206,
3566
- message: "Partial Content"
3567
- },
3568
- {
3569
- code: 207,
3570
- message: "Multi-Status"
3571
- },
3572
- {
3573
- code: 208,
3574
- message: "Already Reported"
3575
- },
3576
- {
3577
- code: 226,
3578
- message: "IM Used"
3579
- },
3580
- {
3581
- code: 300,
3582
- message: "Multiple Choices"
3583
- },
3584
- {
3585
- code: 301,
3586
- message: "Moved Permanently"
3587
- },
3588
- {
3589
- code: 302,
3590
- message: "Found"
3591
- },
3592
- {
3593
- code: 303,
3594
- message: "See Other"
3595
- },
3596
- {
3597
- code: 304,
3598
- message: "Not Modified"
3599
- },
3600
- {
3601
- code: 305,
3602
- message: "Use Proxy"
3603
- },
3604
- {
3605
- code: 306,
3606
- message: "Switch Proxy"
3607
- },
3608
- {
3609
- code: 307,
3610
- message: "Temporary Redirect"
3611
- },
3612
- {
3613
- code: 308,
3614
- message: "Permanent Redirect"
3615
- },
3616
- {
3617
- code: 400,
3618
- message: "Bad Request"
3619
- },
3620
- {
3621
- code: 401,
3622
- message: "Unauthorized"
3623
- },
3624
- {
3625
- code: 402,
3626
- message: "Payment Required"
3627
- },
3628
- {
3629
- code: 403,
3630
- message: "Forbidden"
3631
- },
3632
- {
3633
- code: 404,
3634
- message: "Not Found"
3635
- },
3636
- {
3637
- code: 405,
3638
- message: "Method Not Allowed"
3639
- },
3640
- {
3641
- code: 406,
3642
- message: "Not Acceptable"
3643
- },
3644
- {
3645
- code: 407,
3646
- message: "Proxy Authentication Required"
3647
- },
3648
- {
3649
- code: 408,
3650
- message: "Request Timeout"
3651
- },
3652
- {
3653
- code: 409,
3654
- message: "Conflict"
3655
- },
3656
- {
3657
- code: 410,
3658
- message: "Gone"
3659
- },
3660
- {
3661
- code: 411,
3662
- message: "Length Required"
3663
- },
3664
- {
3665
- code: 412,
3666
- message: "Precondition Failed"
3667
- },
3668
- {
3669
- code: 413,
3670
- message: "Payload Too Large"
3671
- },
3672
- {
3673
- code: 414,
3674
- message: "URI Too Long"
3675
- },
3676
- {
3677
- code: 415,
3678
- message: "Unsupported Media Type"
3679
- },
3680
- {
3681
- code: 416,
3682
- message: "Range Not Satisfiable"
3683
- },
3684
- {
3685
- code: 417,
3686
- message: "Expectation Failed"
3687
- },
3688
- {
3689
- code: 418,
3690
- message: "I'm a teapot"
3691
- },
3692
- {
3693
- code: 421,
3694
- message: "Misdirected Request"
3695
- },
3696
- {
3697
- code: 422,
3698
- message: "Unprocessable Entity"
3699
- },
3700
- {
3701
- code: 423,
3702
- message: "Locked"
3703
- },
3704
- {
3705
- code: 424,
3706
- message: "Failed Dependency"
3707
- },
3708
- {
3709
- code: 425,
3710
- message: "Too Early"
3711
- },
3712
- {
3713
- code: 426,
3714
- message: "Upgrade Required"
3715
- },
3716
- {
3717
- code: 428,
3718
- message: "Precondition Required"
3719
- },
3720
- {
3721
- code: 429,
3722
- message: "Too Many Requests"
3723
- },
3724
- {
3725
- code: 431,
3726
- message: "Request Header Fields Too Large"
3727
- },
3728
- {
3729
- code: 451,
3730
- message: "Unavailable For Legal Reasons"
3731
- },
3732
- {
3733
- code: 500,
3734
- message: "Internal Server Error"
3735
- },
3736
- {
3737
- code: 501,
3738
- message: "Not Implemented"
3739
- },
3740
- {
3741
- code: 502,
3742
- message: "Bad Gateway"
3743
- },
3744
- {
3745
- code: 503,
3746
- message: "Service Unavailable"
3747
- },
3748
- {
3749
- code: 504,
3750
- message: "Gateway Timeout"
3751
- },
3752
- {
3753
- code: 505,
3754
- message: "HTTP Version Not Supported"
3755
- },
3756
- {
3757
- code: 506,
3758
- message: "Variant Also Negotiates"
3759
- },
3760
- {
3761
- code: 507,
3762
- message: "Insufficient Storage"
3763
- },
3764
- {
3765
- code: 508,
3766
- message: "Loop Detected"
3767
- },
3768
- {
3769
- code: 510,
3770
- message: "Not Extended"
3771
- },
3772
- {
3773
- code: 511,
3774
- message: "Network Authentication Required"
3775
- }
3776
- ];
3777
- var statusSchema = {
3778
- description: "Return status code or random status code if more than one are given ",
3779
- tags: [
3780
- "Status Codes"
3781
- ],
3782
- params: {
3783
- type: "object",
3784
- properties: {
3785
- code: {
3786
- type: "string",
3787
- description: "HTTP status code to be returned"
3788
- }
3789
- }
3790
- },
3791
- response: {
3792
- 200: {
3793
- type: "object",
3794
- properties: {
3795
- status: {
3796
- type: "string"
3797
- }
3798
- }
3799
- }
3800
- }
3801
- };
3802
- var statusCodeRoute = /* @__PURE__ */ __name((fastify) => {
3803
- const handler = /* @__PURE__ */ __name(async (request, reply) => {
3804
- let { code } = request.params;
3805
- if (!code || !statusCodes.map((status) => status.code).includes(Number(code))) {
3806
- code = statusCodes[randomInt(0, statusCodes.length)].code.toString();
3807
- }
3808
- await reply.code(Number(code)).send({
3809
- status: `Response with status code ${code}`
3810
- });
3811
- }, "handler");
3812
- fastify.get("/status/:code?", {
3813
- schema: statusSchema
3814
- }, handler);
3815
- fastify.post("/status/:code?", {
3816
- schema: statusSchema
3817
- }, handler);
3818
- fastify.put("/status/:code?", {
3819
- schema: statusSchema
3820
- }, handler);
3821
- fastify.patch("/status/:code?", {
3822
- schema: statusSchema
3823
- }, handler);
3824
- fastify.delete("/status/:code?", {
3825
- schema: statusSchema
3826
- }, handler);
3827
- }, "statusCodeRoute");
3828
-
3829
- // src/swagger.ts
3830
- import { fastifySwaggerUi } from "@fastify/swagger-ui";
3831
-
3832
- // package.json
3833
- var package_default = {
3834
- name: "@jaredwray/mockhttp",
3835
- version: "1.5.0",
3836
- description: "Mock Http - Easy way to mock http with httpbin replacement",
3837
- type: "module",
3838
- main: "dist/index.js",
3839
- directories: {
3840
- test: "test"
3841
- },
3842
- scripts: {
3843
- lint: "biome check --write --error-on-warnings",
3844
- test: "pnpm lint && vitest run --coverage",
3845
- "test:ci": "biome check --error-on-warnings && vitest run --coverage",
3846
- dev: "pnpm lint && tsx src/index.ts --watch",
3847
- build: "rimraf ./dist && tsup src/index.ts --format esm --dts --clean",
3848
- "docker:build": "docker build -t jaredwray/mockhttp .",
3849
- "docker:run": "docker run -p 3000:3000 jaredwray/mockhttp",
3850
- "docker:push": "docker push jaredwray/mockhttp",
3851
- "docker:stop": 'docker stop $(docker ps -a -q --filter ancestor=jaredwray/mockhttp --format="{{.ID}}")',
3852
- "docker:readme": "tsx ./scripts/create-docker-readme.ts",
3853
- "docker:compose:up": "docker-compose up -d",
3854
- "docker:compose:down": "docker-compose down",
3855
- prepare: "pnpm run build",
3856
- start: "node dist/index.js"
3857
- },
3858
- repository: {
3859
- type: "git",
3860
- url: "git+https://github.com/jaredwray/mockhttp.git"
3861
- },
3862
- keywords: [
3863
- "mock",
3864
- "http",
3865
- "httpbin",
3866
- "testing",
3867
- "api",
3868
- "fastify",
3869
- "docker",
3870
- "typescript",
3871
- "rest",
3872
- "mock-server",
3873
- "api-testing",
3874
- "development",
3875
- "http-testing",
3876
- "httpbin-alternative",
3877
- "endpoints"
3878
- ],
3879
- author: "Jared Wray <me@jaredwray.com>",
3880
- license: "MIT",
3881
- bugs: {
3882
- url: "https://github.com/jaredwray/mockhttp/issues"
3883
- },
3884
- homepage: "https://github.com/jaredwray/mockhttp#readme",
3885
- devDependencies: {
3886
- "@biomejs/biome": "^2.4.7",
3887
- "@swc/core": "^1.15.18",
3888
- "@types/html-escaper": "^3.0.4",
3889
- "@vitest/coverage-v8": "^4.1.0",
3890
- rimraf: "^6.1.3",
3891
- tsup: "^8.5.1",
3892
- tsx: "^4.21.0",
3893
- typescript: "^5.9.3",
3894
- vitest: "^4.1.0"
3895
- },
3896
- dependencies: {
3897
- "@fastify/cookie": "^11.0.2",
3898
- "@fastify/helmet": "^13.0.2",
3899
- "@fastify/rate-limit": "^10.3.0",
3900
- "@fastify/static": "^9.0.0",
3901
- "@fastify/swagger": "^9.7.0",
3902
- "@fastify/swagger-ui": "^5.2.5",
3903
- "@scalar/api-reference": "^1.48.7",
3904
- "detect-port": "^2.1.0",
3905
- fastify: "^5.8.2",
3906
- hookified: "^2.1.0",
3907
- "html-escaper": "^3.0.3",
3908
- pino: "^10.3.1",
3909
- "pino-pretty": "^13.1.3"
3910
- },
3911
- files: [
3912
- "dist",
3913
- "public",
3914
- "LICENCE"
3915
- ],
3916
- pnpm: {
3917
- overrides: {
3918
- "set-cookie-parser": "2.7.2"
3919
- }
3920
- }
3921
- };
3922
-
3923
- // src/swagger.ts
3924
- var description = `
3925
- A simple HTTP server that can be used to mock HTTP responses for testing purposes. Inspired by [httpbin](https://httpbin.org/) and built using \`nodejs\` and \`fastify\` with the idea of running it via https://mockhttp.org, via docker \`jaredwray/mockhttp\`, or nodejs \`npm install jaredwray/mockhttp\`.
3926
-
3927
- * [GitHub Repository](https://github.com/jaredwray/mockhttp)
3928
- * [Docker Image](https://hub.docker.com/r/jaredwray/mockhttp)
3929
- * [NPM Package](https://www.npmjs.com/package/jaredwray/mockhttp)
3930
-
3931
- # About mockhttp.org
3932
-
3933
- [mockhttp.org](https://mockhttp.org) is a free service that runs this codebase and allows you to use it for testing purposes. It is a simple way to mock HTTP responses for testing purposes. Ran via [Cloudflare](https://cloudflare.com) and [Google Cloud Run](https://cloud.google.com/run/) across 7 regions globally and can do millions of requests per second.
3934
- `;
3935
- var fastifySwaggerConfig = {
3936
- openapi: {
3937
- info: {
3938
- title: "Mock HTTP API",
3939
- description,
3940
- version: package_default.version
3941
- },
3942
- consumes: [
3943
- "application/json"
3944
- ],
3945
- produces: [
3946
- "application/json"
3947
- ]
3948
- }
3949
- };
3950
- var registerSwaggerUi = /* @__PURE__ */ __name(async (fastify) => {
3951
- await fastify.register(fastifySwaggerUi, {
3952
- routePrefix: "/docs",
3953
- uiConfig: {
3954
- docExpansion: "none",
3955
- deepLinking: false
3956
- },
3957
- uiHooks: {
3958
- /* v8 ignore next -- @preserve */
3959
- onRequest(_request, _reply, next) {
3960
- next();
3961
- },
3962
- /* v8 ignore next -- @preserve */
3963
- preHandler(_request, _reply, next) {
3964
- next();
3965
- }
3966
- },
3967
- staticCSP: true,
3968
- /* v8 ignore next -- @preserve */
3969
- transformSpecification: /* @__PURE__ */ __name((swaggerObject, _request, _reply) => swaggerObject, "transformSpecification"),
3970
- transformSpecificationClone: true
3971
- });
3972
- }, "registerSwaggerUi");
3973
-
3974
- // src/tap-manager.ts
3975
- import { randomUUID as randomUUID2 } from "crypto";
3976
- var TapManager = class {
3977
- static {
3978
- __name(this, "TapManager");
3979
- }
3980
- _injections;
3981
- constructor() {
3982
- this._injections = /* @__PURE__ */ new Map();
3983
- }
3984
- /**
3985
- * Get all active injection taps
3986
- */
3987
- get injections() {
3988
- return this._injections;
3989
- }
3990
- /**
3991
- * Check if there are any active injections
3992
- */
3993
- get hasInjections() {
3994
- return this._injections.size > 0;
3995
- }
3996
- /**
3997
- * Inject a custom response for requests matching the given criteria
3998
- * @param response - The response configuration or a function that returns it
3999
- * @param matcher - Optional criteria to match requests
4000
- * @returns A tap object that can be used to remove the injection
4001
- */
4002
- inject(response, matcher) {
4003
- const id = randomUUID2();
4004
- const tap = {
4005
- id,
4006
- response,
4007
- matcher
4008
- };
4009
- this._injections.set(id, tap);
4010
- return tap;
4011
- }
4012
- /**
4013
- * Remove an injection by its tap object or ID
4014
- * @param tapOrId - The tap object or tap ID to remove
4015
- * @returns true if the injection was removed, false if it wasn't found
4016
- */
4017
- removeInjection(tapOrId) {
4018
- const id = typeof tapOrId === "string" ? tapOrId : tapOrId.id;
4019
- return this._injections.delete(id);
4020
- }
4021
- /**
4022
- * Find the first injection that matches the given request
4023
- * @param request - The Fastify request object
4024
- * @returns The matching injection tap, or undefined if no match
4025
- */
4026
- matchRequest(request) {
4027
- for (const tap of this._injections.values()) {
4028
- if (this.requestMatches(request, tap.matcher)) {
4029
- return tap;
4030
- }
4031
- }
4032
- return void 0;
4033
- }
4034
- /**
4035
- * Clear all injections
4036
- */
4037
- clear() {
4038
- this._injections.clear();
4039
- }
4040
- /**
4041
- * Check if a request matches the given matcher criteria
4042
- * @param request - The Fastify request object
4043
- * @param matcher - The matcher criteria (undefined matches all requests)
4044
- * @returns true if the request matches
4045
- */
4046
- requestMatches(request, matcher) {
4047
- if (!matcher) {
4048
- return true;
4049
- }
4050
- if (matcher.url) {
4051
- if (!this.urlMatches(request.url, matcher.url)) {
4052
- return false;
4053
- }
4054
- }
4055
- if (matcher.hostname) {
4056
- if (request.hostname !== matcher.hostname) {
4057
- return false;
4058
- }
4059
- }
4060
- if (matcher.method) {
4061
- if (request.method.toUpperCase() !== matcher.method.toUpperCase()) {
4062
- return false;
4063
- }
4064
- }
4065
- if (matcher.headers) {
4066
- for (const [key, value] of Object.entries(matcher.headers)) {
4067
- const requestHeader = request.headers[key.toLowerCase()];
4068
- if (requestHeader !== value) {
4069
- return false;
4070
- }
4071
- }
4072
- }
4073
- return true;
4074
- }
4075
- /**
4076
- * Check if a URL matches a pattern (supports wildcards with *)
4077
- * @param url - The URL to test
4078
- * @param pattern - The pattern to match against
4079
- * @returns true if the URL matches the pattern
4080
- */
4081
- urlMatches(url, pattern) {
4082
- const urlPath = url.split("?")[0];
4083
- if (pattern === urlPath) {
4084
- return true;
4085
- }
4086
- if (pattern.includes("*")) {
4087
- const regexPattern = pattern.split("*").map((part) => part.replaceAll(/[.*+?^${}()|[\]\\]/g, "\\$&")).join(".*");
4088
- const regex = new RegExp(`^${regexPattern}$`);
4089
- return regex.test(urlPath);
4090
- }
4091
- return false;
4092
- }
4093
- };
4094
-
4095
- // src/mock-http.ts
4096
- var MockHttp = class extends Hookified {
4097
- static {
4098
- __name(this, "MockHttp");
4099
- }
4100
- _port = 3e3;
4101
- _host = "0.0.0.0";
4102
- _autoDetectPort = true;
4103
- _helmet = true;
4104
- _apiDocs = true;
4105
- _logging = true;
4106
- _httpBin = {
4107
- httpMethods: true,
4108
- redirects: true,
4109
- requestInspection: true,
4110
- responseInspection: true,
4111
- responseFormats: true,
4112
- statusCodes: true,
4113
- cookies: true,
4114
- anything: true,
4115
- auth: true,
4116
- images: true,
4117
- dynamicData: true
4118
- };
4119
- _rateLimit = {
4120
- max: 1e3,
4121
- timeWindow: "1 minute",
4122
- allowList: [
4123
- "127.0.0.1",
4124
- "::1"
4125
- ]
4126
- };
4127
- _http2 = false;
4128
- _http1 = true;
4129
- _https;
4130
- _httpsCredentials;
4131
- _server = Fastify();
4132
- _taps = new TapManager();
4133
- constructor(options) {
4134
- super(options?.hookOptions);
4135
- if (options?.port) {
4136
- this._port = options.port;
4137
- }
4138
- if (options?.host) {
4139
- this._host = options.host;
4140
- }
4141
- if (options?.helmet !== void 0) {
4142
- this._helmet = options.helmet;
4143
- }
4144
- if (options?.apiDocs !== void 0) {
4145
- this._apiDocs = options.apiDocs;
4146
- }
4147
- if (options?.httpBin !== void 0) {
4148
- this._httpBin = options.httpBin;
4149
- }
4150
- if (options?.rateLimit !== void 0) {
4151
- if (options.rateLimit === false) {
4152
- this._rateLimit = void 0;
4153
- } else {
4154
- this._rateLimit = options.rateLimit;
4155
- }
4156
- }
4157
- if (options?.logging !== void 0) {
4158
- this._logging = options.logging;
4159
- }
4160
- if (options?.http2 !== void 0) {
4161
- this._http2 = options.http2;
4162
- }
4163
- if (options?.http1 !== void 0) {
4164
- this._http1 = options.http1;
4165
- }
4166
- if (options?.https !== void 0) {
4167
- if (options.https === true) {
4168
- this._https = {
4169
- autoGenerate: true
4170
- };
4171
- } else if (options.https === false) {
4172
- this._https = void 0;
4173
- } else {
4174
- this._https = options.https;
4175
- }
4176
- }
4177
- }
4178
- /**
4179
- * The port to listen on. If not provided, defaults to 3000 and detects the next available port if in use.
4180
- * @default 3000
4181
- */
4182
- get port() {
4183
- return this._port;
4184
- }
4185
- /**
4186
- * The port to listen on. If not provided, defaults to 3000 and detects the next available port if in use.
4187
- * @default 3000
4188
- */
4189
- set port(port) {
4190
- this._port = port;
4191
- }
4192
- /**
4193
- * The host to listen on. If not provided, defaults to 'localhost'.
4194
- * @default 'localhost'
4195
- */
4196
- get host() {
4197
- return this._host;
4198
- }
4199
- /**
4200
- * The host to listen on. If not provided, defaults to 'localhost'.
4201
- * @default 'localhost'
4202
- */
4203
- set host(host) {
4204
- this._host = host;
4205
- }
4206
- /**
4207
- * Whether to automatically detect the next available port if the provided port is in use. Defaults to true.
4208
- * @default true
4209
- */
4210
- get autoDetectPort() {
4211
- return this._autoDetectPort;
4212
- }
4213
- /**
4214
- * Whether to automatically detect the next available port if the provided port is in use. Defaults to true.
4215
- * @default true
4216
- */
4217
- set autoDetectPort(autoDetectPort) {
4218
- this._autoDetectPort = autoDetectPort;
4219
- }
4220
- /**
4221
- * Whether to use Helmet for security headers. Defaults to true.
4222
- * @default true
4223
- */
4224
- get helmet() {
4225
- return this._helmet;
4226
- }
4227
- /**
4228
- * Whether to use Helmet for security headers. Defaults to true.
4229
- * @default true
4230
- */
4231
- set helmet(helmet) {
4232
- this._helmet = helmet;
4233
- }
4234
- /**
4235
- * Whether to use Swagger for API documentation. Defaults to true.
4236
- * @default true
4237
- */
4238
- get apiDocs() {
4239
- return this._apiDocs;
4240
- }
4241
- /**
4242
- * Whether to use Swagger for API documentation. Defaults to true.
4243
- * @default true
4244
- */
4245
- set apiDocs(apiDocs) {
4246
- this._apiDocs = apiDocs;
4247
- }
4248
- /**
4249
- * Whether to enable logging. Defaults to true.
4250
- * @default true
4251
- */
4252
- get logging() {
4253
- return this._logging;
4254
- }
4255
- /**
4256
- * Whether to enable logging. Defaults to true.
4257
- * @default true
4258
- */
4259
- set logging(logging) {
4260
- this._logging = logging;
4261
- }
4262
- /**
4263
- * HTTPS configuration. Set to true to auto-generate a self-signed certificate,
4264
- * or provide an HttpsOptions object with cert/key or auto-generation options.
4265
- */
4266
- get https() {
4267
- return this._https;
4268
- }
4269
- /**
4270
- * HTTPS configuration. Set to true to auto-generate a self-signed certificate,
4271
- * or provide an HttpsOptions object with cert/key or auto-generation options.
4272
- */
4273
- set https(value) {
4274
- if (value === true) {
4275
- this._https = {
4276
- autoGenerate: true
4277
- };
4278
- } else if (value === false || value === void 0) {
4279
- this._https = void 0;
4280
- } else {
4281
- this._https = value;
4282
- }
4283
- }
4284
- /**
4285
- * Whether the server is running in HTTPS mode.
4286
- */
4287
- get isHttps() {
4288
- return this._httpsCredentials !== void 0;
4289
- }
4290
- /**
4291
- * Whether HTTP/2 support is enabled.
4292
- * @default false
4293
- */
4294
- get http2() {
4295
- return this._http2;
4296
- }
4297
- /**
4298
- * Whether HTTP/2 support is enabled.
4299
- * @default false
4300
- */
4301
- set http2(http2) {
4302
- this._http2 = http2;
4303
- }
4304
- /**
4305
- * Whether to allow HTTP/1.1 fallback when using HTTP/2 with HTTPS.
4306
- * @default true
4307
- */
4308
- get http1() {
4309
- return this._http1;
4310
- }
4311
- /**
4312
- * Whether to allow HTTP/1.1 fallback when using HTTP/2 with HTTPS.
4313
- * @default true
4314
- */
4315
- set http1(http1) {
4316
- this._http1 = http1;
4317
- }
4318
- /**
4319
- * HTTP Bin options. Defaults to all enabled.
4320
- */
4321
- get httpBin() {
4322
- return this._httpBin;
4323
- }
4324
- /**
4325
- * HTTP Bin options. Defaults to all enabled.
4326
- */
4327
- set httpBin(httpBinary) {
4328
- this._httpBin = httpBinary;
4329
- }
4330
- /**
4331
- * Rate limiting options. Defaults to 1000 requests per minute (localhost excluded).
4332
- * Set to undefined to disable rate limiting, or provide custom options to configure.
4333
- * @default { max: 1000, timeWindow: "1 minute", allowList: ["127.0.0.1", "::1"] }
4334
- */
4335
- get rateLimit() {
4336
- return this._rateLimit;
4337
- }
4338
- /**
4339
- * Rate limiting options. Defaults to 1000 requests per minute (localhost excluded).
4340
- * Set to undefined to disable rate limiting, or provide custom options to configure.
4341
- *
4342
- * Note: Changing this property requires restarting the server (close() then start()) for changes to take effect.
4343
- * @default { max: 1000, timeWindow: "1 minute", allowList: ["127.0.0.1", "::1"] }
4344
- */
4345
- set rateLimit(rateLimit) {
4346
- this._rateLimit = rateLimit;
4347
- }
4348
- /**
4349
- * The Fastify server instance.
4350
- */
4351
- get server() {
4352
- return this._server;
4353
- }
4354
- /**
4355
- * The Fastify server instance.
4356
- */
4357
- set server(server) {
4358
- this._server = server;
4359
- }
4360
- /**
4361
- * The TapManager instance for managing injection taps.
4362
- */
4363
- get taps() {
4364
- return this._taps;
4365
- }
4366
- /**
4367
- * The TapManager instance for managing injection taps.
4368
- */
4369
- set taps(taps) {
4370
- this._taps = taps;
4371
- }
4372
- /**
4373
- * Start the Fastify server. If the server is already running, it will be closed and restarted.
4374
- */
4375
- async start() {
4376
- this._httpsCredentials = void 0;
4377
- if (this._https) {
4378
- this._httpsCredentials = await this.resolveHttpsCredentials(this._https);
4379
- }
4380
- try {
4381
- if (this._server) {
4382
- await this._server.close();
4383
- }
4384
- const fastifyOptions = {
4385
- ...getFastifyConfig(this._logging)
4386
- };
4387
- if (this._http2) {
4388
- fastifyOptions.http2 = true;
4389
- }
4390
- if (this._httpsCredentials) {
4391
- const httpsOptions = {
4392
- key: this._httpsCredentials.key,
4393
- cert: this._httpsCredentials.cert
4394
- };
4395
- if (this._http2) {
4396
- httpsOptions.allowHTTP1 = this._http1;
4397
- }
4398
- fastifyOptions.https = httpsOptions;
4399
- }
4400
- this._server = Fastify(fastifyOptions);
4401
- this._server.addHook("onRequest", async (request, reply) => {
4402
- const matchedTap = this._taps.matchRequest(request);
4403
- if (matchedTap) {
4404
- const injectionResponse = typeof matchedTap.response === "function" ? matchedTap.response(request) : matchedTap.response;
4405
- const { response, statusCode = 200, headers } = injectionResponse;
4406
- reply.code(statusCode);
4407
- if (headers) {
4408
- for (const [key, value] of Object.entries(headers)) {
4409
- reply.header(key, value);
4410
- }
4411
- }
4412
- return reply.send(response);
4413
- }
4414
- });
4415
- await this._server.register(fastifyStatic, {
4416
- root: path.resolve("./node_modules/@scalar/api-reference/dist"),
4417
- prefix: "/scalar"
4418
- });
4419
- await this.server.register(fastifyStatic, {
4420
- root: path.resolve("./public"),
4421
- decorateReply: false
4422
- });
4423
- await this._server.register(sitemapRoute);
4424
- if (this._helmet) {
4425
- await this._server.register(fastifyHelmet, {
4426
- contentSecurityPolicy: {
4427
- directives: {
4428
- defaultSrc: [
4429
- "'self'"
4430
- ],
4431
- scriptSrc: [
4432
- "'self'",
4433
- "'unsafe-inline'",
4434
- "'wasm-unsafe-eval'"
4435
- ],
4436
- styleSrc: [
4437
- "'self'",
4438
- "'unsafe-inline'"
4439
- ],
4440
- imgSrc: [
4441
- "'self'",
4442
- "data:",
4443
- "https:"
4444
- ],
4445
- fontSrc: [
4446
- "'self'",
4447
- "data:"
4448
- ],
4449
- connectSrc: [
4450
- "'self'"
4451
- ],
4452
- frameSrc: [
4453
- "'self'"
4454
- ],
4455
- objectSrc: [
4456
- "'none'"
4457
- ],
4458
- upgradeInsecureRequests: []
4459
- }
4460
- }
4461
- });
4462
- }
4463
- if (this._rateLimit) {
4464
- await this._server.register(fastifyRateLimit, this._rateLimit);
4465
- }
4466
- if (this._apiDocs) {
4467
- await this.registerApiDocs();
4468
- }
4469
- const { httpMethods, redirects, requestInspection, responseInspection, responseFormats, statusCodes: statusCodes2, cookies, anything, auth, images, dynamicData } = this._httpBin;
4470
- if (httpMethods) {
4471
- await this.registerHttpMethods();
4472
- }
4473
- if (statusCodes2) {
4474
- await this.registerStatusCodeRoutes();
4475
- }
4476
- if (requestInspection) {
4477
- await this.registerRequestInspectionRoutes();
4478
- }
4479
- if (responseInspection) {
4480
- await this.registerResponseInspectionRoutes();
4481
- }
4482
- if (responseFormats) {
4483
- await this.registerResponseFormatRoutes();
4484
- }
4485
- if (redirects) {
4486
- await this.registerRedirectRoutes();
4487
- }
4488
- if (cookies) {
4489
- await this.registerCookieRoutes();
4490
- }
4491
- if (anything) {
4492
- await this.registerAnythingRoutes();
4493
- }
4494
- if (auth) {
4495
- await this.registerAuthRoutes();
4496
- }
4497
- if (images) {
4498
- await this.registerImageRoutes();
4499
- }
4500
- if (dynamicData) {
4501
- await this.registerDynamicDataRoutes();
4502
- }
4503
- if (this._autoDetectPort) {
4504
- const originalPort = this._port;
4505
- this._port = await this.detectPort();
4506
- if (originalPort !== this._port) {
4507
- this._server.log.info(`Port ${originalPort} is in use, detected next available port: ${this._port}`);
4508
- }
4509
- }
4510
- await this._server.listen({
4511
- port: this._port,
4512
- host: this._host
4513
- });
4514
- } catch (error) {
4515
- this._server.log.error(error);
4516
- }
4517
- }
4518
- /**
4519
- * Close the Fastify server.
4520
- */
4521
- async close() {
4522
- await this._server.close();
4523
- }
4524
- /**
4525
- * Detect the next available port to run on.
4526
- * @returns The port that is available to run on
4527
- */
4528
- async detectPort() {
4529
- const { port } = this;
4530
- return detect(port);
4531
- }
4532
- /**
4533
- * This will register the API documentation routes including openapi and swagger ui.
4534
- * @param fastifyInstance - the server instance to register the routes on.
4535
- */
4536
- async registerApiDocs(fastifyInstance) {
4537
- const fastify = fastifyInstance ?? this._server;
4538
- await fastify.register(fastifySwagger, fastifySwaggerConfig);
4539
- await registerSwaggerUi(fastify);
4540
- await fastify.register(indexRoute);
4541
- }
4542
- /**
4543
- * Register the HTTP methods routes.
4544
- * @param fastifyInstance - the server instance to register the routes on.
4545
- */
4546
- async registerHttpMethods(fastifyInstance) {
4547
- const fastify = fastifyInstance ?? this._server;
4548
- await fastify.register(getRoute);
4549
- await fastify.register(postRoute);
4550
- await fastify.register(deleteRoute);
4551
- await fastify.register(putRoute);
4552
- await fastify.register(patchRoute);
4553
- }
4554
- /**
4555
- * Register the status code routes.
4556
- * @param fastifyInstance - the server instance to register the routes on.
4557
- */
4558
- async registerStatusCodeRoutes(fastifyInstance) {
4559
- const fastify = fastifyInstance ?? this._server;
4560
- await fastify.register(statusCodeRoute);
4561
- }
4562
- /**
4563
- * Register the request inspection routes.
4564
- * @param fastifyInstance - the server instance to register the routes on.
4565
- */
4566
- async registerRequestInspectionRoutes(fastifyInstance) {
4567
- const fastify = fastifyInstance ?? this._server;
4568
- await fastify.register(ipRoute);
4569
- await fastify.register(headersRoute);
4570
- await fastify.register(userAgentRoute);
4571
- }
4572
- /**
4573
- * Register the response inspection routes.
4574
- * @param fastifyInstance - the server instance to register the routes on.
4575
- */
4576
- async registerResponseInspectionRoutes(fastifyInstance) {
4577
- const fastify = fastifyInstance ?? this._server;
4578
- await fastify.register(cacheRoutes);
4579
- await fastify.register(etagRoutes);
4580
- await fastify.register(responseHeadersRoutes);
4581
- }
4582
- async registerResponseFormatRoutes(fastifyInstance) {
4583
- const fastify = fastifyInstance ?? this._server;
4584
- await fastify.register(responseFormatRoutes);
4585
- }
4586
- /**
4587
- * Register the redirect routes.
4588
- * @param fastifyInstance - the server instance to register the routes on.
4589
- */
4590
- async registerRedirectRoutes(fastifyInstance) {
4591
- const fastify = fastifyInstance ?? this._server;
4592
- await fastify.register(absoluteRedirectRoute);
4593
- await fastify.register(relativeRedirectRoute);
4594
- await fastify.register(redirectToRoute);
4595
- }
4596
- /**
4597
- * Register the cookie routes.
4598
- * @param fastifyInstance - the server instance to register the routes on.
4599
- */
4600
- async registerCookieRoutes(fastifyInstance) {
4601
- const fastify = fastifyInstance ?? this._server;
4602
- await fastify.register(fastifyCookie);
4603
- await fastify.register(getCookiesRoute);
4604
- await fastify.register(postCookieRoute);
4605
- await fastify.register(deleteCookieRoute);
4606
- }
4607
- /**
4608
- * Register the anything routes.
4609
- * @param fastifyInstance - the server instance to register the routes on.
4610
- */
4611
- async registerAnythingRoutes(fastifyInstance) {
4612
- const fastify = fastifyInstance ?? this._server;
4613
- await fastify.register(anythingRoute);
4614
- }
4615
- /**
4616
- * Register the auth routes.
4617
- * @param fastifyInstance - the server instance to register the routes on.
4618
- */
4619
- async registerAuthRoutes(fastifyInstance) {
4620
- const fastify = fastifyInstance ?? this._server;
4621
- await fastify.register(basicAuthRoute);
4622
- await fastify.register(hiddenBasicAuthRoute);
4623
- await fastify.register(bearerAuthRoute);
4624
- await fastify.register(digestAuthRoute);
4625
- }
4626
- /**
4627
- * Register the image routes.
4628
- * @param fastifyInstance - the server instance to register the routes on.
4629
- */
4630
- async registerImageRoutes(fastifyInstance) {
4631
- const fastify = fastifyInstance ?? this._server;
4632
- await fastify.register(imageRoutes);
4633
- }
4634
- /**
4635
- * Register the dynamic data routes.
4636
- * @param fastifyInstance - the server instance to register the routes on.
4637
- */
4638
- async registerDynamicDataRoutes(fastifyInstance) {
4639
- const fastify = fastifyInstance ?? this._server;
4640
- await fastify.register(uuidRoute);
4641
- await fastify.register(bytesRoute);
4642
- await fastify.register(streamBytesRoute);
4643
- await fastify.register(delayRoute);
4644
- await fastify.register(base64Route);
4645
- await fastify.register(streamRoute);
4646
- await fastify.register(rangeRoute);
4647
- await fastify.register(dripRoute);
4648
- await fastify.register(linksRoute);
4649
- }
4650
- async resolveHttpsCredentials(options) {
4651
- if (options.cert && options.key) {
4652
- const cert = await this.loadPemValue(options.cert);
4653
- const key = await this.loadPemValue(options.key);
4654
- return {
4655
- cert,
4656
- key
4657
- };
4658
- }
4659
- if ((options.cert || options.key) && options.autoGenerate !== true) {
4660
- throw new Error("HTTPS options must include both 'cert' and 'key'. Only one was provided.");
4661
- }
4662
- if (options.autoGenerate !== false) {
4663
- const result = generateCertificate(options.certificateOptions);
4664
- return {
4665
- cert: result.cert,
4666
- key: result.key
4667
- };
4668
- }
4669
- throw new Error("HTTPS is enabled but no certificate was provided and autoGenerate is false.");
4670
- }
4671
- async loadPemValue(value) {
4672
- if (value.startsWith("-----")) {
4673
- return value;
4674
- }
4675
- return fsPromises2.readFile(value, "utf8");
4676
- }
4677
- };
4678
-
4679
- // src/index.ts
4680
- var start = /* @__PURE__ */ __name(async () => {
4681
- const mockHttp = new MockHttp();
4682
- if (process2.env.PORT) {
4683
- mockHttp.port = Number.parseInt(process2.env.PORT, 10);
4684
- }
4685
- if (process2.env.HOST) {
4686
- mockHttp.host = process2.env.HOST;
4687
- }
4688
- if (process2.env.LOGGING === "false") {
4689
- mockHttp.logging = false;
4690
- }
4691
- if (process2.env.HTTP2 === "true") {
4692
- mockHttp.http2 = true;
4693
- }
4694
- await mockHttp.start();
4695
- return mockHttp;
4696
- }, "start");
4697
- if (import.meta.url === `file://${process2.argv[1]}`) {
4698
- await start();
4699
- }
4700
- export {
4701
- MockHttp as default,
4702
- generateCertificate,
4703
- generateCertificateFiles,
4704
- MockHttp as mockhttp,
4705
- start
4706
- };
4707
- /* v8 ignore next -- @preserve */
4708
- /* v8 ignore next 2 -- @preserve */
4709
- /* v8 ignore next 3 -- @preserve */