@radatek/microserver 3.0.1 → 3.0.3
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/microserver.d.ts +8 -9
- package/microserver.js +126 -126
- package/package.json +1 -1
package/microserver.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* MicroServer
|
|
3
|
-
* @version 3.0.
|
|
3
|
+
* @version 3.0.3
|
|
4
4
|
* @package @radatek/microserver
|
|
5
5
|
* @copyright Darius Kisonas 2022
|
|
6
6
|
* @license MIT
|
|
@@ -32,9 +32,6 @@ export declare class WebSocketError extends Error {
|
|
|
32
32
|
statusCode: number;
|
|
33
33
|
constructor(text?: string, code?: number);
|
|
34
34
|
}
|
|
35
|
-
type DeferPromise<T = void> = Promise<T> & {
|
|
36
|
-
resolve: (res?: T | Error) => void;
|
|
37
|
-
};
|
|
38
35
|
/** Middleware */
|
|
39
36
|
export interface Middleware {
|
|
40
37
|
(req: ServerRequest, res: ServerResponse, next: Function): any;
|
|
@@ -50,7 +47,7 @@ export declare abstract class Plugin {
|
|
|
50
47
|
constructor();
|
|
51
48
|
}
|
|
52
49
|
interface PluginClass {
|
|
53
|
-
new (options
|
|
50
|
+
new (options: any, server: MicroServer): Plugin;
|
|
54
51
|
}
|
|
55
52
|
export type ServerRequestBody<T = any> = T extends Model<infer U extends ModelSchema> ? ModelDocument<U> : Record<string, any>;
|
|
56
53
|
/** Extended http.IncomingMessage */
|
|
@@ -89,12 +86,12 @@ export declare class ServerRequest<T = any> extends http.IncomingMessage {
|
|
|
89
86
|
rawBody: Buffer[];
|
|
90
87
|
/** Request raw body size */
|
|
91
88
|
rawBodySize: number;
|
|
92
|
-
_body?: ServerRequestBody<T>;
|
|
93
|
-
_isReady: DeferPromise | undefined;
|
|
94
89
|
private constructor();
|
|
95
|
-
static extend(req: http.IncomingMessage, server: MicroServer): ServerRequest;
|
|
90
|
+
static extend(req: http.IncomingMessage, res: http.ServerResponse, server: MicroServer): ServerRequest;
|
|
96
91
|
get isReady(): boolean;
|
|
97
92
|
waitReady(): Promise<void>;
|
|
93
|
+
setReady(err?: Error): void;
|
|
94
|
+
setBody(body: ServerRequestBody<T>): void;
|
|
98
95
|
/** Update request url */
|
|
99
96
|
updateUrl(url: string): this;
|
|
100
97
|
/** Rewrite request url */
|
|
@@ -351,7 +348,6 @@ export declare class WebSocketPlugin extends Plugin {
|
|
|
351
348
|
constructor(options?: any, server?: MicroServer);
|
|
352
349
|
addUpgradeHandler(srv: http.Server): void;
|
|
353
350
|
upgradeHandler(server: MicroServer, req: ServerRequest, socket: net.Socket, head: any): void;
|
|
354
|
-
static create(req: ServerRequest, options?: WebSocketOptions): WebSocket;
|
|
355
351
|
}
|
|
356
352
|
/** Trust proxy plugin, adds `req.ip` and `req.localip` */
|
|
357
353
|
export declare class TrustProxyPlugin extends Plugin {
|
|
@@ -392,6 +388,8 @@ export interface StaticFilesOptions {
|
|
|
392
388
|
etag?: boolean;
|
|
393
389
|
/** Max file age in seconds */
|
|
394
390
|
maxAge?: number;
|
|
391
|
+
/** static errors file for status code, '*' - default */
|
|
392
|
+
errors?: Record<string, string>;
|
|
395
393
|
}
|
|
396
394
|
export interface ServeFileOptions {
|
|
397
395
|
/** path */
|
|
@@ -445,6 +443,7 @@ export declare class StaticFilesPlugin extends Plugin {
|
|
|
445
443
|
/** Max file age in seconds (default: 31536000) */
|
|
446
444
|
maxAge?: number;
|
|
447
445
|
prefix: string;
|
|
446
|
+
errors?: Record<string, string>;
|
|
448
447
|
constructor(options?: StaticFilesOptions | string, server?: MicroServer);
|
|
449
448
|
/** Default static files handler */
|
|
450
449
|
handler(req: ServerRequest, res: ServerResponse, next: Function): any;
|
package/microserver.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* MicroServer
|
|
3
|
-
* @version 3.0.
|
|
3
|
+
* @version 3.0.3
|
|
4
4
|
* @package @radatek/microserver
|
|
5
5
|
* @copyright Darius Kisonas 2022
|
|
6
6
|
* @license MIT
|
|
@@ -65,13 +65,10 @@ export class WebSocketError extends Error {
|
|
|
65
65
|
}
|
|
66
66
|
function deferPromise(cb) {
|
|
67
67
|
let _resolve;
|
|
68
|
-
const p = new Promise((resolve
|
|
68
|
+
const p = new Promise((resolve) => {
|
|
69
69
|
_resolve = (res) => {
|
|
70
70
|
cb?.(res);
|
|
71
|
-
|
|
72
|
-
reject(res);
|
|
73
|
-
else
|
|
74
|
-
resolve(res);
|
|
71
|
+
resolve(res);
|
|
75
72
|
};
|
|
76
73
|
});
|
|
77
74
|
p.resolve = _resolve;
|
|
@@ -121,11 +118,11 @@ export class ServerRequest extends http.IncomingMessage {
|
|
|
121
118
|
rawBodySize;
|
|
122
119
|
_body;
|
|
123
120
|
_isReady;
|
|
124
|
-
constructor(server) {
|
|
121
|
+
constructor(res, server) {
|
|
125
122
|
super(new net.Socket());
|
|
126
|
-
ServerRequest.extend(this, server);
|
|
123
|
+
ServerRequest.extend(this, res, server);
|
|
127
124
|
}
|
|
128
|
-
static extend(req, server) {
|
|
125
|
+
static extend(req, res, server) {
|
|
129
126
|
const reqNew = Object.setPrototypeOf(req, ServerRequest.prototype);
|
|
130
127
|
let ip = req.socket.remoteAddress || '::1';
|
|
131
128
|
if (ip.startsWith('::ffff:'))
|
|
@@ -144,6 +141,15 @@ export class ServerRequest extends http.IncomingMessage {
|
|
|
144
141
|
rawBody: [],
|
|
145
142
|
rawBodySize: 0
|
|
146
143
|
});
|
|
144
|
+
if (req.readable && !req.complete) {
|
|
145
|
+
reqNew._isReady = deferPromise((err) => {
|
|
146
|
+
reqNew._isReady = undefined;
|
|
147
|
+
if (err && res && !res.headersSent) {
|
|
148
|
+
res.statusCode = 'statusCode' in err ? err.statusCode : 400;
|
|
149
|
+
res.end(http.STATUS_CODES[res.statusCode] || 'Error');
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
}
|
|
147
153
|
reqNew.updateUrl(req.url || '/');
|
|
148
154
|
return reqNew;
|
|
149
155
|
}
|
|
@@ -153,7 +159,15 @@ export class ServerRequest extends http.IncomingMessage {
|
|
|
153
159
|
async waitReady() {
|
|
154
160
|
if (this._isReady === undefined)
|
|
155
161
|
return;
|
|
156
|
-
await this._isReady;
|
|
162
|
+
const res = await this._isReady;
|
|
163
|
+
if (res && res instanceof Error)
|
|
164
|
+
throw res;
|
|
165
|
+
}
|
|
166
|
+
setReady(err) {
|
|
167
|
+
this._isReady?.resolve(err);
|
|
168
|
+
}
|
|
169
|
+
setBody(body) {
|
|
170
|
+
this._body = body;
|
|
157
171
|
}
|
|
158
172
|
/** Update request url */
|
|
159
173
|
updateUrl(url) {
|
|
@@ -194,7 +208,8 @@ export class ServerResponse extends http.ServerResponse {
|
|
|
194
208
|
isJson;
|
|
195
209
|
headersOnly;
|
|
196
210
|
constructor(server) {
|
|
197
|
-
super(
|
|
211
|
+
super(new http.IncomingMessage(new net.Socket()));
|
|
212
|
+
ServerRequest.extend(this.req, this, server);
|
|
198
213
|
ServerResponse.extend(this);
|
|
199
214
|
}
|
|
200
215
|
static extend(res) {
|
|
@@ -260,30 +275,31 @@ export class ServerResponse extends http.ServerResponse {
|
|
|
260
275
|
send(data = '') {
|
|
261
276
|
if (this.headersSent)
|
|
262
277
|
return;
|
|
263
|
-
if (
|
|
264
|
-
this.req.pause();
|
|
265
|
-
this.setHeader('Connection', 'close');
|
|
266
|
-
}
|
|
267
|
-
if (data instanceof Readable)
|
|
268
|
-
return (data.pipe(this, { end: true }), void 0);
|
|
269
|
-
if (!this.getHeader('Content-Type') && !(data instanceof Buffer)) {
|
|
278
|
+
if (typeof data === 'object' || this.isJson) {
|
|
270
279
|
if (data instanceof Error)
|
|
271
280
|
return this.error(data);
|
|
272
|
-
if (
|
|
273
|
-
data
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
if (data[0] === '{' || data[1] === '[')
|
|
279
|
-
this.setHeader('Content-Type', 'application/json');
|
|
280
|
-
else if (data[0] === '<' && (data.startsWith('<!DOCTYPE') || data.startsWith('<html')))
|
|
281
|
-
this.setHeader('Content-Type', 'text/html');
|
|
281
|
+
if (data instanceof Readable)
|
|
282
|
+
return (data.pipe(this, { end: true }), void 0);
|
|
283
|
+
if (data instanceof Buffer) {
|
|
284
|
+
this.setHeader('Content-Length', data.byteLength);
|
|
285
|
+
if (this.headersOnly)
|
|
286
|
+
this.end();
|
|
282
287
|
else
|
|
283
|
-
this.
|
|
288
|
+
this.end(data);
|
|
289
|
+
return;
|
|
284
290
|
}
|
|
291
|
+
data = JSON.stringify(typeof data === 'string' ? { message: data } : data);
|
|
292
|
+
this.setHeader('Content-Type', 'application/json');
|
|
285
293
|
}
|
|
286
294
|
data = data.toString();
|
|
295
|
+
if (!this.getHeader('Content-Type')) {
|
|
296
|
+
if (data[0] === '{' || data[1] === '[')
|
|
297
|
+
this.setHeader('Content-Type', 'application/json');
|
|
298
|
+
else if (data[0] === '<' && (data.startsWith('<!DOCTYPE') || data.startsWith('<html')))
|
|
299
|
+
this.setHeader('Content-Type', 'text/html');
|
|
300
|
+
else
|
|
301
|
+
this.setHeader('Content-Type', 'text/plain');
|
|
302
|
+
}
|
|
287
303
|
this.setHeader('Content-Length', Buffer.byteLength(data, 'utf8'));
|
|
288
304
|
if (this.headersOnly)
|
|
289
305
|
this.end();
|
|
@@ -342,6 +358,7 @@ export class ServerResponse extends http.ServerResponse {
|
|
|
342
358
|
export class MicroServer extends EventEmitter {
|
|
343
359
|
config;
|
|
344
360
|
auth;
|
|
361
|
+
_plugins = {};
|
|
345
362
|
_stack = [];
|
|
346
363
|
_router = new RouterPlugin();
|
|
347
364
|
_worker = new Worker();
|
|
@@ -571,17 +588,8 @@ export class MicroServer extends EventEmitter {
|
|
|
571
588
|
}
|
|
572
589
|
/** Default server handler */
|
|
573
590
|
handler(req, res) {
|
|
574
|
-
ServerRequest.extend(req, this);
|
|
591
|
+
ServerRequest.extend(req, res, this);
|
|
575
592
|
ServerResponse.extend(res);
|
|
576
|
-
if (req.readable) {
|
|
577
|
-
req._isReady = deferPromise((err) => {
|
|
578
|
-
req._isReady = undefined;
|
|
579
|
-
if (err) {
|
|
580
|
-
if (!res.headersSent)
|
|
581
|
-
res.error('statusCode' in err ? err.statusCode : 400);
|
|
582
|
-
}
|
|
583
|
-
});
|
|
584
|
-
}
|
|
585
593
|
this._router.walk(this._stack, req, res, () => res.error(404));
|
|
586
594
|
//this.handlerRouter(req, res, () => this.handlerLast(req, res))
|
|
587
595
|
}
|
|
@@ -596,6 +604,7 @@ export class MicroServer extends EventEmitter {
|
|
|
596
604
|
/** Clear routes and middlewares */
|
|
597
605
|
clear() {
|
|
598
606
|
this._stack = [];
|
|
607
|
+
this._plugins = {};
|
|
599
608
|
this._router.clear();
|
|
600
609
|
this._plugin(this._router);
|
|
601
610
|
return this;
|
|
@@ -689,6 +698,7 @@ export class MicroServer extends EventEmitter {
|
|
|
689
698
|
await this.use(routes);
|
|
690
699
|
}
|
|
691
700
|
if (plugin.handler && plugin.name) {
|
|
701
|
+
this._plugins[plugin.name] = plugin;
|
|
692
702
|
this.emit('plugin', plugin.name);
|
|
693
703
|
this.emit('plugin:' + plugin.name);
|
|
694
704
|
this._worker.endJob('plugin:' + plugin.name);
|
|
@@ -702,8 +712,7 @@ export class MicroServer extends EventEmitter {
|
|
|
702
712
|
this._stack.splice(idx + 1, 0, middleware);
|
|
703
713
|
}
|
|
704
714
|
getPlugin(id) {
|
|
705
|
-
|
|
706
|
-
return p?.plugin;
|
|
715
|
+
return this._plugins[id];
|
|
707
716
|
}
|
|
708
717
|
async waitPlugin(id) {
|
|
709
718
|
const p = this.getPlugin(id);
|
|
@@ -989,20 +998,9 @@ export class BodyPlugin extends Plugin {
|
|
|
989
998
|
handler(req, res, next) {
|
|
990
999
|
if (req.complete || req.method === 'GET') {
|
|
991
1000
|
if (!req.body)
|
|
992
|
-
req.
|
|
1001
|
+
req.setBody({});
|
|
993
1002
|
return next();
|
|
994
1003
|
}
|
|
995
|
-
req._isReady = deferPromise((err) => {
|
|
996
|
-
req._isReady = undefined;
|
|
997
|
-
if (err) {
|
|
998
|
-
if (!req.complete)
|
|
999
|
-
req.pause();
|
|
1000
|
-
if (!res.headersSent)
|
|
1001
|
-
res.error('statusCode' in err ? err.statusCode : 400);
|
|
1002
|
-
}
|
|
1003
|
-
else if (req.complete)
|
|
1004
|
-
res.removeHeader('Connection');
|
|
1005
|
-
});
|
|
1006
1004
|
const contentType = req.headers['content-type'] || '';
|
|
1007
1005
|
if (contentType.startsWith('multipart/form-data')) {
|
|
1008
1006
|
req.pause();
|
|
@@ -1010,44 +1008,35 @@ export class BodyPlugin extends Plugin {
|
|
|
1010
1008
|
return next();
|
|
1011
1009
|
}
|
|
1012
1010
|
if (parseInt(req.headers['content-length'] || '-1') > this._maxBodySize) {
|
|
1013
|
-
return req.
|
|
1011
|
+
return req.setReady(new ResponseError("too big", 413));
|
|
1014
1012
|
}
|
|
1015
1013
|
req.once('error', () => { })
|
|
1016
1014
|
.on('data', chunk => {
|
|
1017
1015
|
req.rawBodySize += chunk.length;
|
|
1018
1016
|
if (req.rawBodySize >= this._maxBodySize)
|
|
1019
|
-
req.
|
|
1017
|
+
req.setReady(new ResponseError("too big", 413));
|
|
1020
1018
|
else
|
|
1021
1019
|
req.rawBody.push(chunk);
|
|
1022
1020
|
})
|
|
1023
1021
|
.once('end', () => {
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
}
|
|
1043
|
-
else
|
|
1044
|
-
req._body = {};
|
|
1045
|
-
}
|
|
1046
|
-
return req._body;
|
|
1047
|
-
},
|
|
1048
|
-
configurable: true,
|
|
1049
|
-
enumerable: true
|
|
1050
|
-
});
|
|
1022
|
+
let charset = contentType.match(/charset=(\S+)/)?.[1];
|
|
1023
|
+
if (charset !== 'utf8' && charset !== 'latin1' && charset !== 'ascii')
|
|
1024
|
+
charset = 'utf8';
|
|
1025
|
+
const bodyString = Buffer.concat(req.rawBody).toString(charset);
|
|
1026
|
+
if (contentType.startsWith('application/x-www-form-urlencoded')) {
|
|
1027
|
+
req.setBody(querystring.parse(bodyString));
|
|
1028
|
+
}
|
|
1029
|
+
else if (bodyString.startsWith('{') || bodyString.startsWith('[')) {
|
|
1030
|
+
try {
|
|
1031
|
+
req.setBody(JSON.parse(bodyString));
|
|
1032
|
+
}
|
|
1033
|
+
catch {
|
|
1034
|
+
return res.jsonError(405);
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
else
|
|
1038
|
+
req.setBody({});
|
|
1039
|
+
req.setReady();
|
|
1051
1040
|
return next();
|
|
1052
1041
|
});
|
|
1053
1042
|
}
|
|
@@ -1068,19 +1057,6 @@ export class UploadPlugin extends Plugin {
|
|
|
1068
1057
|
const contentType = req.headers['content-type'] || '';
|
|
1069
1058
|
if (!contentType.startsWith('multipart/form-data'))
|
|
1070
1059
|
return next();
|
|
1071
|
-
if (!req._isReady) {
|
|
1072
|
-
req._isReady = deferPromise((err) => {
|
|
1073
|
-
req._isReady = undefined;
|
|
1074
|
-
if (err) {
|
|
1075
|
-
req.pause();
|
|
1076
|
-
if (!res.headersSent)
|
|
1077
|
-
res.setHeader('Connection', 'close');
|
|
1078
|
-
res.error('statusCode' in err ? err.statusCode : 400);
|
|
1079
|
-
}
|
|
1080
|
-
else
|
|
1081
|
-
res.removeHeader('Connection');
|
|
1082
|
-
});
|
|
1083
|
-
}
|
|
1084
1060
|
req.pause();
|
|
1085
1061
|
res.setHeader('Connection', 'close');
|
|
1086
1062
|
if (!this._uploadDir)
|
|
@@ -1148,8 +1124,10 @@ export class UploadPlugin extends Plugin {
|
|
|
1148
1124
|
fileStream.end();
|
|
1149
1125
|
lastFile.size += nextBoundaryIndex - 2;
|
|
1150
1126
|
fileStream = undefined;
|
|
1151
|
-
if (buffer[nextBoundaryIndexEnd] === 45)
|
|
1152
|
-
|
|
1127
|
+
if (buffer[nextBoundaryIndexEnd] === 45) {
|
|
1128
|
+
res.removeHeader('Connection');
|
|
1129
|
+
req.setReady();
|
|
1130
|
+
}
|
|
1153
1131
|
buffer = buffer.subarray(nextBoundaryIndex);
|
|
1154
1132
|
}
|
|
1155
1133
|
else {
|
|
@@ -1166,7 +1144,7 @@ export class UploadPlugin extends Plugin {
|
|
|
1166
1144
|
};
|
|
1167
1145
|
const _removeTempFiles = () => {
|
|
1168
1146
|
if (!req.isReady)
|
|
1169
|
-
req.
|
|
1147
|
+
req.setReady(new Error('Upload error'));
|
|
1170
1148
|
if (fileStream) {
|
|
1171
1149
|
fileStream.close();
|
|
1172
1150
|
fileStream = undefined;
|
|
@@ -1177,12 +1155,12 @@ export class UploadPlugin extends Plugin {
|
|
|
1177
1155
|
delete f.filePath;
|
|
1178
1156
|
});
|
|
1179
1157
|
files.splice(0);
|
|
1180
|
-
req.
|
|
1158
|
+
req.setReady();
|
|
1181
1159
|
};
|
|
1182
1160
|
next();
|
|
1183
|
-
req.once('error', () => req.
|
|
1161
|
+
req.once('error', () => req.setReady(new Error('Upload error')))
|
|
1184
1162
|
.on('data', chunk => chunkParse(chunk))
|
|
1185
|
-
.once('end', () => req.
|
|
1163
|
+
.once('end', () => req.setReady(new Error('Upload error')));
|
|
1186
1164
|
res.once('finish', () => _removeTempFiles());
|
|
1187
1165
|
res.once('error', () => _removeTempFiles());
|
|
1188
1166
|
res.once('close', () => _removeTempFiles());
|
|
@@ -1226,9 +1204,12 @@ export class WebSocket extends EventEmitter {
|
|
|
1226
1204
|
this._options.deflate = true;
|
|
1227
1205
|
}
|
|
1228
1206
|
this.ready = true;
|
|
1229
|
-
this._upgrade(key, headers)
|
|
1207
|
+
this._upgrade(key, headers, () => {
|
|
1208
|
+
req.setReady();
|
|
1209
|
+
this.emit('open');
|
|
1210
|
+
});
|
|
1230
1211
|
}
|
|
1231
|
-
_upgrade(key, headers = []) {
|
|
1212
|
+
_upgrade(key, headers = [], cb) {
|
|
1232
1213
|
const digest = crypto.createHash('sha1')
|
|
1233
1214
|
.update(key + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11')
|
|
1234
1215
|
.digest('base64');
|
|
@@ -1241,7 +1222,7 @@ export class WebSocket extends EventEmitter {
|
|
|
1241
1222
|
'',
|
|
1242
1223
|
''
|
|
1243
1224
|
];
|
|
1244
|
-
this._socket.write(headers.join('\r\n'));
|
|
1225
|
+
this._socket.write(headers.join('\r\n'), cb);
|
|
1245
1226
|
this._socket.on('error', this._errorHandler.bind(this));
|
|
1246
1227
|
this._socket.on('data', this._dataHandler.bind(this));
|
|
1247
1228
|
this._socket.on('close', () => this.emit('close'));
|
|
@@ -1507,7 +1488,6 @@ export class WebSocketPlugin extends Plugin {
|
|
|
1507
1488
|
srv.on('upgrade', this._handler);
|
|
1508
1489
|
}
|
|
1509
1490
|
upgradeHandler(server, req, socket, head) {
|
|
1510
|
-
ServerRequest.extend(req, server);
|
|
1511
1491
|
const host = req.headers.host || '';
|
|
1512
1492
|
const vhostPlugin = server.getPlugin('vhost');
|
|
1513
1493
|
const vserver = vhostPlugin?.vhosts?.[host] || server;
|
|
@@ -1520,7 +1500,7 @@ export class WebSocketPlugin extends Plugin {
|
|
|
1520
1500
|
statusCode: 200,
|
|
1521
1501
|
socket,
|
|
1522
1502
|
server,
|
|
1523
|
-
|
|
1503
|
+
end(data) {
|
|
1524
1504
|
if (res.headersSent)
|
|
1525
1505
|
throw new Error('Headers already sent');
|
|
1526
1506
|
let code = res.statusCode || 403;
|
|
@@ -1534,7 +1514,7 @@ export class WebSocketPlugin extends Plugin {
|
|
|
1534
1514
|
const headers = [
|
|
1535
1515
|
`HTTP/1.1 ${code} ${http.STATUS_CODES[code]}`,
|
|
1536
1516
|
'Connection: close',
|
|
1537
|
-
'Content-Type: text/
|
|
1517
|
+
'Content-Type: text/plain',
|
|
1538
1518
|
`Content-Length: ${Buffer.byteLength(data)}`,
|
|
1539
1519
|
'',
|
|
1540
1520
|
data
|
|
@@ -1543,22 +1523,25 @@ export class WebSocketPlugin extends Plugin {
|
|
|
1543
1523
|
},
|
|
1544
1524
|
error(code) {
|
|
1545
1525
|
res.statusCode = code || 403;
|
|
1546
|
-
res.
|
|
1547
|
-
},
|
|
1548
|
-
end(data) {
|
|
1549
|
-
res.write(data);
|
|
1526
|
+
res.end();
|
|
1550
1527
|
},
|
|
1551
1528
|
send(data) {
|
|
1552
|
-
res.
|
|
1529
|
+
res.end(data);
|
|
1553
1530
|
},
|
|
1554
1531
|
getHeader() { },
|
|
1555
1532
|
setHeader() { }
|
|
1556
1533
|
};
|
|
1534
|
+
ServerRequest.extend(req, res, server);
|
|
1535
|
+
let _ws;
|
|
1536
|
+
Object.defineProperty(req, 'websocket', {
|
|
1537
|
+
get: () => {
|
|
1538
|
+
if (!_ws)
|
|
1539
|
+
_ws = new WebSocket(req, server.config.websocket);
|
|
1540
|
+
return _ws;
|
|
1541
|
+
},
|
|
1542
|
+
enumerable: true
|
|
1543
|
+
});
|
|
1557
1544
|
vserver.handler(req, res);
|
|
1558
|
-
//vserver.handlerRouter(req, res as unknown as ServerResponse, () => res.error(404))
|
|
1559
|
-
}
|
|
1560
|
-
static create(req, options) {
|
|
1561
|
-
return new WebSocket(req, options);
|
|
1562
1545
|
}
|
|
1563
1546
|
}
|
|
1564
1547
|
// #endregion WebSocket
|
|
@@ -1685,6 +1668,7 @@ export class StaticFilesPlugin extends Plugin {
|
|
|
1685
1668
|
/** Max file age in seconds (default: 31536000) */
|
|
1686
1669
|
maxAge;
|
|
1687
1670
|
prefix;
|
|
1671
|
+
errors;
|
|
1688
1672
|
constructor(options, server) {
|
|
1689
1673
|
super();
|
|
1690
1674
|
if (!options)
|
|
@@ -1695,23 +1679,37 @@ export class StaticFilesPlugin extends Plugin {
|
|
|
1695
1679
|
if (server && !server.getPlugin('static'))
|
|
1696
1680
|
this.name = 'static';
|
|
1697
1681
|
this.mimeTypes = options.mimeTypes ? { ...StaticFilesPlugin.mimeTypes, ...options.mimeTypes } : Object.freeze(StaticFilesPlugin.mimeTypes);
|
|
1698
|
-
this.root = path.resolve(
|
|
1682
|
+
this.root = (options.root && path.isAbsolute(options.root) ? options.root : path.resolve(options.root || options?.path || 'public')).replace(/[\/\\]$/, '') + path.sep;
|
|
1699
1683
|
this.ignore = (options.ignore || []).map((p) => path.normalize(path.join(this.root, p)) + path.sep);
|
|
1700
1684
|
this.index = options.index || 'index.html';
|
|
1701
1685
|
this.handlers = options.handlers;
|
|
1702
1686
|
this.lastModified = options.lastModified !== false;
|
|
1703
1687
|
this.etag = options.etag !== false;
|
|
1704
1688
|
this.maxAge = options.maxAge;
|
|
1689
|
+
this.errors = options.errors;
|
|
1705
1690
|
this.prefix = ('/' + (options.path?.replace(/^[.\/]*/, '') || '').replace(/\/$/, '')).replace(/\/$/, '');
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1691
|
+
const defSend = ServerResponse.prototype.send;
|
|
1692
|
+
ServerResponse.prototype.send = function (data) {
|
|
1693
|
+
const plugin = this.req.server.getPlugin('static');
|
|
1694
|
+
if (this.statusCode < 400 || this.isJson || typeof data !== 'string' || !plugin?.errors || this.getHeader('Content-Type'))
|
|
1695
|
+
return defSend.call(this, data);
|
|
1696
|
+
const errFile = plugin.errors[this.statusCode] || plugin.errors['*'];
|
|
1697
|
+
if (errFile)
|
|
1698
|
+
plugin.serveFile(this.req, this, { path: errFile, mimeType: 'text/html' });
|
|
1699
|
+
return defSend.call(this, data);
|
|
1700
|
+
};
|
|
1701
|
+
ServerResponse.prototype.file = function (path) {
|
|
1702
|
+
const plugin = this.req.server.getPlugin('static');
|
|
1703
|
+
if (!plugin)
|
|
1704
|
+
throw new Error('Server error');
|
|
1705
|
+
plugin.serveFile(this.req, this, typeof path === 'object' ? path : {
|
|
1711
1706
|
path,
|
|
1712
1707
|
mimeType: StaticFilesPlugin.mimeTypes[extname(path)] || 'application/octet-stream'
|
|
1713
1708
|
});
|
|
1714
1709
|
};
|
|
1710
|
+
}
|
|
1711
|
+
/** Default static files handler */
|
|
1712
|
+
handler(req, res, next) {
|
|
1715
1713
|
if (req.method !== 'GET')
|
|
1716
1714
|
return next();
|
|
1717
1715
|
if (!('path' in req.params)) { // global handler
|
|
@@ -1756,10 +1754,12 @@ export class StaticFilesPlugin extends Plugin {
|
|
|
1756
1754
|
serveFile(req, res, options) {
|
|
1757
1755
|
const filePath = path.isAbsolute(options.path) ? options.path : path.join(options.root || this.root, options.path);
|
|
1758
1756
|
const statRes = (err, stats) => {
|
|
1759
|
-
if (err)
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1757
|
+
if (err || !stats.isFile()) {
|
|
1758
|
+
if (res.statusCode < 400)
|
|
1759
|
+
return res.error(404);
|
|
1760
|
+
res.end(res.statusCode + ' ' + http.STATUS_CODES[res.statusCode]);
|
|
1761
|
+
return;
|
|
1762
|
+
}
|
|
1763
1763
|
if (!res.getHeader('Content-Type')) {
|
|
1764
1764
|
if (options.mimeType)
|
|
1765
1765
|
res.setHeader('Content-Type', options.mimeType);
|