@radatek/microserver 3.0.3 → 3.0.4
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/README.md +193 -0
- package/microserver.d.ts +29 -24
- package/microserver.js +39 -39
- package/package.json +1 -1
package/README.md
ADDED
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
## HTTP MicroServer
|
|
2
|
+
|
|
3
|
+
Lightweight all-in-one http web server without dependencies
|
|
4
|
+
|
|
5
|
+
Features:
|
|
6
|
+
- fast REST API router
|
|
7
|
+
- form/json body decoder
|
|
8
|
+
- file upload
|
|
9
|
+
- websockets
|
|
10
|
+
- authentication
|
|
11
|
+
- plain/hashed passwords
|
|
12
|
+
- virtual hosts
|
|
13
|
+
- static files
|
|
14
|
+
- rewrite
|
|
15
|
+
- redirect
|
|
16
|
+
- reverse proxy routes
|
|
17
|
+
- trust ip for reverse proxy
|
|
18
|
+
- json file storage with autosave
|
|
19
|
+
- tls with automatic certificate reload
|
|
20
|
+
- data model with validation and mongodb interface
|
|
21
|
+
- typescript model schema translation
|
|
22
|
+
- simple file/memory storage
|
|
23
|
+
- promises as middleware
|
|
24
|
+
- controller class
|
|
25
|
+
- access rights per route
|
|
26
|
+
- access rights per model field
|
|
27
|
+
|
|
28
|
+
### Usage examples:
|
|
29
|
+
|
|
30
|
+
Simple router:
|
|
31
|
+
|
|
32
|
+
```ts
|
|
33
|
+
import { MicroServer, AccessDenied, StandardPlugins, StaticPlugin } from '@radatek/microserver'
|
|
34
|
+
|
|
35
|
+
const server = new MicroServer({
|
|
36
|
+
listen: 8080,
|
|
37
|
+
auth: {
|
|
38
|
+
users: {
|
|
39
|
+
usr: {
|
|
40
|
+
password: 'secret'
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
})
|
|
45
|
+
server.use(StandardPlugins)
|
|
46
|
+
server.use('GET /api/hello/:id',
|
|
47
|
+
(req: ServerRequset, res: ServerResponse) =>
|
|
48
|
+
({message:'Hello ' + req.params.id + '!'}))
|
|
49
|
+
server.use('POST /api/login',
|
|
50
|
+
(req: ServerRequset, res: ServerResponse) =>
|
|
51
|
+
{
|
|
52
|
+
const user = await req.auth.login(req.body.user, req.body.password)
|
|
53
|
+
return user ? {user} : new AccessDenied()
|
|
54
|
+
})
|
|
55
|
+
server.use('GET /api/protected', 'acl:auth',
|
|
56
|
+
(req: ServerRequset, res: ServerResponse) =>
|
|
57
|
+
({message:'Secret resource'}))
|
|
58
|
+
server.use(StaticPlugin, {root:'public'})
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Using WebSockets
|
|
62
|
+
|
|
63
|
+
```ts
|
|
64
|
+
import { MicroServer, WebSocketsPlugin } from '@radatek/microserver'
|
|
65
|
+
|
|
66
|
+
const server = new MicroServer({
|
|
67
|
+
listen: 8080
|
|
68
|
+
})
|
|
69
|
+
server.use(WebSocketsPlugin)
|
|
70
|
+
server.use('WEBSOCKET /ws', (req: ServerRequset, res: ServerResponse) => {
|
|
71
|
+
req.websocket.on('message', (data) => console.log(data))
|
|
72
|
+
req.websocket.on('close', () => console.log('closed'))
|
|
73
|
+
})
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Using data schema:
|
|
77
|
+
|
|
78
|
+
```js
|
|
79
|
+
import { MicroServer, Model, MicroCollection } from '@radatek/microserver'
|
|
80
|
+
|
|
81
|
+
const db = new MicroCollectionStore('./data')
|
|
82
|
+
// or using MicroDB collection
|
|
83
|
+
//const db = new MicroDB('microdb://data')
|
|
84
|
+
|
|
85
|
+
const usersCollection = await db.collection('users')
|
|
86
|
+
|
|
87
|
+
const userProfile = new Model({
|
|
88
|
+
_id: 'string',
|
|
89
|
+
name: { type: 'string', required: true },
|
|
90
|
+
email: { type: 'string', format: 'email' },
|
|
91
|
+
password: { type: 'string', canRead: false },
|
|
92
|
+
role: { type: 'string' },
|
|
93
|
+
acl: { type: 'object' },
|
|
94
|
+
}, { collection: usersCollection, name: 'user' })
|
|
95
|
+
|
|
96
|
+
const server = new MicroServer({
|
|
97
|
+
listen:8080,
|
|
98
|
+
auth: {
|
|
99
|
+
users: (user, password) => userProfile.get(password ? {_id: user, password } : {_id: user})
|
|
100
|
+
}
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
await userProfile.insert({name: 'admin', password: 'secret', role: 'admin', acl: {'user/*': true}})
|
|
104
|
+
|
|
105
|
+
server.use('POST /login', async (req) => {
|
|
106
|
+
const user = await req.auth.login(req.body.user, req.body.password)
|
|
107
|
+
return user ? { user } : 403
|
|
108
|
+
})
|
|
109
|
+
// authenticated user allways has auth access
|
|
110
|
+
server.use('GET /profile', 'acl:auth', req => ({ user: req.user }))
|
|
111
|
+
// get all users if role='admin'
|
|
112
|
+
server.use('GET /admin/users', 'role:admin', userProfile)
|
|
113
|
+
// get user by id if has acl 'user/get'
|
|
114
|
+
server.use('GET /admin/user/:id', 'acl:user/get', userProfile)
|
|
115
|
+
// insert new user if role='admin' and has acl 'user/insert'
|
|
116
|
+
server.use('POST /admin/user', 'role:admin', 'acl:user/insert', userProfile)
|
|
117
|
+
// update user if has acl 'user/update'
|
|
118
|
+
server.use('PUT /admin/user/:id', 'acl:user/update', userProfile)
|
|
119
|
+
// delete user if has acl 'user/update'
|
|
120
|
+
server.use('DELETE /admin/user/:id', 'acl:user/delete', userProfile)
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Using controller:
|
|
124
|
+
|
|
125
|
+
```ts
|
|
126
|
+
const server = new MicroServer({
|
|
127
|
+
listen: 8080,
|
|
128
|
+
auth: {
|
|
129
|
+
users: {
|
|
130
|
+
usr: {
|
|
131
|
+
password: 'secret',
|
|
132
|
+
acl: {
|
|
133
|
+
user: true,
|
|
134
|
+
'user/insert': true
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
})
|
|
140
|
+
server.use(StandardPlugins)
|
|
141
|
+
|
|
142
|
+
new MicroCollectionStore('data') // initialize simple file store for models
|
|
143
|
+
//new MicroCollectionStore() // initialize simple memory store for models
|
|
144
|
+
|
|
145
|
+
class RestApi extends Controller {
|
|
146
|
+
static acl = '' // default acl
|
|
147
|
+
|
|
148
|
+
gethello(id) { // same as 'GET /hello'
|
|
149
|
+
return {message:'Hello ' + id + '!'}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
async post_login() { // same as 'POST /login'
|
|
153
|
+
const user = await this.auth.login(this.body.user, this.body.password)
|
|
154
|
+
return user ? {user} : 403
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
static 'acl:protected' = 'user'
|
|
158
|
+
static 'url:protected' = 'GET /protected'
|
|
159
|
+
protected() {
|
|
160
|
+
return {message:'Protected'}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
server.use('/api', RestApi)
|
|
165
|
+
|
|
166
|
+
const AddressModel = new Model({
|
|
167
|
+
street: String,
|
|
168
|
+
city: String
|
|
169
|
+
})
|
|
170
|
+
|
|
171
|
+
const UserModel = Model.define({
|
|
172
|
+
name: { type: String, require: true },
|
|
173
|
+
address: AddressModel
|
|
174
|
+
})
|
|
175
|
+
|
|
176
|
+
class UserController extends Controller<typeof UserModel> {
|
|
177
|
+
static model = UserModel
|
|
178
|
+
static name = 'user'
|
|
179
|
+
|
|
180
|
+
static 'acl:get' = 'user/get'
|
|
181
|
+
async get(id: string) { // same as 'GET user'
|
|
182
|
+
return {user: await this.model.findOne({id})}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
static 'acl:insert' = 'user/insert'
|
|
186
|
+
async insert() { // same as 'POST user'
|
|
187
|
+
await this.model.insert(this.req.body)
|
|
188
|
+
return {}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
server.use('/api/user', UserController)
|
|
193
|
+
```
|
package/microserver.d.ts
CHANGED
|
@@ -87,10 +87,15 @@ export declare class ServerRequest<T = any> extends http.IncomingMessage {
|
|
|
87
87
|
/** Request raw body size */
|
|
88
88
|
rawBodySize: number;
|
|
89
89
|
private constructor();
|
|
90
|
+
/** Extend http.IncomingMessage */
|
|
90
91
|
static extend(req: http.IncomingMessage, res: http.ServerResponse, server: MicroServer): ServerRequest;
|
|
92
|
+
/** Check if request is ready */
|
|
91
93
|
get isReady(): boolean;
|
|
94
|
+
/** Wait for request to be ready, usualy used to wait for file upload */
|
|
92
95
|
waitReady(): Promise<void>;
|
|
96
|
+
/** Signal request as ready */
|
|
93
97
|
setReady(err?: Error): void;
|
|
98
|
+
/** Set request body */
|
|
94
99
|
setBody(body: ServerRequestBody<T>): void;
|
|
95
100
|
/** Update request url */
|
|
96
101
|
updateUrl(url: string): this;
|
|
@@ -108,9 +113,10 @@ export declare class ServerRequest<T = any> extends http.IncomingMessage {
|
|
|
108
113
|
/** Extends http.ServerResponse */
|
|
109
114
|
export declare class ServerResponse<T = any> extends http.ServerResponse {
|
|
110
115
|
readonly req: ServerRequest<T>;
|
|
116
|
+
/** Should response be json */
|
|
111
117
|
isJson: boolean;
|
|
112
|
-
headersOnly: boolean;
|
|
113
118
|
private constructor();
|
|
119
|
+
/** Extends http.ServerResponse */
|
|
114
120
|
static extend(res: http.ServerResponse): void;
|
|
115
121
|
/** Send error reponse */
|
|
116
122
|
error(error: string | number | Error): void;
|
|
@@ -167,11 +173,13 @@ interface MicroServerEvents {
|
|
|
167
173
|
}
|
|
168
174
|
/** Lighweight HTTP server */
|
|
169
175
|
export declare class MicroServer extends EventEmitter {
|
|
176
|
+
/** MicroServer configuration */
|
|
170
177
|
config: MicroServerConfig;
|
|
178
|
+
/** Authorization object */
|
|
171
179
|
auth?: Auth;
|
|
172
|
-
/**
|
|
180
|
+
/** All sockets */
|
|
173
181
|
sockets: Set<net.Socket> | undefined;
|
|
174
|
-
/**
|
|
182
|
+
/** Server instances */
|
|
175
183
|
servers: Set<net.Server> | undefined;
|
|
176
184
|
/** @param {MicroServerConfig} [config] MicroServer configuration */
|
|
177
185
|
constructor(config?: MicroServerConfig);
|
|
@@ -184,40 +192,39 @@ export declare class MicroServer extends EventEmitter {
|
|
|
184
192
|
waitReady(): Promise<void>;
|
|
185
193
|
/** Listen server, should be used only if config.listen is not set */
|
|
186
194
|
listen(config: ListenConfig): Promise<void>;
|
|
187
|
-
/** 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' */
|
|
188
|
-
_bind(fn: string | Function | object): Function;
|
|
189
195
|
/** Default server handler */
|
|
190
196
|
handler(req: ServerRequest, res: ServerResponse): void;
|
|
191
|
-
/** Last request handler */
|
|
192
|
-
handlerLast(req: ServerRequest, res: ServerResponse, next?: Function): any;
|
|
193
197
|
/** Clear routes and middlewares */
|
|
194
198
|
clear(): this;
|
|
195
199
|
/**
|
|
196
|
-
* Add middleware
|
|
200
|
+
* Add middleware, plugin or routes to server.
|
|
197
201
|
* Middlewares may return promises for res.jsonSuccess(...), throw errors for res.error(...), return string or {} for res.send(...)
|
|
198
202
|
* RouteURL: 'METHOD /suburl', 'METHOD', '* /suburl'
|
|
199
203
|
*/
|
|
200
204
|
use(...args: [
|
|
201
205
|
Middleware | Plugin | ControllerClass | RoutesSet
|
|
202
206
|
] | [Promise<Middleware | Plugin | ControllerClass | RoutesSet>] | RoutesList | [RouteURL, RoutesSet] | [PluginClass | Promise<PluginClass>, options?: any]): Promise<void>;
|
|
207
|
+
/** Add middleware to stack, with optional priority */
|
|
203
208
|
addStack(middleware: Middleware): void;
|
|
209
|
+
/** Get plugin */
|
|
204
210
|
getPlugin(id: string): Plugin | undefined;
|
|
211
|
+
/** Wait for plugin */
|
|
205
212
|
waitPlugin(id: string): Promise<Plugin>;
|
|
206
|
-
/** Add route, alias to `server.
|
|
213
|
+
/** Add route, alias to `server.use(url, ...args)` */
|
|
207
214
|
all(url: `/${string}`, ...args: RoutesMiddleware[]): MicroServer;
|
|
208
|
-
/** Add route, alias to `server.
|
|
215
|
+
/** Add route, alias to `server.use('GET ' + url, ...args)` */
|
|
209
216
|
get(url: `/${string}`, ...args: RoutesMiddleware[]): MicroServer;
|
|
210
|
-
/** Add route, alias to `server.
|
|
217
|
+
/** Add route, alias to `server.use('POST ' + url, ...args)` */
|
|
211
218
|
post(url: `/${string}`, ...args: RoutesMiddleware[]): MicroServer;
|
|
212
|
-
/** Add route, alias to `server.
|
|
219
|
+
/** Add route, alias to `server.use('PUT ' + url, ...args)` */
|
|
213
220
|
put(url: `/${string}`, ...args: RoutesMiddleware[]): MicroServer;
|
|
214
|
-
/** Add route, alias to `server.
|
|
221
|
+
/** Add route, alias to `server.use('PATCH ' + url, ...args)` */
|
|
215
222
|
patch(url: `/${string}`, ...args: RoutesMiddleware[]): MicroServer;
|
|
216
|
-
/** Add route, alias to `server.
|
|
223
|
+
/** Add route, alias to `server.use('DELETE ' + url, ...args)` */
|
|
217
224
|
delete(url: `/${string}`, ...args: RoutesMiddleware[]): MicroServer;
|
|
218
|
-
/** Add websocket handler, alias to `server.
|
|
225
|
+
/** Add websocket handler, alias to `server.use('WEBSOCKET ' + url, ...args)` */
|
|
219
226
|
websocket(url: `/${string}`, ...args: RoutesMiddleware[]): MicroServer;
|
|
220
|
-
/** Add router hook, alias to `server.
|
|
227
|
+
/** Add router hook, alias to `server.hook(url, ...args)` */
|
|
221
228
|
hook(url: RouteURL, ...args: RoutesMiddleware[]): MicroServer;
|
|
222
229
|
/** Check if middleware allready added */
|
|
223
230
|
has(mid: Middleware): boolean;
|
|
@@ -263,8 +270,6 @@ export declare class CorsPlugin extends Plugin {
|
|
|
263
270
|
export declare class MethodsPlugin extends Plugin {
|
|
264
271
|
priority: number;
|
|
265
272
|
name: string;
|
|
266
|
-
_methods: string;
|
|
267
|
-
_methodsIdx: Record<string, boolean>;
|
|
268
273
|
constructor(methods?: string);
|
|
269
274
|
handler(req: ServerRequest, res: ServerResponse, next: Function): any;
|
|
270
275
|
}
|
|
@@ -274,7 +279,6 @@ export interface BodyOptions {
|
|
|
274
279
|
export declare class BodyPlugin extends Plugin {
|
|
275
280
|
priority: number;
|
|
276
281
|
name: string;
|
|
277
|
-
_maxBodySize: number;
|
|
278
282
|
constructor(options?: BodyOptions);
|
|
279
283
|
handler(req: ServerRequest, res: ServerResponse, next: () => void): void;
|
|
280
284
|
}
|
|
@@ -292,8 +296,6 @@ export interface UploadFile {
|
|
|
292
296
|
export declare class UploadPlugin extends Plugin {
|
|
293
297
|
priority: number;
|
|
294
298
|
name: string;
|
|
295
|
-
_maxFileSize: number;
|
|
296
|
-
_uploadDir?: string;
|
|
297
299
|
constructor(options?: UploadOptions);
|
|
298
300
|
handler(req: ServerRequest, res: ServerResponse, next: () => void): void;
|
|
299
301
|
}
|
|
@@ -335,7 +337,6 @@ export declare class WebSocket extends EventEmitter {
|
|
|
335
337
|
ping(buffer?: Buffer): void;
|
|
336
338
|
/** Send pong frame */
|
|
337
339
|
pong(buffer?: Buffer): void;
|
|
338
|
-
protected _sendFrame(opcode: number, data: Buffer, cb?: () => void): void;
|
|
339
340
|
on<K extends keyof WebSocketEvents>(event: K, listener: WebSocketEvents[K]): this;
|
|
340
341
|
addListener<K extends keyof WebSocketEvents>(event: K, listener: WebSocketEvents[K]): this;
|
|
341
342
|
once<K extends keyof WebSocketEvents>(event: K, listener: WebSocketEvents[K]): this;
|
|
@@ -344,9 +345,8 @@ export declare class WebSocket extends EventEmitter {
|
|
|
344
345
|
}
|
|
345
346
|
export declare class WebSocketPlugin extends Plugin {
|
|
346
347
|
name: string;
|
|
347
|
-
_handler: (req: ServerRequest, socket: net.Socket, head: any) => void;
|
|
348
348
|
constructor(options?: any, server?: MicroServer);
|
|
349
|
-
addUpgradeHandler
|
|
349
|
+
private addUpgradeHandler;
|
|
350
350
|
upgradeHandler(server: MicroServer, req: ServerRequest, socket: net.Socket, head: any): void;
|
|
351
351
|
}
|
|
352
352
|
/** Trust proxy plugin, adds `req.ip` and `req.localip` */
|
|
@@ -447,6 +447,7 @@ export declare class StaticFilesPlugin extends Plugin {
|
|
|
447
447
|
constructor(options?: StaticFilesOptions | string, server?: MicroServer);
|
|
448
448
|
/** Default static files handler */
|
|
449
449
|
handler(req: ServerRequest, res: ServerResponse, next: Function): any;
|
|
450
|
+
/** Send static file */
|
|
450
451
|
serveFile(req: ServerRequest, res: ServerResponse, options: ServeFileOptions): void;
|
|
451
452
|
}
|
|
452
453
|
/** Proxy plugin options */
|
|
@@ -705,7 +706,9 @@ export declare class FileStore {
|
|
|
705
706
|
constructor(options?: FileStoreOptions);
|
|
706
707
|
/** cleanup cache */
|
|
707
708
|
cleanup(): void;
|
|
709
|
+
/** close store */
|
|
708
710
|
close(): Promise<void>;
|
|
711
|
+
/** sync data to disk */
|
|
709
712
|
sync(): Promise<void>;
|
|
710
713
|
/** load json file data */
|
|
711
714
|
load(name: string, autosave?: boolean): Promise<any>;
|
|
@@ -923,10 +926,12 @@ export declare class MicroCollection<TSchema extends ModelSchema = any> {
|
|
|
923
926
|
deleteOne(query: Query): Promise<number>;
|
|
924
927
|
/** Delete all matching documents */
|
|
925
928
|
deleteMany(query: Query): Promise<number>;
|
|
929
|
+
/** Update one matching document */
|
|
926
930
|
updateOne(query: Query, update: Record<string, any>, options?: FindOptions): Promise<{
|
|
927
931
|
upsertedId: any;
|
|
928
932
|
modifiedCount: number;
|
|
929
933
|
}>;
|
|
934
|
+
/** Update many matching documents */
|
|
930
935
|
updateMany(query: Query, update: Record<string, any>, options?: FindOptions): Promise<{
|
|
931
936
|
upsertedId: any;
|
|
932
937
|
modifiedCount: number;
|
package/microserver.js
CHANGED
|
@@ -122,6 +122,7 @@ export class ServerRequest extends http.IncomingMessage {
|
|
|
122
122
|
super(new net.Socket());
|
|
123
123
|
ServerRequest.extend(this, res, server);
|
|
124
124
|
}
|
|
125
|
+
/** Extend http.IncomingMessage */
|
|
125
126
|
static extend(req, res, server) {
|
|
126
127
|
const reqNew = Object.setPrototypeOf(req, ServerRequest.prototype);
|
|
127
128
|
let ip = req.socket.remoteAddress || '::1';
|
|
@@ -153,9 +154,11 @@ export class ServerRequest extends http.IncomingMessage {
|
|
|
153
154
|
reqNew.updateUrl(req.url || '/');
|
|
154
155
|
return reqNew;
|
|
155
156
|
}
|
|
157
|
+
/** Check if request is ready */
|
|
156
158
|
get isReady() {
|
|
157
159
|
return this._isReady === undefined;
|
|
158
160
|
}
|
|
161
|
+
/** Wait for request to be ready, usualy used to wait for file upload */
|
|
159
162
|
async waitReady() {
|
|
160
163
|
if (this._isReady === undefined)
|
|
161
164
|
return;
|
|
@@ -163,9 +166,11 @@ export class ServerRequest extends http.IncomingMessage {
|
|
|
163
166
|
if (res && res instanceof Error)
|
|
164
167
|
throw res;
|
|
165
168
|
}
|
|
169
|
+
/** Signal request as ready */
|
|
166
170
|
setReady(err) {
|
|
167
171
|
this._isReady?.resolve(err);
|
|
168
172
|
}
|
|
173
|
+
/** Set request body */
|
|
169
174
|
setBody(body) {
|
|
170
175
|
this._body = body;
|
|
171
176
|
}
|
|
@@ -205,19 +210,19 @@ export class ServerRequest extends http.IncomingMessage {
|
|
|
205
210
|
}
|
|
206
211
|
/** Extends http.ServerResponse */
|
|
207
212
|
export class ServerResponse extends http.ServerResponse {
|
|
213
|
+
/** Should response be json */
|
|
208
214
|
isJson;
|
|
209
|
-
headersOnly;
|
|
210
215
|
constructor(server) {
|
|
211
216
|
super(new http.IncomingMessage(new net.Socket()));
|
|
212
217
|
ServerRequest.extend(this.req, this, server);
|
|
213
218
|
ServerResponse.extend(this);
|
|
214
219
|
}
|
|
220
|
+
/** Extends http.ServerResponse */
|
|
215
221
|
static extend(res) {
|
|
216
222
|
Object.setPrototypeOf(res, ServerResponse.prototype);
|
|
217
223
|
Object.assign(res, {
|
|
218
224
|
statusCode: 200,
|
|
219
|
-
isJson: false
|
|
220
|
-
headersOnly: false
|
|
225
|
+
isJson: false
|
|
221
226
|
});
|
|
222
227
|
}
|
|
223
228
|
/** Send error reponse */
|
|
@@ -282,7 +287,7 @@ export class ServerResponse extends http.ServerResponse {
|
|
|
282
287
|
return (data.pipe(this, { end: true }), void 0);
|
|
283
288
|
if (data instanceof Buffer) {
|
|
284
289
|
this.setHeader('Content-Length', data.byteLength);
|
|
285
|
-
if (this.
|
|
290
|
+
if (this.statusCode === 304 || this.req.method === 'HEAD')
|
|
286
291
|
this.end();
|
|
287
292
|
else
|
|
288
293
|
this.end(data);
|
|
@@ -301,7 +306,7 @@ export class ServerResponse extends http.ServerResponse {
|
|
|
301
306
|
this.setHeader('Content-Type', 'text/plain');
|
|
302
307
|
}
|
|
303
308
|
this.setHeader('Content-Length', Buffer.byteLength(data, 'utf8'));
|
|
304
|
-
if (this.
|
|
309
|
+
if (this.statusCode === 304 || this.req.method === 'HEAD')
|
|
305
310
|
this.end();
|
|
306
311
|
else
|
|
307
312
|
this.end(data, 'utf8');
|
|
@@ -356,15 +361,17 @@ export class ServerResponse extends http.ServerResponse {
|
|
|
356
361
|
}
|
|
357
362
|
/** Lighweight HTTP server */
|
|
358
363
|
export class MicroServer extends EventEmitter {
|
|
364
|
+
/** MicroServer configuration */
|
|
359
365
|
config;
|
|
366
|
+
/** Authorization object */
|
|
360
367
|
auth;
|
|
361
368
|
_plugins = {};
|
|
362
369
|
_stack = [];
|
|
363
370
|
_router = new RouterPlugin();
|
|
364
371
|
_worker = new Worker();
|
|
365
|
-
/**
|
|
372
|
+
/** All sockets */
|
|
366
373
|
sockets;
|
|
367
|
-
/**
|
|
374
|
+
/** Server instances */
|
|
368
375
|
servers;
|
|
369
376
|
/** @param {MicroServerConfig} [config] MicroServer configuration */
|
|
370
377
|
constructor(config) {
|
|
@@ -491,7 +498,7 @@ export class MicroServer extends EventEmitter {
|
|
|
491
498
|
});
|
|
492
499
|
return this._worker.wait('listen');
|
|
493
500
|
}
|
|
494
|
-
|
|
501
|
+
/* 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' */
|
|
495
502
|
_bind(fn) {
|
|
496
503
|
if (typeof fn === 'string') {
|
|
497
504
|
let name = fn;
|
|
@@ -591,15 +598,6 @@ export class MicroServer extends EventEmitter {
|
|
|
591
598
|
ServerRequest.extend(req, res, this);
|
|
592
599
|
ServerResponse.extend(res);
|
|
593
600
|
this._router.walk(this._stack, req, res, () => res.error(404));
|
|
594
|
-
//this.handlerRouter(req, res, () => this.handlerLast(req, res))
|
|
595
|
-
}
|
|
596
|
-
/** Last request handler */
|
|
597
|
-
handlerLast(req, res, next) {
|
|
598
|
-
if (res.headersSent || res.closed)
|
|
599
|
-
return;
|
|
600
|
-
if (!next)
|
|
601
|
-
next = () => res.error(404);
|
|
602
|
-
return next();
|
|
603
601
|
}
|
|
604
602
|
/** Clear routes and middlewares */
|
|
605
603
|
clear() {
|
|
@@ -610,7 +608,7 @@ export class MicroServer extends EventEmitter {
|
|
|
610
608
|
return this;
|
|
611
609
|
}
|
|
612
610
|
/**
|
|
613
|
-
* Add middleware
|
|
611
|
+
* Add middleware, plugin or routes to server.
|
|
614
612
|
* Middlewares may return promises for res.jsonSuccess(...), throw errors for res.error(...), return string or {} for res.send(...)
|
|
615
613
|
* RouteURL: 'METHOD /suburl', 'METHOD', '* /suburl'
|
|
616
614
|
*/
|
|
@@ -704,6 +702,7 @@ export class MicroServer extends EventEmitter {
|
|
|
704
702
|
this._worker.endJob('plugin:' + plugin.name);
|
|
705
703
|
}
|
|
706
704
|
}
|
|
705
|
+
/** Add middleware to stack, with optional priority */
|
|
707
706
|
addStack(middleware) {
|
|
708
707
|
if (middleware.plugin?.name && this.getPlugin(middleware.plugin.name))
|
|
709
708
|
throw new Error(`Plugin ${middleware.plugin.name} already added`);
|
|
@@ -711,9 +710,11 @@ export class MicroServer extends EventEmitter {
|
|
|
711
710
|
const idx = this._stack.findLastIndex(f => (f.priority || 0) <= priority);
|
|
712
711
|
this._stack.splice(idx + 1, 0, middleware);
|
|
713
712
|
}
|
|
713
|
+
/** Get plugin */
|
|
714
714
|
getPlugin(id) {
|
|
715
715
|
return this._plugins[id];
|
|
716
716
|
}
|
|
717
|
+
/** Wait for plugin */
|
|
717
718
|
async waitPlugin(id) {
|
|
718
719
|
const p = this.getPlugin(id);
|
|
719
720
|
if (p)
|
|
@@ -722,42 +723,42 @@ export class MicroServer extends EventEmitter {
|
|
|
722
723
|
await this._worker.wait('plugin:' + id);
|
|
723
724
|
return this.getPlugin(id);
|
|
724
725
|
}
|
|
725
|
-
/** Add route, alias to `server.
|
|
726
|
+
/** Add route, alias to `server.use(url, ...args)` */
|
|
726
727
|
all(url, ...args) {
|
|
727
728
|
this.use('* ' + url, ...args);
|
|
728
729
|
return this;
|
|
729
730
|
}
|
|
730
|
-
/** Add route, alias to `server.
|
|
731
|
+
/** Add route, alias to `server.use('GET ' + url, ...args)` */
|
|
731
732
|
get(url, ...args) {
|
|
732
733
|
this.use('GET ' + url, ...args);
|
|
733
734
|
return this;
|
|
734
735
|
}
|
|
735
|
-
/** Add route, alias to `server.
|
|
736
|
+
/** Add route, alias to `server.use('POST ' + url, ...args)` */
|
|
736
737
|
post(url, ...args) {
|
|
737
738
|
this.use('POST ' + url, ...args);
|
|
738
739
|
return this;
|
|
739
740
|
}
|
|
740
|
-
/** Add route, alias to `server.
|
|
741
|
+
/** Add route, alias to `server.use('PUT ' + url, ...args)` */
|
|
741
742
|
put(url, ...args) {
|
|
742
743
|
this.use('PUT ' + url, ...args);
|
|
743
744
|
return this;
|
|
744
745
|
}
|
|
745
|
-
/** Add route, alias to `server.
|
|
746
|
+
/** Add route, alias to `server.use('PATCH ' + url, ...args)` */
|
|
746
747
|
patch(url, ...args) {
|
|
747
748
|
this.use('PATCH ' + url, ...args);
|
|
748
749
|
return this;
|
|
749
750
|
}
|
|
750
|
-
/** Add route, alias to `server.
|
|
751
|
+
/** Add route, alias to `server.use('DELETE ' + url, ...args)` */
|
|
751
752
|
delete(url, ...args) {
|
|
752
753
|
this.use('DELETE ' + url, ...args);
|
|
753
754
|
return this;
|
|
754
755
|
}
|
|
755
|
-
/** Add websocket handler, alias to `server.
|
|
756
|
+
/** Add websocket handler, alias to `server.use('WEBSOCKET ' + url, ...args)` */
|
|
756
757
|
websocket(url, ...args) {
|
|
757
758
|
this.use('WEBSOCKET ' + url, ...args);
|
|
758
759
|
return this;
|
|
759
760
|
}
|
|
760
|
-
/** Add router hook, alias to `server.
|
|
761
|
+
/** Add router hook, alias to `server.hook(url, ...args)` */
|
|
761
762
|
hook(url, ...args) {
|
|
762
763
|
const m = url.match(/^([A-Z]+) (.*)/) || ['', 'hook', url];
|
|
763
764
|
this._router.add(m[1], m[2], args.filter(m => m).map(m => this._bind(m)), false);
|
|
@@ -800,7 +801,6 @@ class RouterItem {
|
|
|
800
801
|
class RouterPlugin extends Plugin {
|
|
801
802
|
priority = 100;
|
|
802
803
|
name = 'router';
|
|
803
|
-
//stack: Middleware[] = []
|
|
804
804
|
_tree = {};
|
|
805
805
|
constructor() {
|
|
806
806
|
super();
|
|
@@ -923,7 +923,8 @@ class RouterPlugin extends Plugin {
|
|
|
923
923
|
this._tree = {};
|
|
924
924
|
}
|
|
925
925
|
handler(req, res, next) {
|
|
926
|
-
|
|
926
|
+
const method = req.method === 'HEAD' ? 'GET' : req.method || 'GET';
|
|
927
|
+
this.walk(this._getStack(req.pathname, ['hook', method, '*']), req, res, next);
|
|
927
928
|
}
|
|
928
929
|
}
|
|
929
930
|
export class CorsPlugin extends Plugin {
|
|
@@ -980,10 +981,6 @@ export class MethodsPlugin extends Plugin {
|
|
|
980
981
|
res.setHeader('Allow', this._methods);
|
|
981
982
|
return res.status(405).end();
|
|
982
983
|
}
|
|
983
|
-
if (req.method === 'HEAD') {
|
|
984
|
-
req.method = 'GET';
|
|
985
|
-
res.headersOnly = true;
|
|
986
|
-
}
|
|
987
984
|
return next();
|
|
988
985
|
}
|
|
989
986
|
}
|
|
@@ -996,7 +993,7 @@ export class BodyPlugin extends Plugin {
|
|
|
996
993
|
this._maxBodySize = options?.maxBodySize || defaultMaxBodySize;
|
|
997
994
|
}
|
|
998
995
|
handler(req, res, next) {
|
|
999
|
-
if (req.complete || req.method === 'GET') {
|
|
996
|
+
if (req.complete || req.method === 'GET' || req.method === 'HEAD') {
|
|
1000
997
|
if (!req.body)
|
|
1001
998
|
req.setBody({});
|
|
1002
999
|
return next();
|
|
@@ -1052,7 +1049,7 @@ export class UploadPlugin extends Plugin {
|
|
|
1052
1049
|
this._uploadDir = options?.uploadDir;
|
|
1053
1050
|
}
|
|
1054
1051
|
handler(req, res, next) {
|
|
1055
|
-
if (!req.readable || req.method === 'GET')
|
|
1052
|
+
if (!req.readable || req.method === 'GET' || req.method === 'HEAD')
|
|
1056
1053
|
return next();
|
|
1057
1054
|
const contentType = req.headers['content-type'] || '';
|
|
1058
1055
|
if (!contentType.startsWith('multipart/form-data'))
|
|
@@ -1710,7 +1707,7 @@ export class StaticFilesPlugin extends Plugin {
|
|
|
1710
1707
|
}
|
|
1711
1708
|
/** Default static files handler */
|
|
1712
1709
|
handler(req, res, next) {
|
|
1713
|
-
if (req.method !== 'GET')
|
|
1710
|
+
if (req.method !== 'GET' && req.method !== 'HEAD')
|
|
1714
1711
|
return next();
|
|
1715
1712
|
if (!('path' in req.params)) { // global handler
|
|
1716
1713
|
if (req.path.startsWith(this.prefix) && (req.path === this.prefix || req.path[this.prefix.length] === '/')) {
|
|
@@ -1751,6 +1748,7 @@ export class StaticFilesPlugin extends Plugin {
|
|
|
1751
1748
|
});
|
|
1752
1749
|
});
|
|
1753
1750
|
}
|
|
1751
|
+
/** Send static file */
|
|
1754
1752
|
serveFile(req, res, options) {
|
|
1755
1753
|
const filePath = path.isAbsolute(options.path) ? options.path : path.join(options.root || this.root, options.path);
|
|
1756
1754
|
const statRes = (err, stats) => {
|
|
@@ -1773,14 +1771,12 @@ export class StaticFilesPlugin extends Plugin {
|
|
|
1773
1771
|
res.setHeader('Content-Length', stats.size);
|
|
1774
1772
|
if (options.etag !== false) {
|
|
1775
1773
|
const etag = '"' + etagPrefix + stats.mtime.getTime().toString(32) + '"';
|
|
1776
|
-
if (req.headers['if-none-match'] === etag || req.headers['if-modified-since'] === stats.mtime.toUTCString())
|
|
1774
|
+
if (req.headers['if-none-match'] === etag || req.headers['if-modified-since'] === stats.mtime.toUTCString())
|
|
1777
1775
|
res.statusCode = 304;
|
|
1778
|
-
res.headersOnly = true;
|
|
1779
|
-
}
|
|
1780
1776
|
}
|
|
1781
1777
|
if (options.maxAge)
|
|
1782
1778
|
res.setHeader('Cache-Control', 'max-age=' + options.maxAge);
|
|
1783
|
-
if (res.
|
|
1779
|
+
if (res.statusCode === 304 || req.method === 'HEAD') {
|
|
1784
1780
|
res.end();
|
|
1785
1781
|
return;
|
|
1786
1782
|
}
|
|
@@ -2577,11 +2573,13 @@ export class FileStore {
|
|
|
2577
2573
|
});
|
|
2578
2574
|
return p;
|
|
2579
2575
|
}
|
|
2576
|
+
/** close store */
|
|
2580
2577
|
async close() {
|
|
2581
2578
|
await this.sync();
|
|
2582
2579
|
this._iter = 0;
|
|
2583
2580
|
this._cache = {};
|
|
2584
2581
|
}
|
|
2582
|
+
/** sync data to disk */
|
|
2585
2583
|
async sync() {
|
|
2586
2584
|
for (const name in this._cache) {
|
|
2587
2585
|
for (const key in this._cache)
|
|
@@ -3313,10 +3311,12 @@ export class MicroCollection {
|
|
|
3313
3311
|
async deleteMany(query) {
|
|
3314
3312
|
return (await this.updateMany(query, {}, { delete: true })).modifiedCount;
|
|
3315
3313
|
}
|
|
3314
|
+
/** Update one matching document */
|
|
3316
3315
|
async updateOne(query, update, options) {
|
|
3317
3316
|
const res = await this.updateMany(query, update, { ...options, limit: 1 });
|
|
3318
3317
|
return res;
|
|
3319
3318
|
}
|
|
3319
|
+
/** Update many matching documents */
|
|
3320
3320
|
async updateMany(query, update, options) {
|
|
3321
3321
|
let res = { upsertedId: undefined, modifiedCount: 0 };
|
|
3322
3322
|
if (!query)
|