velocious 1.0.97 → 1.0.99
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/eslint.config.js +1 -0
- package/package.json +2 -1
- package/spec/database/connection/drivers/mysql/query-parser-spec.js +4 -4
- package/spec/http-server/post-spec.js +2 -0
- package/src/application.js +27 -9
- package/src/configuration-resolver.js +29 -10
- package/src/configuration-types.js +44 -0
- package/src/configuration.js +63 -33
- package/src/database/drivers/base-column.js +6 -1
- package/src/database/drivers/base-columns-index.js +11 -1
- package/src/database/drivers/base-foreign-key.js +45 -0
- package/src/database/drivers/base-table.js +24 -2
- package/src/database/drivers/base.js +211 -39
- package/src/database/drivers/mssql/index.js +1 -3
- package/src/database/drivers/sqlite/sql/alter-table.js +4 -2
- package/src/database/handler.js +5 -0
- package/src/database/migration/index.js +79 -20
- package/src/database/migrator/files-finder.js +21 -22
- package/src/database/migrator/types.js +29 -0
- package/src/database/migrator.js +98 -59
- package/src/database/pool/async-tracked-multi-connection.js +42 -7
- package/src/database/pool/base-methods-forward.js +37 -0
- package/src/database/pool/base.js +79 -46
- package/src/database/pool/single-multi-use.js +18 -3
- package/src/database/query/alter-table-base.js +4 -4
- package/src/database/query/base.js +9 -2
- package/src/database/query/create-database-base.js +8 -0
- package/src/database/query/create-index-base.js +20 -5
- package/src/database/query/create-table-base.js +28 -9
- package/src/database/query/from-base.js +17 -0
- package/src/database/query/from-plain.js +8 -3
- package/src/database/query/from-table.js +8 -3
- package/src/database/query/index.js +43 -32
- package/src/database/query/join-base.js +28 -1
- package/src/database/query/join-object.js +67 -0
- package/src/database/query/join-plain.js +6 -1
- package/src/database/query/order-base.js +18 -0
- package/src/database/query/order-plain.js +8 -2
- package/src/database/query/select-base.js +15 -0
- package/src/database/query/select-plain.js +6 -1
- package/src/database/query/select-table-and-column.js +8 -2
- package/src/database/query/where-base.js +23 -1
- package/src/database/query/where-hash.js +15 -0
- package/src/database/query/where-plain.js +6 -0
- package/src/database/query-parser/base-query-parser.js +8 -2
- package/src/database/query-parser/from-parser.js +2 -0
- package/src/database/query-parser/joins-parser.js +10 -45
- package/src/database/query-parser/select-parser.js +2 -0
- package/src/database/record/index.js +1 -1
- package/src/database/table-data/index.js +39 -121
- package/src/database/table-data/table-column.js +54 -25
- package/src/database/table-data/table-foreign-key.js +5 -3
- package/src/database/table-data/table-index.js +12 -6
- package/src/database/table-data/table-reference.js +2 -0
- package/src/database/use-database.js +4 -2
- package/src/environment-handlers/base.js +41 -8
- package/src/environment-handlers/node/cli/commands/destroy/migration.js +3 -0
- package/src/environment-handlers/node/cli/commands/generate/migration.js +3 -0
- package/src/environment-handlers/node/cli/commands/generate/model.js +3 -0
- package/src/environment-handlers/node/cli/commands/init.js +3 -0
- package/src/environment-handlers/node.js +59 -28
- package/src/http-client/header.js +6 -0
- package/src/http-client/request.js +31 -5
- package/src/http-client/response.js +31 -7
- package/src/http-server/client/index.js +24 -4
- package/src/http-server/client/request-buffer/form-data-part.js +11 -0
- package/src/http-server/client/request-buffer/header.js +6 -0
- package/src/http-server/client/request-buffer/index.js +91 -13
- package/src/http-server/client/request-parser.js +26 -0
- package/src/http-server/client/request-runner.js +15 -3
- package/src/http-server/client/request.js +17 -0
- package/src/http-server/client/response.js +41 -1
- package/src/http-server/index.js +32 -4
- package/src/http-server/server-client.js +33 -2
- package/src/http-server/worker-handler/index.js +42 -9
- package/src/http-server/worker-handler/worker-script.js +2 -0
- package/src/http-server/worker-handler/worker-thread.js +34 -6
- package/src/logger.js +21 -15
- package/src/routes/app-routes.js +1 -1
- package/src/testing/test-files-finder.js +8 -4
- package/src/testing/test-runner.js +76 -24
- package/src/utils/backtrace-cleaner.js +6 -4
- package/src/utils/ensure-error.js +13 -0
- package/src/utils/file-exists.js +3 -1
- package/src/utils/rest-args-error.js +2 -0
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
1
3
|
import {EventEmitter} from "events"
|
|
2
4
|
import FormDataPart from "./form-data-part.js"
|
|
3
5
|
import Header from "./header.js"
|
|
@@ -8,18 +10,38 @@ import querystring from "querystring"
|
|
|
8
10
|
|
|
9
11
|
export default class RequestBuffer {
|
|
10
12
|
bodyLength = 0
|
|
13
|
+
|
|
14
|
+
/** @type {number[]} */
|
|
11
15
|
data = []
|
|
16
|
+
|
|
12
17
|
events = new EventEmitter()
|
|
18
|
+
|
|
19
|
+
/** @type {Record<string, Header>} */
|
|
13
20
|
headersByName = {}
|
|
21
|
+
|
|
22
|
+
multiPartyFormData = false
|
|
23
|
+
|
|
14
24
|
params = {}
|
|
15
25
|
readingBody = false
|
|
16
26
|
state = "status"
|
|
17
27
|
|
|
28
|
+
/**
|
|
29
|
+
* @param {object} args
|
|
30
|
+
* @param {import("../../../configuration.js").default} args.configuration
|
|
31
|
+
*/
|
|
18
32
|
constructor({configuration}) {
|
|
19
33
|
this.configuration = configuration
|
|
20
34
|
this.logger = new Logger(this, {debug: false})
|
|
21
35
|
}
|
|
22
36
|
|
|
37
|
+
destroy() {
|
|
38
|
+
// Do nothing for now...
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* @param {Buffer} data
|
|
43
|
+
* @returns {void}
|
|
44
|
+
*/
|
|
23
45
|
feed(data) {
|
|
24
46
|
for (const char of data) {
|
|
25
47
|
if (this.readingBody) this.bodyLength += 1
|
|
@@ -40,6 +62,10 @@ export default class RequestBuffer {
|
|
|
40
62
|
|
|
41
63
|
break
|
|
42
64
|
case "multi-part-form-data-body":
|
|
65
|
+
if (!this.formDataPart) throw new Error("FormData part not initialized")
|
|
66
|
+
if (!this.boundaryLineEnd) throw new Error("Boundary line end not initialized")
|
|
67
|
+
if (!this.boundaryLineNext) throw new Error("Boundary line next not initialized")
|
|
68
|
+
|
|
43
69
|
const body = this.formDataPart.body // eslint-disable-line no-case-declarations
|
|
44
70
|
|
|
45
71
|
body.push(char)
|
|
@@ -71,6 +97,8 @@ export default class RequestBuffer {
|
|
|
71
97
|
|
|
72
98
|
break
|
|
73
99
|
case "post-body":
|
|
100
|
+
if (!this.postBodyChars) throw new Error("postBodyChars not initialized")
|
|
101
|
+
|
|
74
102
|
this.postBodyChars[this.bodyLength - 1] = char
|
|
75
103
|
|
|
76
104
|
if (this.contentLength && this.bodyLength >= this.contentLength) {
|
|
@@ -84,6 +112,10 @@ export default class RequestBuffer {
|
|
|
84
112
|
}
|
|
85
113
|
}
|
|
86
114
|
|
|
115
|
+
/**
|
|
116
|
+
* @param {string} name
|
|
117
|
+
* @returns {Header}
|
|
118
|
+
*/
|
|
87
119
|
getHeader(name) {
|
|
88
120
|
const result = this.headersByName[name.toLowerCase().trim()]
|
|
89
121
|
|
|
@@ -92,7 +124,11 @@ export default class RequestBuffer {
|
|
|
92
124
|
return result
|
|
93
125
|
}
|
|
94
126
|
|
|
127
|
+
/**
|
|
128
|
+
* @returns {Record<string, string>}
|
|
129
|
+
*/
|
|
95
130
|
getHeadersHash() {
|
|
131
|
+
/** @type {Record<string, string>} */
|
|
96
132
|
const result = {}
|
|
97
133
|
|
|
98
134
|
for (const headerFormattedName in this.headersByName) {
|
|
@@ -104,9 +140,14 @@ export default class RequestBuffer {
|
|
|
104
140
|
return result
|
|
105
141
|
}
|
|
106
142
|
|
|
143
|
+
/**
|
|
144
|
+
* @returns {void}
|
|
145
|
+
*/
|
|
107
146
|
formDataPartDone() {
|
|
108
147
|
const formDataPart = this.formDataPart
|
|
109
148
|
|
|
149
|
+
if (!formDataPart) throw new Error("formDataPart wasnt set")
|
|
150
|
+
|
|
110
151
|
this.formDataPart = undefined
|
|
111
152
|
formDataPart.finish()
|
|
112
153
|
|
|
@@ -117,11 +158,18 @@ export default class RequestBuffer {
|
|
|
117
158
|
return this.multiPartyFormData
|
|
118
159
|
}
|
|
119
160
|
|
|
161
|
+
/**
|
|
162
|
+
* @returns {void}
|
|
163
|
+
*/
|
|
120
164
|
newFormDataPart() {
|
|
121
165
|
this.formDataPart = new FormDataPart()
|
|
122
166
|
this.setState("multi-part-form-data-header")
|
|
123
167
|
}
|
|
124
168
|
|
|
169
|
+
/**
|
|
170
|
+
* @param {string} line
|
|
171
|
+
* @returns {void}
|
|
172
|
+
*/
|
|
125
173
|
parse(line) {
|
|
126
174
|
if (this.state == "status") {
|
|
127
175
|
this.parseStatusLine(line)
|
|
@@ -139,8 +187,10 @@ export default class RequestBuffer {
|
|
|
139
187
|
const header = this.readHeaderFromLine(line)
|
|
140
188
|
|
|
141
189
|
if (header) {
|
|
190
|
+
if (!this.formDataPart) throw new Error("formDataPart not set")
|
|
191
|
+
|
|
142
192
|
this.formDataPart.addHeader(header)
|
|
143
|
-
this.state == "multi-part-form-data"
|
|
193
|
+
//this.state == "multi-part-form-data"
|
|
144
194
|
} else if (line == "\r\n") {
|
|
145
195
|
this.setState("multi-part-form-data-body")
|
|
146
196
|
}
|
|
@@ -149,6 +199,10 @@ export default class RequestBuffer {
|
|
|
149
199
|
}
|
|
150
200
|
}
|
|
151
201
|
|
|
202
|
+
/**
|
|
203
|
+
* @param {string} line
|
|
204
|
+
* @returns {Header | undefined}
|
|
205
|
+
*/
|
|
152
206
|
readHeaderFromLine(line) {
|
|
153
207
|
const match = line.match(/^(.+): (.+)\r\n/)
|
|
154
208
|
|
|
@@ -159,6 +213,9 @@ export default class RequestBuffer {
|
|
|
159
213
|
}
|
|
160
214
|
}
|
|
161
215
|
|
|
216
|
+
/**
|
|
217
|
+
* @param {Header} header
|
|
218
|
+
*/
|
|
162
219
|
addHeader(header) {
|
|
163
220
|
const formattedName = header.getFormattedName()
|
|
164
221
|
|
|
@@ -167,6 +224,10 @@ export default class RequestBuffer {
|
|
|
167
224
|
if (formattedName == "content-length") this.contentLength = parseInt(header.getValue())
|
|
168
225
|
}
|
|
169
226
|
|
|
227
|
+
/**
|
|
228
|
+
* @param {string} line
|
|
229
|
+
* @returns {void}
|
|
230
|
+
*/
|
|
170
231
|
parseHeader(line) {
|
|
171
232
|
const header = this.readHeaderFromLine(line)
|
|
172
233
|
|
|
@@ -175,9 +236,9 @@ export default class RequestBuffer {
|
|
|
175
236
|
this.addHeader(header)
|
|
176
237
|
this.events.emit("header", header)
|
|
177
238
|
} else if (line == "\r\n") {
|
|
178
|
-
if (this.httpMethod
|
|
239
|
+
if (this.httpMethod?.toUpperCase() == "GET" || this.httpMethod?.toUpperCase() == "OPTIONS") {
|
|
179
240
|
this.completeRequest()
|
|
180
|
-
} else if (this.httpMethod
|
|
241
|
+
} else if (this.httpMethod?.toUpperCase() == "POST") {
|
|
181
242
|
this.readingBody = true
|
|
182
243
|
this.bodyLength = 0
|
|
183
244
|
|
|
@@ -196,8 +257,11 @@ export default class RequestBuffer {
|
|
|
196
257
|
} else if (!this.contentLength) {
|
|
197
258
|
throw new Error("Content length hasn't been set")
|
|
198
259
|
} else {
|
|
199
|
-
|
|
200
|
-
this.postBodyChars =
|
|
260
|
+
/** @type {number[]} */
|
|
261
|
+
this.postBodyChars = []
|
|
262
|
+
|
|
263
|
+
// this.postBodyBuffer = new ArrayBuffer(this.contentLength)
|
|
264
|
+
// this.postBodyChars = new Uint8Array(this.postBodyBuffer)
|
|
201
265
|
|
|
202
266
|
this.setState("post-body")
|
|
203
267
|
}
|
|
@@ -208,6 +272,10 @@ export default class RequestBuffer {
|
|
|
208
272
|
}
|
|
209
273
|
}
|
|
210
274
|
|
|
275
|
+
/**
|
|
276
|
+
* @param {string} line
|
|
277
|
+
* @returns {void}
|
|
278
|
+
*/
|
|
211
279
|
parseStatusLine(line) {
|
|
212
280
|
const match = line.match(/^(GET|OPTIONS|POST) (.+?) HTTP\/(.+)\r\n/)
|
|
213
281
|
|
|
@@ -223,14 +291,20 @@ export default class RequestBuffer {
|
|
|
223
291
|
}
|
|
224
292
|
|
|
225
293
|
postRequestDone() {
|
|
226
|
-
|
|
294
|
+
if (this.postBodyChars) {
|
|
295
|
+
this.postBody = String.fromCharCode.apply(null, this.postBodyChars)
|
|
296
|
+
}
|
|
227
297
|
|
|
228
298
|
delete this.postBodyChars
|
|
229
|
-
delete this.postBodyBuffer
|
|
299
|
+
// delete this.postBodyBuffer
|
|
230
300
|
|
|
231
301
|
this.completeRequest()
|
|
232
302
|
}
|
|
233
303
|
|
|
304
|
+
/**
|
|
305
|
+
* @param {string} newState
|
|
306
|
+
* @returns {void}
|
|
307
|
+
*/
|
|
234
308
|
setState(newState) {
|
|
235
309
|
this.logger.debug(() => [`Changing state from ${this.state} to ${newState}`])
|
|
236
310
|
this.state = newState
|
|
@@ -251,16 +325,20 @@ export default class RequestBuffer {
|
|
|
251
325
|
}
|
|
252
326
|
|
|
253
327
|
parseApplicationJsonParams() {
|
|
254
|
-
|
|
328
|
+
if (this.postBody) {
|
|
329
|
+
const newParams = JSON.parse(this.postBody)
|
|
255
330
|
|
|
256
|
-
|
|
331
|
+
incorporate(this.params, newParams)
|
|
332
|
+
}
|
|
257
333
|
}
|
|
258
334
|
|
|
259
335
|
parseQueryStringPostParams() {
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
336
|
+
if (this.postBody) {
|
|
337
|
+
const unparsedParams = querystring.parse(this.postBody)
|
|
338
|
+
const paramsToObject = new ParamsToObject(unparsedParams)
|
|
339
|
+
const newParams = paramsToObject.toObject()
|
|
263
340
|
|
|
264
|
-
|
|
341
|
+
incorporate(this.params, newParams)
|
|
342
|
+
}
|
|
265
343
|
}
|
|
266
344
|
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
1
3
|
import {digg} from "diggerize"
|
|
2
4
|
import {EventEmitter} from "events"
|
|
3
5
|
import {incorporate} from "incorporator"
|
|
@@ -5,6 +7,10 @@ import ParamsToObject from "./params-to-object.js"
|
|
|
5
7
|
import RequestBuffer from "./request-buffer/index.js"
|
|
6
8
|
|
|
7
9
|
export default class VelociousHttpServerClientRequestParser {
|
|
10
|
+
/**
|
|
11
|
+
* @param {object} args
|
|
12
|
+
* @param {import("../../configuration.js").default} args.configuration
|
|
13
|
+
*/
|
|
8
14
|
constructor({configuration}) {
|
|
9
15
|
if (!configuration) throw new Error("No configuration given")
|
|
10
16
|
|
|
@@ -26,7 +32,12 @@ export default class VelociousHttpServerClientRequestParser {
|
|
|
26
32
|
this.requestBuffer.destroy()
|
|
27
33
|
}
|
|
28
34
|
|
|
35
|
+
/**
|
|
36
|
+
* @param {import("./request-buffer/form-data-part.js").default} formDataPart
|
|
37
|
+
* @returns {void}
|
|
38
|
+
*/
|
|
29
39
|
onFormDataPart = (formDataPart) => {
|
|
40
|
+
/** @type {Record<string, string>} */
|
|
30
41
|
const unorderedParams = {}
|
|
31
42
|
|
|
32
43
|
unorderedParams[formDataPart.getName()] = formDataPart.getValue()
|
|
@@ -37,10 +48,25 @@ export default class VelociousHttpServerClientRequestParser {
|
|
|
37
48
|
incorporate(this.params, newParams)
|
|
38
49
|
}
|
|
39
50
|
|
|
51
|
+
/**
|
|
52
|
+
* @param {Buffer} data
|
|
53
|
+
*/
|
|
40
54
|
feed = (data) => this.requestBuffer.feed(data)
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* @param {string} name
|
|
58
|
+
*/
|
|
41
59
|
getHeader(name) { return this.requestBuffer.getHeader(name)?.value }
|
|
42
60
|
getHeaders() { return this.requestBuffer.getHeadersHash() }
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* @returns {string}
|
|
64
|
+
*/
|
|
43
65
|
getHttpMethod() { return digg(this, "requestBuffer", "httpMethod") }
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* @returns {string}
|
|
69
|
+
*/
|
|
44
70
|
getHttpVersion() { return digg(this, "requestBuffer", "httpVersion") }
|
|
45
71
|
|
|
46
72
|
_getHostMatch() {
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
1
3
|
import BacktraceCleaner from "../../utils/backtrace-cleaner.js"
|
|
4
|
+
import ensureError from "../../utils/ensure-error.js"
|
|
2
5
|
import EventEmitter from "events"
|
|
3
6
|
import {Logger} from "../../logger.js"
|
|
4
7
|
import Response from "./response.js"
|
|
@@ -7,6 +10,11 @@ import RoutesResolver from "../../routes/resolver.js"
|
|
|
7
10
|
export default class VelociousHttpServerClientRequestRunner {
|
|
8
11
|
events = new EventEmitter()
|
|
9
12
|
|
|
13
|
+
/**
|
|
14
|
+
* @param {object} args
|
|
15
|
+
* @param {import("../../configuration.js").default} args.configuration
|
|
16
|
+
* @param {import("./request.js").default} args.request
|
|
17
|
+
*/
|
|
10
18
|
constructor({configuration, request}) {
|
|
11
19
|
if (!configuration) throw new Error("No configuration given")
|
|
12
20
|
if (!request) throw new Error("No request given")
|
|
@@ -30,8 +38,10 @@ export default class VelociousHttpServerClientRequestRunner {
|
|
|
30
38
|
// Before we checked if the sec-fetch-mode was "cors", but it seems the sec-fetch-mode isn't always present
|
|
31
39
|
await this.logger.debug(() => ["Run CORS", {httpMethod: request.httpMethod(), secFetchMode: request.header("sec-fetch-mode")}])
|
|
32
40
|
|
|
33
|
-
|
|
34
|
-
|
|
41
|
+
const cors = configuration.getCors()
|
|
42
|
+
|
|
43
|
+
if (cors) {
|
|
44
|
+
await cors({request, response})
|
|
35
45
|
}
|
|
36
46
|
|
|
37
47
|
if (request.httpMethod() == "OPTIONS" && request.header("sec-fetch-mode") == "cors") {
|
|
@@ -43,7 +53,9 @@ export default class VelociousHttpServerClientRequestRunner {
|
|
|
43
53
|
|
|
44
54
|
await routesResolver.resolve()
|
|
45
55
|
}
|
|
46
|
-
} catch (
|
|
56
|
+
} catch (e) {
|
|
57
|
+
const error = ensureError(e)
|
|
58
|
+
|
|
47
59
|
await this.logger.error(() => [`Error while running request: ${BacktraceCleaner.getCleanedStack(error)}`])
|
|
48
60
|
|
|
49
61
|
response.setStatus(500)
|
|
@@ -1,8 +1,15 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
1
3
|
import {digg} from "diggerize"
|
|
2
4
|
import RequestParser from "./request-parser.js"
|
|
3
5
|
import restArgsError from "../../utils/rest-args-error.js"
|
|
4
6
|
|
|
5
7
|
export default class VelociousHttpServerClientRequest {
|
|
8
|
+
/**
|
|
9
|
+
* @param {object} args
|
|
10
|
+
* @param {import("./index.js").default} args.client
|
|
11
|
+
* @param {import("../../configuration.js").default} args.configuration
|
|
12
|
+
*/
|
|
6
13
|
constructor({client, configuration, ...restArgs}) {
|
|
7
14
|
restArgsError(restArgs)
|
|
8
15
|
|
|
@@ -12,7 +19,17 @@ export default class VelociousHttpServerClientRequest {
|
|
|
12
19
|
}
|
|
13
20
|
|
|
14
21
|
baseURL() { return `${this.protocol()}://${this.hostWithPort()}` }
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* @param {Buffer} data
|
|
25
|
+
* @returns {void}
|
|
26
|
+
*/
|
|
15
27
|
feed(data) { return this.requestParser.feed(data) }
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* @param {string} headerName
|
|
31
|
+
* @returns {string | null}
|
|
32
|
+
*/
|
|
16
33
|
header(headerName) { return this.getRequestBuffer().getHeader(headerName)?.getValue() }
|
|
17
34
|
headers() { return this.getRequestBuffer().getHeadersHash() }
|
|
18
35
|
httpMethod() { return this.requestParser.getHttpMethod() }
|
|
@@ -1,11 +1,25 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
1
3
|
export default class VelociousHttpServerClientResponse {
|
|
2
|
-
|
|
4
|
+
/** @type {string | null} */
|
|
5
|
+
body = null
|
|
6
|
+
|
|
7
|
+
/** @type {Record<string, string[]>} */
|
|
3
8
|
headers = {}
|
|
4
9
|
|
|
10
|
+
/**
|
|
11
|
+
* @param {object} args
|
|
12
|
+
* @param {import("../../configuration.js").default} args.configuration
|
|
13
|
+
*/
|
|
5
14
|
constructor({configuration}) {
|
|
6
15
|
this.configuration = configuration
|
|
7
16
|
}
|
|
8
17
|
|
|
18
|
+
/**
|
|
19
|
+
* @param {string} key
|
|
20
|
+
* @param {string} value
|
|
21
|
+
* @returns {void}
|
|
22
|
+
*/
|
|
9
23
|
addHeader(key, value) {
|
|
10
24
|
if (!(key in this.headers)) {
|
|
11
25
|
this.headers[key] = []
|
|
@@ -14,10 +28,18 @@ export default class VelociousHttpServerClientResponse {
|
|
|
14
28
|
this.headers[key].push(value)
|
|
15
29
|
}
|
|
16
30
|
|
|
31
|
+
/**
|
|
32
|
+
* @param {string} key
|
|
33
|
+
* @param {string} value
|
|
34
|
+
* @returns {void}
|
|
35
|
+
*/
|
|
17
36
|
setHeader(key, value) {
|
|
18
37
|
this.headers[key] = [value]
|
|
19
38
|
}
|
|
20
39
|
|
|
40
|
+
/**
|
|
41
|
+
* @returns {string | null}
|
|
42
|
+
*/
|
|
21
43
|
getBody() {
|
|
22
44
|
if (this.body !== undefined) {
|
|
23
45
|
return this.body
|
|
@@ -26,23 +48,41 @@ export default class VelociousHttpServerClientResponse {
|
|
|
26
48
|
throw new Error("No body has been set")
|
|
27
49
|
}
|
|
28
50
|
|
|
51
|
+
/**
|
|
52
|
+
* @returns {number}
|
|
53
|
+
*/
|
|
29
54
|
getStatusCode() {
|
|
30
55
|
return this.statusCode || 200
|
|
31
56
|
}
|
|
32
57
|
|
|
58
|
+
/**
|
|
59
|
+
* @returns {string}
|
|
60
|
+
*/
|
|
33
61
|
getStatusMessage() {
|
|
34
62
|
return this.statusMessage || "OK"
|
|
35
63
|
}
|
|
36
64
|
|
|
65
|
+
/**
|
|
66
|
+
* @param {string} value
|
|
67
|
+
* @returns {void}
|
|
68
|
+
*/
|
|
37
69
|
setBody(value) {
|
|
38
70
|
this.body = value
|
|
39
71
|
}
|
|
40
72
|
|
|
73
|
+
/**
|
|
74
|
+
* @param {Error} error
|
|
75
|
+
* @returns {void}
|
|
76
|
+
*/
|
|
41
77
|
setErrorBody(error) {
|
|
42
78
|
this.setHeader("Content-Type", "text/plain; charset=UTF-8")
|
|
43
79
|
this.setBody(`${error.message}\n\n${error.stack}`)
|
|
44
80
|
}
|
|
45
81
|
|
|
82
|
+
/**
|
|
83
|
+
* @param {number | string} status
|
|
84
|
+
* @returns {void}
|
|
85
|
+
*/
|
|
46
86
|
setStatus(status) {
|
|
47
87
|
if (status == "success" || status == 200) {
|
|
48
88
|
this.statusCode = 200
|
package/src/http-server/index.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
1
3
|
import {digg} from "diggerize"
|
|
2
4
|
import EventEmitter from "events"
|
|
3
5
|
import {Logger} from "../logger.js"
|
|
@@ -7,11 +9,23 @@ import WorkerHandler from "./worker-handler/index.js"
|
|
|
7
9
|
|
|
8
10
|
export default class VelociousHttpServer {
|
|
9
11
|
clientCount = 0
|
|
12
|
+
|
|
13
|
+
/** @type {Record<string, ServerClient>} */
|
|
10
14
|
clients = {}
|
|
15
|
+
|
|
11
16
|
events = new EventEmitter()
|
|
12
17
|
workerCount = 0
|
|
18
|
+
|
|
19
|
+
/** @type {WorkerHandler[]} */
|
|
13
20
|
workerHandlers = []
|
|
14
21
|
|
|
22
|
+
/**
|
|
23
|
+
* @param {object} args
|
|
24
|
+
* @param {import("../configuration.js").default} args.configuration
|
|
25
|
+
* @param {string} [args.host]
|
|
26
|
+
* @param {number} [args.port]
|
|
27
|
+
* @param {number} [args.maxWorkers]
|
|
28
|
+
*/
|
|
15
29
|
constructor({configuration, host, maxWorkers, port}) {
|
|
16
30
|
this.configuration = configuration
|
|
17
31
|
this.logger = new Logger(this)
|
|
@@ -33,10 +47,12 @@ export default class VelociousHttpServer {
|
|
|
33
47
|
|
|
34
48
|
_netServerListen() {
|
|
35
49
|
return new Promise((resolve, reject) => {
|
|
50
|
+
if (!this.netServer) throw new Error("No netServer")
|
|
51
|
+
|
|
36
52
|
try {
|
|
37
53
|
this.netServer.listen(this.port, this.host, () => {
|
|
38
54
|
this.logger.debug(`Velocious listening on ${this.host}:${this.port}`)
|
|
39
|
-
resolve()
|
|
55
|
+
resolve(null)
|
|
40
56
|
})
|
|
41
57
|
} catch (error) {
|
|
42
58
|
reject(error)
|
|
@@ -54,7 +70,11 @@ export default class VelociousHttpServer {
|
|
|
54
70
|
* @returns {boolean}
|
|
55
71
|
*/
|
|
56
72
|
isActive() {
|
|
57
|
-
|
|
73
|
+
if (this.netServer) {
|
|
74
|
+
return this.netServer.listening
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return false
|
|
58
78
|
}
|
|
59
79
|
|
|
60
80
|
async stopClients() {
|
|
@@ -71,11 +91,13 @@ export default class VelociousHttpServer {
|
|
|
71
91
|
|
|
72
92
|
stopServer() {
|
|
73
93
|
return new Promise((resolve, reject) => {
|
|
94
|
+
if (!this.netServer) throw new Error("No netServer to stop")
|
|
95
|
+
|
|
74
96
|
this.netServer.close((error) => {
|
|
75
97
|
if (error) {
|
|
76
98
|
reject(error)
|
|
77
99
|
} else {
|
|
78
|
-
resolve()
|
|
100
|
+
resolve(null)
|
|
79
101
|
}
|
|
80
102
|
})
|
|
81
103
|
})
|
|
@@ -90,6 +112,9 @@ export default class VelociousHttpServer {
|
|
|
90
112
|
this.events.emit("close")
|
|
91
113
|
}
|
|
92
114
|
|
|
115
|
+
/**
|
|
116
|
+
* @param {import("net").Socket} socket
|
|
117
|
+
*/
|
|
93
118
|
onConnection = (socket) => {
|
|
94
119
|
const clientCount = this.clientCount
|
|
95
120
|
|
|
@@ -110,6 +135,9 @@ export default class VelociousHttpServer {
|
|
|
110
135
|
this.clients[clientCount] = client
|
|
111
136
|
}
|
|
112
137
|
|
|
138
|
+
/**
|
|
139
|
+
* @param {ServerClient} client
|
|
140
|
+
*/
|
|
113
141
|
onClientClose = (client) => {
|
|
114
142
|
const clientCount = digg(client, "clientCount")
|
|
115
143
|
const oldClientsLength = Object.keys(this.clients).length
|
|
@@ -140,7 +168,7 @@ export default class VelociousHttpServer {
|
|
|
140
168
|
workerHandlerToUse() {
|
|
141
169
|
this.logger.debug(`Worker handlers length: ${this.workerHandlers.length}`)
|
|
142
170
|
|
|
143
|
-
const randomWorkerNumber =
|
|
171
|
+
const randomWorkerNumber = Math.floor(Math.random() * this.workerHandlers.length)
|
|
144
172
|
const workerHandler = this.workerHandlers[randomWorkerNumber]
|
|
145
173
|
|
|
146
174
|
if (!workerHandler) {
|
|
@@ -1,9 +1,17 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
1
3
|
import EventEmitter from "events"
|
|
2
4
|
import {Logger} from "../logger.js"
|
|
3
5
|
|
|
4
6
|
export default class ServerClient {
|
|
5
7
|
events = new EventEmitter()
|
|
6
8
|
|
|
9
|
+
/**
|
|
10
|
+
* @param {object} args
|
|
11
|
+
* @param {import("../configuration.js").default} args.configuration
|
|
12
|
+
* @param {import("net").Socket} args.socket
|
|
13
|
+
* @param {number} args.clientCount
|
|
14
|
+
*/
|
|
7
15
|
constructor({configuration, socket, clientCount}) {
|
|
8
16
|
if (!configuration) throw new Error("No configuration given")
|
|
9
17
|
|
|
@@ -15,18 +23,26 @@ export default class ServerClient {
|
|
|
15
23
|
socket.on("end", this.onSocketEnd)
|
|
16
24
|
}
|
|
17
25
|
|
|
18
|
-
listen
|
|
26
|
+
listen() {
|
|
27
|
+
this.socket.on("data", this.onSocketData)
|
|
28
|
+
}
|
|
19
29
|
|
|
20
30
|
end() {
|
|
21
31
|
return new Promise((resolve) => {
|
|
22
|
-
this.socket.once("close", () => resolve())
|
|
32
|
+
this.socket.once("close", () => resolve(null))
|
|
23
33
|
this.socket.end()
|
|
24
34
|
})
|
|
25
35
|
}
|
|
26
36
|
|
|
37
|
+
/**
|
|
38
|
+
* @param {Buffer} chunk
|
|
39
|
+
* @returns {void}
|
|
40
|
+
*/
|
|
27
41
|
onSocketData = (chunk) => {
|
|
28
42
|
this.logger.debug(() => [`Socket ${this.clientCount}: ${chunk}`])
|
|
29
43
|
|
|
44
|
+
if (!this.worker) throw new Error("No worker")
|
|
45
|
+
|
|
30
46
|
this.worker.postMessage({
|
|
31
47
|
command: "clientWrite",
|
|
32
48
|
chunk,
|
|
@@ -34,11 +50,18 @@ export default class ServerClient {
|
|
|
34
50
|
})
|
|
35
51
|
}
|
|
36
52
|
|
|
53
|
+
/**
|
|
54
|
+
* @returns {void}
|
|
55
|
+
*/
|
|
37
56
|
onSocketEnd = () => {
|
|
38
57
|
this.logger.debug(`Socket ${this.clientCount} end`)
|
|
39
58
|
this.events.emit("close", this)
|
|
40
59
|
}
|
|
41
60
|
|
|
61
|
+
/**
|
|
62
|
+
* @param {string} data
|
|
63
|
+
* @returns {Promise<void>}
|
|
64
|
+
*/
|
|
42
65
|
async send(data) {
|
|
43
66
|
return new Promise((resolve) => {
|
|
44
67
|
this.logger.debug("Send", data)
|
|
@@ -47,4 +70,12 @@ export default class ServerClient {
|
|
|
47
70
|
})
|
|
48
71
|
})
|
|
49
72
|
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* @param {import("worker_threads").Worker} newWorker
|
|
76
|
+
* @returns {void}
|
|
77
|
+
*/
|
|
78
|
+
setWorker(newWorker) {
|
|
79
|
+
this.worker = newWorker
|
|
80
|
+
}
|
|
50
81
|
}
|