@radatek/microserver 3.0.5 → 3.0.6

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.5
3
+ * @version 3.0.6
4
4
  * @package @radatek/microserver
5
5
  * @copyright Darius Kisonas 2022
6
6
  * @license MIT
@@ -86,7 +86,6 @@ export declare class ServerRequest<T = any> extends http.IncomingMessage {
86
86
  rawBody: Buffer[];
87
87
  /** Request raw body size */
88
88
  rawBodySize: number;
89
- private constructor();
90
89
  /** Extend http.IncomingMessage */
91
90
  static extend(req: http.IncomingMessage, res: http.ServerResponse, server: MicroServer): ServerRequest;
92
91
  /** Check if request is ready */
@@ -115,7 +114,6 @@ export declare class ServerResponse<T = any> extends http.ServerResponse {
115
114
  readonly req: ServerRequest<T>;
116
115
  /** Should response be json */
117
116
  isJson: boolean;
118
- private constructor();
119
117
  /** Extends http.ServerResponse */
120
118
  static extend(res: http.ServerResponse): void;
121
119
  /** Send error reponse */
@@ -329,8 +327,6 @@ export declare class WebSocket extends EventEmitter {
329
327
  constructor(req: ServerRequest, options?: WebSocketOptions);
330
328
  /** Close connection */
331
329
  close(reason?: number, data?: Buffer): void;
332
- /** Generate WebSocket frame from data */
333
- static getFrame(data: number | string | Buffer | undefined, options?: any): Buffer;
334
330
  /** Send data */
335
331
  send(data: string | Buffer): void;
336
332
  /** Send ping frame */
@@ -346,8 +342,7 @@ export declare class WebSocket extends EventEmitter {
346
342
  export declare class WebSocketPlugin extends Plugin {
347
343
  name: string;
348
344
  constructor(options?: any, server?: MicroServer);
349
- private addUpgradeHandler;
350
- upgradeHandler(server: MicroServer, req: ServerRequest, socket: net.Socket, head: any): void;
345
+ upgradeHandler(server: MicroServer, req: ServerRequest, socket: net.Socket, head: any): any;
351
346
  }
352
347
  /** Trust proxy plugin, adds `req.ip` and `req.localip` */
353
348
  export declare class TrustProxyPlugin extends Plugin {
@@ -584,22 +579,11 @@ export declare class Auth {
584
579
  };
585
580
  /** Encode token */
586
581
  encode(data: string, expire?: number): string;
587
- /**
588
- * Check acl over authenticated user with: `id`, `group/*`, `*`
589
- * @param {string} id - to authenticate: `id`, `group/id`, `model/action`, comma separated best: true => false => def
590
- * @param {boolean} [def=false] - default access
591
- */
582
+ /** Check acl over authenticated user with: `id`, `group/*`, `*` */
592
583
  acl(id: string, def?: boolean): boolean;
593
- /**
594
- * Authenticate user and setup cookie
595
- * @param {string|UserInfo} usr - user id used with options.users to retrieve user object. User object must contain `id` and `acl` object (Ex. usr = {id:'usr', acl:{'users/*':true}})
596
- * @param {string} [psw] - user password (if used for user authentication with options.users)
597
- * @param {number} [expire] - expire time in seconds (default: options.expire)
598
- */
584
+ /** Generate token from user info */
599
585
  token(usr: string | UserInfo | undefined, psw: string | undefined, expire?: number): Promise<string | undefined>;
600
- /**
601
- * Authenticate user and setup cookie
602
- */
586
+ /** Authenticate user and setup cookie */
603
587
  login(usr: string | UserInfo | undefined, psw?: string, options?: {
604
588
  expire?: number;
605
589
  salt?: string;
@@ -937,4 +921,4 @@ export declare class MicroCollection<TSchema extends ModelSchema = any> {
937
921
  modifiedCount: number;
938
922
  }>;
939
923
  }
940
-
924
+ export {};
package/microserver.js CHANGED
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * MicroServer
3
- * @version 3.0.5
3
+ * @version 3.0.6
4
4
  * @package @radatek/microserver
5
5
  * @copyright Darius Kisonas 2022
6
6
  * @license MIT
@@ -116,8 +116,11 @@ export class ServerRequest extends http.IncomingMessage {
116
116
  rawBody;
117
117
  /** Request raw body size */
118
118
  rawBodySize;
119
+ // @internal
119
120
  _body;
121
+ // @internal
120
122
  _isReady;
123
+ // @internal
121
124
  constructor(res, server) {
122
125
  super(new net.Socket());
123
126
  ServerRequest.extend(this, res, server);
@@ -212,6 +215,7 @@ export class ServerRequest extends http.IncomingMessage {
212
215
  export class ServerResponse extends http.ServerResponse {
213
216
  /** Should response be json */
214
217
  isJson;
218
+ // @internal
215
219
  constructor(server) {
216
220
  super(new http.IncomingMessage(new net.Socket()));
217
221
  ServerRequest.extend(this.req, this, server);
@@ -365,9 +369,13 @@ export class MicroServer extends EventEmitter {
365
369
  config;
366
370
  /** Authorization object */
367
371
  auth;
372
+ // @internal
368
373
  _plugins = {};
374
+ // @internal
369
375
  _stack = [];
376
+ // @internal
370
377
  _router = new RouterPlugin();
378
+ // @internal
371
379
  _worker = new Worker();
372
380
  /** All sockets */
373
381
  sockets;
@@ -499,6 +507,7 @@ export class MicroServer extends EventEmitter {
499
507
  return this._worker.wait('listen');
500
508
  }
501
509
  /* bind middleware or create one from string like: 'redirect:302,https://redirect.to', 'error:422', 'param:name=value', 'acl:users/get', 'model:User', 'group:Users', 'user:admin' */
510
+ // @internal
502
511
  _bind(fn) {
503
512
  if (typeof fn === 'string') {
504
513
  let name = fn;
@@ -683,6 +692,7 @@ export class MicroServer extends EventEmitter {
683
692
  this._router.add(method, url, args.filter(o => o).map((o) => this._bind(o)), true);
684
693
  return this._worker.endJob();
685
694
  }
695
+ // @internal
686
696
  async _plugin(plugin) {
687
697
  if (plugin.handler) {
688
698
  const middleware = plugin.handler.bind(plugin);
@@ -793,16 +803,23 @@ export class MicroServer extends EventEmitter {
793
803
  }
794
804
  // #region RouterPlugin
795
805
  class RouterItem {
806
+ // @internal
796
807
  _stack; // add if middlewares added if not last
808
+ // @internal
797
809
  _next; // next middlewares
810
+ // @internal
798
811
  _paramName; // param name if param is used
812
+ // @internal
799
813
  _paramWild;
814
+ // @internal
800
815
  _withParam; // next middlewares if param is used
816
+ // @internal
801
817
  _last;
802
818
  }
803
819
  class RouterPlugin extends Plugin {
804
820
  priority = 100;
805
821
  name = 'router';
822
+ // @internal
806
823
  _tree = {};
807
824
  constructor() {
808
825
  super();
@@ -852,6 +869,7 @@ class RouterPlugin extends Plugin {
852
869
  node._stack.push(...middlewares);
853
870
  }
854
871
  }
872
+ // @internal
855
873
  _getStack(path, treeItems) {
856
874
  const out = [];
857
875
  const segments = path.split('/').filter(s => s);
@@ -965,7 +983,9 @@ export class CorsPlugin extends Plugin {
965
983
  export class MethodsPlugin extends Plugin {
966
984
  priority = -90;
967
985
  name = 'methods';
986
+ // @internal
968
987
  _methods;
988
+ // @internal
969
989
  _methodsIdx;
970
990
  constructor(methods) {
971
991
  super();
@@ -989,6 +1009,7 @@ export class MethodsPlugin extends Plugin {
989
1009
  export class BodyPlugin extends Plugin {
990
1010
  priority = -80;
991
1011
  name = 'body';
1012
+ // @internal
992
1013
  _maxBodySize;
993
1014
  constructor(options) {
994
1015
  super();
@@ -1043,7 +1064,9 @@ export class BodyPlugin extends Plugin {
1043
1064
  export class UploadPlugin extends Plugin {
1044
1065
  priority = -70;
1045
1066
  name = 'upload';
1067
+ // @internal
1046
1068
  _maxFileSize;
1069
+ // @internal
1047
1070
  _uploadDir;
1048
1071
  constructor(options) {
1049
1072
  super();
@@ -1133,6 +1156,10 @@ export class UploadPlugin extends Plugin {
1133
1156
  const safeWriteLength = buffer.length - lookahead;
1134
1157
  if (safeWriteLength > 0) {
1135
1158
  lastFile.size += safeWriteLength;
1159
+ if (lastFile.size > this._maxFileSize) {
1160
+ req.setReady(new ResponseError("file too big", 413));
1161
+ return;
1162
+ }
1136
1163
  fileStream.write(buffer.subarray(0, safeWriteLength));
1137
1164
  buffer = buffer.subarray(safeWriteLength);
1138
1165
  }
@@ -1169,10 +1196,15 @@ const EMPTY_BUFFER = Buffer.alloc(0);
1169
1196
  const DEFLATE_TRAILER = Buffer.from([0x00, 0x00, 0xff, 0xff]);
1170
1197
  /** WebSocket class */
1171
1198
  export class WebSocket extends EventEmitter {
1199
+ // @internal
1172
1200
  _socket;
1201
+ // @internal
1173
1202
  _frame;
1203
+ // @internal
1174
1204
  _buffers = [EMPTY_BUFFER];
1205
+ // @internal
1175
1206
  _buffersLength = 0;
1207
+ // @internal
1176
1208
  _options;
1177
1209
  ready = false;
1178
1210
  constructor(req, options) {
@@ -1191,7 +1223,7 @@ export class WebSocket extends EventEmitter {
1191
1223
  const version = +(req.headers['sec-websocket-version'] || 0);
1192
1224
  const extensions = req.headers['sec-websocket-extensions'];
1193
1225
  const headers = [];
1194
- if (!key || !upgrade || upgrade.toLocaleLowerCase() !== 'websocket' || version !== 13 || req.method !== 'GET') {
1226
+ if (!key || upgrade?.toLocaleLowerCase() !== 'websocket' || version !== 13) {
1195
1227
  this._abort('Invalid WebSocket request', 400);
1196
1228
  return;
1197
1229
  }
@@ -1202,12 +1234,12 @@ export class WebSocket extends EventEmitter {
1202
1234
  headers.push(header);
1203
1235
  this._options.deflate = true;
1204
1236
  }
1205
- this.ready = true;
1206
1237
  this._upgrade(key, headers, () => {
1207
- req.setReady();
1238
+ this.ready = true;
1208
1239
  this.emit('open');
1209
1240
  });
1210
1241
  }
1242
+ // @internal
1211
1243
  _upgrade(key, headers = [], cb) {
1212
1244
  const digest = crypto.createHash('sha1')
1213
1245
  .update(key + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11')
@@ -1238,45 +1270,6 @@ export class WebSocket extends EventEmitter {
1238
1270
  }
1239
1271
  return this._sendFrame(0x88, data || EMPTY_BUFFER, () => this._socket.destroy());
1240
1272
  }
1241
- /** Generate WebSocket frame from data */
1242
- static getFrame(data, options) {
1243
- let msgType = 8;
1244
- let dataLength = 0;
1245
- if (typeof data === 'string') {
1246
- msgType = 1;
1247
- dataLength = Buffer.byteLength(data, 'utf8');
1248
- }
1249
- else if (data instanceof Buffer) {
1250
- msgType = 2;
1251
- dataLength = data.length;
1252
- }
1253
- else if (typeof data === 'number') {
1254
- msgType = data;
1255
- }
1256
- const headerSize = 2 + (dataLength < 126 ? 0 : dataLength < 65536 ? 2 : 8) + (dataLength && options?.mask ? 4 : 0);
1257
- const frame = Buffer.allocUnsafe(headerSize + dataLength);
1258
- frame[0] = 0x80 | msgType;
1259
- frame[1] = dataLength > 65535 ? 127 : dataLength > 125 ? 126 : dataLength;
1260
- if (dataLength > 65535)
1261
- frame.writeBigUInt64BE(dataLength, 2);
1262
- else if (dataLength > 125)
1263
- frame.writeUInt16BE(dataLength, 2);
1264
- if (dataLength && frame.length > dataLength) {
1265
- if (typeof data === 'string')
1266
- frame.write(data, headerSize, 'utf8');
1267
- else
1268
- data.copy(frame, headerSize);
1269
- }
1270
- if (dataLength && options?.mask) {
1271
- let i = headerSize, h = headerSize - 4;
1272
- for (let i = 0; i < 4; i++)
1273
- frame[h + i] = Math.floor(Math.random() * 256);
1274
- for (let j = 0; j < dataLength; j++, i++) {
1275
- frame[i] ^= frame[h + (j & 3)];
1276
- }
1277
- }
1278
- return frame;
1279
- }
1280
1273
  /** Send data */
1281
1274
  send(data) {
1282
1275
  let msgType = typeof data === 'string' ? 1 : 2;
@@ -1298,6 +1291,7 @@ export class WebSocket extends EventEmitter {
1298
1291
  else
1299
1292
  return this._sendFrame(0x80 | msgType, data);
1300
1293
  }
1294
+ // @internal
1301
1295
  _errorHandler(error) {
1302
1296
  this.emit('error', error);
1303
1297
  if (this.ready)
@@ -1306,6 +1300,7 @@ export class WebSocket extends EventEmitter {
1306
1300
  this._socket.destroy();
1307
1301
  this.ready = false;
1308
1302
  }
1303
+ // @internal
1309
1304
  _headerLength(buffer) {
1310
1305
  if (this._frame)
1311
1306
  return 0;
@@ -1314,6 +1309,7 @@ export class WebSocket extends EventEmitter {
1314
1309
  let hederInfo = buffer[1];
1315
1310
  return 2 + (hederInfo & 0x80 ? 4 : 0) + ((hederInfo & 0x7F) === 126 ? 2 : 0) + ((hederInfo & 0x7F) === 127 ? 8 : 0);
1316
1311
  }
1312
+ // @internal
1317
1313
  _dataHandler(data) {
1318
1314
  while (data.length) {
1319
1315
  let frame = this._frame;
@@ -1420,6 +1416,7 @@ export class WebSocket extends EventEmitter {
1420
1416
  this._buffers.push(EMPTY_BUFFER);
1421
1417
  }
1422
1418
  }
1419
+ // @internal
1423
1420
  _abort(message, code, headers) {
1424
1421
  code = code || 400;
1425
1422
  message = message || http.STATUS_CODES[code] || 'Closed';
@@ -1446,6 +1443,7 @@ export class WebSocket extends EventEmitter {
1446
1443
  pong(buffer) {
1447
1444
  this._sendFrame(0x8A, buffer || EMPTY_BUFFER);
1448
1445
  }
1446
+ // @internal
1449
1447
  _sendFrame(opcode, data, cb) {
1450
1448
  if (!this.ready)
1451
1449
  return;
@@ -1465,24 +1463,21 @@ export class WebSocket extends EventEmitter {
1465
1463
  else
1466
1464
  this._socket.write(frame, () => this._socket.write(data, cb));
1467
1465
  }
1468
- on(event, listener) { return super.on(event, listener); }
1469
- addListener(event, listener) { return super.addListener(event, listener); }
1470
- once(event, listener) { return super.once(event, listener); }
1471
- off(event, listener) { return super.off(event, listener); }
1472
- removeListener(event, listener) { return super.removeListener(event, listener); }
1473
1466
  }
1474
1467
  export class WebSocketPlugin extends Plugin {
1475
1468
  name = 'websocket';
1469
+ // @internal
1476
1470
  _handler;
1477
1471
  constructor(options, server) {
1478
1472
  super();
1479
1473
  if (!server)
1480
1474
  throw new Error('Server instance is required');
1481
1475
  this._handler = this.upgradeHandler.bind(this, server);
1482
- server.servers?.forEach(srv => this.addUpgradeHandler(srv));
1483
- server.on('listen', (port, address, srv) => this.addUpgradeHandler(srv));
1476
+ server.servers?.forEach(srv => this._addUpgradeHandler(srv));
1477
+ server.on('listen', (port, address, srv) => this._addUpgradeHandler(srv));
1484
1478
  }
1485
- addUpgradeHandler(srv) {
1479
+ // @internal
1480
+ _addUpgradeHandler(srv) {
1486
1481
  if (!srv.listeners('upgrade').includes(this._handler))
1487
1482
  srv.on('upgrade', this._handler);
1488
1483
  }
@@ -1490,7 +1485,6 @@ export class WebSocketPlugin extends Plugin {
1490
1485
  const host = req.headers.host || '';
1491
1486
  const vhostPlugin = server.getPlugin('vhost');
1492
1487
  const vserver = vhostPlugin?.vhosts?.[host] || server;
1493
- req.method = 'WEBSOCKET';
1494
1488
  const res = {
1495
1489
  req,
1496
1490
  get headersSent() {
@@ -1521,7 +1515,9 @@ export class WebSocketPlugin extends Plugin {
1521
1515
  socket.write(headers.join('\r\n'), () => { socket.destroy(); });
1522
1516
  },
1523
1517
  error(code) {
1524
- res.statusCode = code || 403;
1518
+ if (typeof code !== 'number')
1519
+ code = 405;
1520
+ res.statusCode = code || 405;
1525
1521
  res.end();
1526
1522
  },
1527
1523
  send(data) {
@@ -1531,15 +1527,18 @@ export class WebSocketPlugin extends Plugin {
1531
1527
  setHeader() { }
1532
1528
  };
1533
1529
  ServerRequest.extend(req, res, server);
1534
- let _ws;
1530
+ let ws;
1535
1531
  Object.defineProperty(req, 'websocket', {
1536
1532
  get: () => {
1537
- if (!_ws)
1538
- _ws = new WebSocket(req, server.config.websocket);
1539
- return _ws;
1533
+ if (!ws)
1534
+ ws = new WebSocket(req, server.config.websocket);
1535
+ return ws;
1540
1536
  },
1541
1537
  enumerable: true
1542
1538
  });
1539
+ if (req.method !== 'GET' || req.headers.upgrade?.toLowerCase() !== 'websocket')
1540
+ return res.error(400);
1541
+ req.method = 'WEBSOCKET';
1543
1542
  vserver.handler(req, res);
1544
1543
  }
1545
1544
  }
@@ -1549,6 +1548,7 @@ export class WebSocketPlugin extends Plugin {
1549
1548
  export class TrustProxyPlugin extends Plugin {
1550
1549
  priority = -60;
1551
1550
  name = 'trustproxy';
1551
+ // @internal
1552
1552
  _trustProxy = [];
1553
1553
  constructor(options) {
1554
1554
  super();
@@ -1965,11 +1965,7 @@ export class Auth {
1965
1965
  encrypted = iv.toString('base64').slice(0, 22) + cipher.getAuthTag().toString('base64').slice(0, 22) + encrypted;
1966
1966
  return encrypted.replace(/==?/, '').replace(/\//g, '.').replace(/\+/g, '-');
1967
1967
  }
1968
- /**
1969
- * Check acl over authenticated user with: `id`, `group/*`, `*`
1970
- * @param {string} id - to authenticate: `id`, `group/id`, `model/action`, comma separated best: true => false => def
1971
- * @param {boolean} [def=false] - default access
1972
- */
1968
+ /** Check acl over authenticated user with: `id`, `group/*`, `*` */
1973
1969
  acl(id, def = false) {
1974
1970
  if (!this.req?.user)
1975
1971
  return false;
@@ -1991,12 +1987,7 @@ export class Auth {
1991
1987
  access = reqAcl['*'];
1992
1988
  return access ?? def;
1993
1989
  }
1994
- /**
1995
- * Authenticate user and setup cookie
1996
- * @param {string|UserInfo} usr - user id used with options.users to retrieve user object. User object must contain `id` and `acl` object (Ex. usr = {id:'usr', acl:{'users/*':true}})
1997
- * @param {string} [psw] - user password (if used for user authentication with options.users)
1998
- * @param {number} [expire] - expire time in seconds (default: options.expire)
1999
- */
1990
+ /** Generate token from user info */
2000
1991
  async token(usr, psw, expire) {
2001
1992
  let data;
2002
1993
  if (typeof usr === 'object' && usr && (usr.id || usr._id))
@@ -2012,9 +2003,7 @@ export class Auth {
2012
2003
  if (data)
2013
2004
  return this.encode(data, expire);
2014
2005
  }
2015
- /**
2016
- * Authenticate user and setup cookie
2017
- */
2006
+ /** Authenticate user and setup cookie */
2018
2007
  async login(usr, psw, options) {
2019
2008
  let usrInfo;
2020
2009
  if (typeof usr === 'object')
@@ -2487,7 +2476,9 @@ export class Controller {
2487
2476
  // #endregion Controller
2488
2477
  // #region Worker
2489
2478
  class WorkerJob {
2479
+ // @internal
2490
2480
  _promises = [];
2481
+ // @internal
2491
2482
  _busy = 0;
2492
2483
  start() {
2493
2484
  this._busy++;
@@ -2505,7 +2496,9 @@ class WorkerJob {
2505
2496
  }
2506
2497
  }
2507
2498
  class Worker {
2499
+ // @internal
2508
2500
  _id = 0;
2501
+ // @internal
2509
2502
  _jobs = {};
2510
2503
  isBusy(id) {
2511
2504
  const job = this._jobs[id || 'ready'];
@@ -2534,11 +2527,17 @@ class Worker {
2534
2527
  }
2535
2528
  /** JSON File store */
2536
2529
  export class FileStore {
2530
+ // @internal
2537
2531
  _cache;
2532
+ // @internal
2538
2533
  _dir;
2534
+ // @internal
2539
2535
  _cacheTimeout;
2536
+ // @internal
2540
2537
  _cacheItems;
2538
+ // @internal
2541
2539
  _debounceTimeout;
2540
+ // @internal
2542
2541
  _iter;
2543
2542
  constructor(options) {
2544
2543
  this._cache = {};
@@ -2562,7 +2561,9 @@ export class FileStore {
2562
2561
  }
2563
2562
  }
2564
2563
  }
2564
+ // @internal
2565
2565
  _queue = Promise.resolve();
2566
+ // @internal
2566
2567
  async _sync(cb) {
2567
2568
  let r;
2568
2569
  let p = new Promise(resolve => r = resolve);
@@ -2741,8 +2742,11 @@ function newObjectId() {
2741
2742
  return (new Date().getTime() / 1000 | 0).toString(16) + globalObjectId.toString('hex');
2742
2743
  }
2743
2744
  class ModelCollectionsInternal {
2745
+ // @internal
2744
2746
  _ready;
2747
+ // @internal
2745
2748
  _wait = new Promise(resolve => this._ready = resolve);
2749
+ // @internal
2746
2750
  _db;
2747
2751
  set db(db) {
2748
2752
  Promise.resolve(db).then(db => this._ready(this._db = db));
@@ -3045,6 +3049,7 @@ export class Model {
3045
3049
  }
3046
3050
  return res;
3047
3051
  }
3052
+ // @internal
3048
3053
  _fieldFunction(value, def) {
3049
3054
  if (typeof value === 'string' && value.startsWith('${') && value.endsWith('}')) {
3050
3055
  const names = value.slice(2, -1).split('.');
@@ -3062,6 +3067,7 @@ export class Model {
3062
3067
  return () => def;
3063
3068
  return () => value;
3064
3069
  }
3070
+ // @internal
3065
3071
  _validateField(value, options) {
3066
3072
  const field = options.field;
3067
3073
  if (value == null) {
@@ -3189,7 +3195,9 @@ export class Model {
3189
3195
  }
3190
3196
  /** Collection factory */
3191
3197
  export class MicroCollectionStore {
3198
+ // @internal
3192
3199
  _collections = new Map();
3200
+ // @internal
3193
3201
  _store;
3194
3202
  constructor(dataPath, storeTimeDelay) {
3195
3203
  if (dataPath)
@@ -3212,6 +3220,7 @@ export class MicroCollection {
3212
3220
  name;
3213
3221
  /** Collection data */
3214
3222
  data;
3223
+ // @internal
3215
3224
  _save;
3216
3225
  constructor(options = {}) {
3217
3226
  this.name = options.name || this.constructor.name;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@radatek/microserver",
3
- "version": "3.0.5",
3
+ "version": "3.0.6",
4
4
  "description": "HTTP MicroServer",
5
5
  "author": "Darius Kisonas",
6
6
  "license": "MIT",