@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.d.mts +494 -0
- package/dist/index.mjs +3492 -0
- package/package.json +26 -16
- package/dist/index.d.ts +0 -490
- package/dist/index.js +0 -4709
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 */
|