@wooksjs/event-http 0.4.26 → 0.4.27
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 +8 -8
- package/dist/index.cjs +360 -379
- package/dist/index.d.ts +123 -136
- package/dist/index.mjs +360 -379
- package/package.json +3 -3
package/dist/index.mjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { createEventContext, useEventContext, useEventId, attachHook, useEventLogger } from '@wooksjs/event-core';
|
|
2
2
|
import { URLSearchParams } from 'url';
|
|
3
|
-
import { Readable } from 'stream';
|
|
4
|
-
import { WooksAdapterBase } from 'wooks';
|
|
5
3
|
import http from 'http';
|
|
4
|
+
import { WooksAdapterBase } from 'wooks';
|
|
5
|
+
import { Readable } from 'stream';
|
|
6
6
|
|
|
7
7
|
function createHttpContext(data, options) {
|
|
8
8
|
return createEventContext({
|
|
@@ -17,58 +17,106 @@ function useHttpContext() {
|
|
|
17
17
|
return useEventContext('HTTP');
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
+
function escapeRegex(s) {
|
|
21
|
+
return s.replace(/[$()*+./?[\\\]^{|}-]/g, '\\$&');
|
|
22
|
+
}
|
|
23
|
+
function safeDecode(f, v) {
|
|
24
|
+
try {
|
|
25
|
+
return f(v);
|
|
26
|
+
}
|
|
27
|
+
catch (error) {
|
|
28
|
+
return v;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
function safeDecodeURIComponent(uri) {
|
|
32
|
+
if (!uri.includes('%')) {
|
|
33
|
+
return uri;
|
|
34
|
+
}
|
|
35
|
+
return safeDecode(decodeURIComponent, uri);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function convertTime(time, unit = 'ms') {
|
|
39
|
+
if (typeof time === 'number') {
|
|
40
|
+
return time / units[unit];
|
|
41
|
+
}
|
|
42
|
+
const rg = /(\d+)(\w+)/g;
|
|
43
|
+
let t = 0;
|
|
44
|
+
let r;
|
|
45
|
+
while ((r = rg.exec(time))) {
|
|
46
|
+
t += Number(r[1]) * (units[r[2]] || 0);
|
|
47
|
+
}
|
|
48
|
+
return t / units[unit];
|
|
49
|
+
}
|
|
50
|
+
const units = {
|
|
51
|
+
ms: 1,
|
|
52
|
+
s: 1000,
|
|
53
|
+
m: 1000 * 60,
|
|
54
|
+
h: 1000 * 60 * 60,
|
|
55
|
+
d: 1000 * 60 * 60 * 24,
|
|
56
|
+
w: 1000 * 60 * 60 * 24 * 7,
|
|
57
|
+
M: 1000 * 60 * 60 * 24 * 30,
|
|
58
|
+
Y: 1000 * 60 * 60 * 24 * 365,
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
function renderCookie(key, data) {
|
|
62
|
+
let attrs = '';
|
|
63
|
+
for (const [a, v] of Object.entries(data.attrs)) {
|
|
64
|
+
const func = cookieAttrFunc[a];
|
|
65
|
+
if (typeof func === 'function') {
|
|
66
|
+
const val = func(v);
|
|
67
|
+
attrs += val ? `; ${val}` : '';
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
throw new TypeError(`Unknown Set-Cookie attribute ${a}`);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return `${key}=${encodeURIComponent(data.value)}${attrs}`;
|
|
74
|
+
}
|
|
75
|
+
const cookieAttrFunc = {
|
|
76
|
+
expires: (v) => `Expires=${typeof v === 'string' || typeof v === 'number' ? new Date(v).toUTCString() : v.toUTCString()}`,
|
|
77
|
+
maxAge: (v) => `Max-Age=${convertTime(v, 's').toString()}`,
|
|
78
|
+
domain: (v) => `Domain=${v}`,
|
|
79
|
+
path: (v) => `Path=${v}`,
|
|
80
|
+
secure: (v) => (v ? 'Secure' : ''),
|
|
81
|
+
httpOnly: (v) => (v ? 'HttpOnly' : ''),
|
|
82
|
+
sameSite: (v) => v ? `SameSite=${typeof v === 'string' ? v : 'Strict'}` : '',
|
|
83
|
+
};
|
|
84
|
+
|
|
20
85
|
const xForwardedFor = 'x-forwarded-for';
|
|
21
86
|
function useRequest() {
|
|
22
87
|
const { store } = useHttpContext();
|
|
23
88
|
const { init } = store('request');
|
|
24
89
|
const event = store('event');
|
|
25
|
-
const
|
|
26
|
-
const rawBody = () => init('rawBody', () => {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
body = Buffer.concat([body, chunk]);
|
|
31
|
-
});
|
|
32
|
-
req.on('error', function (err) {
|
|
33
|
-
reject(err);
|
|
34
|
-
});
|
|
35
|
-
req.on('end', function () {
|
|
36
|
-
resolve(body);
|
|
37
|
-
});
|
|
90
|
+
const req = event.get('req');
|
|
91
|
+
const rawBody = async () => init('rawBody', async () => new Promise((resolve, reject) => {
|
|
92
|
+
let body = Buffer.from('');
|
|
93
|
+
req.on('data', chunk => {
|
|
94
|
+
body = Buffer.concat([body, chunk]);
|
|
38
95
|
});
|
|
39
|
-
|
|
96
|
+
req.on('error', err => {
|
|
97
|
+
reject(err);
|
|
98
|
+
});
|
|
99
|
+
req.on('end', () => {
|
|
100
|
+
resolve(body);
|
|
101
|
+
});
|
|
102
|
+
}));
|
|
40
103
|
const reqId = useEventId().getId;
|
|
41
104
|
const forwardedIp = () => init('forwardedIp', () => {
|
|
42
|
-
if (typeof req.headers[xForwardedFor] === 'string' &&
|
|
43
|
-
req.headers[xForwardedFor])
|
|
44
|
-
return req.headers[xForwardedFor]
|
|
45
|
-
.split(',')
|
|
46
|
-
.shift()
|
|
47
|
-
?.trim();
|
|
105
|
+
if (typeof req.headers[xForwardedFor] === 'string' && req.headers[xForwardedFor]) {
|
|
106
|
+
return req.headers[xForwardedFor].split(',').shift()?.trim();
|
|
48
107
|
}
|
|
49
108
|
else {
|
|
50
109
|
return '';
|
|
51
110
|
}
|
|
52
111
|
});
|
|
53
|
-
const remoteIp = () => init('remoteIp', () => req.socket
|
|
112
|
+
const remoteIp = () => init('remoteIp', () => req.socket.remoteAddress || req.connection.remoteAddress || '');
|
|
54
113
|
function getIp(options) {
|
|
55
|
-
|
|
56
|
-
return forwardedIp() || getIp();
|
|
57
|
-
}
|
|
58
|
-
else {
|
|
59
|
-
return remoteIp();
|
|
60
|
-
}
|
|
114
|
+
return options?.trustProxy ? forwardedIp() || getIp() : remoteIp();
|
|
61
115
|
}
|
|
62
|
-
const getIpList = () => init('ipList', () => {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
'',
|
|
67
|
-
forwarded: (req.headers[xForwardedFor] || '')
|
|
68
|
-
.split(',')
|
|
69
|
-
.map((s) => s.trim()),
|
|
70
|
-
};
|
|
71
|
-
});
|
|
116
|
+
const getIpList = () => init('ipList', () => ({
|
|
117
|
+
remoteIp: req.socket.remoteAddress || req.connection.remoteAddress || '',
|
|
118
|
+
forwarded: (req.headers[xForwardedFor] || '').split(',').map(s => s.trim()),
|
|
119
|
+
}));
|
|
72
120
|
return {
|
|
73
121
|
rawRequest: req,
|
|
74
122
|
url: req.url,
|
|
@@ -111,13 +159,73 @@ function useSetHeader(name) {
|
|
|
111
159
|
return hook(name);
|
|
112
160
|
}
|
|
113
161
|
|
|
162
|
+
function useCookies() {
|
|
163
|
+
const { store } = useHttpContext();
|
|
164
|
+
const { cookie } = useHeaders();
|
|
165
|
+
const { init } = store('cookies');
|
|
166
|
+
const getCookie = (name) => init(name, () => {
|
|
167
|
+
if (cookie) {
|
|
168
|
+
const result = new RegExp(`(?:^|; )${escapeRegex(name)}=(.*?)(?:;?$|; )`, 'i').exec(cookie);
|
|
169
|
+
return result?.[1] ? safeDecodeURIComponent(result[1]) : null;
|
|
170
|
+
}
|
|
171
|
+
else {
|
|
172
|
+
return null;
|
|
173
|
+
}
|
|
174
|
+
});
|
|
175
|
+
return {
|
|
176
|
+
rawCookies: cookie,
|
|
177
|
+
getCookie,
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
function useSetCookies() {
|
|
181
|
+
const { store } = useHttpContext();
|
|
182
|
+
const cookiesStore = store('setCookies');
|
|
183
|
+
function setCookie(name, value, attrs) {
|
|
184
|
+
cookiesStore.set(name, {
|
|
185
|
+
value,
|
|
186
|
+
attrs: attrs || {},
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
function cookies() {
|
|
190
|
+
return cookiesStore
|
|
191
|
+
.entries()
|
|
192
|
+
.filter(a => Boolean(a[1]))
|
|
193
|
+
.map(([key, value]) => renderCookie(key, value));
|
|
194
|
+
}
|
|
195
|
+
return {
|
|
196
|
+
setCookie,
|
|
197
|
+
getCookie: cookiesStore.get,
|
|
198
|
+
removeCookie: cookiesStore.del,
|
|
199
|
+
clearCookies: cookiesStore.clear,
|
|
200
|
+
cookies,
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
function useSetCookie(name) {
|
|
204
|
+
const { setCookie, getCookie } = useSetCookies();
|
|
205
|
+
const valueHook = attachHook({
|
|
206
|
+
name,
|
|
207
|
+
type: 'cookie',
|
|
208
|
+
}, {
|
|
209
|
+
get: () => getCookie(name).value,
|
|
210
|
+
set: (value) => {
|
|
211
|
+
setCookie(name, value, getCookie(name).attrs);
|
|
212
|
+
},
|
|
213
|
+
});
|
|
214
|
+
return attachHook(valueHook, {
|
|
215
|
+
get: () => getCookie(name).attrs,
|
|
216
|
+
set: (attrs) => {
|
|
217
|
+
setCookie(name, getCookie(name).value || '', attrs);
|
|
218
|
+
},
|
|
219
|
+
}, 'attrs');
|
|
220
|
+
}
|
|
221
|
+
|
|
114
222
|
function useAccept() {
|
|
115
223
|
const { store } = useHttpContext();
|
|
116
224
|
const { accept } = useHeaders();
|
|
117
225
|
const accepts = (mime) => {
|
|
118
226
|
const { set, get, has } = store('accept');
|
|
119
227
|
if (!has(mime)) {
|
|
120
|
-
return set(mime,
|
|
228
|
+
return set(mime, Boolean(accept && (accept === '*/*' || accept.includes(mime))));
|
|
121
229
|
}
|
|
122
230
|
return get(mime);
|
|
123
231
|
};
|
|
@@ -169,62 +277,39 @@ function useAuthorization() {
|
|
|
169
277
|
};
|
|
170
278
|
}
|
|
171
279
|
|
|
172
|
-
function convertTime(time, unit = 'ms') {
|
|
173
|
-
if (typeof time === 'number')
|
|
174
|
-
return time / units[unit];
|
|
175
|
-
const rg = /(\d+)(\w+)/g;
|
|
176
|
-
let t = 0;
|
|
177
|
-
let r;
|
|
178
|
-
while ((r = rg.exec(time))) {
|
|
179
|
-
t += Number(r[1]) * (units[r[2]] || 0);
|
|
180
|
-
}
|
|
181
|
-
return t / units[unit];
|
|
182
|
-
}
|
|
183
|
-
const units = {
|
|
184
|
-
ms: 1,
|
|
185
|
-
s: 1000,
|
|
186
|
-
m: 1000 * 60,
|
|
187
|
-
h: 1000 * 60 * 60,
|
|
188
|
-
d: 1000 * 60 * 60 * 24,
|
|
189
|
-
w: 1000 * 60 * 60 * 24 * 7,
|
|
190
|
-
M: 1000 * 60 * 60 * 24 * 30,
|
|
191
|
-
Y: 1000 * 60 * 60 * 24 * 365,
|
|
192
|
-
};
|
|
193
|
-
|
|
194
280
|
function renderCacheControl(data) {
|
|
195
281
|
let attrs = '';
|
|
196
282
|
for (const [a, v] of Object.entries(data)) {
|
|
197
|
-
if (
|
|
283
|
+
if (v === undefined) {
|
|
198
284
|
continue;
|
|
285
|
+
}
|
|
199
286
|
const func = cacheControlFunc[a];
|
|
200
287
|
if (typeof func === 'function') {
|
|
201
288
|
const val = func(v);
|
|
202
289
|
if (val) {
|
|
203
|
-
attrs += attrs ?
|
|
290
|
+
attrs += attrs ? `, ${val}` : val;
|
|
204
291
|
}
|
|
205
292
|
}
|
|
206
293
|
else {
|
|
207
|
-
throw new
|
|
294
|
+
throw new TypeError(`Unknown Cache-Control attribute ${a}`);
|
|
208
295
|
}
|
|
209
296
|
}
|
|
210
297
|
return attrs;
|
|
211
298
|
}
|
|
212
299
|
const cacheControlFunc = {
|
|
213
|
-
mustRevalidate: (v) => v ? 'must-revalidate' : '',
|
|
300
|
+
mustRevalidate: (v) => (v ? 'must-revalidate' : ''),
|
|
214
301
|
noCache: (v) => v ? (typeof v === 'string' ? `no-cache="${v}"` : 'no-cache') : '',
|
|
215
302
|
noStore: (v) => (v ? 'no-store' : ''),
|
|
216
303
|
noTransform: (v) => (v ? 'no-transform' : ''),
|
|
217
304
|
public: (v) => (v ? 'public' : ''),
|
|
218
305
|
private: (v) => v ? (typeof v === 'string' ? `private="${v}"` : 'private') : '',
|
|
219
|
-
proxyRevalidate: (v) => v ? 'proxy-revalidate' : '',
|
|
220
|
-
maxAge: (v) =>
|
|
221
|
-
sMaxage: (v) =>
|
|
306
|
+
proxyRevalidate: (v) => (v ? 'proxy-revalidate' : ''),
|
|
307
|
+
maxAge: (v) => `max-age=${convertTime(v, 's').toString()}`,
|
|
308
|
+
sMaxage: (v) => `s-maxage=${convertTime(v, 's').toString()}`,
|
|
222
309
|
};
|
|
223
310
|
|
|
224
311
|
const renderAge = (v) => convertTime(v, 's').toString();
|
|
225
|
-
const renderExpires = (v) => typeof v === 'string' || typeof v === 'number'
|
|
226
|
-
? new Date(v).toUTCString()
|
|
227
|
-
: v.toUTCString();
|
|
312
|
+
const renderExpires = (v) => typeof v === 'string' || typeof v === 'number' ? new Date(v).toUTCString() : v.toUTCString();
|
|
228
313
|
const renderPragmaNoCache = (v) => (v ? 'no-cache' : '');
|
|
229
314
|
function useSetCacheControl() {
|
|
230
315
|
const { setHeader } = useSetHeaders();
|
|
@@ -251,15 +336,16 @@ function useSetCacheControl() {
|
|
|
251
336
|
function useResponse() {
|
|
252
337
|
const { store } = useHttpContext();
|
|
253
338
|
const event = store('event');
|
|
254
|
-
const
|
|
339
|
+
const res = event.get('res');
|
|
255
340
|
const responded = store('response').hook('responded');
|
|
256
341
|
const statusCode = store('status').hook('code');
|
|
257
342
|
function status(code) {
|
|
258
|
-
return (statusCode.value = code
|
|
343
|
+
return (statusCode.value = code || statusCode.value);
|
|
259
344
|
}
|
|
260
345
|
const rawResponse = (options) => {
|
|
261
|
-
if (!options || !options.passthrough)
|
|
346
|
+
if (!options || !options.passthrough) {
|
|
262
347
|
responded.value = true;
|
|
348
|
+
}
|
|
263
349
|
return res;
|
|
264
350
|
};
|
|
265
351
|
return {
|
|
@@ -276,108 +362,6 @@ function useStatus() {
|
|
|
276
362
|
return store('status').hook('code');
|
|
277
363
|
}
|
|
278
364
|
|
|
279
|
-
function renderCookie(key, data) {
|
|
280
|
-
let attrs = '';
|
|
281
|
-
for (const [a, v] of Object.entries(data.attrs)) {
|
|
282
|
-
const func = cookieAttrFunc[a];
|
|
283
|
-
if (typeof func === 'function') {
|
|
284
|
-
const val = func(v);
|
|
285
|
-
attrs += val ? '; ' + val : '';
|
|
286
|
-
}
|
|
287
|
-
else {
|
|
288
|
-
throw new Error('Unknown Set-Cookie attribute ' + a);
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
return `${key}=${encodeURIComponent(data.value)}${attrs}`;
|
|
292
|
-
}
|
|
293
|
-
const cookieAttrFunc = {
|
|
294
|
-
expires: (v) => 'Expires=' +
|
|
295
|
-
(typeof v === 'string' || typeof v === 'number'
|
|
296
|
-
? new Date(v).toUTCString()
|
|
297
|
-
: v.toUTCString()),
|
|
298
|
-
maxAge: (v) => 'Max-Age=' + convertTime(v, 's').toString(),
|
|
299
|
-
domain: (v) => 'Domain=' + v,
|
|
300
|
-
path: (v) => 'Path=' + v,
|
|
301
|
-
secure: (v) => (v ? 'Secure' : ''),
|
|
302
|
-
httpOnly: (v) => (v ? 'HttpOnly' : ''),
|
|
303
|
-
sameSite: (v) => v ? 'SameSite=' + (typeof v === 'string' ? v : 'Strict') : '',
|
|
304
|
-
};
|
|
305
|
-
|
|
306
|
-
function escapeRegex(s) {
|
|
307
|
-
return s.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
|
|
308
|
-
}
|
|
309
|
-
function safeDecode(f, v) {
|
|
310
|
-
try {
|
|
311
|
-
return f(v);
|
|
312
|
-
}
|
|
313
|
-
catch (e) {
|
|
314
|
-
return v;
|
|
315
|
-
}
|
|
316
|
-
}
|
|
317
|
-
function safeDecodeURIComponent(uri) {
|
|
318
|
-
if (uri.indexOf('%') < 0)
|
|
319
|
-
return uri;
|
|
320
|
-
return safeDecode(decodeURIComponent, uri);
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
function useCookies() {
|
|
324
|
-
const { store } = useHttpContext();
|
|
325
|
-
const { cookie } = useHeaders();
|
|
326
|
-
const { init } = store('cookies');
|
|
327
|
-
const getCookie = (name) => init(name, () => {
|
|
328
|
-
if (cookie) {
|
|
329
|
-
const result = new RegExp(`(?:^|; )${escapeRegex(name)}=(.*?)(?:;?$|; )`, 'i').exec(cookie);
|
|
330
|
-
return result && result[1]
|
|
331
|
-
? safeDecodeURIComponent(result[1])
|
|
332
|
-
: null;
|
|
333
|
-
}
|
|
334
|
-
else {
|
|
335
|
-
return null;
|
|
336
|
-
}
|
|
337
|
-
});
|
|
338
|
-
return {
|
|
339
|
-
rawCookies: cookie,
|
|
340
|
-
getCookie,
|
|
341
|
-
};
|
|
342
|
-
}
|
|
343
|
-
function useSetCookies() {
|
|
344
|
-
const { store } = useHttpContext();
|
|
345
|
-
const cookiesStore = store('setCookies');
|
|
346
|
-
function setCookie(name, value, attrs) {
|
|
347
|
-
cookiesStore.set(name, {
|
|
348
|
-
value,
|
|
349
|
-
attrs: attrs || {},
|
|
350
|
-
});
|
|
351
|
-
}
|
|
352
|
-
function cookies() {
|
|
353
|
-
return cookiesStore
|
|
354
|
-
.entries()
|
|
355
|
-
.filter((a) => !!a[1])
|
|
356
|
-
.map(([key, value]) => renderCookie(key, value));
|
|
357
|
-
}
|
|
358
|
-
return {
|
|
359
|
-
setCookie,
|
|
360
|
-
getCookie: cookiesStore.get,
|
|
361
|
-
removeCookie: cookiesStore.del,
|
|
362
|
-
clearCookies: cookiesStore.clear,
|
|
363
|
-
cookies,
|
|
364
|
-
};
|
|
365
|
-
}
|
|
366
|
-
function useSetCookie(name) {
|
|
367
|
-
const { setCookie, getCookie } = useSetCookies();
|
|
368
|
-
const valueHook = attachHook({
|
|
369
|
-
name,
|
|
370
|
-
type: 'cookie',
|
|
371
|
-
}, {
|
|
372
|
-
get: () => getCookie(name)?.value,
|
|
373
|
-
set: (value) => setCookie(name, value, getCookie(name)?.attrs),
|
|
374
|
-
});
|
|
375
|
-
return attachHook(valueHook, {
|
|
376
|
-
get: () => getCookie(name)?.attrs,
|
|
377
|
-
set: (attrs) => setCookie(name, getCookie(name)?.value || '', attrs),
|
|
378
|
-
}, 'attrs');
|
|
379
|
-
}
|
|
380
|
-
|
|
381
365
|
class WooksURLSearchParams extends URLSearchParams {
|
|
382
366
|
toJson() {
|
|
383
367
|
const json = {};
|
|
@@ -418,89 +402,91 @@ class BaseHttpResponseRenderer {
|
|
|
418
402
|
if (typeof response.body === 'string' ||
|
|
419
403
|
typeof response.body === 'boolean' ||
|
|
420
404
|
typeof response.body === 'number') {
|
|
421
|
-
if (!response.getContentType())
|
|
405
|
+
if (!response.getContentType()) {
|
|
422
406
|
response.setContentType('text/plain');
|
|
407
|
+
}
|
|
423
408
|
return response.body.toString();
|
|
424
409
|
}
|
|
425
|
-
if (
|
|
410
|
+
if (response.body === undefined) {
|
|
426
411
|
return '';
|
|
427
412
|
}
|
|
428
413
|
if (response.body instanceof Uint8Array) {
|
|
429
414
|
return response.body;
|
|
430
415
|
}
|
|
431
416
|
if (typeof response.body === 'object') {
|
|
432
|
-
if (!response.getContentType())
|
|
417
|
+
if (!response.getContentType()) {
|
|
433
418
|
response.setContentType('application/json');
|
|
419
|
+
}
|
|
434
420
|
return JSON.stringify(response.body);
|
|
435
421
|
}
|
|
436
|
-
throw new Error(
|
|
422
|
+
throw new Error(`Unsupported body format "${typeof response.body}"`);
|
|
437
423
|
}
|
|
438
424
|
}
|
|
439
425
|
|
|
440
426
|
const httpStatusCodes = {
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
427
|
+
100: 'Continue',
|
|
428
|
+
101: 'Switching protocols',
|
|
429
|
+
102: 'Processing',
|
|
430
|
+
103: 'Early Hints',
|
|
431
|
+
200: 'OK',
|
|
432
|
+
201: 'Created',
|
|
433
|
+
202: 'Accepted',
|
|
434
|
+
203: 'Non-Authoritative Information',
|
|
435
|
+
204: 'No Content',
|
|
436
|
+
205: 'Reset Content',
|
|
437
|
+
206: 'Partial Content',
|
|
438
|
+
207: 'Multi-Status',
|
|
439
|
+
208: 'Already Reported',
|
|
440
|
+
226: 'IM Used',
|
|
441
|
+
300: 'Multiple Choices',
|
|
442
|
+
301: 'Moved Permanently',
|
|
443
|
+
302: 'Found (Previously "Moved Temporarily")',
|
|
444
|
+
303: 'See Other',
|
|
445
|
+
304: 'Not Modified',
|
|
446
|
+
305: 'Use Proxy',
|
|
447
|
+
306: 'Switch Proxy',
|
|
448
|
+
307: 'Temporary Redirect',
|
|
449
|
+
308: 'Permanent Redirect',
|
|
450
|
+
400: 'Bad Request',
|
|
451
|
+
401: 'Unauthorized',
|
|
452
|
+
402: 'Payment Required',
|
|
453
|
+
403: 'Forbidden',
|
|
454
|
+
404: 'Not Found',
|
|
455
|
+
405: 'Method Not Allowed',
|
|
456
|
+
406: 'Not Acceptable',
|
|
457
|
+
407: 'Proxy Authentication Required',
|
|
458
|
+
408: 'Request Timeout',
|
|
459
|
+
409: 'Conflict',
|
|
460
|
+
410: 'Gone',
|
|
461
|
+
411: 'Length Required',
|
|
462
|
+
412: 'Precondition Failed',
|
|
463
|
+
413: 'Payload Too Large',
|
|
464
|
+
414: 'URI Too Long',
|
|
465
|
+
415: 'Unsupported Media Type',
|
|
466
|
+
416: 'Range Not Satisfiable',
|
|
467
|
+
417: 'Expectation Failed',
|
|
468
|
+
418: "I'm a Teapot",
|
|
469
|
+
421: 'Misdirected Request',
|
|
470
|
+
422: 'Unprocessable Entity',
|
|
471
|
+
423: 'Locked',
|
|
472
|
+
424: 'Failed Dependency',
|
|
473
|
+
425: 'Too Early',
|
|
474
|
+
426: 'Upgrade Required',
|
|
475
|
+
428: 'Precondition Required',
|
|
476
|
+
429: 'Too Many Requests',
|
|
477
|
+
431: 'Request Header Fields Too Large',
|
|
478
|
+
451: 'Unavailable For Legal Reasons',
|
|
479
|
+
500: 'Internal Server Error',
|
|
480
|
+
501: 'Not Implemented',
|
|
481
|
+
502: 'Bad Gateway',
|
|
482
|
+
503: 'Service Unavailable',
|
|
483
|
+
504: 'Gateway Timeout',
|
|
484
|
+
505: 'HTTP Version Not Supported',
|
|
485
|
+
506: 'Variant Also Negotiates',
|
|
486
|
+
507: 'Insufficient Storage',
|
|
487
|
+
508: 'Loop Detected',
|
|
488
|
+
510: 'Not Extended',
|
|
489
|
+
511: 'Network Authentication Required',
|
|
504
490
|
};
|
|
505
491
|
var EHttpStatusCode;
|
|
506
492
|
(function (EHttpStatusCode) {
|
|
@@ -569,6 +555,110 @@ var EHttpStatusCode;
|
|
|
569
555
|
EHttpStatusCode[EHttpStatusCode["NetworkAuthenticationRequired"] = 511] = "NetworkAuthenticationRequired";
|
|
570
556
|
})(EHttpStatusCode || (EHttpStatusCode = {}));
|
|
571
557
|
|
|
558
|
+
const preStyles = 'font-family: monospace;' +
|
|
559
|
+
'width: 100%;' +
|
|
560
|
+
'max-width: 900px;' +
|
|
561
|
+
'padding: 10px;' +
|
|
562
|
+
'margin: 20px auto;' +
|
|
563
|
+
'border-radius: 8px;' +
|
|
564
|
+
'background-color: #494949;' +
|
|
565
|
+
'box-shadow: 0px 0px 3px 2px rgb(255 255 255 / 20%);';
|
|
566
|
+
class HttpErrorRenderer extends BaseHttpResponseRenderer {
|
|
567
|
+
renderHtml(response) {
|
|
568
|
+
const data = response.body || {};
|
|
569
|
+
response.setContentType('text/html');
|
|
570
|
+
const keys = Object.keys(data).filter(key => !['statusCode', 'error', 'message'].includes(key));
|
|
571
|
+
return ('<html style="background-color: #333; color: #bbb;">' +
|
|
572
|
+
`<head><title>${data.statusCode} ${httpStatusCodes[data.statusCode]}</title></head>` +
|
|
573
|
+
`<body><center><h1>${data.statusCode} ${httpStatusCodes[data.statusCode]}</h1></center>` +
|
|
574
|
+
`<center><h4>${data.message}</h1></center><hr color="#666">` +
|
|
575
|
+
`<center style="color: #666;"> Wooks v${"0.4.27"} </center>` +
|
|
576
|
+
`${keys.length > 0
|
|
577
|
+
? `<pre style="${preStyles}">${JSON.stringify({
|
|
578
|
+
...data,
|
|
579
|
+
statusCode: undefined,
|
|
580
|
+
message: undefined,
|
|
581
|
+
error: undefined,
|
|
582
|
+
}, null, ' ')}</pre>`
|
|
583
|
+
: ''}` +
|
|
584
|
+
'</body></html>');
|
|
585
|
+
}
|
|
586
|
+
renderText(response) {
|
|
587
|
+
const data = response.body || {};
|
|
588
|
+
response.setContentType('text/plain');
|
|
589
|
+
const keys = Object.keys(data).filter(key => !['statusCode', 'error', 'message'].includes(key));
|
|
590
|
+
return (`${data.statusCode} ${httpStatusCodes[data.statusCode]}\n${data.message}` +
|
|
591
|
+
`\n\n${keys.length > 0
|
|
592
|
+
? `${JSON.stringify({
|
|
593
|
+
...data,
|
|
594
|
+
statusCode: undefined,
|
|
595
|
+
message: undefined,
|
|
596
|
+
error: undefined,
|
|
597
|
+
}, null, ' ')}`
|
|
598
|
+
: ''}`);
|
|
599
|
+
}
|
|
600
|
+
renderJson(response) {
|
|
601
|
+
const data = response.body || {};
|
|
602
|
+
response.setContentType('application/json');
|
|
603
|
+
const keys = Object.keys(data).filter(key => !['statusCode', 'error', 'message'].includes(key));
|
|
604
|
+
return (`{"statusCode":${escapeQuotes(data.statusCode)},` +
|
|
605
|
+
`"error":"${escapeQuotes(data.error)}",` +
|
|
606
|
+
`"message":"${escapeQuotes(data.message)}"` +
|
|
607
|
+
`${keys.length > 0
|
|
608
|
+
?
|
|
609
|
+
`,${keys.map(k => `"${escapeQuotes(k)}":${JSON.stringify(data[k])}`).join(',')}`
|
|
610
|
+
: ''}}`);
|
|
611
|
+
}
|
|
612
|
+
render(response) {
|
|
613
|
+
const { acceptsJson, acceptsText, acceptsHtml } = useAccept();
|
|
614
|
+
response.status = response.body?.statusCode || 500;
|
|
615
|
+
if (acceptsJson()) {
|
|
616
|
+
return this.renderJson(response);
|
|
617
|
+
}
|
|
618
|
+
else if (acceptsHtml()) {
|
|
619
|
+
return this.renderHtml(response);
|
|
620
|
+
}
|
|
621
|
+
else if (acceptsText()) {
|
|
622
|
+
return this.renderText(response);
|
|
623
|
+
}
|
|
624
|
+
else {
|
|
625
|
+
return this.renderJson(response);
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
function escapeQuotes(s) {
|
|
630
|
+
return (typeof s === 'number' ? s : s || '').toString().replace(/"/g, '\\"');
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
class HttpError extends Error {
|
|
634
|
+
constructor(code = 500, _body = '') {
|
|
635
|
+
super(typeof _body === 'string' ? _body : _body.message);
|
|
636
|
+
this.code = code;
|
|
637
|
+
this._body = _body;
|
|
638
|
+
this.name = 'HttpError';
|
|
639
|
+
}
|
|
640
|
+
get body() {
|
|
641
|
+
return typeof this._body === 'string'
|
|
642
|
+
? {
|
|
643
|
+
statusCode: this.code,
|
|
644
|
+
message: this.message,
|
|
645
|
+
error: httpStatusCodes[this.code],
|
|
646
|
+
}
|
|
647
|
+
: {
|
|
648
|
+
...this._body,
|
|
649
|
+
statusCode: this.code,
|
|
650
|
+
message: this.message,
|
|
651
|
+
error: httpStatusCodes[this.code],
|
|
652
|
+
};
|
|
653
|
+
}
|
|
654
|
+
attachRenderer(renderer) {
|
|
655
|
+
this.renderer = renderer;
|
|
656
|
+
}
|
|
657
|
+
getRenderer() {
|
|
658
|
+
return this.renderer;
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
|
|
572
662
|
const defaultStatus = {
|
|
573
663
|
GET: EHttpStatusCode.OK,
|
|
574
664
|
POST: EHttpStatusCode.Created,
|
|
@@ -649,7 +739,7 @@ class BaseHttpResponse {
|
|
|
649
739
|
...this._headers,
|
|
650
740
|
};
|
|
651
741
|
const setCookie = [...newCookies, ...cookies()];
|
|
652
|
-
if (setCookie
|
|
742
|
+
if (setCookie.length > 0) {
|
|
653
743
|
this._headers['set-cookie'] = setCookie;
|
|
654
744
|
}
|
|
655
745
|
return this;
|
|
@@ -696,7 +786,7 @@ class BaseHttpResponse {
|
|
|
696
786
|
}
|
|
697
787
|
else {
|
|
698
788
|
return new Promise((resolve, reject) => {
|
|
699
|
-
stream.on('error',
|
|
789
|
+
stream.on('error', e => {
|
|
700
790
|
stream.destroy();
|
|
701
791
|
res.end();
|
|
702
792
|
reject(e);
|
|
@@ -709,8 +799,7 @@ class BaseHttpResponse {
|
|
|
709
799
|
});
|
|
710
800
|
}
|
|
711
801
|
}
|
|
712
|
-
else if (globalThis.Response &&
|
|
713
|
-
this.body instanceof Response) {
|
|
802
|
+
else if (globalThis.Response && this.body instanceof Response) {
|
|
714
803
|
this.mergeFetchStatus(this.body.status);
|
|
715
804
|
if (method === 'HEAD') {
|
|
716
805
|
res.end();
|
|
@@ -733,10 +822,12 @@ class BaseHttpResponse {
|
|
|
733
822
|
else {
|
|
734
823
|
const renderedBody = this.renderer.render(this);
|
|
735
824
|
this.mergeStatus(renderedBody);
|
|
736
|
-
res
|
|
825
|
+
res
|
|
826
|
+
.writeHead(this.status, {
|
|
737
827
|
'content-length': Buffer.byteLength(renderedBody),
|
|
738
828
|
...this._headers,
|
|
739
|
-
})
|
|
829
|
+
})
|
|
830
|
+
.end(method === 'HEAD' ? '' : renderedBody);
|
|
740
831
|
}
|
|
741
832
|
}
|
|
742
833
|
}
|
|
@@ -747,133 +838,21 @@ async function respondWithFetch(fetchBody, res) {
|
|
|
747
838
|
res.write(chunk);
|
|
748
839
|
}
|
|
749
840
|
}
|
|
750
|
-
catch (
|
|
841
|
+
catch (error) {
|
|
751
842
|
}
|
|
752
843
|
}
|
|
753
844
|
res.end();
|
|
754
845
|
}
|
|
755
846
|
|
|
756
|
-
const preStyles = 'font-family: monospace;' +
|
|
757
|
-
'width: 100%;' +
|
|
758
|
-
'max-width: 900px;' +
|
|
759
|
-
'padding: 10px;' +
|
|
760
|
-
'margin: 20px auto;' +
|
|
761
|
-
'border-radius: 8px;' +
|
|
762
|
-
'background-color: #494949;' +
|
|
763
|
-
'box-shadow: 0px 0px 3px 2px rgb(255 255 255 / 20%);';
|
|
764
|
-
class HttpErrorRenderer extends BaseHttpResponseRenderer {
|
|
765
|
-
renderHtml(response) {
|
|
766
|
-
const data = response.body || {};
|
|
767
|
-
response.setContentType('text/html');
|
|
768
|
-
const keys = Object.keys(data).filter((key) => !['statusCode', 'error', 'message'].includes(key));
|
|
769
|
-
return ('<html style="background-color: #333; color: #bbb;">' +
|
|
770
|
-
`<head><title>${data.statusCode} ${httpStatusCodes[data.statusCode]}</title></head>` +
|
|
771
|
-
`<body><center><h1>${data.statusCode} ${httpStatusCodes[data.statusCode]}</h1></center>` +
|
|
772
|
-
`<center><h4>${data.message}</h1></center><hr color="#666">` +
|
|
773
|
-
`<center style="color: #666;"> Wooks v${"0.4.26"} </center>` +
|
|
774
|
-
`${keys.length
|
|
775
|
-
? `<pre style="${preStyles}">${JSON.stringify({
|
|
776
|
-
...data,
|
|
777
|
-
statusCode: undefined,
|
|
778
|
-
message: undefined,
|
|
779
|
-
error: undefined,
|
|
780
|
-
}, null, ' ')}</pre>`
|
|
781
|
-
: ''}` +
|
|
782
|
-
'</body></html>');
|
|
783
|
-
}
|
|
784
|
-
renderText(response) {
|
|
785
|
-
const data = response.body || {};
|
|
786
|
-
response.setContentType('text/plain');
|
|
787
|
-
const keys = Object.keys(data).filter((key) => !['statusCode', 'error', 'message'].includes(key));
|
|
788
|
-
return (`${data.statusCode} ${httpStatusCodes[data.statusCode]}\n${data.message}` +
|
|
789
|
-
`\n\n${keys.length
|
|
790
|
-
? `${JSON.stringify({
|
|
791
|
-
...data,
|
|
792
|
-
statusCode: undefined,
|
|
793
|
-
message: undefined,
|
|
794
|
-
error: undefined,
|
|
795
|
-
}, null, ' ')}`
|
|
796
|
-
: ''}`);
|
|
797
|
-
}
|
|
798
|
-
renderJson(response) {
|
|
799
|
-
const data = response.body || {};
|
|
800
|
-
response.setContentType('application/json');
|
|
801
|
-
const keys = Object.keys(data).filter((key) => !['statusCode', 'error', 'message'].includes(key));
|
|
802
|
-
return (`{"statusCode":${escapeQuotes(data.statusCode)},` +
|
|
803
|
-
`"error":"${escapeQuotes(data.error)}",` +
|
|
804
|
-
`"message":"${escapeQuotes(data.message)}"` +
|
|
805
|
-
`${keys.length
|
|
806
|
-
? ',' +
|
|
807
|
-
keys
|
|
808
|
-
.map((k) => `"${escapeQuotes(k)}":${JSON.stringify(data[k])}`)
|
|
809
|
-
.join(',')
|
|
810
|
-
: ''}}`);
|
|
811
|
-
}
|
|
812
|
-
render(response) {
|
|
813
|
-
const { acceptsJson, acceptsText, acceptsHtml } = useAccept();
|
|
814
|
-
response.status = response.body?.statusCode || 500;
|
|
815
|
-
if (acceptsJson()) {
|
|
816
|
-
return this.renderJson(response);
|
|
817
|
-
}
|
|
818
|
-
else if (acceptsHtml()) {
|
|
819
|
-
return this.renderHtml(response);
|
|
820
|
-
}
|
|
821
|
-
else if (acceptsText()) {
|
|
822
|
-
return this.renderText(response);
|
|
823
|
-
}
|
|
824
|
-
else {
|
|
825
|
-
return this.renderJson(response);
|
|
826
|
-
}
|
|
827
|
-
}
|
|
828
|
-
}
|
|
829
|
-
function escapeQuotes(s) {
|
|
830
|
-
return (typeof s === 'number' ? s : s || '')
|
|
831
|
-
.toString()
|
|
832
|
-
.replace(/[\""]/g, '\\"');
|
|
833
|
-
}
|
|
834
|
-
|
|
835
|
-
class HttpError extends Error {
|
|
836
|
-
constructor(code = 500, _body = '') {
|
|
837
|
-
super(typeof _body === 'string' ? _body : _body.message);
|
|
838
|
-
this.code = code;
|
|
839
|
-
this._body = _body;
|
|
840
|
-
}
|
|
841
|
-
get body() {
|
|
842
|
-
return typeof this._body === 'string'
|
|
843
|
-
? {
|
|
844
|
-
statusCode: this.code,
|
|
845
|
-
message: this.message,
|
|
846
|
-
error: httpStatusCodes[this.code],
|
|
847
|
-
}
|
|
848
|
-
: {
|
|
849
|
-
...this._body,
|
|
850
|
-
statusCode: this.code,
|
|
851
|
-
message: this.message,
|
|
852
|
-
error: httpStatusCodes[this.code],
|
|
853
|
-
};
|
|
854
|
-
}
|
|
855
|
-
attachRenderer(renderer) {
|
|
856
|
-
this.renderer = renderer;
|
|
857
|
-
}
|
|
858
|
-
getRenderer() {
|
|
859
|
-
return this.renderer;
|
|
860
|
-
}
|
|
861
|
-
}
|
|
862
|
-
|
|
863
847
|
function createWooksResponder(renderer = new BaseHttpResponseRenderer(), errorRenderer = new HttpErrorRenderer()) {
|
|
864
848
|
function createResponse(data) {
|
|
865
849
|
const { hasResponded } = useResponse();
|
|
866
|
-
if (hasResponded())
|
|
850
|
+
if (hasResponded()) {
|
|
867
851
|
return null;
|
|
852
|
+
}
|
|
868
853
|
if (data instanceof Error) {
|
|
869
854
|
const r = new BaseHttpResponse(errorRenderer);
|
|
870
|
-
|
|
871
|
-
if (data instanceof HttpError) {
|
|
872
|
-
httpError = data;
|
|
873
|
-
}
|
|
874
|
-
else {
|
|
875
|
-
httpError = new HttpError(500, data.message);
|
|
876
|
-
}
|
|
855
|
+
const httpError = data instanceof HttpError ? data : new HttpError(500, data.message);
|
|
877
856
|
r.setBody(httpError.body);
|
|
878
857
|
return r;
|
|
879
858
|
}
|
|
@@ -886,7 +865,7 @@ function createWooksResponder(renderer = new BaseHttpResponseRenderer(), errorRe
|
|
|
886
865
|
}
|
|
887
866
|
return {
|
|
888
867
|
createResponse,
|
|
889
|
-
respond: (data) => createResponse(data)?.respond(),
|
|
868
|
+
respond: async (data) => createResponse(data)?.respond(),
|
|
890
869
|
};
|
|
891
870
|
}
|
|
892
871
|
|
|
@@ -929,12 +908,14 @@ class WooksHttp extends WooksAdapterBase {
|
|
|
929
908
|
server.listen(...args);
|
|
930
909
|
});
|
|
931
910
|
}
|
|
932
|
-
close(server) {
|
|
911
|
+
async close(server) {
|
|
933
912
|
const srv = server || this.server;
|
|
934
913
|
return new Promise((resolve, reject) => {
|
|
935
|
-
srv?.close(
|
|
936
|
-
if (err)
|
|
937
|
-
|
|
914
|
+
srv?.close(err => {
|
|
915
|
+
if (err) {
|
|
916
|
+
reject(err);
|
|
917
|
+
return;
|
|
918
|
+
}
|
|
938
919
|
resolve(srv);
|
|
939
920
|
});
|
|
940
921
|
});
|
|
@@ -946,8 +927,8 @@ class WooksHttp extends WooksAdapterBase {
|
|
|
946
927
|
this.server = server;
|
|
947
928
|
}
|
|
948
929
|
respond(data) {
|
|
949
|
-
void this.responder.respond(data)
|
|
950
|
-
this.logger.error('Uncought response exception',
|
|
930
|
+
void this.responder.respond(data).catch(error => {
|
|
931
|
+
this.logger.error('Uncought response exception', error);
|
|
951
932
|
});
|
|
952
933
|
}
|
|
953
934
|
getServerCb() {
|
|
@@ -958,10 +939,10 @@ class WooksHttp extends WooksAdapterBase {
|
|
|
958
939
|
try {
|
|
959
940
|
await this.processHandlers(handlers || [this.opts?.onNotFound]);
|
|
960
941
|
}
|
|
961
|
-
catch (
|
|
962
|
-
this.logger.error('Internal error, please report',
|
|
942
|
+
catch (error) {
|
|
943
|
+
this.logger.error('Internal error, please report', error);
|
|
963
944
|
restoreCtx();
|
|
964
|
-
this.respond(
|
|
945
|
+
this.respond(error);
|
|
965
946
|
clearCtx();
|
|
966
947
|
}
|
|
967
948
|
}
|
|
@@ -986,11 +967,11 @@ class WooksHttp extends WooksAdapterBase {
|
|
|
986
967
|
clearCtx();
|
|
987
968
|
break;
|
|
988
969
|
}
|
|
989
|
-
catch (
|
|
990
|
-
this.logger.error(`Uncought route handler exception: ${store('event').get('req').url || ''}`,
|
|
970
|
+
catch (error) {
|
|
971
|
+
this.logger.error(`Uncought route handler exception: ${store('event').get('req').url || ''}`, error);
|
|
991
972
|
if (isLastHandler) {
|
|
992
973
|
restoreCtx();
|
|
993
|
-
this.respond(
|
|
974
|
+
this.respond(error);
|
|
994
975
|
clearCtx();
|
|
995
976
|
}
|
|
996
977
|
}
|