@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.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,40 +17,99 @@ 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 = () => init('rawBody', () => 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
114
|
if (options?.trustProxy) {
|
|
56
115
|
return forwardedIp() || getIp();
|
|
@@ -59,16 +118,10 @@ function useRequest() {
|
|
|
59
118
|
return remoteIp();
|
|
60
119
|
}
|
|
61
120
|
}
|
|
62
|
-
const getIpList = () => init('ipList', () => {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
'',
|
|
67
|
-
forwarded: (req.headers[xForwardedFor] || '')
|
|
68
|
-
.split(',')
|
|
69
|
-
.map((s) => s.trim()),
|
|
70
|
-
};
|
|
71
|
-
});
|
|
121
|
+
const getIpList = () => init('ipList', () => ({
|
|
122
|
+
remoteIp: req.socket.remoteAddress || req.connection.remoteAddress || '',
|
|
123
|
+
forwarded: (req.headers[xForwardedFor] || '').split(',').map(s => s.trim()),
|
|
124
|
+
}));
|
|
72
125
|
return {
|
|
73
126
|
rawRequest: req,
|
|
74
127
|
url: req.url,
|
|
@@ -111,13 +164,73 @@ function useSetHeader(name) {
|
|
|
111
164
|
return hook(name);
|
|
112
165
|
}
|
|
113
166
|
|
|
167
|
+
function useCookies() {
|
|
168
|
+
const { store } = useHttpContext();
|
|
169
|
+
const { cookie } = useHeaders();
|
|
170
|
+
const { init } = store('cookies');
|
|
171
|
+
const getCookie = (name) => init(name, () => {
|
|
172
|
+
if (cookie) {
|
|
173
|
+
const result = new RegExp(`(?:^|; )${escapeRegex(name)}=(.*?)(?:;?$|; )`, 'i').exec(cookie);
|
|
174
|
+
return result?.[1] ? safeDecodeURIComponent(result[1]) : null;
|
|
175
|
+
}
|
|
176
|
+
else {
|
|
177
|
+
return null;
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
return {
|
|
181
|
+
rawCookies: cookie,
|
|
182
|
+
getCookie,
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
function useSetCookies() {
|
|
186
|
+
const { store } = useHttpContext();
|
|
187
|
+
const cookiesStore = store('setCookies');
|
|
188
|
+
function setCookie(name, value, attrs) {
|
|
189
|
+
cookiesStore.set(name, {
|
|
190
|
+
value,
|
|
191
|
+
attrs: attrs || {},
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
function cookies() {
|
|
195
|
+
return cookiesStore
|
|
196
|
+
.entries()
|
|
197
|
+
.filter(a => !!a[1])
|
|
198
|
+
.map(([key, value]) => renderCookie(key, value));
|
|
199
|
+
}
|
|
200
|
+
return {
|
|
201
|
+
setCookie,
|
|
202
|
+
getCookie: cookiesStore.get,
|
|
203
|
+
removeCookie: cookiesStore.del,
|
|
204
|
+
clearCookies: cookiesStore.clear,
|
|
205
|
+
cookies,
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
function useSetCookie(name) {
|
|
209
|
+
const { setCookie, getCookie } = useSetCookies();
|
|
210
|
+
const valueHook = attachHook({
|
|
211
|
+
name,
|
|
212
|
+
type: 'cookie',
|
|
213
|
+
}, {
|
|
214
|
+
get: () => getCookie(name)?.value,
|
|
215
|
+
set: (value) => {
|
|
216
|
+
setCookie(name, value, getCookie(name)?.attrs);
|
|
217
|
+
},
|
|
218
|
+
});
|
|
219
|
+
return attachHook(valueHook, {
|
|
220
|
+
get: () => getCookie(name)?.attrs,
|
|
221
|
+
set: (attrs) => {
|
|
222
|
+
setCookie(name, getCookie(name)?.value || '', attrs);
|
|
223
|
+
},
|
|
224
|
+
}, 'attrs');
|
|
225
|
+
}
|
|
226
|
+
|
|
114
227
|
function useAccept() {
|
|
115
228
|
const { store } = useHttpContext();
|
|
116
229
|
const { accept } = useHeaders();
|
|
117
230
|
const accepts = (mime) => {
|
|
118
231
|
const { set, get, has } = store('accept');
|
|
119
232
|
if (!has(mime)) {
|
|
120
|
-
return set(mime, !!(accept && (accept === '*/*' || accept.
|
|
233
|
+
return set(mime, !!(accept && (accept === '*/*' || accept.includes(mime))));
|
|
121
234
|
}
|
|
122
235
|
return get(mime);
|
|
123
236
|
};
|
|
@@ -169,62 +282,39 @@ function useAuthorization() {
|
|
|
169
282
|
};
|
|
170
283
|
}
|
|
171
284
|
|
|
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
285
|
function renderCacheControl(data) {
|
|
195
286
|
let attrs = '';
|
|
196
287
|
for (const [a, v] of Object.entries(data)) {
|
|
197
|
-
if (
|
|
288
|
+
if (v === undefined) {
|
|
198
289
|
continue;
|
|
290
|
+
}
|
|
199
291
|
const func = cacheControlFunc[a];
|
|
200
292
|
if (typeof func === 'function') {
|
|
201
293
|
const val = func(v);
|
|
202
294
|
if (val) {
|
|
203
|
-
attrs += attrs ?
|
|
295
|
+
attrs += attrs ? `, ${val}` : val;
|
|
204
296
|
}
|
|
205
297
|
}
|
|
206
298
|
else {
|
|
207
|
-
throw new
|
|
299
|
+
throw new TypeError(`Unknown Cache-Control attribute ${a}`);
|
|
208
300
|
}
|
|
209
301
|
}
|
|
210
302
|
return attrs;
|
|
211
303
|
}
|
|
212
304
|
const cacheControlFunc = {
|
|
213
|
-
mustRevalidate: (v) => v ? 'must-revalidate' : '',
|
|
305
|
+
mustRevalidate: (v) => (v ? 'must-revalidate' : ''),
|
|
214
306
|
noCache: (v) => v ? (typeof v === 'string' ? `no-cache="${v}"` : 'no-cache') : '',
|
|
215
307
|
noStore: (v) => (v ? 'no-store' : ''),
|
|
216
308
|
noTransform: (v) => (v ? 'no-transform' : ''),
|
|
217
309
|
public: (v) => (v ? 'public' : ''),
|
|
218
310
|
private: (v) => v ? (typeof v === 'string' ? `private="${v}"` : 'private') : '',
|
|
219
|
-
proxyRevalidate: (v) => v ? 'proxy-revalidate' : '',
|
|
220
|
-
maxAge: (v) =>
|
|
221
|
-
sMaxage: (v) =>
|
|
311
|
+
proxyRevalidate: (v) => (v ? 'proxy-revalidate' : ''),
|
|
312
|
+
maxAge: (v) => `max-age=${convertTime(v, 's').toString()}`,
|
|
313
|
+
sMaxage: (v) => `s-maxage=${convertTime(v, 's').toString()}`,
|
|
222
314
|
};
|
|
223
315
|
|
|
224
316
|
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();
|
|
317
|
+
const renderExpires = (v) => typeof v === 'string' || typeof v === 'number' ? new Date(v).toUTCString() : v.toUTCString();
|
|
228
318
|
const renderPragmaNoCache = (v) => (v ? 'no-cache' : '');
|
|
229
319
|
function useSetCacheControl() {
|
|
230
320
|
const { setHeader } = useSetHeaders();
|
|
@@ -251,15 +341,16 @@ function useSetCacheControl() {
|
|
|
251
341
|
function useResponse() {
|
|
252
342
|
const { store } = useHttpContext();
|
|
253
343
|
const event = store('event');
|
|
254
|
-
const
|
|
344
|
+
const res = event.get('res');
|
|
255
345
|
const responded = store('response').hook('responded');
|
|
256
346
|
const statusCode = store('status').hook('code');
|
|
257
347
|
function status(code) {
|
|
258
348
|
return (statusCode.value = code ? code : statusCode.value);
|
|
259
349
|
}
|
|
260
350
|
const rawResponse = (options) => {
|
|
261
|
-
if (!options || !options.passthrough)
|
|
351
|
+
if (!options || !options.passthrough) {
|
|
262
352
|
responded.value = true;
|
|
353
|
+
}
|
|
263
354
|
return res;
|
|
264
355
|
};
|
|
265
356
|
return {
|
|
@@ -276,108 +367,6 @@ function useStatus() {
|
|
|
276
367
|
return store('status').hook('code');
|
|
277
368
|
}
|
|
278
369
|
|
|
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
370
|
class WooksURLSearchParams extends URLSearchParams {
|
|
382
371
|
toJson() {
|
|
383
372
|
const json = {};
|
|
@@ -418,89 +407,91 @@ class BaseHttpResponseRenderer {
|
|
|
418
407
|
if (typeof response.body === 'string' ||
|
|
419
408
|
typeof response.body === 'boolean' ||
|
|
420
409
|
typeof response.body === 'number') {
|
|
421
|
-
if (!response.getContentType())
|
|
410
|
+
if (!response.getContentType()) {
|
|
422
411
|
response.setContentType('text/plain');
|
|
412
|
+
}
|
|
423
413
|
return response.body.toString();
|
|
424
414
|
}
|
|
425
|
-
if (
|
|
415
|
+
if (response.body === undefined) {
|
|
426
416
|
return '';
|
|
427
417
|
}
|
|
428
418
|
if (response.body instanceof Uint8Array) {
|
|
429
419
|
return response.body;
|
|
430
420
|
}
|
|
431
421
|
if (typeof response.body === 'object') {
|
|
432
|
-
if (!response.getContentType())
|
|
422
|
+
if (!response.getContentType()) {
|
|
433
423
|
response.setContentType('application/json');
|
|
424
|
+
}
|
|
434
425
|
return JSON.stringify(response.body);
|
|
435
426
|
}
|
|
436
|
-
throw new Error(
|
|
427
|
+
throw new Error(`Unsupported body format "${typeof response.body}"`);
|
|
437
428
|
}
|
|
438
429
|
}
|
|
439
430
|
|
|
440
431
|
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
|
-
|
|
432
|
+
100: 'Continue',
|
|
433
|
+
101: 'Switching protocols',
|
|
434
|
+
102: 'Processing',
|
|
435
|
+
103: 'Early Hints',
|
|
436
|
+
200: 'OK',
|
|
437
|
+
201: 'Created',
|
|
438
|
+
202: 'Accepted',
|
|
439
|
+
203: 'Non-Authoritative Information',
|
|
440
|
+
204: 'No Content',
|
|
441
|
+
205: 'Reset Content',
|
|
442
|
+
206: 'Partial Content',
|
|
443
|
+
207: 'Multi-Status',
|
|
444
|
+
208: 'Already Reported',
|
|
445
|
+
226: 'IM Used',
|
|
446
|
+
300: 'Multiple Choices',
|
|
447
|
+
301: 'Moved Permanently',
|
|
448
|
+
302: 'Found (Previously "Moved Temporarily")',
|
|
449
|
+
303: 'See Other',
|
|
450
|
+
304: 'Not Modified',
|
|
451
|
+
305: 'Use Proxy',
|
|
452
|
+
306: 'Switch Proxy',
|
|
453
|
+
307: 'Temporary Redirect',
|
|
454
|
+
308: 'Permanent Redirect',
|
|
455
|
+
400: 'Bad Request',
|
|
456
|
+
401: 'Unauthorized',
|
|
457
|
+
402: 'Payment Required',
|
|
458
|
+
403: 'Forbidden',
|
|
459
|
+
404: 'Not Found',
|
|
460
|
+
405: 'Method Not Allowed',
|
|
461
|
+
406: 'Not Acceptable',
|
|
462
|
+
407: 'Proxy Authentication Required',
|
|
463
|
+
408: 'Request Timeout',
|
|
464
|
+
409: 'Conflict',
|
|
465
|
+
410: 'Gone',
|
|
466
|
+
411: 'Length Required',
|
|
467
|
+
412: 'Precondition Failed',
|
|
468
|
+
413: 'Payload Too Large',
|
|
469
|
+
414: 'URI Too Long',
|
|
470
|
+
415: 'Unsupported Media Type',
|
|
471
|
+
416: 'Range Not Satisfiable',
|
|
472
|
+
417: 'Expectation Failed',
|
|
473
|
+
418: "I'm a Teapot",
|
|
474
|
+
421: 'Misdirected Request',
|
|
475
|
+
422: 'Unprocessable Entity',
|
|
476
|
+
423: 'Locked',
|
|
477
|
+
424: 'Failed Dependency',
|
|
478
|
+
425: 'Too Early',
|
|
479
|
+
426: 'Upgrade Required',
|
|
480
|
+
428: 'Precondition Required',
|
|
481
|
+
429: 'Too Many Requests',
|
|
482
|
+
431: 'Request Header Fields Too Large',
|
|
483
|
+
451: 'Unavailable For Legal Reasons',
|
|
484
|
+
500: 'Internal Server Error',
|
|
485
|
+
501: 'Not Implemented',
|
|
486
|
+
502: 'Bad Gateway',
|
|
487
|
+
503: 'Service Unavailable',
|
|
488
|
+
504: 'Gateway Timeout',
|
|
489
|
+
505: 'HTTP Version Not Supported',
|
|
490
|
+
506: 'Variant Also Negotiates',
|
|
491
|
+
507: 'Insufficient Storage',
|
|
492
|
+
508: 'Loop Detected',
|
|
493
|
+
510: 'Not Extended',
|
|
494
|
+
511: 'Network Authentication Required',
|
|
504
495
|
};
|
|
505
496
|
var EHttpStatusCode;
|
|
506
497
|
(function (EHttpStatusCode) {
|
|
@@ -569,6 +560,109 @@ var EHttpStatusCode;
|
|
|
569
560
|
EHttpStatusCode[EHttpStatusCode["NetworkAuthenticationRequired"] = 511] = "NetworkAuthenticationRequired";
|
|
570
561
|
})(EHttpStatusCode || (EHttpStatusCode = {}));
|
|
571
562
|
|
|
563
|
+
const preStyles = 'font-family: monospace;' +
|
|
564
|
+
'width: 100%;' +
|
|
565
|
+
'max-width: 900px;' +
|
|
566
|
+
'padding: 10px;' +
|
|
567
|
+
'margin: 20px auto;' +
|
|
568
|
+
'border-radius: 8px;' +
|
|
569
|
+
'background-color: #494949;' +
|
|
570
|
+
'box-shadow: 0px 0px 3px 2px rgb(255 255 255 / 20%);';
|
|
571
|
+
class HttpErrorRenderer extends BaseHttpResponseRenderer {
|
|
572
|
+
renderHtml(response) {
|
|
573
|
+
const data = response.body || {};
|
|
574
|
+
response.setContentType('text/html');
|
|
575
|
+
const keys = Object.keys(data).filter(key => !['statusCode', 'error', 'message'].includes(key));
|
|
576
|
+
return ('<html style="background-color: #333; color: #bbb;">' +
|
|
577
|
+
`<head><title>${data.statusCode} ${httpStatusCodes[data.statusCode]}</title></head>` +
|
|
578
|
+
`<body><center><h1>${data.statusCode} ${httpStatusCodes[data.statusCode]}</h1></center>` +
|
|
579
|
+
`<center><h4>${data.message}</h1></center><hr color="#666">` +
|
|
580
|
+
`<center style="color: #666;"> Wooks v${"0.4.28"} </center>` +
|
|
581
|
+
`${keys.length > 0
|
|
582
|
+
? `<pre style="${preStyles}">${JSON.stringify({
|
|
583
|
+
...data,
|
|
584
|
+
statusCode: undefined,
|
|
585
|
+
message: undefined,
|
|
586
|
+
error: undefined,
|
|
587
|
+
}, null, ' ')}</pre>`
|
|
588
|
+
: ''}` +
|
|
589
|
+
'</body></html>');
|
|
590
|
+
}
|
|
591
|
+
renderText(response) {
|
|
592
|
+
const data = response.body || {};
|
|
593
|
+
response.setContentType('text/plain');
|
|
594
|
+
const keys = Object.keys(data).filter(key => !['statusCode', 'error', 'message'].includes(key));
|
|
595
|
+
return (`${data.statusCode} ${httpStatusCodes[data.statusCode]}\n${data.message}` +
|
|
596
|
+
`\n\n${keys.length > 0
|
|
597
|
+
? `${JSON.stringify({
|
|
598
|
+
...data,
|
|
599
|
+
statusCode: undefined,
|
|
600
|
+
message: undefined,
|
|
601
|
+
error: undefined,
|
|
602
|
+
}, null, ' ')}`
|
|
603
|
+
: ''}`);
|
|
604
|
+
}
|
|
605
|
+
renderJson(response) {
|
|
606
|
+
const data = response.body || {};
|
|
607
|
+
response.setContentType('application/json');
|
|
608
|
+
const keys = Object.keys(data).filter(key => !['statusCode', 'error', 'message'].includes(key));
|
|
609
|
+
return (`{"statusCode":${escapeQuotes(data.statusCode)},` +
|
|
610
|
+
`"error":"${escapeQuotes(data.error)}",` +
|
|
611
|
+
`"message":"${escapeQuotes(data.message)}"` +
|
|
612
|
+
`${keys.length > 0
|
|
613
|
+
? `,${keys.map(k => `"${escapeQuotes(k)}":${JSON.stringify(data[k])}`).join(',')}`
|
|
614
|
+
: ''}}`);
|
|
615
|
+
}
|
|
616
|
+
render(response) {
|
|
617
|
+
const { acceptsJson, acceptsText, acceptsHtml } = useAccept();
|
|
618
|
+
response.status = response.body?.statusCode || 500;
|
|
619
|
+
if (acceptsJson()) {
|
|
620
|
+
return this.renderJson(response);
|
|
621
|
+
}
|
|
622
|
+
else if (acceptsHtml()) {
|
|
623
|
+
return this.renderHtml(response);
|
|
624
|
+
}
|
|
625
|
+
else if (acceptsText()) {
|
|
626
|
+
return this.renderText(response);
|
|
627
|
+
}
|
|
628
|
+
else {
|
|
629
|
+
return this.renderJson(response);
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
function escapeQuotes(s) {
|
|
634
|
+
return (typeof s === 'number' ? s : s || '').toString().replace(/"/g, '\\"');
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
class HttpError extends Error {
|
|
638
|
+
constructor(code = 500, _body = '') {
|
|
639
|
+
super(typeof _body === 'string' ? _body : _body.message);
|
|
640
|
+
this.code = code;
|
|
641
|
+
this._body = _body;
|
|
642
|
+
this.name = 'HttpError';
|
|
643
|
+
}
|
|
644
|
+
get body() {
|
|
645
|
+
return typeof this._body === 'string'
|
|
646
|
+
? {
|
|
647
|
+
statusCode: this.code,
|
|
648
|
+
message: this.message,
|
|
649
|
+
error: httpStatusCodes[this.code],
|
|
650
|
+
}
|
|
651
|
+
: {
|
|
652
|
+
...this._body,
|
|
653
|
+
statusCode: this.code,
|
|
654
|
+
message: this.message,
|
|
655
|
+
error: httpStatusCodes[this.code],
|
|
656
|
+
};
|
|
657
|
+
}
|
|
658
|
+
attachRenderer(renderer) {
|
|
659
|
+
this.renderer = renderer;
|
|
660
|
+
}
|
|
661
|
+
getRenderer() {
|
|
662
|
+
return this.renderer;
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
|
|
572
666
|
const defaultStatus = {
|
|
573
667
|
GET: EHttpStatusCode.OK,
|
|
574
668
|
POST: EHttpStatusCode.Created,
|
|
@@ -649,7 +743,7 @@ class BaseHttpResponse {
|
|
|
649
743
|
...this._headers,
|
|
650
744
|
};
|
|
651
745
|
const setCookie = [...newCookies, ...cookies()];
|
|
652
|
-
if (setCookie
|
|
746
|
+
if (setCookie.length > 0) {
|
|
653
747
|
this._headers['set-cookie'] = setCookie;
|
|
654
748
|
}
|
|
655
749
|
return this;
|
|
@@ -675,7 +769,7 @@ class BaseHttpResponse {
|
|
|
675
769
|
async respond() {
|
|
676
770
|
const { rawResponse, hasResponded } = useResponse();
|
|
677
771
|
const { method, rawRequest } = useRequest();
|
|
678
|
-
const logger = useEventLogger('http-response');
|
|
772
|
+
const logger = useEventLogger('http-response') || console;
|
|
679
773
|
if (hasResponded()) {
|
|
680
774
|
this.panic('The response was already sent.', logger);
|
|
681
775
|
}
|
|
@@ -696,7 +790,7 @@ class BaseHttpResponse {
|
|
|
696
790
|
}
|
|
697
791
|
else {
|
|
698
792
|
return new Promise((resolve, reject) => {
|
|
699
|
-
stream.on('error',
|
|
793
|
+
stream.on('error', e => {
|
|
700
794
|
stream.destroy();
|
|
701
795
|
res.end();
|
|
702
796
|
reject(e);
|
|
@@ -709,8 +803,7 @@ class BaseHttpResponse {
|
|
|
709
803
|
});
|
|
710
804
|
}
|
|
711
805
|
}
|
|
712
|
-
else if (globalThis.Response &&
|
|
713
|
-
this.body instanceof Response) {
|
|
806
|
+
else if (globalThis.Response && this.body instanceof Response) {
|
|
714
807
|
this.mergeFetchStatus(this.body.status);
|
|
715
808
|
if (method === 'HEAD') {
|
|
716
809
|
res.end();
|
|
@@ -733,10 +826,12 @@ class BaseHttpResponse {
|
|
|
733
826
|
else {
|
|
734
827
|
const renderedBody = this.renderer.render(this);
|
|
735
828
|
this.mergeStatus(renderedBody);
|
|
736
|
-
res
|
|
829
|
+
res
|
|
830
|
+
.writeHead(this.status, {
|
|
737
831
|
'content-length': Buffer.byteLength(renderedBody),
|
|
738
832
|
...this._headers,
|
|
739
|
-
})
|
|
833
|
+
})
|
|
834
|
+
.end(method === 'HEAD' ? '' : renderedBody);
|
|
740
835
|
}
|
|
741
836
|
}
|
|
742
837
|
}
|
|
@@ -747,124 +842,18 @@ async function respondWithFetch(fetchBody, res) {
|
|
|
747
842
|
res.write(chunk);
|
|
748
843
|
}
|
|
749
844
|
}
|
|
750
|
-
catch (
|
|
845
|
+
catch (error) {
|
|
751
846
|
}
|
|
752
847
|
}
|
|
753
848
|
res.end();
|
|
754
849
|
}
|
|
755
850
|
|
|
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
851
|
function createWooksResponder(renderer = new BaseHttpResponseRenderer(), errorRenderer = new HttpErrorRenderer()) {
|
|
864
852
|
function createResponse(data) {
|
|
865
853
|
const { hasResponded } = useResponse();
|
|
866
|
-
if (hasResponded())
|
|
854
|
+
if (hasResponded()) {
|
|
867
855
|
return null;
|
|
856
|
+
}
|
|
868
857
|
if (data instanceof Error) {
|
|
869
858
|
const r = new BaseHttpResponse(errorRenderer);
|
|
870
859
|
let httpError;
|
|
@@ -921,20 +910,22 @@ class WooksHttp extends WooksAdapterBase {
|
|
|
921
910
|
options(path, handler) {
|
|
922
911
|
return this.on('OPTIONS', path, handler);
|
|
923
912
|
}
|
|
924
|
-
async listen(
|
|
913
|
+
async listen(port, hostname, backlog, listeningListener) {
|
|
925
914
|
const server = (this.server = http.createServer(this.getServerCb()));
|
|
926
915
|
return new Promise((resolve, reject) => {
|
|
927
916
|
server.once('listening', resolve);
|
|
928
917
|
server.once('error', reject);
|
|
929
|
-
server.listen(
|
|
918
|
+
server.listen(port, hostname, backlog, listeningListener);
|
|
930
919
|
});
|
|
931
920
|
}
|
|
932
921
|
close(server) {
|
|
933
922
|
const srv = server || this.server;
|
|
934
923
|
return new Promise((resolve, reject) => {
|
|
935
|
-
srv?.close(
|
|
936
|
-
if (err)
|
|
937
|
-
|
|
924
|
+
srv?.close(err => {
|
|
925
|
+
if (err) {
|
|
926
|
+
reject(err);
|
|
927
|
+
return;
|
|
928
|
+
}
|
|
938
929
|
resolve(srv);
|
|
939
930
|
});
|
|
940
931
|
});
|
|
@@ -946,7 +937,7 @@ class WooksHttp extends WooksAdapterBase {
|
|
|
946
937
|
this.server = server;
|
|
947
938
|
}
|
|
948
939
|
respond(data) {
|
|
949
|
-
void this.responder.respond(data)?.catch(
|
|
940
|
+
void this.responder.respond(data)?.catch(e => {
|
|
950
941
|
this.logger.error('Uncought response exception', e);
|
|
951
942
|
});
|
|
952
943
|
}
|
|
@@ -958,10 +949,10 @@ class WooksHttp extends WooksAdapterBase {
|
|
|
958
949
|
try {
|
|
959
950
|
await this.processHandlers(handlers || [this.opts?.onNotFound]);
|
|
960
951
|
}
|
|
961
|
-
catch (
|
|
962
|
-
this.logger.error('Internal error, please report',
|
|
952
|
+
catch (error) {
|
|
953
|
+
this.logger.error('Internal error, please report', error);
|
|
963
954
|
restoreCtx();
|
|
964
|
-
this.respond(
|
|
955
|
+
this.respond(error);
|
|
965
956
|
clearCtx();
|
|
966
957
|
}
|
|
967
958
|
}
|
|
@@ -986,11 +977,11 @@ class WooksHttp extends WooksAdapterBase {
|
|
|
986
977
|
clearCtx();
|
|
987
978
|
break;
|
|
988
979
|
}
|
|
989
|
-
catch (
|
|
990
|
-
this.logger.error(`Uncought route handler exception: ${store('event').get('req')
|
|
980
|
+
catch (error) {
|
|
981
|
+
this.logger.error(`Uncought route handler exception: ${store('event').get('req')?.url || ''}`, error);
|
|
991
982
|
if (isLastHandler) {
|
|
992
983
|
restoreCtx();
|
|
993
|
-
this.respond(
|
|
984
|
+
this.respond(error);
|
|
994
985
|
clearCtx();
|
|
995
986
|
}
|
|
996
987
|
}
|