@radatek/microserver 2.0.2 → 2.1.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/dist/microserver.d.ts +39 -14
- package/dist/microserver.js +96 -43
- package/package.json +1 -1
- package/readme.md +50 -47
package/dist/microserver.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* MicroServer
|
|
3
|
-
* @version 2.0
|
|
3
|
+
* @version 2.1.0
|
|
4
4
|
* @package @radatek/microserver
|
|
5
5
|
* @copyright Darius Kisonas 2022
|
|
6
6
|
* @license MIT
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
import http from 'http';
|
|
9
9
|
import net from 'net';
|
|
10
10
|
import { Readable } from 'stream';
|
|
11
|
+
import fs from 'fs';
|
|
11
12
|
import { EventEmitter } from 'events';
|
|
12
13
|
export declare class Warning extends Error {
|
|
13
14
|
constructor(text: string);
|
|
@@ -40,7 +41,6 @@ export declare abstract class Plugin {
|
|
|
40
41
|
name?: string;
|
|
41
42
|
priority?: number;
|
|
42
43
|
handler?(req: ServerRequest, res: ServerResponse, next: Function): void;
|
|
43
|
-
controller?: typeof Controller;
|
|
44
44
|
routes?: () => Routes | Routes;
|
|
45
45
|
constructor(router: Router, ...args: any);
|
|
46
46
|
}
|
|
@@ -65,8 +65,8 @@ export declare class ServerRequest extends http.IncomingMessage {
|
|
|
65
65
|
baseUrl: string;
|
|
66
66
|
/** Original url */
|
|
67
67
|
originalUrl?: string;
|
|
68
|
-
/**
|
|
69
|
-
|
|
68
|
+
/** Query parameters */
|
|
69
|
+
query: {
|
|
70
70
|
[key: string]: string;
|
|
71
71
|
};
|
|
72
72
|
/** Router named parameters */
|
|
@@ -117,17 +117,20 @@ export declare class ServerResponse extends http.ServerResponse {
|
|
|
117
117
|
headersOnly: boolean;
|
|
118
118
|
private constructor();
|
|
119
119
|
/** Send error reponse */
|
|
120
|
-
error(error: string | number | Error
|
|
120
|
+
error(error: string | number | Error): void;
|
|
121
121
|
/** Sets Content-Type acording to data and sends response */
|
|
122
122
|
send(data?: string | Buffer | Error | Readable | object): void;
|
|
123
123
|
/** Send json response */
|
|
124
124
|
json(data: any): void;
|
|
125
125
|
/** Send json response in form { success: false, error: err } */
|
|
126
|
-
jsonError(error: string | number | object | Error
|
|
126
|
+
jsonError(error: string | number | object | Error): void;
|
|
127
127
|
/** Send json response in form { success: true, ... } */
|
|
128
|
-
jsonSuccess(data?: object | string
|
|
128
|
+
jsonSuccess(data?: object | string): void;
|
|
129
129
|
/** Send redirect response to specified URL with optional status code (default: 302) */
|
|
130
130
|
redirect(code: number | string, url?: string): void;
|
|
131
|
+
/** Set status code */
|
|
132
|
+
status(code: number): this;
|
|
133
|
+
download(path: string, filename?: string): void;
|
|
131
134
|
}
|
|
132
135
|
/** WebSocket options */
|
|
133
136
|
export interface WebSocketOptions {
|
|
@@ -376,7 +379,7 @@ export declare class MicroServer extends EventEmitter {
|
|
|
376
379
|
listen(config?: ListenConfig): Promise<unknown>;
|
|
377
380
|
/** 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' */
|
|
378
381
|
bind(fn: string | Function | object): Function;
|
|
379
|
-
/** Add middleware, routes, etc.. see {
|
|
382
|
+
/** Add middleware, routes, etc.. see {router.use} */
|
|
380
383
|
use(...args: any): MicroServer;
|
|
381
384
|
/** Default server handler */
|
|
382
385
|
handler(req: ServerRequest, res: ServerResponse): void;
|
|
@@ -389,17 +392,19 @@ export declare class MicroServer extends EventEmitter {
|
|
|
389
392
|
handlerUpgrade(req: ServerRequest, socket: net.Socket, head: any): void;
|
|
390
393
|
/** Close server instance */
|
|
391
394
|
close(): Promise<void>;
|
|
392
|
-
/** Add route, alias to `server.router.
|
|
395
|
+
/** Add route, alias to `server.router.use(url, ...args)` */
|
|
396
|
+
all(url: string, ...args: any): MicroServer;
|
|
397
|
+
/** Add route, alias to `server.router.use('GET ' + url, ...args)` */
|
|
393
398
|
get(url: string, ...args: any): MicroServer;
|
|
394
|
-
/** Add route, alias to `server.router.
|
|
399
|
+
/** Add route, alias to `server.router.use('POST ' + url, ...args)` */
|
|
395
400
|
post(url: string, ...args: any): MicroServer;
|
|
396
|
-
/** Add route, alias to `server.router.
|
|
401
|
+
/** Add route, alias to `server.router.use('PUT ' + url, ...args)` */
|
|
397
402
|
put(url: string, ...args: any): MicroServer;
|
|
398
|
-
/** Add route, alias to `server.router.
|
|
403
|
+
/** Add route, alias to `server.router.use('PATCH ' + url, ...args)` */
|
|
399
404
|
patch(url: string, ...args: any): MicroServer;
|
|
400
|
-
/** Add route, alias to `server.router.
|
|
405
|
+
/** Add route, alias to `server.router.use('DELETE ' + url, ...args)` */
|
|
401
406
|
delete(url: string, ...args: any): MicroServer;
|
|
402
|
-
/** Add websocket handler, alias to `server.router.
|
|
407
|
+
/** Add websocket handler, alias to `server.router.use('WEBSOCKET ' + url, ...args)` */
|
|
403
408
|
websocket(url: string, ...args: any): MicroServer;
|
|
404
409
|
/** Add router hook, alias to `server.router.hook(url, ...args)` */
|
|
405
410
|
hook(url: string, ...args: any): MicroServer;
|
|
@@ -429,6 +434,26 @@ export interface StaticOptions {
|
|
|
429
434
|
/** Max file age in seconds */
|
|
430
435
|
maxAge?: number;
|
|
431
436
|
}
|
|
437
|
+
export interface ServeFileOptions {
|
|
438
|
+
/** path */
|
|
439
|
+
path: string;
|
|
440
|
+
/** root */
|
|
441
|
+
root?: string;
|
|
442
|
+
/** file name */
|
|
443
|
+
filename?: string;
|
|
444
|
+
/** file mime type */
|
|
445
|
+
mimeType?: string;
|
|
446
|
+
/** last modified date */
|
|
447
|
+
lastModified?: boolean;
|
|
448
|
+
/** etag */
|
|
449
|
+
etag?: boolean;
|
|
450
|
+
/** max age */
|
|
451
|
+
maxAge?: number;
|
|
452
|
+
/** range */
|
|
453
|
+
range?: boolean;
|
|
454
|
+
/** stat */
|
|
455
|
+
stats?: fs.Stats;
|
|
456
|
+
}
|
|
432
457
|
/** Proxy plugin options */
|
|
433
458
|
export interface ProxyPluginOptions {
|
|
434
459
|
/** Base path */
|
package/dist/microserver.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* MicroServer
|
|
3
|
-
* @version 2.0
|
|
3
|
+
* @version 2.1.0
|
|
4
4
|
* @package @radatek/microserver
|
|
5
5
|
* @copyright Darius Kisonas 2022
|
|
6
6
|
* @license MIT
|
|
@@ -12,7 +12,7 @@ import tls from 'tls';
|
|
|
12
12
|
import querystring from 'querystring';
|
|
13
13
|
import { Readable } from 'stream';
|
|
14
14
|
import fs from 'fs';
|
|
15
|
-
import path from 'path';
|
|
15
|
+
import path, { basename, extname } from 'path';
|
|
16
16
|
import crypto from 'crypto';
|
|
17
17
|
import zlib from 'zlib';
|
|
18
18
|
import { EventEmitter } from 'events';
|
|
@@ -92,8 +92,8 @@ export class ServerRequest extends http.IncomingMessage {
|
|
|
92
92
|
this.pathname = pathname;
|
|
93
93
|
this.path = pathname.slice(pathname.lastIndexOf('/'));
|
|
94
94
|
this.baseUrl = pathname.slice(0, pathname.length - this.path.length);
|
|
95
|
-
this.
|
|
96
|
-
parsedUrl.searchParams.forEach((v, k) => this.
|
|
95
|
+
this.query = {};
|
|
96
|
+
parsedUrl.searchParams.forEach((v, k) => this.query[k] = v);
|
|
97
97
|
}
|
|
98
98
|
/** Rewrite request url */
|
|
99
99
|
rewrite(url) {
|
|
@@ -313,8 +313,9 @@ export class ServerResponse extends http.ServerResponse {
|
|
|
313
313
|
this.statusCode = 200;
|
|
314
314
|
}
|
|
315
315
|
/** Send error reponse */
|
|
316
|
-
error(error
|
|
316
|
+
error(error) {
|
|
317
317
|
let code = 0;
|
|
318
|
+
let text;
|
|
318
319
|
if (error instanceof Error) {
|
|
319
320
|
if ('statusCode' in error)
|
|
320
321
|
code = error.statusCode;
|
|
@@ -322,7 +323,7 @@ export class ServerResponse extends http.ServerResponse {
|
|
|
322
323
|
}
|
|
323
324
|
else if (typeof error === 'number') {
|
|
324
325
|
code = error;
|
|
325
|
-
text =
|
|
326
|
+
text = commonCodes[code] || 'Error';
|
|
326
327
|
}
|
|
327
328
|
else
|
|
328
329
|
text = error.toString();
|
|
@@ -399,19 +400,17 @@ export class ServerResponse extends http.ServerResponse {
|
|
|
399
400
|
this.send(data);
|
|
400
401
|
}
|
|
401
402
|
/** Send json response in form { success: false, error: err } */
|
|
402
|
-
jsonError(error
|
|
403
|
+
jsonError(error) {
|
|
403
404
|
this.isJson = true;
|
|
404
|
-
this.statusCode = code || 200;
|
|
405
405
|
if (typeof error === 'number')
|
|
406
|
-
|
|
406
|
+
error = http.STATUS_CODES[error] || 'Error';
|
|
407
407
|
if (error instanceof Error)
|
|
408
408
|
return this.json(error);
|
|
409
409
|
this.json(typeof error === 'string' ? { success: false, error } : { success: false, ...error });
|
|
410
410
|
}
|
|
411
411
|
/** Send json response in form { success: true, ... } */
|
|
412
|
-
jsonSuccess(data
|
|
412
|
+
jsonSuccess(data) {
|
|
413
413
|
this.isJson = true;
|
|
414
|
-
this.statusCode = code || 200;
|
|
415
414
|
if (data instanceof Error)
|
|
416
415
|
return this.json(data);
|
|
417
416
|
this.json(typeof data === 'string' ? { success: true, message: data } : { success: true, ...data });
|
|
@@ -427,6 +426,18 @@ export class ServerResponse extends http.ServerResponse {
|
|
|
427
426
|
this.statusCode = code || 302;
|
|
428
427
|
this.end();
|
|
429
428
|
}
|
|
429
|
+
/** Set status code */
|
|
430
|
+
status(code) {
|
|
431
|
+
this.statusCode = code;
|
|
432
|
+
return this;
|
|
433
|
+
}
|
|
434
|
+
download(path, filename) {
|
|
435
|
+
StaticPlugin.serveFile(this.req, this, {
|
|
436
|
+
path: path,
|
|
437
|
+
filename: filename || basename(path),
|
|
438
|
+
mimeType: StaticPlugin.mimeTypes[extname(path)] || 'application/octet-stream'
|
|
439
|
+
});
|
|
440
|
+
}
|
|
430
441
|
}
|
|
431
442
|
const EMPTY_BUFFER = Buffer.alloc(0);
|
|
432
443
|
const DEFLATE_TRAILER = Buffer.from([0x00, 0x00, 0xff, 0xff]);
|
|
@@ -1193,8 +1204,6 @@ export class Router extends EventEmitter {
|
|
|
1193
1204
|
else
|
|
1194
1205
|
this.use(plugin.routes);
|
|
1195
1206
|
}
|
|
1196
|
-
if (plugin.controller)
|
|
1197
|
-
this.use(plugin.controller);
|
|
1198
1207
|
return this;
|
|
1199
1208
|
}
|
|
1200
1209
|
/** Add hook */
|
|
@@ -1438,7 +1447,7 @@ export class MicroServer extends EventEmitter {
|
|
|
1438
1447
|
throw new Error('Invalid middleware: ' + String.toString.call(fn));
|
|
1439
1448
|
return fn.bind(this);
|
|
1440
1449
|
}
|
|
1441
|
-
/** Add middleware, routes, etc.. see {
|
|
1450
|
+
/** Add middleware, routes, etc.. see {router.use} */
|
|
1442
1451
|
use(...args) {
|
|
1443
1452
|
this.router.use(...args);
|
|
1444
1453
|
return this;
|
|
@@ -1587,32 +1596,37 @@ export class MicroServer extends EventEmitter {
|
|
|
1587
1596
|
this.emit('close');
|
|
1588
1597
|
});
|
|
1589
1598
|
}
|
|
1590
|
-
/** Add route, alias to `server.router.
|
|
1599
|
+
/** Add route, alias to `server.router.use(url, ...args)` */
|
|
1600
|
+
all(url, ...args) {
|
|
1601
|
+
this.router.use(url, ...args);
|
|
1602
|
+
return this;
|
|
1603
|
+
}
|
|
1604
|
+
/** Add route, alias to `server.router.use('GET ' + url, ...args)` */
|
|
1591
1605
|
get(url, ...args) {
|
|
1592
1606
|
this.router.use('GET ' + url, ...args);
|
|
1593
1607
|
return this;
|
|
1594
1608
|
}
|
|
1595
|
-
/** Add route, alias to `server.router.
|
|
1609
|
+
/** Add route, alias to `server.router.use('POST ' + url, ...args)` */
|
|
1596
1610
|
post(url, ...args) {
|
|
1597
1611
|
this.router.use('POST ' + url, ...args);
|
|
1598
1612
|
return this;
|
|
1599
1613
|
}
|
|
1600
|
-
/** Add route, alias to `server.router.
|
|
1614
|
+
/** Add route, alias to `server.router.use('PUT ' + url, ...args)` */
|
|
1601
1615
|
put(url, ...args) {
|
|
1602
1616
|
this.router.use('PUT ' + url, ...args);
|
|
1603
1617
|
return this;
|
|
1604
1618
|
}
|
|
1605
|
-
/** Add route, alias to `server.router.
|
|
1619
|
+
/** Add route, alias to `server.router.use('PATCH ' + url, ...args)` */
|
|
1606
1620
|
patch(url, ...args) {
|
|
1607
1621
|
this.router.use('PATCH ' + url, ...args);
|
|
1608
1622
|
return this;
|
|
1609
1623
|
}
|
|
1610
|
-
/** Add route, alias to `server.router.
|
|
1624
|
+
/** Add route, alias to `server.router.use('DELETE ' + url, ...args)` */
|
|
1611
1625
|
delete(url, ...args) {
|
|
1612
1626
|
this.router.use('DELETE ' + url, ...args);
|
|
1613
1627
|
return this;
|
|
1614
1628
|
}
|
|
1615
|
-
/** Add websocket handler, alias to `server.router.
|
|
1629
|
+
/** Add websocket handler, alias to `server.router.use('WEBSOCKET ' + url, ...args)` */
|
|
1616
1630
|
websocket(url, ...args) {
|
|
1617
1631
|
this.router.use('WEBSOCKET ' + url, ...args);
|
|
1618
1632
|
return this;
|
|
@@ -1697,7 +1711,7 @@ class StaticPlugin extends Plugin {
|
|
|
1697
1711
|
options = {};
|
|
1698
1712
|
if (typeof options === 'string')
|
|
1699
1713
|
options = { path: options };
|
|
1700
|
-
this.mimeTypes = { ...StaticPlugin.mimeTypes, ...options.mimeTypes };
|
|
1714
|
+
this.mimeTypes = options.mimeTypes ? { ...StaticPlugin.mimeTypes, ...options.mimeTypes } : Object.freeze(StaticPlugin.mimeTypes);
|
|
1701
1715
|
this.root = path.resolve((options.root || options?.path || 'public').replace(/^\//, '')) + path.sep;
|
|
1702
1716
|
this.ignore = (options.ignore || []).map((p) => path.normalize(path.join(this.root, p)) + path.sep);
|
|
1703
1717
|
this.index = options.index || 'index.html';
|
|
@@ -1714,7 +1728,7 @@ class StaticPlugin extends Plugin {
|
|
|
1714
1728
|
let filename = path.normalize(path.join(this.root, (req.params && req.params.path) || req.pathname));
|
|
1715
1729
|
if (!filename.startsWith(this.root)) // check root access
|
|
1716
1730
|
return next();
|
|
1717
|
-
const firstch =
|
|
1731
|
+
const firstch = basename(filename)[0];
|
|
1718
1732
|
if (firstch === '.' || firstch === '_') // hidden file
|
|
1719
1733
|
return next();
|
|
1720
1734
|
if (filename.endsWith(path.sep))
|
|
@@ -1736,27 +1750,61 @@ class StaticPlugin extends Plugin {
|
|
|
1736
1750
|
req.filename = filename;
|
|
1737
1751
|
return handler.call(this, req, res, next);
|
|
1738
1752
|
}
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1753
|
+
StaticPlugin.serveFile(req, res, {
|
|
1754
|
+
path: filename,
|
|
1755
|
+
mimeType,
|
|
1756
|
+
stats
|
|
1757
|
+
});
|
|
1758
|
+
});
|
|
1759
|
+
}
|
|
1760
|
+
static serveFile(req, res, options) {
|
|
1761
|
+
const filePath = options.root ? path.join(options.root, options.path) : options.path;
|
|
1762
|
+
const statRes = (err, stats) => {
|
|
1763
|
+
if (err)
|
|
1764
|
+
return res.error(err);
|
|
1765
|
+
if (!stats.isFile())
|
|
1766
|
+
return res.error(404);
|
|
1767
|
+
if (!res.getHeader('Content-Type')) {
|
|
1768
|
+
if (options.mimeType)
|
|
1769
|
+
res.setHeader('Content-Type', options.mimeType);
|
|
1770
|
+
else
|
|
1771
|
+
res.setHeader('Content-Type', this.mimeTypes[path.extname(options.path)] || 'application/octet-stream');
|
|
1772
|
+
}
|
|
1773
|
+
if (options.filename)
|
|
1774
|
+
res.setHeader('Content-Disposition', 'attachment; filename="' + options.filename + '"');
|
|
1775
|
+
if (options.lastModified !== false)
|
|
1744
1776
|
res.setHeader('Last-Modified', stats.mtime.toUTCString());
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1777
|
+
res.setHeader('Content-Length', stats.size);
|
|
1778
|
+
if (options.etag !== false) {
|
|
1779
|
+
const etag = '"' + etagPrefix + stats.mtime.getTime().toString(32) + '"';
|
|
1780
|
+
if (req.headers['if-none-match'] === etag || req.headers['if-modified-since'] === stats.mtime.toUTCString()) {
|
|
1781
|
+
res.statusCode = 304;
|
|
1782
|
+
res.headersOnly = true;
|
|
1783
|
+
}
|
|
1784
|
+
}
|
|
1785
|
+
if (options.maxAge)
|
|
1786
|
+
res.setHeader('Cache-Control', 'max-age=' + options.maxAge);
|
|
1749
1787
|
if (res.headersOnly) {
|
|
1750
|
-
res.
|
|
1751
|
-
return
|
|
1788
|
+
res.end();
|
|
1789
|
+
return;
|
|
1752
1790
|
}
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1791
|
+
const streamOptions = { start: 0, end: stats.size - 1 };
|
|
1792
|
+
if (options.range !== false) {
|
|
1793
|
+
const range = req.headers['range'];
|
|
1794
|
+
if (range && range.startsWith('bytes=')) {
|
|
1795
|
+
const parts = range.slice(6).split('-');
|
|
1796
|
+
streamOptions.start = parseInt(parts[0]) || 0;
|
|
1797
|
+
streamOptions.end = parts[1] ? parseInt(parts[1]) : stats.size - 1;
|
|
1798
|
+
res.setHeader('Content-Range', `bytes ${streamOptions.start}-${streamOptions.end}/${stats.size}`);
|
|
1799
|
+
res.setHeader('Content-Length', streamOptions.end - streamOptions.start + 1);
|
|
1800
|
+
}
|
|
1756
1801
|
}
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1802
|
+
fs.createReadStream(filePath, streamOptions).pipe(res);
|
|
1803
|
+
};
|
|
1804
|
+
if (!options.stats)
|
|
1805
|
+
fs.stat(filePath, statRes);
|
|
1806
|
+
else
|
|
1807
|
+
statRes(null, options.stats);
|
|
1760
1808
|
}
|
|
1761
1809
|
}
|
|
1762
1810
|
/** Default mime types */
|
|
@@ -1770,12 +1818,17 @@ StaticPlugin.mimeTypes = {
|
|
|
1770
1818
|
'.css': 'text/css',
|
|
1771
1819
|
'.png': 'image/png',
|
|
1772
1820
|
'.jpg': 'image/jpeg',
|
|
1773
|
-
'.mp3': 'audio/mpeg',
|
|
1774
1821
|
'.svg': 'image/svg+xml',
|
|
1822
|
+
'.mp3': 'audio/mpeg',
|
|
1823
|
+
'.ogg': 'audio/ogg',
|
|
1824
|
+
'.mp4': 'video/mp4',
|
|
1775
1825
|
'.pdf': 'application/pdf',
|
|
1776
1826
|
'.woff': 'application/x-font-woff',
|
|
1777
1827
|
'.woff2': 'application/x-font-woff2',
|
|
1778
|
-
'.ttf': 'application/x-font-ttf'
|
|
1828
|
+
'.ttf': 'application/x-font-ttf',
|
|
1829
|
+
'.gz': 'application/gzip',
|
|
1830
|
+
'.zip': 'application/zip',
|
|
1831
|
+
'.tgz': 'application/gzip',
|
|
1779
1832
|
};
|
|
1780
1833
|
MicroServer.plugins.static = StaticPlugin;
|
|
1781
1834
|
export class ProxyPlugin extends Plugin {
|
|
@@ -2201,7 +2254,7 @@ class AuthPlugin extends Plugin {
|
|
|
2201
2254
|
if (sid)
|
|
2202
2255
|
token = sid.slice(sid.indexOf('=') + 1);
|
|
2203
2256
|
if (!token)
|
|
2204
|
-
token = req.
|
|
2257
|
+
token = req.query.token;
|
|
2205
2258
|
if (token) {
|
|
2206
2259
|
const now = new Date().getTime();
|
|
2207
2260
|
let usr, expire;
|
|
@@ -2794,7 +2847,7 @@ export class Model {
|
|
|
2794
2847
|
/** Microserver middleware */
|
|
2795
2848
|
handler(req, res) {
|
|
2796
2849
|
res.isJson = true;
|
|
2797
|
-
let filter, filterStr = req.
|
|
2850
|
+
let filter, filterStr = req.query.filter;
|
|
2798
2851
|
if (filterStr) {
|
|
2799
2852
|
try {
|
|
2800
2853
|
if (!filterStr.startsWith('{'))
|
package/package.json
CHANGED
package/readme.md
CHANGED
|
@@ -1,35 +1,36 @@
|
|
|
1
1
|
## HTTP MicroServer
|
|
2
2
|
|
|
3
|
-
Lightweight all-in-one http web server
|
|
3
|
+
Lightweight all-in-one http web server without dependencies
|
|
4
4
|
|
|
5
5
|
Features:
|
|
6
6
|
- fast REST API router
|
|
7
7
|
- form/json body decoder
|
|
8
8
|
- file upload
|
|
9
|
+
- websockets
|
|
9
10
|
- authentication
|
|
10
11
|
- plain/hashed passwords
|
|
11
12
|
- virtual hosts
|
|
12
13
|
- static files
|
|
14
|
+
- rewrite
|
|
15
|
+
- redirect
|
|
13
16
|
- reverse proxy routes
|
|
14
17
|
- trust ip for reverse proxy
|
|
15
18
|
- json file storage with autosave
|
|
16
|
-
- websockets (single file module from ws package)
|
|
17
19
|
- tls with automatic certificate reload
|
|
18
20
|
- data model with validation and mongodb interface
|
|
19
|
-
- simple file/memory storage
|
|
21
|
+
- simple file/memory storage
|
|
20
22
|
- promises as middleware
|
|
21
23
|
- controller class
|
|
22
24
|
- access rights per route
|
|
23
25
|
- access rights per model field
|
|
24
|
-
- rewrite
|
|
25
|
-
- redirect
|
|
26
|
-
- express like interface
|
|
27
26
|
|
|
28
27
|
### Usage examples:
|
|
29
28
|
|
|
30
29
|
Simple router:
|
|
31
30
|
|
|
32
31
|
```ts
|
|
32
|
+
import { MicroServer, AccessDenied } from '@radatek/microserver'
|
|
33
|
+
|
|
33
34
|
const server = new MicroServer({
|
|
34
35
|
listen: 8080,
|
|
35
36
|
auth: {
|
|
@@ -47,7 +48,7 @@ server.use('POST /api/login',
|
|
|
47
48
|
(req: ServerRequset, res: ServerResponse) =>
|
|
48
49
|
{
|
|
49
50
|
const user = await req.auth.login(req.body.user, req.body.password)
|
|
50
|
-
return user ? {user} :
|
|
51
|
+
return user ? {user} : new AccessDenied()
|
|
51
52
|
})
|
|
52
53
|
server.use('GET /api/protected', 'acl:auth',
|
|
53
54
|
(req: ServerRequset, res: ServerResponse) =>
|
|
@@ -55,49 +56,14 @@ server.use('GET /api/protected', 'acl:auth',
|
|
|
55
56
|
server.use('static', {root:'public'})
|
|
56
57
|
```
|
|
57
58
|
|
|
58
|
-
Using
|
|
59
|
-
|
|
60
|
-
```ts
|
|
61
|
-
class RestApi extends Controller {
|
|
62
|
-
static acl = '' // default acl
|
|
63
|
-
|
|
64
|
-
gethello(id) {
|
|
65
|
-
return {message:'Hello ' + id + '!'}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
async postlogin() {
|
|
69
|
-
const user = await this.auth.login(this.body.user, this.body.password)
|
|
70
|
-
return user ? {user} : 403
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
static 'acl:protected' = 'user'
|
|
74
|
-
static 'url:protected' = 'GET /protected'
|
|
75
|
-
protected() {
|
|
76
|
-
return {message:'Protected'}
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
const server = new MicroServer({
|
|
81
|
-
listen: 8080,
|
|
82
|
-
auth: {
|
|
83
|
-
users: {
|
|
84
|
-
usr: {
|
|
85
|
-
password: 'secret',
|
|
86
|
-
acl: {user: true}
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
})
|
|
91
|
-
server.use('/api', RestApi)
|
|
92
|
-
```
|
|
93
|
-
|
|
94
|
-
`Model.handler` automatically detects usage for standard functions: get,insert,update,delete
|
|
59
|
+
Using data schema:
|
|
95
60
|
|
|
96
61
|
```js
|
|
97
|
-
import { MicroServer, Model, MicroCollection, FileStore } from '@
|
|
62
|
+
import { MicroServer, Model, MicroCollection, FileStore } from '@radatek/microserver'
|
|
98
63
|
|
|
99
64
|
const usersCollection = new MicroCollection({ store: new FileStore({ dir: 'data' }), name: 'users' })
|
|
100
|
-
//
|
|
65
|
+
// or using MicroDB collection
|
|
66
|
+
const usersCollection = await db.collection('users')
|
|
101
67
|
|
|
102
68
|
const userProfile = new Model({
|
|
103
69
|
_id: 'string',
|
|
@@ -115,7 +81,7 @@ const server = new MicroServer({
|
|
|
115
81
|
}
|
|
116
82
|
})
|
|
117
83
|
|
|
118
|
-
userProfile.insert({name: 'admin', password: 'secret', role: 'admin', acl: {'user/*': true}})
|
|
84
|
+
await userProfile.insert({name: 'admin', password: 'secret', role: 'admin', acl: {'user/*': true}})
|
|
119
85
|
|
|
120
86
|
server.use('POST /login', async (req) => {
|
|
121
87
|
const user = await req.auth.login(req.body.user, req.body.password)
|
|
@@ -134,3 +100,40 @@ server.use('PUT /admin/user/:id', 'acl:user/update', userProfile)
|
|
|
134
100
|
// delete user if has acl 'user/update'
|
|
135
101
|
server.use('DELETE /admin/user/:id', 'acl:user/delete', userProfile)
|
|
136
102
|
```
|
|
103
|
+
|
|
104
|
+
Using controller:
|
|
105
|
+
|
|
106
|
+
```ts
|
|
107
|
+
const server = new MicroServer({
|
|
108
|
+
listen: 8080,
|
|
109
|
+
auth: {
|
|
110
|
+
users: {
|
|
111
|
+
usr: {
|
|
112
|
+
password: 'secret',
|
|
113
|
+
acl: {user: true}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
class RestApi extends Controller {
|
|
120
|
+
static acl = '' // default acl
|
|
121
|
+
|
|
122
|
+
gethello(id) {
|
|
123
|
+
return {message:'Hello ' + id + '!'}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
async postlogin() {
|
|
127
|
+
const user = await this.auth.login(this.body.user, this.body.password)
|
|
128
|
+
return user ? {user} : 403
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
static 'acl:protected' = 'user'
|
|
132
|
+
static 'url:protected' = 'GET /protected'
|
|
133
|
+
protected() {
|
|
134
|
+
return {message:'Protected'}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
server.use('/api', RestApi)
|
|
139
|
+
```
|