barehttp 1.0.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +185 -28
- package/lib/context/execution.d.ts +7 -0
- package/lib/context/execution.js +14 -0
- package/lib/context/index.d.ts +10 -0
- package/lib/context/index.js +46 -0
- package/lib/env.d.ts +5 -0
- package/lib/env.js +5 -0
- package/lib/index.d.ts +5 -0
- package/lib/index.js +3 -0
- package/lib/logger/index.d.ts +16 -0
- package/lib/logger/index.js +26 -0
- package/lib/logger/serializers.d.ts +28 -0
- package/lib/logger/serializers.js +78 -0
- package/lib/middlewares/cookies/cookie-manager.d.ts +25 -0
- package/lib/middlewares/cookies/cookie-manager.js +68 -0
- package/lib/middlewares/cookies/signer.d.ts +8 -0
- package/lib/middlewares/cookies/signer.js +25 -0
- package/lib/middlewares/cors/cors.d.ts +38 -0
- package/lib/middlewares/cors/cors.js +164 -0
- package/lib/request.d.ts +84 -0
- package/lib/request.js +260 -0
- package/lib/schemas/custom-schema.d.ts +32 -0
- package/lib/schemas/custom-schema.js +62 -0
- package/lib/schemas/dirty-tsm.d.ts +1 -0
- package/lib/schemas/dirty-tsm.js +199 -0
- package/lib/schemas/generator.d.ts +7 -0
- package/lib/schemas/generator.js +179 -0
- package/lib/schemas/helpers.d.ts +27 -0
- package/lib/schemas/helpers.js +40 -0
- package/lib/schemas/json-schema.d.ts +2 -0
- package/lib/schemas/json-schema.js +48 -0
- package/lib/schemas/openami-schema.d.ts +2 -0
- package/lib/schemas/openami-schema.js +59 -0
- package/lib/schemas/project.d.ts +1 -0
- package/lib/schemas/project.js +1 -0
- package/lib/server.d.ts +154 -0
- package/lib/server.js +396 -0
- package/lib/utils/content-type.d.ts +54 -0
- package/lib/utils/content-type.js +54 -0
- package/lib/utils/http-methods.d.ts +11 -0
- package/lib/utils/http-methods.js +9 -0
- package/lib/utils/index.d.ts +4 -0
- package/lib/utils/index.js +4 -0
- package/lib/utils/safe-json.d.ts +2 -0
- package/lib/utils/safe-json.js +18 -0
- package/lib/utils/status-codes.d.ts +339 -0
- package/lib/utils/status-codes.js +339 -0
- package/lib/utils/status-phrases.d.ts +338 -0
- package/lib/utils/status-phrases.js +339 -0
- package/lib/websocket.d.ts +36 -0
- package/lib/websocket.js +176 -0
- package/package.json +64 -32
- package/.eslintrc.js +0 -47
- package/.github/workflows/release.yml +0 -27
- package/.jest-setup.js +0 -1
- package/jest.config.js +0 -8
- package/prettier.config.js +0 -6
- package/src/context/context.test.ts +0 -30
- package/src/context/execution.ts +0 -17
- package/src/context/index.ts +0 -61
- package/src/env.ts +0 -5
- package/src/examples/bare-http.ts +0 -36
- package/src/examples/express.ts +0 -11
- package/src/examples/fastify.ts +0 -18
- package/src/index.ts +0 -4
- package/src/logger/index.ts +0 -67
- package/src/logger/serializers.test.ts +0 -186
- package/src/logger/serializers.ts +0 -109
- package/src/middlewares/cookies/cookie-manager.ts +0 -86
- package/src/middlewares/cookies/signer.ts +0 -30
- package/src/report.ts +0 -25
- package/src/request.test.ts +0 -143
- package/src/request.ts +0 -277
- package/src/server.integration.test.ts +0 -296
- package/src/server.middlewares.test.ts +0 -93
- package/src/server.routes.test.ts +0 -71
- package/src/server.ts +0 -450
- package/src/utils/content-type.ts +0 -59
- package/src/utils/index.ts +0 -2
- package/src/utils/safe-json.ts +0 -17
- package/src/utils/status-codes.ts +0 -339
- package/src/utils/status-phrases.ts +0 -339
- package/tsconfig.json +0 -24
|
@@ -1,86 +0,0 @@
|
|
|
1
|
-
import cookie from 'cookie';
|
|
2
|
-
|
|
3
|
-
import { secretsOperator } from './signer';
|
|
4
|
-
|
|
5
|
-
import { logMe } from '../../logger';
|
|
6
|
-
|
|
7
|
-
import type { BareRequest } from '../../request';
|
|
8
|
-
|
|
9
|
-
export type CookieManagerOptions = cookie.CookieSerializeOptions & {
|
|
10
|
-
signed?: boolean;
|
|
11
|
-
parseOptions?: cookie.CookieParseOptions;
|
|
12
|
-
secret?: string | string[];
|
|
13
|
-
};
|
|
14
|
-
|
|
15
|
-
export class CookieManager {
|
|
16
|
-
signer: null | ReturnType<typeof secretsOperator>;
|
|
17
|
-
|
|
18
|
-
constructor(private options: CookieManagerOptions = {}, private flow: BareRequest) {
|
|
19
|
-
const secret = this.options.secret || '';
|
|
20
|
-
const enableRotation = Array.isArray(secret);
|
|
21
|
-
this.signer = typeof secret === 'string' || enableRotation ? secretsOperator(secret) : null;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
setCookie(
|
|
25
|
-
name: string,
|
|
26
|
-
value: string,
|
|
27
|
-
options?: CookieManagerOptions,
|
|
28
|
-
signer?: ReturnType<typeof secretsOperator>,
|
|
29
|
-
) {
|
|
30
|
-
const localSigner = signer || this.signer;
|
|
31
|
-
const opts = options || this.options;
|
|
32
|
-
if (opts.expires && Number.isInteger(opts.expires)) {
|
|
33
|
-
opts.expires = new Date(opts.expires);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
if (opts.signed && localSigner) {
|
|
37
|
-
value = localSigner.sign(value);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
const serialized = cookie.serialize(name, value, opts);
|
|
41
|
-
let setCookie = this.flow.getHeader('Set-Cookie');
|
|
42
|
-
|
|
43
|
-
if (!setCookie) {
|
|
44
|
-
this.flow.setHeader('Set-Cookie', serialized);
|
|
45
|
-
return;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
if (typeof setCookie === 'string') {
|
|
49
|
-
setCookie = [setCookie];
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
setCookie.push(serialized);
|
|
53
|
-
this.flow.setHeader('Set-Cookie', setCookie);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
clearCookie(name: string, options: CookieManagerOptions = {}) {
|
|
57
|
-
const opts = {
|
|
58
|
-
path: '/',
|
|
59
|
-
...options,
|
|
60
|
-
expires: new Date(1),
|
|
61
|
-
signed: undefined,
|
|
62
|
-
maxAge: undefined,
|
|
63
|
-
};
|
|
64
|
-
|
|
65
|
-
return this.setCookie(name, '', opts);
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
parseCookie(rawCookie?: string): { [k: string]: string } {
|
|
69
|
-
if (!rawCookie) return {};
|
|
70
|
-
const result = {};
|
|
71
|
-
const values = rawCookie?.split(';');
|
|
72
|
-
for (let i = 0; i < values.length - 1; i++) {
|
|
73
|
-
const split = values[i].trim().split('=');
|
|
74
|
-
if (split.length == 2) result[split[0]] = split[1];
|
|
75
|
-
}
|
|
76
|
-
return result;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
unsignCookie(value) {
|
|
80
|
-
if (!this.signer) {
|
|
81
|
-
logMe.error('No signer defined for the cookies, unsign wont work');
|
|
82
|
-
return;
|
|
83
|
-
}
|
|
84
|
-
return this.signer.unsign(value);
|
|
85
|
-
}
|
|
86
|
-
}
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
import cookieSignature from 'cookie-signature';
|
|
2
|
-
|
|
3
|
-
export function secretsOperator(secret: string | string[]) {
|
|
4
|
-
const secrets = Array.isArray(secret) ? secret : [secret];
|
|
5
|
-
const [signingKey] = secrets;
|
|
6
|
-
|
|
7
|
-
return {
|
|
8
|
-
sign(value: string) {
|
|
9
|
-
return cookieSignature.sign(value, signingKey);
|
|
10
|
-
},
|
|
11
|
-
unsign(signedValue) {
|
|
12
|
-
let valid = false;
|
|
13
|
-
let renew = false;
|
|
14
|
-
let value: string | null = null;
|
|
15
|
-
|
|
16
|
-
for (const key of secrets) {
|
|
17
|
-
const result = cookieSignature.unsign(signedValue, key);
|
|
18
|
-
|
|
19
|
-
if (result !== false) {
|
|
20
|
-
valid = true;
|
|
21
|
-
renew = key !== signingKey;
|
|
22
|
-
value = result;
|
|
23
|
-
break;
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
return { valid, renew, value };
|
|
28
|
-
},
|
|
29
|
-
};
|
|
30
|
-
}
|
package/src/report.ts
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import { RouteReport } from './server';
|
|
2
|
-
|
|
3
|
-
export function generateReport(routes: Map<string, RouteReport>) {
|
|
4
|
-
const lines: string[] = [];
|
|
5
|
-
|
|
6
|
-
lines.push(
|
|
7
|
-
'<!DOCTYPE html><html><head><title>Routes usage</title><meta charset="utf-8"></head><body><table style="border: 2px;border-style: ridge;border-radius: 5px;padding: 10px;"><tr><th>Route</th><th>Hits</th><th>Successes</th><th>Fails</th></tr>',
|
|
8
|
-
);
|
|
9
|
-
|
|
10
|
-
const sorted = [...routes].sort(([a], [b]) => {
|
|
11
|
-
if (a > b) return 1;
|
|
12
|
-
else if (b > a) return -1;
|
|
13
|
-
return 0;
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
sorted.forEach(([route, stats]) => {
|
|
17
|
-
lines.push(
|
|
18
|
-
`<tr><td>${route}</td><td>${stats.hits}</td><td>${stats.success}</td><td>${stats.fails}</td></tr>`,
|
|
19
|
-
);
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
lines.push('</table></body></html>');
|
|
23
|
-
|
|
24
|
-
return lines.join('');
|
|
25
|
-
}
|
package/src/request.test.ts
DELETED
|
@@ -1,143 +0,0 @@
|
|
|
1
|
-
import { BareRequest } from './request';
|
|
2
|
-
|
|
3
|
-
import { IncomingMessage, ServerResponse } from 'http';
|
|
4
|
-
import { Socket } from 'net';
|
|
5
|
-
|
|
6
|
-
const sock = new Socket({ readable: true, writable: true });
|
|
7
|
-
|
|
8
|
-
const createRequest = () => {
|
|
9
|
-
const inc = new IncomingMessage(sock);
|
|
10
|
-
const out = new ServerResponse(inc);
|
|
11
|
-
const request = new BareRequest(inc, out);
|
|
12
|
-
return { inc, out, request };
|
|
13
|
-
};
|
|
14
|
-
|
|
15
|
-
test('Generates uuid upon instantiation', () => {
|
|
16
|
-
const { request } = createRequest();
|
|
17
|
-
|
|
18
|
-
expect(request.uuid).not.toBeUndefined();
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
test('Gets an uuid upon instantiation from headers', () => {
|
|
22
|
-
const { inc, out } = createRequest();
|
|
23
|
-
|
|
24
|
-
inc.headers['x-request-id'] = 'SOME_UUID';
|
|
25
|
-
|
|
26
|
-
const request = new BareRequest(inc, out);
|
|
27
|
-
|
|
28
|
-
expect(request.uuid).toBe('SOME_UUID');
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
test('Adds logging listener if set up', () => {
|
|
32
|
-
const { inc, out } = createRequest();
|
|
33
|
-
|
|
34
|
-
const listeners = out.listenerCount('close');
|
|
35
|
-
|
|
36
|
-
new BareRequest(inc, out, true);
|
|
37
|
-
|
|
38
|
-
expect(out.listenerCount('close')).toBe(listeners + 1);
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
test('Gets response header', () => {
|
|
42
|
-
const { request } = createRequest();
|
|
43
|
-
|
|
44
|
-
request['headers']['Content-Length'] = '99';
|
|
45
|
-
|
|
46
|
-
expect(request.getHeader('Content-Length')).toBe('99');
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
test('Adds basic headers to the request upon instantiation', () => {
|
|
50
|
-
const { request } = createRequest();
|
|
51
|
-
|
|
52
|
-
expect(request.getHeader('Content-Type')).toBe('text/plain');
|
|
53
|
-
expect(request.getHeader('X-Request-Id')).toEqual(expect.any(String));
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
// headers
|
|
57
|
-
test('Disables cache', () => {
|
|
58
|
-
const { request } = createRequest();
|
|
59
|
-
request.disableCache();
|
|
60
|
-
|
|
61
|
-
expect(request['cache']).toBeFalsy();
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
test('Sets cache headers for private; max-age=3800; must-revalidate', () => {
|
|
65
|
-
const { request } = createRequest();
|
|
66
|
-
request.setCache({
|
|
67
|
-
cacheability: 'private',
|
|
68
|
-
expirationKind: 'max-age',
|
|
69
|
-
expirationSeconds: 3800,
|
|
70
|
-
revalidation: 'must-revalidate',
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
expect(request.getHeader('Cache-Control')).toBe('private; max-age=3800; must-revalidate');
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
test('Sets cache headers for no-store; max-age=0; must-revalidate', () => {
|
|
77
|
-
const { request } = createRequest();
|
|
78
|
-
request.setCache({
|
|
79
|
-
cacheability: 'no-store',
|
|
80
|
-
expirationKind: 'max-age',
|
|
81
|
-
expirationSeconds: 0,
|
|
82
|
-
revalidation: 'must-revalidate',
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
expect(request.getHeader('Cache-Control')).toBe('no-store; max-age=0; must-revalidate');
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
test('Sets response header', () => {
|
|
89
|
-
const { request } = createRequest();
|
|
90
|
-
request.setHeader('Vary', 'Origin');
|
|
91
|
-
|
|
92
|
-
expect(request.getHeader('Vary')).toBe('Origin');
|
|
93
|
-
});
|
|
94
|
-
test('Sets multiple response headers', () => {
|
|
95
|
-
const { request } = createRequest();
|
|
96
|
-
request.setHeaders({ Vary: 'Origin', Host: 'http://barehttp.com' });
|
|
97
|
-
|
|
98
|
-
expect(request.getHeader('Vary')).toBe('Origin');
|
|
99
|
-
expect(request.getHeader('Host')).toBe('http://barehttp.com');
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
test('Does not get request cookie if cookie manager is disabled', () => {
|
|
103
|
-
const token = '34his7dh3is8dyabf04y8ei';
|
|
104
|
-
const { inc, request } = createRequest();
|
|
105
|
-
inc.headers['cookie'] = `access_token=${token};`;
|
|
106
|
-
|
|
107
|
-
request['populateCookies']();
|
|
108
|
-
|
|
109
|
-
expect(request.getCookie('access_token')).toBe(undefined);
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
test('Gets request cookie if cookie manager is enabled', () => {
|
|
113
|
-
const token = '34his7dh3is8dyabf04y8ei';
|
|
114
|
-
const { inc, request } = createRequest();
|
|
115
|
-
inc.headers['cookie'] = `access_token=${token};`;
|
|
116
|
-
|
|
117
|
-
request['attachCookieManager']();
|
|
118
|
-
request['populateCookies']();
|
|
119
|
-
|
|
120
|
-
expect(request.getCookie('access_token')).toBe(token);
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
test('Gets all request cookies', () => {
|
|
124
|
-
const token = '34his7dh3is8dyabf04y8ei';
|
|
125
|
-
const refererId = '8237485';
|
|
126
|
-
const { inc, request } = createRequest();
|
|
127
|
-
inc.headers['cookie'] = `access_token=${token}; referer_id=${refererId};`;
|
|
128
|
-
|
|
129
|
-
request['attachCookieManager']();
|
|
130
|
-
request['populateCookies']();
|
|
131
|
-
|
|
132
|
-
expect(request.getCookie('access_token')).toBe(token);
|
|
133
|
-
expect(request.getCookie('referer_id')).toBe(refererId);
|
|
134
|
-
});
|
|
135
|
-
|
|
136
|
-
test('Sets response code (HTTP)', () => {});
|
|
137
|
-
test('Sets and sends response code (HTTP)', () => {});
|
|
138
|
-
|
|
139
|
-
test('Streams a response stream to the socket', () => {});
|
|
140
|
-
test('Sends a JSON to the response', () => {});
|
|
141
|
-
test('Sends a serializable data to the response', () => {});
|
|
142
|
-
|
|
143
|
-
test('Reads body correctly', () => {});
|
package/src/request.ts
DELETED
|
@@ -1,277 +0,0 @@
|
|
|
1
|
-
import hyperid from 'hyperid';
|
|
2
|
-
|
|
3
|
-
import { StatusCodes, StatusPhrases } from './utils/';
|
|
4
|
-
import { JSONParse, JSONStringify } from './utils/safe-json';
|
|
5
|
-
import { logHttp, logMe } from './logger';
|
|
6
|
-
import { ContentType } from './utils/content-type';
|
|
7
|
-
import { CookieManager, CookieManagerOptions } from './middlewares/cookies/cookie-manager';
|
|
8
|
-
|
|
9
|
-
import { types } from 'util';
|
|
10
|
-
|
|
11
|
-
import type { IncomingMessage, ServerResponse } from 'http';
|
|
12
|
-
const generateId = hyperid();
|
|
13
|
-
|
|
14
|
-
type Codes<K extends keyof typeof StatusCodes = keyof typeof StatusCodes> = {
|
|
15
|
-
[L in typeof StatusCodes[K]]: typeof StatusPhrases[K];
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
type Cacheability = 'public' | 'private' | 'no-cache' | 'no-store';
|
|
19
|
-
type ExpirationType =
|
|
20
|
-
| 'max-age'
|
|
21
|
-
| 's-maxage'
|
|
22
|
-
| 'max-stale'
|
|
23
|
-
| 'min-fresh'
|
|
24
|
-
| 'stale-while-revalidate'
|
|
25
|
-
| 'stale-if-error';
|
|
26
|
-
type Revalidation = 'must-revalidate' | 'proxy-revalidate' | 'immutable';
|
|
27
|
-
|
|
28
|
-
export type CacheOpts = {
|
|
29
|
-
cacheability: Cacheability;
|
|
30
|
-
expirationKind: ExpirationType;
|
|
31
|
-
/**
|
|
32
|
-
* Default 3600
|
|
33
|
-
*/
|
|
34
|
-
expirationSeconds?: number;
|
|
35
|
-
revalidation?: Revalidation;
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
const statusTuples = Object.entries(StatusCodes).reduce((acc, [name, status]) => {
|
|
39
|
-
acc[status] = StatusPhrases[name];
|
|
40
|
-
return acc;
|
|
41
|
-
}, {} as Codes);
|
|
42
|
-
|
|
43
|
-
export class BareRequest {
|
|
44
|
-
uuid: string;
|
|
45
|
-
params: { [k: string]: string | undefined } = {};
|
|
46
|
-
remoteIp?: string;
|
|
47
|
-
requestBody?: any;
|
|
48
|
-
requestHeaders: { [key: string]: any };
|
|
49
|
-
statusToSend = 200;
|
|
50
|
-
cm?: CookieManager;
|
|
51
|
-
sent = false;
|
|
52
|
-
|
|
53
|
-
private cache = true;
|
|
54
|
-
private startTime: [seconds: number, nanoseconds: number];
|
|
55
|
-
private startDate = new Date();
|
|
56
|
-
private remoteClient = '';
|
|
57
|
-
private countTimeFormat: 'ms' | 's' = 's';
|
|
58
|
-
private headers: { [header: string]: string | string[] } = {};
|
|
59
|
-
private cookies: { [cooke: string]: string } = {};
|
|
60
|
-
private contentType?: keyof typeof ContentType;
|
|
61
|
-
private timeout?: NodeJS.Timeout;
|
|
62
|
-
|
|
63
|
-
constructor(
|
|
64
|
-
public _originalRequest: IncomingMessage,
|
|
65
|
-
public _originalResponse: ServerResponse,
|
|
66
|
-
logging?: boolean,
|
|
67
|
-
) {
|
|
68
|
-
this.uuid = (_originalRequest.headers['x-request-id'] as string) || generateId();
|
|
69
|
-
this.remoteIp = _originalRequest.socket.remoteAddress;
|
|
70
|
-
this.contentType = this._originalRequest.headers['content-type'] as any;
|
|
71
|
-
this.requestHeaders = this._originalRequest.headers;
|
|
72
|
-
|
|
73
|
-
_originalRequest['id'] = this.uuid; // to receive an id later on in the route handler
|
|
74
|
-
|
|
75
|
-
this.setHeaders({ 'Content-Type': 'text/plain', 'X-Request-Id': this.uuid });
|
|
76
|
-
this.startTime = process.hrtime();
|
|
77
|
-
|
|
78
|
-
// call logging section
|
|
79
|
-
if (logging === true) {
|
|
80
|
-
_originalResponse.on('close', () =>
|
|
81
|
-
logHttp(
|
|
82
|
-
this.headers,
|
|
83
|
-
this.startDate,
|
|
84
|
-
this.remoteClient,
|
|
85
|
-
_originalRequest,
|
|
86
|
-
_originalResponse,
|
|
87
|
-
),
|
|
88
|
-
);
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
private readBody() {
|
|
93
|
-
if (['POST', 'PATCH', 'PUT'].includes(this._originalRequest.method!))
|
|
94
|
-
return new Promise<void>((resolve, reject) => {
|
|
95
|
-
const temp: any = [];
|
|
96
|
-
this._originalRequest
|
|
97
|
-
.on('data', (chunk) => temp.push(chunk))
|
|
98
|
-
.on('end', () => {
|
|
99
|
-
const parsed = this.classifyRequestBody(temp);
|
|
100
|
-
if (types.isNativeError(parsed)) reject(parsed);
|
|
101
|
-
this.requestBody = parsed;
|
|
102
|
-
resolve();
|
|
103
|
-
})
|
|
104
|
-
.on('error', reject);
|
|
105
|
-
});
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
private attachCookieManager(opts?: CookieManagerOptions) {
|
|
109
|
-
this.cm = new CookieManager(opts, this);
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
private populateCookies() {
|
|
113
|
-
this.cookies = this.cm?.parseCookie(this._originalRequest.headers.cookie) || {};
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
private classifyRequestBody(data: Buffer[]) {
|
|
117
|
-
const wholeChunk = Buffer.concat(data);
|
|
118
|
-
switch (this.contentType) {
|
|
119
|
-
case 'text/plain':
|
|
120
|
-
return wholeChunk.toString();
|
|
121
|
-
case 'application/json':
|
|
122
|
-
return JSONParse(wholeChunk.toString());
|
|
123
|
-
case 'application/x-www-form-urlencoded':
|
|
124
|
-
const store = {};
|
|
125
|
-
for (const curr of wholeChunk.toString().split('&')) {
|
|
126
|
-
const [key, value] = curr.split('=');
|
|
127
|
-
if (!key || !value) return null; // form urlencoded is not correct
|
|
128
|
-
store[key] = value;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
return store;
|
|
132
|
-
default:
|
|
133
|
-
return wholeChunk;
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
private setRemoteClient(remoteClient: string) {
|
|
138
|
-
this.remoteClient = remoteClient;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
private setRequestTime() {
|
|
142
|
-
const diff = process.hrtime(this.startTime);
|
|
143
|
-
|
|
144
|
-
const time =
|
|
145
|
-
diff[0] * (this.countTimeFormat === 's' ? 1 : 1e3) +
|
|
146
|
-
diff[1] * (this.countTimeFormat === 's' ? 1e-9 : 1e-6);
|
|
147
|
-
|
|
148
|
-
this.setHeaders({
|
|
149
|
-
'X-Processing-Time': time,
|
|
150
|
-
'X-Processing-Time-Mode': this.countTimeFormat === 's' ? 'seconds' : 'milliseconds',
|
|
151
|
-
});
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
private setTimeFormat(format: 's' | 'ms') {
|
|
155
|
-
this.countTimeFormat = format;
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
private cleanHeader(header: string) {
|
|
159
|
-
delete this.headers[header];
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
private attachTimeout(timeout: number) {
|
|
163
|
-
if (this.timeout) clearTimeout(this.timeout);
|
|
164
|
-
this.timeout = setTimeout(() => {
|
|
165
|
-
this.status(503);
|
|
166
|
-
this.send('Server aborted connection by overtime');
|
|
167
|
-
}, timeout);
|
|
168
|
-
|
|
169
|
-
// attach listener to clear the timeout
|
|
170
|
-
this._originalResponse.on('close', () => this.timeout && clearTimeout(this.timeout));
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
private setParams(params: { [k: string]: string | undefined }) {
|
|
174
|
-
this.params = params;
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
// ======== PUBLIC APIS ========
|
|
178
|
-
|
|
179
|
-
getHeader(header: string) {
|
|
180
|
-
return this.headers[header];
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
getCookie(cookie: string) {
|
|
184
|
-
return this.cookies[cookie];
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
getCookies() {
|
|
188
|
-
return { ...this.cookies };
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
disableCache() {
|
|
192
|
-
this.cache = false;
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
setCache(cacheOpts: CacheOpts) {
|
|
196
|
-
const cacheHeader: string[] = [];
|
|
197
|
-
const directive = 'Cache-Control';
|
|
198
|
-
|
|
199
|
-
if (cacheOpts.cacheability) cacheHeader.push(cacheOpts.cacheability);
|
|
200
|
-
if (cacheOpts.expirationKind)
|
|
201
|
-
cacheHeader.push(`${cacheOpts.expirationKind}=${cacheOpts.expirationSeconds ?? 3600}`);
|
|
202
|
-
if (cacheOpts.revalidation) cacheHeader.push(cacheOpts.revalidation);
|
|
203
|
-
|
|
204
|
-
if (cacheHeader.length > 0) this.setHeader(directive, cacheHeader.join('; '));
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
setHeader(header: string, value: string | number | string[] | number[]) {
|
|
208
|
-
this.headers[header] = Array.isArray(value) ? value.map((v) => '' + v).join('; ') : '' + value;
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
setHeaders(headers: { [header: string]: string | number }) {
|
|
212
|
-
for (const header of Object.keys(headers)) {
|
|
213
|
-
this.headers[header] = '' + headers[header];
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
status(status: typeof StatusCodes[keyof typeof StatusCodes]) {
|
|
218
|
-
this.statusToSend = status;
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
sendStatus(status: typeof StatusCodes[keyof typeof StatusCodes]) {
|
|
222
|
-
this.status(status);
|
|
223
|
-
this.send();
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
stream<T extends NodeJS.WritableStream>(stream: T) {
|
|
227
|
-
this._originalResponse.pipe(stream, { end: true });
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
json(data: any) {
|
|
231
|
-
// to generate with fast-json-stringify schema issue #1
|
|
232
|
-
const jsoned = JSONStringify(data);
|
|
233
|
-
this.setHeader('Content-Type', 'application/json');
|
|
234
|
-
this.send(jsoned ? jsoned : undefined);
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
send(chunk?: string | ArrayBuffer | NodeJS.ArrayBufferView | SharedArrayBuffer) {
|
|
238
|
-
if (this._originalResponse.socket?.destroyed) {
|
|
239
|
-
logMe.error("Tying to send into closed client's stream");
|
|
240
|
-
return;
|
|
241
|
-
}
|
|
242
|
-
if (this._originalResponse.headersSent) {
|
|
243
|
-
logMe.error('Trying to send with the headers already sent');
|
|
244
|
-
return;
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
this.sent = true;
|
|
248
|
-
|
|
249
|
-
let toSend = chunk;
|
|
250
|
-
switch (chunk?.constructor) {
|
|
251
|
-
case Uint16Array:
|
|
252
|
-
case Uint8Array:
|
|
253
|
-
case Uint32Array:
|
|
254
|
-
toSend = Buffer.from((chunk as any).buffer);
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
// work basic headers
|
|
258
|
-
if (typeof chunk !== 'undefined' && chunk !== null)
|
|
259
|
-
this.setHeader('Content-Length', Buffer.byteLength(chunk, 'utf-8'));
|
|
260
|
-
|
|
261
|
-
if (!this.cache)
|
|
262
|
-
this.setHeaders({ 'Cache-Control': 'no-store', Expire: 0, Pragma: 'no-cache' });
|
|
263
|
-
|
|
264
|
-
if (this.statusToSend >= 400 && this.statusToSend !== 404 && this.statusToSend !== 410)
|
|
265
|
-
this.cleanHeader('Cache-Control');
|
|
266
|
-
|
|
267
|
-
this.setRequestTime();
|
|
268
|
-
|
|
269
|
-
// perform sending
|
|
270
|
-
this._originalResponse.writeHead(
|
|
271
|
-
this.statusToSend,
|
|
272
|
-
statusTuples[this.statusToSend],
|
|
273
|
-
this.headers,
|
|
274
|
-
);
|
|
275
|
-
this._originalResponse.end(toSend);
|
|
276
|
-
}
|
|
277
|
-
}
|