@radatek/microserver 3.0.1 → 3.0.2
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 +4 -1
- package/microserver.js +59 -38
- 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.2
|
|
4
4
|
* @package @radatek/microserver
|
|
5
5
|
* @copyright Darius Kisonas 2022
|
|
6
6
|
* @license MIT
|
|
@@ -392,6 +392,8 @@ export interface StaticFilesOptions {
|
|
|
392
392
|
etag?: boolean;
|
|
393
393
|
/** Max file age in seconds */
|
|
394
394
|
maxAge?: number;
|
|
395
|
+
/** static errors file for status code, '*' - default */
|
|
396
|
+
errors?: Record<string, string>;
|
|
395
397
|
}
|
|
396
398
|
export interface ServeFileOptions {
|
|
397
399
|
/** path */
|
|
@@ -445,6 +447,7 @@ export declare class StaticFilesPlugin extends Plugin {
|
|
|
445
447
|
/** Max file age in seconds (default: 31536000) */
|
|
446
448
|
maxAge?: number;
|
|
447
449
|
prefix: string;
|
|
450
|
+
errors?: Record<string, string>;
|
|
448
451
|
constructor(options?: StaticFilesOptions | string, server?: MicroServer);
|
|
449
452
|
/** Default static files handler */
|
|
450
453
|
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.2
|
|
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;
|
|
@@ -153,7 +150,9 @@ export class ServerRequest extends http.IncomingMessage {
|
|
|
153
150
|
async waitReady() {
|
|
154
151
|
if (this._isReady === undefined)
|
|
155
152
|
return;
|
|
156
|
-
await this._isReady;
|
|
153
|
+
const res = await this._isReady;
|
|
154
|
+
if (res && res instanceof Error)
|
|
155
|
+
throw res;
|
|
157
156
|
}
|
|
158
157
|
/** Update request url */
|
|
159
158
|
updateUrl(url) {
|
|
@@ -260,30 +259,31 @@ export class ServerResponse extends http.ServerResponse {
|
|
|
260
259
|
send(data = '') {
|
|
261
260
|
if (this.headersSent)
|
|
262
261
|
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)) {
|
|
262
|
+
if (typeof data === 'object' || this.isJson) {
|
|
270
263
|
if (data instanceof Error)
|
|
271
264
|
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');
|
|
265
|
+
if (data instanceof Readable)
|
|
266
|
+
return (data.pipe(this, { end: true }), void 0);
|
|
267
|
+
if (data instanceof Buffer) {
|
|
268
|
+
this.setHeader('Content-Length', data.byteLength);
|
|
269
|
+
if (this.headersOnly)
|
|
270
|
+
this.end();
|
|
282
271
|
else
|
|
283
|
-
this.
|
|
272
|
+
this.end(data);
|
|
273
|
+
return;
|
|
284
274
|
}
|
|
275
|
+
data = JSON.stringify(typeof data === 'string' ? { message: data } : data);
|
|
276
|
+
this.setHeader('Content-Type', 'application/json');
|
|
285
277
|
}
|
|
286
278
|
data = data.toString();
|
|
279
|
+
if (!this.getHeader('Content-Type')) {
|
|
280
|
+
if (data[0] === '{' || data[1] === '[')
|
|
281
|
+
this.setHeader('Content-Type', 'application/json');
|
|
282
|
+
else if (data[0] === '<' && (data.startsWith('<!DOCTYPE') || data.startsWith('<html')))
|
|
283
|
+
this.setHeader('Content-Type', 'text/html');
|
|
284
|
+
else
|
|
285
|
+
this.setHeader('Content-Type', 'text/plain');
|
|
286
|
+
}
|
|
287
287
|
this.setHeader('Content-Length', Buffer.byteLength(data, 'utf8'));
|
|
288
288
|
if (this.headersOnly)
|
|
289
289
|
this.end();
|
|
@@ -342,6 +342,7 @@ export class ServerResponse extends http.ServerResponse {
|
|
|
342
342
|
export class MicroServer extends EventEmitter {
|
|
343
343
|
config;
|
|
344
344
|
auth;
|
|
345
|
+
_plugins = {};
|
|
345
346
|
_stack = [];
|
|
346
347
|
_router = new RouterPlugin();
|
|
347
348
|
_worker = new Worker();
|
|
@@ -596,6 +597,7 @@ export class MicroServer extends EventEmitter {
|
|
|
596
597
|
/** Clear routes and middlewares */
|
|
597
598
|
clear() {
|
|
598
599
|
this._stack = [];
|
|
600
|
+
this._plugins = {};
|
|
599
601
|
this._router.clear();
|
|
600
602
|
this._plugin(this._router);
|
|
601
603
|
return this;
|
|
@@ -689,6 +691,7 @@ export class MicroServer extends EventEmitter {
|
|
|
689
691
|
await this.use(routes);
|
|
690
692
|
}
|
|
691
693
|
if (plugin.handler && plugin.name) {
|
|
694
|
+
this._plugins[plugin.name] = plugin;
|
|
692
695
|
this.emit('plugin', plugin.name);
|
|
693
696
|
this.emit('plugin:' + plugin.name);
|
|
694
697
|
this._worker.endJob('plugin:' + plugin.name);
|
|
@@ -702,8 +705,7 @@ export class MicroServer extends EventEmitter {
|
|
|
702
705
|
this._stack.splice(idx + 1, 0, middleware);
|
|
703
706
|
}
|
|
704
707
|
getPlugin(id) {
|
|
705
|
-
|
|
706
|
-
return p?.plugin;
|
|
708
|
+
return this._plugins[id];
|
|
707
709
|
}
|
|
708
710
|
async waitPlugin(id) {
|
|
709
711
|
const p = this.getPlugin(id);
|
|
@@ -1148,8 +1150,10 @@ export class UploadPlugin extends Plugin {
|
|
|
1148
1150
|
fileStream.end();
|
|
1149
1151
|
lastFile.size += nextBoundaryIndex - 2;
|
|
1150
1152
|
fileStream = undefined;
|
|
1151
|
-
if (buffer[nextBoundaryIndexEnd] === 45)
|
|
1153
|
+
if (buffer[nextBoundaryIndexEnd] === 45) {
|
|
1154
|
+
res.removeHeader('Connection');
|
|
1152
1155
|
req._isReady?.resolve();
|
|
1156
|
+
}
|
|
1153
1157
|
buffer = buffer.subarray(nextBoundaryIndex);
|
|
1154
1158
|
}
|
|
1155
1159
|
else {
|
|
@@ -1685,6 +1689,7 @@ export class StaticFilesPlugin extends Plugin {
|
|
|
1685
1689
|
/** Max file age in seconds (default: 31536000) */
|
|
1686
1690
|
maxAge;
|
|
1687
1691
|
prefix;
|
|
1692
|
+
errors;
|
|
1688
1693
|
constructor(options, server) {
|
|
1689
1694
|
super();
|
|
1690
1695
|
if (!options)
|
|
@@ -1695,23 +1700,37 @@ export class StaticFilesPlugin extends Plugin {
|
|
|
1695
1700
|
if (server && !server.getPlugin('static'))
|
|
1696
1701
|
this.name = 'static';
|
|
1697
1702
|
this.mimeTypes = options.mimeTypes ? { ...StaticFilesPlugin.mimeTypes, ...options.mimeTypes } : Object.freeze(StaticFilesPlugin.mimeTypes);
|
|
1698
|
-
this.root = path.resolve(
|
|
1703
|
+
this.root = (options.root && path.isAbsolute(options.root) ? options.root : path.resolve(options.root || options?.path || 'public')).replace(/[\/\\]$/, '') + path.sep;
|
|
1699
1704
|
this.ignore = (options.ignore || []).map((p) => path.normalize(path.join(this.root, p)) + path.sep);
|
|
1700
1705
|
this.index = options.index || 'index.html';
|
|
1701
1706
|
this.handlers = options.handlers;
|
|
1702
1707
|
this.lastModified = options.lastModified !== false;
|
|
1703
1708
|
this.etag = options.etag !== false;
|
|
1704
1709
|
this.maxAge = options.maxAge;
|
|
1710
|
+
this.errors = options.errors;
|
|
1705
1711
|
this.prefix = ('/' + (options.path?.replace(/^[.\/]*/, '') || '').replace(/\/$/, '')).replace(/\/$/, '');
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1712
|
+
const defSend = ServerResponse.prototype.send;
|
|
1713
|
+
ServerResponse.prototype.send = function (data) {
|
|
1714
|
+
const plugin = this.req.server.getPlugin('static');
|
|
1715
|
+
if (this.statusCode < 400 || this.isJson || typeof data !== 'string' || !plugin?.errors || this.getHeader('Content-Type'))
|
|
1716
|
+
return defSend.call(this, data);
|
|
1717
|
+
const errFile = plugin.errors[this.statusCode] || plugin.errors['*'];
|
|
1718
|
+
if (errFile)
|
|
1719
|
+
plugin.serveFile(this.req, this, { path: errFile, mimeType: 'text/html' });
|
|
1720
|
+
return defSend.call(this, data);
|
|
1721
|
+
};
|
|
1722
|
+
ServerResponse.prototype.file = function (path) {
|
|
1723
|
+
const plugin = this.req.server.getPlugin('static');
|
|
1724
|
+
if (!plugin)
|
|
1725
|
+
throw new Error('Server error');
|
|
1726
|
+
plugin.serveFile(this.req, this, typeof path === 'object' ? path : {
|
|
1711
1727
|
path,
|
|
1712
1728
|
mimeType: StaticFilesPlugin.mimeTypes[extname(path)] || 'application/octet-stream'
|
|
1713
1729
|
});
|
|
1714
1730
|
};
|
|
1731
|
+
}
|
|
1732
|
+
/** Default static files handler */
|
|
1733
|
+
handler(req, res, next) {
|
|
1715
1734
|
if (req.method !== 'GET')
|
|
1716
1735
|
return next();
|
|
1717
1736
|
if (!('path' in req.params)) { // global handler
|
|
@@ -1756,10 +1775,12 @@ export class StaticFilesPlugin extends Plugin {
|
|
|
1756
1775
|
serveFile(req, res, options) {
|
|
1757
1776
|
const filePath = path.isAbsolute(options.path) ? options.path : path.join(options.root || this.root, options.path);
|
|
1758
1777
|
const statRes = (err, stats) => {
|
|
1759
|
-
if (err)
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1778
|
+
if (err || !stats.isFile()) {
|
|
1779
|
+
if (res.statusCode < 400)
|
|
1780
|
+
return res.error(404);
|
|
1781
|
+
res.end(res.statusCode + ' ' + http.STATUS_CODES[res.statusCode]);
|
|
1782
|
+
return;
|
|
1783
|
+
}
|
|
1763
1784
|
if (!res.getHeader('Content-Type')) {
|
|
1764
1785
|
if (options.mimeType)
|
|
1765
1786
|
res.setHeader('Content-Type', options.mimeType);
|