@radatek/microserver 3.0.2 → 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 +34 -33
- package/microserver.js +107 -128
- 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
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* MicroServer
|
|
3
|
-
* @version 3.0.
|
|
3
|
+
* @version 3.0.3
|
|
4
4
|
* @package @radatek/microserver
|
|
5
5
|
* @copyright Darius Kisonas 2022
|
|
6
6
|
* @license MIT
|
|
@@ -32,9 +32,6 @@ export declare class WebSocketError extends Error {
|
|
|
32
32
|
statusCode: number;
|
|
33
33
|
constructor(text?: string, code?: number);
|
|
34
34
|
}
|
|
35
|
-
type DeferPromise<T = void> = Promise<T> & {
|
|
36
|
-
resolve: (res?: T | Error) => void;
|
|
37
|
-
};
|
|
38
35
|
/** Middleware */
|
|
39
36
|
export interface Middleware {
|
|
40
37
|
(req: ServerRequest, res: ServerResponse, next: Function): any;
|
|
@@ -50,7 +47,7 @@ export declare abstract class Plugin {
|
|
|
50
47
|
constructor();
|
|
51
48
|
}
|
|
52
49
|
interface PluginClass {
|
|
53
|
-
new (options
|
|
50
|
+
new (options: any, server: MicroServer): Plugin;
|
|
54
51
|
}
|
|
55
52
|
export type ServerRequestBody<T = any> = T extends Model<infer U extends ModelSchema> ? ModelDocument<U> : Record<string, any>;
|
|
56
53
|
/** Extended http.IncomingMessage */
|
|
@@ -89,12 +86,17 @@ export declare class ServerRequest<T = any> extends http.IncomingMessage {
|
|
|
89
86
|
rawBody: Buffer[];
|
|
90
87
|
/** Request raw body size */
|
|
91
88
|
rawBodySize: number;
|
|
92
|
-
_body?: ServerRequestBody<T>;
|
|
93
|
-
_isReady: DeferPromise | undefined;
|
|
94
89
|
private constructor();
|
|
95
|
-
|
|
90
|
+
/** Extend http.IncomingMessage */
|
|
91
|
+
static extend(req: http.IncomingMessage, res: http.ServerResponse, server: MicroServer): ServerRequest;
|
|
92
|
+
/** Check if request is ready */
|
|
96
93
|
get isReady(): boolean;
|
|
94
|
+
/** Wait for request to be ready, usualy used to wait for file upload */
|
|
97
95
|
waitReady(): Promise<void>;
|
|
96
|
+
/** Signal request as ready */
|
|
97
|
+
setReady(err?: Error): void;
|
|
98
|
+
/** Set request body */
|
|
99
|
+
setBody(body: ServerRequestBody<T>): void;
|
|
98
100
|
/** Update request url */
|
|
99
101
|
updateUrl(url: string): this;
|
|
100
102
|
/** Rewrite request url */
|
|
@@ -111,9 +113,10 @@ export declare class ServerRequest<T = any> extends http.IncomingMessage {
|
|
|
111
113
|
/** Extends http.ServerResponse */
|
|
112
114
|
export declare class ServerResponse<T = any> extends http.ServerResponse {
|
|
113
115
|
readonly req: ServerRequest<T>;
|
|
116
|
+
/** Should response be json */
|
|
114
117
|
isJson: boolean;
|
|
115
|
-
headersOnly: boolean;
|
|
116
118
|
private constructor();
|
|
119
|
+
/** Extends http.ServerResponse */
|
|
117
120
|
static extend(res: http.ServerResponse): void;
|
|
118
121
|
/** Send error reponse */
|
|
119
122
|
error(error: string | number | Error): void;
|
|
@@ -170,11 +173,13 @@ interface MicroServerEvents {
|
|
|
170
173
|
}
|
|
171
174
|
/** Lighweight HTTP server */
|
|
172
175
|
export declare class MicroServer extends EventEmitter {
|
|
176
|
+
/** MicroServer configuration */
|
|
173
177
|
config: MicroServerConfig;
|
|
178
|
+
/** Authorization object */
|
|
174
179
|
auth?: Auth;
|
|
175
|
-
/**
|
|
180
|
+
/** All sockets */
|
|
176
181
|
sockets: Set<net.Socket> | undefined;
|
|
177
|
-
/**
|
|
182
|
+
/** Server instances */
|
|
178
183
|
servers: Set<net.Server> | undefined;
|
|
179
184
|
/** @param {MicroServerConfig} [config] MicroServer configuration */
|
|
180
185
|
constructor(config?: MicroServerConfig);
|
|
@@ -187,40 +192,39 @@ export declare class MicroServer extends EventEmitter {
|
|
|
187
192
|
waitReady(): Promise<void>;
|
|
188
193
|
/** Listen server, should be used only if config.listen is not set */
|
|
189
194
|
listen(config: ListenConfig): Promise<void>;
|
|
190
|
-
/** 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' */
|
|
191
|
-
_bind(fn: string | Function | object): Function;
|
|
192
195
|
/** Default server handler */
|
|
193
196
|
handler(req: ServerRequest, res: ServerResponse): void;
|
|
194
|
-
/** Last request handler */
|
|
195
|
-
handlerLast(req: ServerRequest, res: ServerResponse, next?: Function): any;
|
|
196
197
|
/** Clear routes and middlewares */
|
|
197
198
|
clear(): this;
|
|
198
199
|
/**
|
|
199
|
-
* Add middleware
|
|
200
|
+
* Add middleware, plugin or routes to server.
|
|
200
201
|
* Middlewares may return promises for res.jsonSuccess(...), throw errors for res.error(...), return string or {} for res.send(...)
|
|
201
202
|
* RouteURL: 'METHOD /suburl', 'METHOD', '* /suburl'
|
|
202
203
|
*/
|
|
203
204
|
use(...args: [
|
|
204
205
|
Middleware | Plugin | ControllerClass | RoutesSet
|
|
205
206
|
] | [Promise<Middleware | Plugin | ControllerClass | RoutesSet>] | RoutesList | [RouteURL, RoutesSet] | [PluginClass | Promise<PluginClass>, options?: any]): Promise<void>;
|
|
207
|
+
/** Add middleware to stack, with optional priority */
|
|
206
208
|
addStack(middleware: Middleware): void;
|
|
209
|
+
/** Get plugin */
|
|
207
210
|
getPlugin(id: string): Plugin | undefined;
|
|
211
|
+
/** Wait for plugin */
|
|
208
212
|
waitPlugin(id: string): Promise<Plugin>;
|
|
209
|
-
/** Add route, alias to `server.
|
|
213
|
+
/** Add route, alias to `server.use(url, ...args)` */
|
|
210
214
|
all(url: `/${string}`, ...args: RoutesMiddleware[]): MicroServer;
|
|
211
|
-
/** Add route, alias to `server.
|
|
215
|
+
/** Add route, alias to `server.use('GET ' + url, ...args)` */
|
|
212
216
|
get(url: `/${string}`, ...args: RoutesMiddleware[]): MicroServer;
|
|
213
|
-
/** Add route, alias to `server.
|
|
217
|
+
/** Add route, alias to `server.use('POST ' + url, ...args)` */
|
|
214
218
|
post(url: `/${string}`, ...args: RoutesMiddleware[]): MicroServer;
|
|
215
|
-
/** Add route, alias to `server.
|
|
219
|
+
/** Add route, alias to `server.use('PUT ' + url, ...args)` */
|
|
216
220
|
put(url: `/${string}`, ...args: RoutesMiddleware[]): MicroServer;
|
|
217
|
-
/** Add route, alias to `server.
|
|
221
|
+
/** Add route, alias to `server.use('PATCH ' + url, ...args)` */
|
|
218
222
|
patch(url: `/${string}`, ...args: RoutesMiddleware[]): MicroServer;
|
|
219
|
-
/** Add route, alias to `server.
|
|
223
|
+
/** Add route, alias to `server.use('DELETE ' + url, ...args)` */
|
|
220
224
|
delete(url: `/${string}`, ...args: RoutesMiddleware[]): MicroServer;
|
|
221
|
-
/** Add websocket handler, alias to `server.
|
|
225
|
+
/** Add websocket handler, alias to `server.use('WEBSOCKET ' + url, ...args)` */
|
|
222
226
|
websocket(url: `/${string}`, ...args: RoutesMiddleware[]): MicroServer;
|
|
223
|
-
/** Add router hook, alias to `server.
|
|
227
|
+
/** Add router hook, alias to `server.hook(url, ...args)` */
|
|
224
228
|
hook(url: RouteURL, ...args: RoutesMiddleware[]): MicroServer;
|
|
225
229
|
/** Check if middleware allready added */
|
|
226
230
|
has(mid: Middleware): boolean;
|
|
@@ -266,8 +270,6 @@ export declare class CorsPlugin extends Plugin {
|
|
|
266
270
|
export declare class MethodsPlugin extends Plugin {
|
|
267
271
|
priority: number;
|
|
268
272
|
name: string;
|
|
269
|
-
_methods: string;
|
|
270
|
-
_methodsIdx: Record<string, boolean>;
|
|
271
273
|
constructor(methods?: string);
|
|
272
274
|
handler(req: ServerRequest, res: ServerResponse, next: Function): any;
|
|
273
275
|
}
|
|
@@ -277,7 +279,6 @@ export interface BodyOptions {
|
|
|
277
279
|
export declare class BodyPlugin extends Plugin {
|
|
278
280
|
priority: number;
|
|
279
281
|
name: string;
|
|
280
|
-
_maxBodySize: number;
|
|
281
282
|
constructor(options?: BodyOptions);
|
|
282
283
|
handler(req: ServerRequest, res: ServerResponse, next: () => void): void;
|
|
283
284
|
}
|
|
@@ -295,8 +296,6 @@ export interface UploadFile {
|
|
|
295
296
|
export declare class UploadPlugin extends Plugin {
|
|
296
297
|
priority: number;
|
|
297
298
|
name: string;
|
|
298
|
-
_maxFileSize: number;
|
|
299
|
-
_uploadDir?: string;
|
|
300
299
|
constructor(options?: UploadOptions);
|
|
301
300
|
handler(req: ServerRequest, res: ServerResponse, next: () => void): void;
|
|
302
301
|
}
|
|
@@ -338,7 +337,6 @@ export declare class WebSocket extends EventEmitter {
|
|
|
338
337
|
ping(buffer?: Buffer): void;
|
|
339
338
|
/** Send pong frame */
|
|
340
339
|
pong(buffer?: Buffer): void;
|
|
341
|
-
protected _sendFrame(opcode: number, data: Buffer, cb?: () => void): void;
|
|
342
340
|
on<K extends keyof WebSocketEvents>(event: K, listener: WebSocketEvents[K]): this;
|
|
343
341
|
addListener<K extends keyof WebSocketEvents>(event: K, listener: WebSocketEvents[K]): this;
|
|
344
342
|
once<K extends keyof WebSocketEvents>(event: K, listener: WebSocketEvents[K]): this;
|
|
@@ -347,11 +345,9 @@ export declare class WebSocket extends EventEmitter {
|
|
|
347
345
|
}
|
|
348
346
|
export declare class WebSocketPlugin extends Plugin {
|
|
349
347
|
name: string;
|
|
350
|
-
_handler: (req: ServerRequest, socket: net.Socket, head: any) => void;
|
|
351
348
|
constructor(options?: any, server?: MicroServer);
|
|
352
|
-
addUpgradeHandler
|
|
349
|
+
private addUpgradeHandler;
|
|
353
350
|
upgradeHandler(server: MicroServer, req: ServerRequest, socket: net.Socket, head: any): void;
|
|
354
|
-
static create(req: ServerRequest, options?: WebSocketOptions): WebSocket;
|
|
355
351
|
}
|
|
356
352
|
/** Trust proxy plugin, adds `req.ip` and `req.localip` */
|
|
357
353
|
export declare class TrustProxyPlugin extends Plugin {
|
|
@@ -451,6 +447,7 @@ export declare class StaticFilesPlugin extends Plugin {
|
|
|
451
447
|
constructor(options?: StaticFilesOptions | string, server?: MicroServer);
|
|
452
448
|
/** Default static files handler */
|
|
453
449
|
handler(req: ServerRequest, res: ServerResponse, next: Function): any;
|
|
450
|
+
/** Send static file */
|
|
454
451
|
serveFile(req: ServerRequest, res: ServerResponse, options: ServeFileOptions): void;
|
|
455
452
|
}
|
|
456
453
|
/** Proxy plugin options */
|
|
@@ -709,7 +706,9 @@ export declare class FileStore {
|
|
|
709
706
|
constructor(options?: FileStoreOptions);
|
|
710
707
|
/** cleanup cache */
|
|
711
708
|
cleanup(): void;
|
|
709
|
+
/** close store */
|
|
712
710
|
close(): Promise<void>;
|
|
711
|
+
/** sync data to disk */
|
|
713
712
|
sync(): Promise<void>;
|
|
714
713
|
/** load json file data */
|
|
715
714
|
load(name: string, autosave?: boolean): Promise<any>;
|
|
@@ -927,10 +926,12 @@ export declare class MicroCollection<TSchema extends ModelSchema = any> {
|
|
|
927
926
|
deleteOne(query: Query): Promise<number>;
|
|
928
927
|
/** Delete all matching documents */
|
|
929
928
|
deleteMany(query: Query): Promise<number>;
|
|
929
|
+
/** Update one matching document */
|
|
930
930
|
updateOne(query: Query, update: Record<string, any>, options?: FindOptions): Promise<{
|
|
931
931
|
upsertedId: any;
|
|
932
932
|
modifiedCount: number;
|
|
933
933
|
}>;
|
|
934
|
+
/** Update many matching documents */
|
|
934
935
|
updateMany(query: Query, update: Record<string, any>, options?: FindOptions): Promise<{
|
|
935
936
|
upsertedId: any;
|
|
936
937
|
modifiedCount: number;
|
package/microserver.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* MicroServer
|
|
3
|
-
* @version 3.0.
|
|
3
|
+
* @version 3.0.3
|
|
4
4
|
* @package @radatek/microserver
|
|
5
5
|
* @copyright Darius Kisonas 2022
|
|
6
6
|
* @license MIT
|
|
@@ -118,11 +118,12 @@ export class ServerRequest extends http.IncomingMessage {
|
|
|
118
118
|
rawBodySize;
|
|
119
119
|
_body;
|
|
120
120
|
_isReady;
|
|
121
|
-
constructor(server) {
|
|
121
|
+
constructor(res, server) {
|
|
122
122
|
super(new net.Socket());
|
|
123
|
-
ServerRequest.extend(this, server);
|
|
123
|
+
ServerRequest.extend(this, res, server);
|
|
124
124
|
}
|
|
125
|
-
|
|
125
|
+
/** Extend http.IncomingMessage */
|
|
126
|
+
static extend(req, res, server) {
|
|
126
127
|
const reqNew = Object.setPrototypeOf(req, ServerRequest.prototype);
|
|
127
128
|
let ip = req.socket.remoteAddress || '::1';
|
|
128
129
|
if (ip.startsWith('::ffff:'))
|
|
@@ -141,12 +142,23 @@ export class ServerRequest extends http.IncomingMessage {
|
|
|
141
142
|
rawBody: [],
|
|
142
143
|
rawBodySize: 0
|
|
143
144
|
});
|
|
145
|
+
if (req.readable && !req.complete) {
|
|
146
|
+
reqNew._isReady = deferPromise((err) => {
|
|
147
|
+
reqNew._isReady = undefined;
|
|
148
|
+
if (err && res && !res.headersSent) {
|
|
149
|
+
res.statusCode = 'statusCode' in err ? err.statusCode : 400;
|
|
150
|
+
res.end(http.STATUS_CODES[res.statusCode] || 'Error');
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
}
|
|
144
154
|
reqNew.updateUrl(req.url || '/');
|
|
145
155
|
return reqNew;
|
|
146
156
|
}
|
|
157
|
+
/** Check if request is ready */
|
|
147
158
|
get isReady() {
|
|
148
159
|
return this._isReady === undefined;
|
|
149
160
|
}
|
|
161
|
+
/** Wait for request to be ready, usualy used to wait for file upload */
|
|
150
162
|
async waitReady() {
|
|
151
163
|
if (this._isReady === undefined)
|
|
152
164
|
return;
|
|
@@ -154,6 +166,14 @@ export class ServerRequest extends http.IncomingMessage {
|
|
|
154
166
|
if (res && res instanceof Error)
|
|
155
167
|
throw res;
|
|
156
168
|
}
|
|
169
|
+
/** Signal request as ready */
|
|
170
|
+
setReady(err) {
|
|
171
|
+
this._isReady?.resolve(err);
|
|
172
|
+
}
|
|
173
|
+
/** Set request body */
|
|
174
|
+
setBody(body) {
|
|
175
|
+
this._body = body;
|
|
176
|
+
}
|
|
157
177
|
/** Update request url */
|
|
158
178
|
updateUrl(url) {
|
|
159
179
|
this.url = url;
|
|
@@ -190,18 +210,19 @@ export class ServerRequest extends http.IncomingMessage {
|
|
|
190
210
|
}
|
|
191
211
|
/** Extends http.ServerResponse */
|
|
192
212
|
export class ServerResponse extends http.ServerResponse {
|
|
213
|
+
/** Should response be json */
|
|
193
214
|
isJson;
|
|
194
|
-
headersOnly;
|
|
195
215
|
constructor(server) {
|
|
196
|
-
super(
|
|
216
|
+
super(new http.IncomingMessage(new net.Socket()));
|
|
217
|
+
ServerRequest.extend(this.req, this, server);
|
|
197
218
|
ServerResponse.extend(this);
|
|
198
219
|
}
|
|
220
|
+
/** Extends http.ServerResponse */
|
|
199
221
|
static extend(res) {
|
|
200
222
|
Object.setPrototypeOf(res, ServerResponse.prototype);
|
|
201
223
|
Object.assign(res, {
|
|
202
224
|
statusCode: 200,
|
|
203
|
-
isJson: false
|
|
204
|
-
headersOnly: false
|
|
225
|
+
isJson: false
|
|
205
226
|
});
|
|
206
227
|
}
|
|
207
228
|
/** Send error reponse */
|
|
@@ -266,7 +287,7 @@ export class ServerResponse extends http.ServerResponse {
|
|
|
266
287
|
return (data.pipe(this, { end: true }), void 0);
|
|
267
288
|
if (data instanceof Buffer) {
|
|
268
289
|
this.setHeader('Content-Length', data.byteLength);
|
|
269
|
-
if (this.
|
|
290
|
+
if (this.statusCode === 304 || this.req.method === 'HEAD')
|
|
270
291
|
this.end();
|
|
271
292
|
else
|
|
272
293
|
this.end(data);
|
|
@@ -285,7 +306,7 @@ export class ServerResponse extends http.ServerResponse {
|
|
|
285
306
|
this.setHeader('Content-Type', 'text/plain');
|
|
286
307
|
}
|
|
287
308
|
this.setHeader('Content-Length', Buffer.byteLength(data, 'utf8'));
|
|
288
|
-
if (this.
|
|
309
|
+
if (this.statusCode === 304 || this.req.method === 'HEAD')
|
|
289
310
|
this.end();
|
|
290
311
|
else
|
|
291
312
|
this.end(data, 'utf8');
|
|
@@ -340,15 +361,17 @@ export class ServerResponse extends http.ServerResponse {
|
|
|
340
361
|
}
|
|
341
362
|
/** Lighweight HTTP server */
|
|
342
363
|
export class MicroServer extends EventEmitter {
|
|
364
|
+
/** MicroServer configuration */
|
|
343
365
|
config;
|
|
366
|
+
/** Authorization object */
|
|
344
367
|
auth;
|
|
345
368
|
_plugins = {};
|
|
346
369
|
_stack = [];
|
|
347
370
|
_router = new RouterPlugin();
|
|
348
371
|
_worker = new Worker();
|
|
349
|
-
/**
|
|
372
|
+
/** All sockets */
|
|
350
373
|
sockets;
|
|
351
|
-
/**
|
|
374
|
+
/** Server instances */
|
|
352
375
|
servers;
|
|
353
376
|
/** @param {MicroServerConfig} [config] MicroServer configuration */
|
|
354
377
|
constructor(config) {
|
|
@@ -475,7 +498,7 @@ export class MicroServer extends EventEmitter {
|
|
|
475
498
|
});
|
|
476
499
|
return this._worker.wait('listen');
|
|
477
500
|
}
|
|
478
|
-
|
|
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' */
|
|
479
502
|
_bind(fn) {
|
|
480
503
|
if (typeof fn === 'string') {
|
|
481
504
|
let name = fn;
|
|
@@ -572,27 +595,9 @@ export class MicroServer extends EventEmitter {
|
|
|
572
595
|
}
|
|
573
596
|
/** Default server handler */
|
|
574
597
|
handler(req, res) {
|
|
575
|
-
ServerRequest.extend(req, this);
|
|
598
|
+
ServerRequest.extend(req, res, this);
|
|
576
599
|
ServerResponse.extend(res);
|
|
577
|
-
if (req.readable) {
|
|
578
|
-
req._isReady = deferPromise((err) => {
|
|
579
|
-
req._isReady = undefined;
|
|
580
|
-
if (err) {
|
|
581
|
-
if (!res.headersSent)
|
|
582
|
-
res.error('statusCode' in err ? err.statusCode : 400);
|
|
583
|
-
}
|
|
584
|
-
});
|
|
585
|
-
}
|
|
586
600
|
this._router.walk(this._stack, req, res, () => res.error(404));
|
|
587
|
-
//this.handlerRouter(req, res, () => this.handlerLast(req, res))
|
|
588
|
-
}
|
|
589
|
-
/** Last request handler */
|
|
590
|
-
handlerLast(req, res, next) {
|
|
591
|
-
if (res.headersSent || res.closed)
|
|
592
|
-
return;
|
|
593
|
-
if (!next)
|
|
594
|
-
next = () => res.error(404);
|
|
595
|
-
return next();
|
|
596
601
|
}
|
|
597
602
|
/** Clear routes and middlewares */
|
|
598
603
|
clear() {
|
|
@@ -603,7 +608,7 @@ export class MicroServer extends EventEmitter {
|
|
|
603
608
|
return this;
|
|
604
609
|
}
|
|
605
610
|
/**
|
|
606
|
-
* Add middleware
|
|
611
|
+
* Add middleware, plugin or routes to server.
|
|
607
612
|
* Middlewares may return promises for res.jsonSuccess(...), throw errors for res.error(...), return string or {} for res.send(...)
|
|
608
613
|
* RouteURL: 'METHOD /suburl', 'METHOD', '* /suburl'
|
|
609
614
|
*/
|
|
@@ -697,6 +702,7 @@ export class MicroServer extends EventEmitter {
|
|
|
697
702
|
this._worker.endJob('plugin:' + plugin.name);
|
|
698
703
|
}
|
|
699
704
|
}
|
|
705
|
+
/** Add middleware to stack, with optional priority */
|
|
700
706
|
addStack(middleware) {
|
|
701
707
|
if (middleware.plugin?.name && this.getPlugin(middleware.plugin.name))
|
|
702
708
|
throw new Error(`Plugin ${middleware.plugin.name} already added`);
|
|
@@ -704,9 +710,11 @@ export class MicroServer extends EventEmitter {
|
|
|
704
710
|
const idx = this._stack.findLastIndex(f => (f.priority || 0) <= priority);
|
|
705
711
|
this._stack.splice(idx + 1, 0, middleware);
|
|
706
712
|
}
|
|
713
|
+
/** Get plugin */
|
|
707
714
|
getPlugin(id) {
|
|
708
715
|
return this._plugins[id];
|
|
709
716
|
}
|
|
717
|
+
/** Wait for plugin */
|
|
710
718
|
async waitPlugin(id) {
|
|
711
719
|
const p = this.getPlugin(id);
|
|
712
720
|
if (p)
|
|
@@ -715,42 +723,42 @@ export class MicroServer extends EventEmitter {
|
|
|
715
723
|
await this._worker.wait('plugin:' + id);
|
|
716
724
|
return this.getPlugin(id);
|
|
717
725
|
}
|
|
718
|
-
/** Add route, alias to `server.
|
|
726
|
+
/** Add route, alias to `server.use(url, ...args)` */
|
|
719
727
|
all(url, ...args) {
|
|
720
728
|
this.use('* ' + url, ...args);
|
|
721
729
|
return this;
|
|
722
730
|
}
|
|
723
|
-
/** Add route, alias to `server.
|
|
731
|
+
/** Add route, alias to `server.use('GET ' + url, ...args)` */
|
|
724
732
|
get(url, ...args) {
|
|
725
733
|
this.use('GET ' + url, ...args);
|
|
726
734
|
return this;
|
|
727
735
|
}
|
|
728
|
-
/** Add route, alias to `server.
|
|
736
|
+
/** Add route, alias to `server.use('POST ' + url, ...args)` */
|
|
729
737
|
post(url, ...args) {
|
|
730
738
|
this.use('POST ' + url, ...args);
|
|
731
739
|
return this;
|
|
732
740
|
}
|
|
733
|
-
/** Add route, alias to `server.
|
|
741
|
+
/** Add route, alias to `server.use('PUT ' + url, ...args)` */
|
|
734
742
|
put(url, ...args) {
|
|
735
743
|
this.use('PUT ' + url, ...args);
|
|
736
744
|
return this;
|
|
737
745
|
}
|
|
738
|
-
/** Add route, alias to `server.
|
|
746
|
+
/** Add route, alias to `server.use('PATCH ' + url, ...args)` */
|
|
739
747
|
patch(url, ...args) {
|
|
740
748
|
this.use('PATCH ' + url, ...args);
|
|
741
749
|
return this;
|
|
742
750
|
}
|
|
743
|
-
/** Add route, alias to `server.
|
|
751
|
+
/** Add route, alias to `server.use('DELETE ' + url, ...args)` */
|
|
744
752
|
delete(url, ...args) {
|
|
745
753
|
this.use('DELETE ' + url, ...args);
|
|
746
754
|
return this;
|
|
747
755
|
}
|
|
748
|
-
/** Add websocket handler, alias to `server.
|
|
756
|
+
/** Add websocket handler, alias to `server.use('WEBSOCKET ' + url, ...args)` */
|
|
749
757
|
websocket(url, ...args) {
|
|
750
758
|
this.use('WEBSOCKET ' + url, ...args);
|
|
751
759
|
return this;
|
|
752
760
|
}
|
|
753
|
-
/** Add router hook, alias to `server.
|
|
761
|
+
/** Add router hook, alias to `server.hook(url, ...args)` */
|
|
754
762
|
hook(url, ...args) {
|
|
755
763
|
const m = url.match(/^([A-Z]+) (.*)/) || ['', 'hook', url];
|
|
756
764
|
this._router.add(m[1], m[2], args.filter(m => m).map(m => this._bind(m)), false);
|
|
@@ -793,7 +801,6 @@ class RouterItem {
|
|
|
793
801
|
class RouterPlugin extends Plugin {
|
|
794
802
|
priority = 100;
|
|
795
803
|
name = 'router';
|
|
796
|
-
//stack: Middleware[] = []
|
|
797
804
|
_tree = {};
|
|
798
805
|
constructor() {
|
|
799
806
|
super();
|
|
@@ -916,7 +923,8 @@ class RouterPlugin extends Plugin {
|
|
|
916
923
|
this._tree = {};
|
|
917
924
|
}
|
|
918
925
|
handler(req, res, next) {
|
|
919
|
-
|
|
926
|
+
const method = req.method === 'HEAD' ? 'GET' : req.method || 'GET';
|
|
927
|
+
this.walk(this._getStack(req.pathname, ['hook', method, '*']), req, res, next);
|
|
920
928
|
}
|
|
921
929
|
}
|
|
922
930
|
export class CorsPlugin extends Plugin {
|
|
@@ -973,10 +981,6 @@ export class MethodsPlugin extends Plugin {
|
|
|
973
981
|
res.setHeader('Allow', this._methods);
|
|
974
982
|
return res.status(405).end();
|
|
975
983
|
}
|
|
976
|
-
if (req.method === 'HEAD') {
|
|
977
|
-
req.method = 'GET';
|
|
978
|
-
res.headersOnly = true;
|
|
979
|
-
}
|
|
980
984
|
return next();
|
|
981
985
|
}
|
|
982
986
|
}
|
|
@@ -989,22 +993,11 @@ export class BodyPlugin extends Plugin {
|
|
|
989
993
|
this._maxBodySize = options?.maxBodySize || defaultMaxBodySize;
|
|
990
994
|
}
|
|
991
995
|
handler(req, res, next) {
|
|
992
|
-
if (req.complete || req.method === 'GET') {
|
|
996
|
+
if (req.complete || req.method === 'GET' || req.method === 'HEAD') {
|
|
993
997
|
if (!req.body)
|
|
994
|
-
req.
|
|
998
|
+
req.setBody({});
|
|
995
999
|
return next();
|
|
996
1000
|
}
|
|
997
|
-
req._isReady = deferPromise((err) => {
|
|
998
|
-
req._isReady = undefined;
|
|
999
|
-
if (err) {
|
|
1000
|
-
if (!req.complete)
|
|
1001
|
-
req.pause();
|
|
1002
|
-
if (!res.headersSent)
|
|
1003
|
-
res.error('statusCode' in err ? err.statusCode : 400);
|
|
1004
|
-
}
|
|
1005
|
-
else if (req.complete)
|
|
1006
|
-
res.removeHeader('Connection');
|
|
1007
|
-
});
|
|
1008
1001
|
const contentType = req.headers['content-type'] || '';
|
|
1009
1002
|
if (contentType.startsWith('multipart/form-data')) {
|
|
1010
1003
|
req.pause();
|
|
@@ -1012,44 +1005,35 @@ export class BodyPlugin extends Plugin {
|
|
|
1012
1005
|
return next();
|
|
1013
1006
|
}
|
|
1014
1007
|
if (parseInt(req.headers['content-length'] || '-1') > this._maxBodySize) {
|
|
1015
|
-
return req.
|
|
1008
|
+
return req.setReady(new ResponseError("too big", 413));
|
|
1016
1009
|
}
|
|
1017
1010
|
req.once('error', () => { })
|
|
1018
1011
|
.on('data', chunk => {
|
|
1019
1012
|
req.rawBodySize += chunk.length;
|
|
1020
1013
|
if (req.rawBodySize >= this._maxBodySize)
|
|
1021
|
-
req.
|
|
1014
|
+
req.setReady(new ResponseError("too big", 413));
|
|
1022
1015
|
else
|
|
1023
1016
|
req.rawBody.push(chunk);
|
|
1024
1017
|
})
|
|
1025
1018
|
.once('end', () => {
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
}
|
|
1045
|
-
else
|
|
1046
|
-
req._body = {};
|
|
1047
|
-
}
|
|
1048
|
-
return req._body;
|
|
1049
|
-
},
|
|
1050
|
-
configurable: true,
|
|
1051
|
-
enumerable: true
|
|
1052
|
-
});
|
|
1019
|
+
let charset = contentType.match(/charset=(\S+)/)?.[1];
|
|
1020
|
+
if (charset !== 'utf8' && charset !== 'latin1' && charset !== 'ascii')
|
|
1021
|
+
charset = 'utf8';
|
|
1022
|
+
const bodyString = Buffer.concat(req.rawBody).toString(charset);
|
|
1023
|
+
if (contentType.startsWith('application/x-www-form-urlencoded')) {
|
|
1024
|
+
req.setBody(querystring.parse(bodyString));
|
|
1025
|
+
}
|
|
1026
|
+
else if (bodyString.startsWith('{') || bodyString.startsWith('[')) {
|
|
1027
|
+
try {
|
|
1028
|
+
req.setBody(JSON.parse(bodyString));
|
|
1029
|
+
}
|
|
1030
|
+
catch {
|
|
1031
|
+
return res.jsonError(405);
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
1034
|
+
else
|
|
1035
|
+
req.setBody({});
|
|
1036
|
+
req.setReady();
|
|
1053
1037
|
return next();
|
|
1054
1038
|
});
|
|
1055
1039
|
}
|
|
@@ -1065,24 +1049,11 @@ export class UploadPlugin extends Plugin {
|
|
|
1065
1049
|
this._uploadDir = options?.uploadDir;
|
|
1066
1050
|
}
|
|
1067
1051
|
handler(req, res, next) {
|
|
1068
|
-
if (!req.readable || req.method === 'GET')
|
|
1052
|
+
if (!req.readable || req.method === 'GET' || req.method === 'HEAD')
|
|
1069
1053
|
return next();
|
|
1070
1054
|
const contentType = req.headers['content-type'] || '';
|
|
1071
1055
|
if (!contentType.startsWith('multipart/form-data'))
|
|
1072
1056
|
return next();
|
|
1073
|
-
if (!req._isReady) {
|
|
1074
|
-
req._isReady = deferPromise((err) => {
|
|
1075
|
-
req._isReady = undefined;
|
|
1076
|
-
if (err) {
|
|
1077
|
-
req.pause();
|
|
1078
|
-
if (!res.headersSent)
|
|
1079
|
-
res.setHeader('Connection', 'close');
|
|
1080
|
-
res.error('statusCode' in err ? err.statusCode : 400);
|
|
1081
|
-
}
|
|
1082
|
-
else
|
|
1083
|
-
res.removeHeader('Connection');
|
|
1084
|
-
});
|
|
1085
|
-
}
|
|
1086
1057
|
req.pause();
|
|
1087
1058
|
res.setHeader('Connection', 'close');
|
|
1088
1059
|
if (!this._uploadDir)
|
|
@@ -1152,7 +1123,7 @@ export class UploadPlugin extends Plugin {
|
|
|
1152
1123
|
fileStream = undefined;
|
|
1153
1124
|
if (buffer[nextBoundaryIndexEnd] === 45) {
|
|
1154
1125
|
res.removeHeader('Connection');
|
|
1155
|
-
req.
|
|
1126
|
+
req.setReady();
|
|
1156
1127
|
}
|
|
1157
1128
|
buffer = buffer.subarray(nextBoundaryIndex);
|
|
1158
1129
|
}
|
|
@@ -1170,7 +1141,7 @@ export class UploadPlugin extends Plugin {
|
|
|
1170
1141
|
};
|
|
1171
1142
|
const _removeTempFiles = () => {
|
|
1172
1143
|
if (!req.isReady)
|
|
1173
|
-
req.
|
|
1144
|
+
req.setReady(new Error('Upload error'));
|
|
1174
1145
|
if (fileStream) {
|
|
1175
1146
|
fileStream.close();
|
|
1176
1147
|
fileStream = undefined;
|
|
@@ -1181,12 +1152,12 @@ export class UploadPlugin extends Plugin {
|
|
|
1181
1152
|
delete f.filePath;
|
|
1182
1153
|
});
|
|
1183
1154
|
files.splice(0);
|
|
1184
|
-
req.
|
|
1155
|
+
req.setReady();
|
|
1185
1156
|
};
|
|
1186
1157
|
next();
|
|
1187
|
-
req.once('error', () => req.
|
|
1158
|
+
req.once('error', () => req.setReady(new Error('Upload error')))
|
|
1188
1159
|
.on('data', chunk => chunkParse(chunk))
|
|
1189
|
-
.once('end', () => req.
|
|
1160
|
+
.once('end', () => req.setReady(new Error('Upload error')));
|
|
1190
1161
|
res.once('finish', () => _removeTempFiles());
|
|
1191
1162
|
res.once('error', () => _removeTempFiles());
|
|
1192
1163
|
res.once('close', () => _removeTempFiles());
|
|
@@ -1230,9 +1201,12 @@ export class WebSocket extends EventEmitter {
|
|
|
1230
1201
|
this._options.deflate = true;
|
|
1231
1202
|
}
|
|
1232
1203
|
this.ready = true;
|
|
1233
|
-
this._upgrade(key, headers)
|
|
1204
|
+
this._upgrade(key, headers, () => {
|
|
1205
|
+
req.setReady();
|
|
1206
|
+
this.emit('open');
|
|
1207
|
+
});
|
|
1234
1208
|
}
|
|
1235
|
-
_upgrade(key, headers = []) {
|
|
1209
|
+
_upgrade(key, headers = [], cb) {
|
|
1236
1210
|
const digest = crypto.createHash('sha1')
|
|
1237
1211
|
.update(key + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11')
|
|
1238
1212
|
.digest('base64');
|
|
@@ -1245,7 +1219,7 @@ export class WebSocket extends EventEmitter {
|
|
|
1245
1219
|
'',
|
|
1246
1220
|
''
|
|
1247
1221
|
];
|
|
1248
|
-
this._socket.write(headers.join('\r\n'));
|
|
1222
|
+
this._socket.write(headers.join('\r\n'), cb);
|
|
1249
1223
|
this._socket.on('error', this._errorHandler.bind(this));
|
|
1250
1224
|
this._socket.on('data', this._dataHandler.bind(this));
|
|
1251
1225
|
this._socket.on('close', () => this.emit('close'));
|
|
@@ -1511,7 +1485,6 @@ export class WebSocketPlugin extends Plugin {
|
|
|
1511
1485
|
srv.on('upgrade', this._handler);
|
|
1512
1486
|
}
|
|
1513
1487
|
upgradeHandler(server, req, socket, head) {
|
|
1514
|
-
ServerRequest.extend(req, server);
|
|
1515
1488
|
const host = req.headers.host || '';
|
|
1516
1489
|
const vhostPlugin = server.getPlugin('vhost');
|
|
1517
1490
|
const vserver = vhostPlugin?.vhosts?.[host] || server;
|
|
@@ -1524,7 +1497,7 @@ export class WebSocketPlugin extends Plugin {
|
|
|
1524
1497
|
statusCode: 200,
|
|
1525
1498
|
socket,
|
|
1526
1499
|
server,
|
|
1527
|
-
|
|
1500
|
+
end(data) {
|
|
1528
1501
|
if (res.headersSent)
|
|
1529
1502
|
throw new Error('Headers already sent');
|
|
1530
1503
|
let code = res.statusCode || 403;
|
|
@@ -1538,7 +1511,7 @@ export class WebSocketPlugin extends Plugin {
|
|
|
1538
1511
|
const headers = [
|
|
1539
1512
|
`HTTP/1.1 ${code} ${http.STATUS_CODES[code]}`,
|
|
1540
1513
|
'Connection: close',
|
|
1541
|
-
'Content-Type: text/
|
|
1514
|
+
'Content-Type: text/plain',
|
|
1542
1515
|
`Content-Length: ${Buffer.byteLength(data)}`,
|
|
1543
1516
|
'',
|
|
1544
1517
|
data
|
|
@@ -1547,22 +1520,25 @@ export class WebSocketPlugin extends Plugin {
|
|
|
1547
1520
|
},
|
|
1548
1521
|
error(code) {
|
|
1549
1522
|
res.statusCode = code || 403;
|
|
1550
|
-
res.
|
|
1551
|
-
},
|
|
1552
|
-
end(data) {
|
|
1553
|
-
res.write(data);
|
|
1523
|
+
res.end();
|
|
1554
1524
|
},
|
|
1555
1525
|
send(data) {
|
|
1556
|
-
res.
|
|
1526
|
+
res.end(data);
|
|
1557
1527
|
},
|
|
1558
1528
|
getHeader() { },
|
|
1559
1529
|
setHeader() { }
|
|
1560
1530
|
};
|
|
1531
|
+
ServerRequest.extend(req, res, server);
|
|
1532
|
+
let _ws;
|
|
1533
|
+
Object.defineProperty(req, 'websocket', {
|
|
1534
|
+
get: () => {
|
|
1535
|
+
if (!_ws)
|
|
1536
|
+
_ws = new WebSocket(req, server.config.websocket);
|
|
1537
|
+
return _ws;
|
|
1538
|
+
},
|
|
1539
|
+
enumerable: true
|
|
1540
|
+
});
|
|
1561
1541
|
vserver.handler(req, res);
|
|
1562
|
-
//vserver.handlerRouter(req, res as unknown as ServerResponse, () => res.error(404))
|
|
1563
|
-
}
|
|
1564
|
-
static create(req, options) {
|
|
1565
|
-
return new WebSocket(req, options);
|
|
1566
1542
|
}
|
|
1567
1543
|
}
|
|
1568
1544
|
// #endregion WebSocket
|
|
@@ -1731,7 +1707,7 @@ export class StaticFilesPlugin extends Plugin {
|
|
|
1731
1707
|
}
|
|
1732
1708
|
/** Default static files handler */
|
|
1733
1709
|
handler(req, res, next) {
|
|
1734
|
-
if (req.method !== 'GET')
|
|
1710
|
+
if (req.method !== 'GET' && req.method !== 'HEAD')
|
|
1735
1711
|
return next();
|
|
1736
1712
|
if (!('path' in req.params)) { // global handler
|
|
1737
1713
|
if (req.path.startsWith(this.prefix) && (req.path === this.prefix || req.path[this.prefix.length] === '/')) {
|
|
@@ -1772,6 +1748,7 @@ export class StaticFilesPlugin extends Plugin {
|
|
|
1772
1748
|
});
|
|
1773
1749
|
});
|
|
1774
1750
|
}
|
|
1751
|
+
/** Send static file */
|
|
1775
1752
|
serveFile(req, res, options) {
|
|
1776
1753
|
const filePath = path.isAbsolute(options.path) ? options.path : path.join(options.root || this.root, options.path);
|
|
1777
1754
|
const statRes = (err, stats) => {
|
|
@@ -1794,14 +1771,12 @@ export class StaticFilesPlugin extends Plugin {
|
|
|
1794
1771
|
res.setHeader('Content-Length', stats.size);
|
|
1795
1772
|
if (options.etag !== false) {
|
|
1796
1773
|
const etag = '"' + etagPrefix + stats.mtime.getTime().toString(32) + '"';
|
|
1797
|
-
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())
|
|
1798
1775
|
res.statusCode = 304;
|
|
1799
|
-
res.headersOnly = true;
|
|
1800
|
-
}
|
|
1801
1776
|
}
|
|
1802
1777
|
if (options.maxAge)
|
|
1803
1778
|
res.setHeader('Cache-Control', 'max-age=' + options.maxAge);
|
|
1804
|
-
if (res.
|
|
1779
|
+
if (res.statusCode === 304 || req.method === 'HEAD') {
|
|
1805
1780
|
res.end();
|
|
1806
1781
|
return;
|
|
1807
1782
|
}
|
|
@@ -2598,11 +2573,13 @@ export class FileStore {
|
|
|
2598
2573
|
});
|
|
2599
2574
|
return p;
|
|
2600
2575
|
}
|
|
2576
|
+
/** close store */
|
|
2601
2577
|
async close() {
|
|
2602
2578
|
await this.sync();
|
|
2603
2579
|
this._iter = 0;
|
|
2604
2580
|
this._cache = {};
|
|
2605
2581
|
}
|
|
2582
|
+
/** sync data to disk */
|
|
2606
2583
|
async sync() {
|
|
2607
2584
|
for (const name in this._cache) {
|
|
2608
2585
|
for (const key in this._cache)
|
|
@@ -3334,10 +3311,12 @@ export class MicroCollection {
|
|
|
3334
3311
|
async deleteMany(query) {
|
|
3335
3312
|
return (await this.updateMany(query, {}, { delete: true })).modifiedCount;
|
|
3336
3313
|
}
|
|
3314
|
+
/** Update one matching document */
|
|
3337
3315
|
async updateOne(query, update, options) {
|
|
3338
3316
|
const res = await this.updateMany(query, update, { ...options, limit: 1 });
|
|
3339
3317
|
return res;
|
|
3340
3318
|
}
|
|
3319
|
+
/** Update many matching documents */
|
|
3341
3320
|
async updateMany(query, update, options) {
|
|
3342
3321
|
let res = { upsertedId: undefined, modifiedCount: 0 };
|
|
3343
3322
|
if (!query)
|