@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * MicroServer
3
- * @version 3.0.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.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, reject) => {
68
+ const p = new Promise((resolve) => {
69
69
  _resolve = (res) => {
70
70
  cb?.(res);
71
- if (res instanceof Error)
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 (!this.req.complete) {
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 (this.isJson || typeof data === 'object') {
273
- data = JSON.stringify(typeof data === 'string' ? { message: data } : data);
274
- this.setHeader('Content-Type', 'application/json');
275
- }
276
- else {
277
- data = data.toString();
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.setHeader('Content-Type', 'text/plain');
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
- let p = this._stack.find(m => m.plugin?.name === id);
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((options.root || options?.path || 'public').replace(/^\//, '')) + path.sep;
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
- /** Default static files handler */
1708
- handler(req, res, next) {
1709
- res.file = (path) => {
1710
- this.serveFile(req, res, typeof path === 'object' ? path : {
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
- return res.error(err);
1761
- if (!stats.isFile())
1762
- return res.error(404);
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);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@radatek/microserver",
3
- "version": "3.0.1",
3
+ "version": "3.0.2",
4
4
  "description": "HTTP MicroServer",
5
5
  "author": "Darius Kisonas",
6
6
  "license": "MIT",