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