@jaredwray/mockhttp 1.2.0 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +127 -3
- package/dist/index.d.ts +103 -1
- package/dist/index.js +1248 -21
- package/package.json +18 -13
package/dist/index.js
CHANGED
|
@@ -5,6 +5,7 @@ var __name = (target, value) => __defProp(target, "name", { value, configurable:
|
|
|
5
5
|
import process2 from "process";
|
|
6
6
|
|
|
7
7
|
// src/mock-http.ts
|
|
8
|
+
import * as fsPromises2 from "fs/promises";
|
|
8
9
|
import path from "path";
|
|
9
10
|
import fastifyCookie from "@fastify/cookie";
|
|
10
11
|
import fastifyHelmet from "@fastify/helmet";
|
|
@@ -15,6 +16,307 @@ import { detect } from "detect-port";
|
|
|
15
16
|
import Fastify from "fastify";
|
|
16
17
|
import { Hookified } from "hookified";
|
|
17
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
|
+
|
|
18
320
|
// src/fastify-config.ts
|
|
19
321
|
function getFastifyConfig(logging = true) {
|
|
20
322
|
return {
|
|
@@ -328,8 +630,8 @@ var bearerAuthRoute = /* @__PURE__ */ __name((fastify) => {
|
|
|
328
630
|
}, "bearerAuthRoute");
|
|
329
631
|
|
|
330
632
|
// src/routes/auth/digest.ts
|
|
331
|
-
import
|
|
332
|
-
var makeNonce = /* @__PURE__ */ __name(() =>
|
|
633
|
+
import crypto2 from "crypto";
|
|
634
|
+
var makeNonce = /* @__PURE__ */ __name(() => crypto2.randomBytes(16).toString("hex"), "makeNonce");
|
|
333
635
|
var algorithms = /* @__PURE__ */ new Set([
|
|
334
636
|
"MD5",
|
|
335
637
|
"md5",
|
|
@@ -354,7 +656,7 @@ var hashAlg = /* @__PURE__ */ __name((alg) => {
|
|
|
354
656
|
}
|
|
355
657
|
return "md5";
|
|
356
658
|
}, "hashAlg");
|
|
357
|
-
var h = /* @__PURE__ */ __name((algo, data) =>
|
|
659
|
+
var h = /* @__PURE__ */ __name((algo, data) => crypto2.createHash(algo).update(data).digest("hex"), "h");
|
|
358
660
|
var parseDigest = /* @__PURE__ */ __name((header) => {
|
|
359
661
|
if (!header) {
|
|
360
662
|
return void 0;
|
|
@@ -712,6 +1014,808 @@ var postCookieRoute = /* @__PURE__ */ __name((fastify) => {
|
|
|
712
1014
|
});
|
|
713
1015
|
}, "postCookieRoute");
|
|
714
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
|
+
|
|
715
1819
|
// src/routes/http-methods/delete.ts
|
|
716
1820
|
var deleteSchema = {
|
|
717
1821
|
description: "Handles a DELETE request and returns request information",
|
|
@@ -1148,6 +2252,18 @@ var indexRoute = /* @__PURE__ */ __name((fastify) => {
|
|
|
1148
2252
|
<meta
|
|
1149
2253
|
name="viewport"
|
|
1150
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" />
|
|
1151
2267
|
<style>
|
|
1152
2268
|
:root {
|
|
1153
2269
|
--scalar-custom-header-height: 70px;
|
|
@@ -2716,7 +3832,7 @@ import { fastifySwaggerUi } from "@fastify/swagger-ui";
|
|
|
2716
3832
|
// package.json
|
|
2717
3833
|
var package_default = {
|
|
2718
3834
|
name: "@jaredwray/mockhttp",
|
|
2719
|
-
version: "1.
|
|
3835
|
+
version: "1.4.0",
|
|
2720
3836
|
description: "Mock Http - Easy way to mock http with httpbin replacement",
|
|
2721
3837
|
type: "module",
|
|
2722
3838
|
main: "dist/index.js",
|
|
@@ -2767,36 +3883,41 @@ var package_default = {
|
|
|
2767
3883
|
},
|
|
2768
3884
|
homepage: "https://github.com/jaredwray/mockhttp#readme",
|
|
2769
3885
|
devDependencies: {
|
|
2770
|
-
"@biomejs/biome": "^2.3.
|
|
2771
|
-
"@swc/core": "^1.15.
|
|
3886
|
+
"@biomejs/biome": "^2.3.14",
|
|
3887
|
+
"@swc/core": "^1.15.11",
|
|
2772
3888
|
"@types/html-escaper": "^3.0.4",
|
|
2773
|
-
"@vitest/coverage-v8": "^4.0.
|
|
3889
|
+
"@vitest/coverage-v8": "^4.0.18",
|
|
2774
3890
|
rimraf: "^6.1.2",
|
|
2775
3891
|
tsup: "^8.5.1",
|
|
2776
3892
|
tsx: "^4.21.0",
|
|
2777
3893
|
typescript: "^5.9.3",
|
|
2778
|
-
vitest: "^4.0.
|
|
3894
|
+
vitest: "^4.0.18"
|
|
2779
3895
|
},
|
|
2780
3896
|
dependencies: {
|
|
2781
3897
|
"@fastify/cookie": "^11.0.2",
|
|
2782
3898
|
"@fastify/helmet": "^13.0.2",
|
|
2783
3899
|
"@fastify/rate-limit": "^10.3.0",
|
|
2784
|
-
"@fastify/static": "^
|
|
2785
|
-
"@fastify/swagger": "^9.
|
|
2786
|
-
"@fastify/swagger-ui": "^5.2.
|
|
2787
|
-
"@scalar/api-reference": "^1.
|
|
3900
|
+
"@fastify/static": "^9.0.0",
|
|
3901
|
+
"@fastify/swagger": "^9.7.0",
|
|
3902
|
+
"@fastify/swagger-ui": "^5.2.5",
|
|
3903
|
+
"@scalar/api-reference": "^1.44.16",
|
|
2788
3904
|
"detect-port": "^2.1.0",
|
|
2789
|
-
fastify: "^5.
|
|
2790
|
-
hookified: "^1.
|
|
3905
|
+
fastify: "^5.7.4",
|
|
3906
|
+
hookified: "^1.15.1",
|
|
2791
3907
|
"html-escaper": "^3.0.3",
|
|
2792
|
-
pino: "^10.1
|
|
3908
|
+
pino: "^10.3.1",
|
|
2793
3909
|
"pino-pretty": "^13.1.3"
|
|
2794
3910
|
},
|
|
2795
3911
|
files: [
|
|
2796
3912
|
"dist",
|
|
2797
3913
|
"public",
|
|
2798
3914
|
"LICENCE"
|
|
2799
|
-
]
|
|
3915
|
+
],
|
|
3916
|
+
pnpm: {
|
|
3917
|
+
overrides: {
|
|
3918
|
+
"set-cookie-parser": "2.7.2"
|
|
3919
|
+
}
|
|
3920
|
+
}
|
|
2800
3921
|
};
|
|
2801
3922
|
|
|
2802
3923
|
// src/swagger.ts
|
|
@@ -2851,7 +3972,7 @@ var registerSwaggerUi = /* @__PURE__ */ __name(async (fastify) => {
|
|
|
2851
3972
|
}, "registerSwaggerUi");
|
|
2852
3973
|
|
|
2853
3974
|
// src/tap-manager.ts
|
|
2854
|
-
import { randomUUID } from "crypto";
|
|
3975
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
2855
3976
|
var TapManager = class {
|
|
2856
3977
|
static {
|
|
2857
3978
|
__name(this, "TapManager");
|
|
@@ -2879,7 +4000,7 @@ var TapManager = class {
|
|
|
2879
4000
|
* @returns A tap object that can be used to remove the injection
|
|
2880
4001
|
*/
|
|
2881
4002
|
inject(response, matcher) {
|
|
2882
|
-
const id =
|
|
4003
|
+
const id = randomUUID2();
|
|
2883
4004
|
const tap = {
|
|
2884
4005
|
id,
|
|
2885
4006
|
response,
|
|
@@ -2992,7 +4113,8 @@ var MockHttp = class extends Hookified {
|
|
|
2992
4113
|
cookies: true,
|
|
2993
4114
|
anything: true,
|
|
2994
4115
|
auth: true,
|
|
2995
|
-
images: true
|
|
4116
|
+
images: true,
|
|
4117
|
+
dynamicData: true
|
|
2996
4118
|
};
|
|
2997
4119
|
_rateLimit = {
|
|
2998
4120
|
max: 1e3,
|
|
@@ -3002,6 +4124,8 @@ var MockHttp = class extends Hookified {
|
|
|
3002
4124
|
"::1"
|
|
3003
4125
|
]
|
|
3004
4126
|
};
|
|
4127
|
+
_https;
|
|
4128
|
+
_httpsCredentials;
|
|
3005
4129
|
_server = Fastify();
|
|
3006
4130
|
_taps = new TapManager();
|
|
3007
4131
|
constructor(options) {
|
|
@@ -3031,6 +4155,17 @@ var MockHttp = class extends Hookified {
|
|
|
3031
4155
|
if (options?.logging !== void 0) {
|
|
3032
4156
|
this._logging = options.logging;
|
|
3033
4157
|
}
|
|
4158
|
+
if (options?.https !== void 0) {
|
|
4159
|
+
if (options.https === true) {
|
|
4160
|
+
this._https = {
|
|
4161
|
+
autoGenerate: true
|
|
4162
|
+
};
|
|
4163
|
+
} else if (options.https === false) {
|
|
4164
|
+
this._https = void 0;
|
|
4165
|
+
} else {
|
|
4166
|
+
this._https = options.https;
|
|
4167
|
+
}
|
|
4168
|
+
}
|
|
3034
4169
|
}
|
|
3035
4170
|
/**
|
|
3036
4171
|
* The port to listen on. If not provided, defaults to 3000 and detects the next available port if in use.
|
|
@@ -3117,6 +4252,34 @@ var MockHttp = class extends Hookified {
|
|
|
3117
4252
|
this._logging = logging;
|
|
3118
4253
|
}
|
|
3119
4254
|
/**
|
|
4255
|
+
* HTTPS configuration. Set to true to auto-generate a self-signed certificate,
|
|
4256
|
+
* or provide an HttpsOptions object with cert/key or auto-generation options.
|
|
4257
|
+
*/
|
|
4258
|
+
get https() {
|
|
4259
|
+
return this._https;
|
|
4260
|
+
}
|
|
4261
|
+
/**
|
|
4262
|
+
* HTTPS configuration. Set to true to auto-generate a self-signed certificate,
|
|
4263
|
+
* or provide an HttpsOptions object with cert/key or auto-generation options.
|
|
4264
|
+
*/
|
|
4265
|
+
set https(value) {
|
|
4266
|
+
if (value === true) {
|
|
4267
|
+
this._https = {
|
|
4268
|
+
autoGenerate: true
|
|
4269
|
+
};
|
|
4270
|
+
} else if (value === false || value === void 0) {
|
|
4271
|
+
this._https = void 0;
|
|
4272
|
+
} else {
|
|
4273
|
+
this._https = value;
|
|
4274
|
+
}
|
|
4275
|
+
}
|
|
4276
|
+
/**
|
|
4277
|
+
* Whether the server is running in HTTPS mode.
|
|
4278
|
+
*/
|
|
4279
|
+
get isHttps() {
|
|
4280
|
+
return this._httpsCredentials !== void 0;
|
|
4281
|
+
}
|
|
4282
|
+
/**
|
|
3120
4283
|
* HTTP Bin options. Defaults to all enabled.
|
|
3121
4284
|
*/
|
|
3122
4285
|
get httpBin() {
|
|
@@ -3174,11 +4337,25 @@ var MockHttp = class extends Hookified {
|
|
|
3174
4337
|
* Start the Fastify server. If the server is already running, it will be closed and restarted.
|
|
3175
4338
|
*/
|
|
3176
4339
|
async start() {
|
|
4340
|
+
this._httpsCredentials = void 0;
|
|
4341
|
+
if (this._https) {
|
|
4342
|
+
this._httpsCredentials = await this.resolveHttpsCredentials(this._https);
|
|
4343
|
+
}
|
|
3177
4344
|
try {
|
|
3178
4345
|
if (this._server) {
|
|
3179
4346
|
await this._server.close();
|
|
3180
4347
|
}
|
|
3181
|
-
|
|
4348
|
+
if (this._httpsCredentials) {
|
|
4349
|
+
this._server = Fastify({
|
|
4350
|
+
...getFastifyConfig(this._logging),
|
|
4351
|
+
https: {
|
|
4352
|
+
key: this._httpsCredentials.key,
|
|
4353
|
+
cert: this._httpsCredentials.cert
|
|
4354
|
+
}
|
|
4355
|
+
});
|
|
4356
|
+
} else {
|
|
4357
|
+
this._server = Fastify(getFastifyConfig(this._logging));
|
|
4358
|
+
}
|
|
3182
4359
|
this._server.addHook("onRequest", async (request, reply) => {
|
|
3183
4360
|
const matchedTap = this._taps.matchRequest(request);
|
|
3184
4361
|
if (matchedTap) {
|
|
@@ -3247,7 +4424,7 @@ var MockHttp = class extends Hookified {
|
|
|
3247
4424
|
if (this._apiDocs) {
|
|
3248
4425
|
await this.registerApiDocs();
|
|
3249
4426
|
}
|
|
3250
|
-
const { httpMethods, redirects, requestInspection, responseInspection, responseFormats, statusCodes: statusCodes2, cookies, anything, auth, images } = this._httpBin;
|
|
4427
|
+
const { httpMethods, redirects, requestInspection, responseInspection, responseFormats, statusCodes: statusCodes2, cookies, anything, auth, images, dynamicData } = this._httpBin;
|
|
3251
4428
|
if (httpMethods) {
|
|
3252
4429
|
await this.registerHttpMethods();
|
|
3253
4430
|
}
|
|
@@ -3278,6 +4455,9 @@ var MockHttp = class extends Hookified {
|
|
|
3278
4455
|
if (images) {
|
|
3279
4456
|
await this.registerImageRoutes();
|
|
3280
4457
|
}
|
|
4458
|
+
if (dynamicData) {
|
|
4459
|
+
await this.registerDynamicDataRoutes();
|
|
4460
|
+
}
|
|
3281
4461
|
if (this._autoDetectPort) {
|
|
3282
4462
|
const originalPort = this._port;
|
|
3283
4463
|
this._port = await this.detectPort();
|
|
@@ -3409,6 +4589,49 @@ var MockHttp = class extends Hookified {
|
|
|
3409
4589
|
const fastify = fastifyInstance ?? this._server;
|
|
3410
4590
|
await fastify.register(imageRoutes);
|
|
3411
4591
|
}
|
|
4592
|
+
/**
|
|
4593
|
+
* Register the dynamic data routes.
|
|
4594
|
+
* @param fastifyInstance - the server instance to register the routes on.
|
|
4595
|
+
*/
|
|
4596
|
+
async registerDynamicDataRoutes(fastifyInstance) {
|
|
4597
|
+
const fastify = fastifyInstance ?? this._server;
|
|
4598
|
+
await fastify.register(uuidRoute);
|
|
4599
|
+
await fastify.register(bytesRoute);
|
|
4600
|
+
await fastify.register(streamBytesRoute);
|
|
4601
|
+
await fastify.register(delayRoute);
|
|
4602
|
+
await fastify.register(base64Route);
|
|
4603
|
+
await fastify.register(streamRoute);
|
|
4604
|
+
await fastify.register(rangeRoute);
|
|
4605
|
+
await fastify.register(dripRoute);
|
|
4606
|
+
await fastify.register(linksRoute);
|
|
4607
|
+
}
|
|
4608
|
+
async resolveHttpsCredentials(options) {
|
|
4609
|
+
if (options.cert && options.key) {
|
|
4610
|
+
const cert = await this.loadPemValue(options.cert);
|
|
4611
|
+
const key = await this.loadPemValue(options.key);
|
|
4612
|
+
return {
|
|
4613
|
+
cert,
|
|
4614
|
+
key
|
|
4615
|
+
};
|
|
4616
|
+
}
|
|
4617
|
+
if ((options.cert || options.key) && options.autoGenerate !== true) {
|
|
4618
|
+
throw new Error("HTTPS options must include both 'cert' and 'key'. Only one was provided.");
|
|
4619
|
+
}
|
|
4620
|
+
if (options.autoGenerate !== false) {
|
|
4621
|
+
const result = generateCertificate(options.certificateOptions);
|
|
4622
|
+
return {
|
|
4623
|
+
cert: result.cert,
|
|
4624
|
+
key: result.key
|
|
4625
|
+
};
|
|
4626
|
+
}
|
|
4627
|
+
throw new Error("HTTPS is enabled but no certificate was provided and autoGenerate is false.");
|
|
4628
|
+
}
|
|
4629
|
+
async loadPemValue(value) {
|
|
4630
|
+
if (value.startsWith("-----")) {
|
|
4631
|
+
return value;
|
|
4632
|
+
}
|
|
4633
|
+
return fsPromises2.readFile(value, "utf8");
|
|
4634
|
+
}
|
|
3412
4635
|
};
|
|
3413
4636
|
|
|
3414
4637
|
// src/index.ts
|
|
@@ -3431,7 +4654,11 @@ if (import.meta.url === `file://${process2.argv[1]}`) {
|
|
|
3431
4654
|
}
|
|
3432
4655
|
export {
|
|
3433
4656
|
MockHttp as default,
|
|
4657
|
+
generateCertificate,
|
|
4658
|
+
generateCertificateFiles,
|
|
3434
4659
|
MockHttp as mockhttp,
|
|
3435
4660
|
start
|
|
3436
4661
|
};
|
|
3437
4662
|
/* v8 ignore next -- @preserve */
|
|
4663
|
+
/* v8 ignore next 2 -- @preserve */
|
|
4664
|
+
/* v8 ignore next 3 -- @preserve */
|