@muze-nl/simplystore 0.4.6 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +12 -4
- package/scripts/convert.mjs +25 -0
- package/src/command-worker-module.mjs +100 -0
- package/src/command-worker.mjs +13 -0
- package/src/fastParse.mjs +1036 -0
- package/src/fastStringify.mjs +200 -0
- package/src/load-worker.mjs +33 -0
- package/src/{worker-query.mjs → query-worker-module.mjs} +138 -83
- package/src/query-worker.mjs +12 -0
- package/src/server.mjs +303 -218
- package/src/statusCodes.mjs +70 -0
- package/src/symbols.mjs +6 -0
- package/src/util.mjs +3 -3
- package/src/workerPool.mjs +99 -0
- package/data.jsontag +0 -20
- package/src/share.mjs +0 -142
- package/src/worker-query-init.mjs +0 -14
package/src/server.mjs
CHANGED
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
import express from 'express'
|
|
2
2
|
import fs from 'fs'
|
|
3
3
|
import JSONTag from '@muze-nl/jsontag'
|
|
4
|
+
import WorkerPool from './workerPool.mjs'
|
|
5
|
+
import { Worker } from 'worker_threads'
|
|
4
6
|
import { fileURLToPath } from 'url'
|
|
5
|
-
import path from 'path'
|
|
6
|
-
import commands from './commands.mjs'
|
|
7
7
|
import {appendFile} from './util.mjs'
|
|
8
|
-
import
|
|
8
|
+
import path from 'path'
|
|
9
|
+
import httpStatusCodes from './statusCodes.mjs'
|
|
10
|
+
import writeFileAtomic from 'write-file-atomic'
|
|
9
11
|
|
|
12
|
+
const server = express()
|
|
10
13
|
const __dirname = path.dirname(path.dirname(fileURLToPath(import.meta.url)))
|
|
11
|
-
|
|
12
|
-
|
|
14
|
+
let jsontagBuffer = null
|
|
15
|
+
let meta = {}
|
|
13
16
|
|
|
14
17
|
async function main(options) {
|
|
15
18
|
if (!options) {
|
|
@@ -18,80 +21,59 @@ async function main(options) {
|
|
|
18
21
|
const port = options.port || 3000
|
|
19
22
|
const datafile = options.datafile || './data.jsontag'
|
|
20
23
|
const wwwroot = options.wwwroot || __dirname+'/www'
|
|
21
|
-
const
|
|
22
|
-
const queryWorker = options.queryWorker || __dirname+'/src/
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
let options = {
|
|
29
|
-
filename: workerName,
|
|
30
|
-
workerData: jsontag
|
|
31
|
-
}
|
|
32
|
-
if (size) {
|
|
33
|
-
options.maxThreads = size
|
|
34
|
-
}
|
|
35
|
-
return new Piscina(options)
|
|
36
|
-
}
|
|
24
|
+
const maxWorkers = options.maxWorkers || 8
|
|
25
|
+
const queryWorker = options.queryWorker || __dirname+'/src/query-worker.mjs'
|
|
26
|
+
const loadWorker = options.loadWorker || __dirname+'/src/load-worker.mjs'
|
|
27
|
+
const commandWorker = options.commandWorker || __dirname+'/src/command-worker.mjs'
|
|
28
|
+
const commandsFile = options.commandsFile || __dirname+'/src/commands.mjs'
|
|
29
|
+
const commandLog = options.commandLog || './command-log.jsontag'
|
|
30
|
+
const commandStatus = options.commandStatus || './command-status.jsontag'
|
|
37
31
|
|
|
38
|
-
|
|
32
|
+
server.use(express.static(wwwroot))
|
|
39
33
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
server.get('/', (req,res) => {
|
|
43
|
-
res.send('<h1>SimplyStore</h1>') //@TODO: implement something nice
|
|
44
|
-
})
|
|
45
|
-
|
|
46
|
-
server.use(express.static(wwwroot))
|
|
47
|
-
|
|
48
|
-
// allow access to raw body, used to parse a query send as post body
|
|
34
|
+
// allow access to raw body, used to parse a query send as post body
|
|
49
35
|
server.use(express.raw({
|
|
50
36
|
type: (req) => true // parse body on all requests
|
|
51
37
|
}))
|
|
52
38
|
|
|
53
|
-
function
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
39
|
+
function loadData() {
|
|
40
|
+
return new Promise((resolve,reject) => {
|
|
41
|
+
let worker = new Worker(loadWorker)
|
|
42
|
+
worker.on('message', result => {
|
|
43
|
+
resolve(result)
|
|
44
|
+
worker.terminate()
|
|
45
|
+
})
|
|
46
|
+
worker.on('error', error => {
|
|
47
|
+
reject(error)
|
|
48
|
+
worker.terminate()
|
|
49
|
+
})
|
|
50
|
+
worker.postMessage(datafile)
|
|
51
|
+
})
|
|
64
52
|
}
|
|
53
|
+
try {
|
|
54
|
+
let data = await loadData()
|
|
55
|
+
jsontagBuffer = data.data
|
|
56
|
+
meta = data.meta
|
|
57
|
+
} catch(err) {
|
|
58
|
+
console.error('ERROR: SimplyStore cannot load '+datafile, err)
|
|
59
|
+
process.exit(1)
|
|
60
|
+
}
|
|
65
61
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
62
|
+
const queryWorkerInitTask = () => {
|
|
63
|
+
return {
|
|
64
|
+
name: 'init',
|
|
65
|
+
req: {
|
|
66
|
+
body: jsontagBuffer,
|
|
67
|
+
meta
|
|
68
|
+
}
|
|
69
69
|
}
|
|
70
|
-
if (response.jsontag) {
|
|
71
|
-
res.setHeader('content-type','application/jsontag')
|
|
72
|
-
} else {
|
|
73
|
-
res.setHeader('content-type','application/json')
|
|
74
|
-
}
|
|
75
|
-
res.send(response.body)+"\n"
|
|
76
70
|
}
|
|
77
71
|
|
|
78
|
-
|
|
79
|
-
if (result.code) {
|
|
80
|
-
res.status(result.code)
|
|
81
|
-
}
|
|
82
|
-
if (req.accepts('application/jsontag')) {
|
|
83
|
-
res.setHeader('content-type','application/jsontag')
|
|
84
|
-
res.send(JSONTag.stringify(result, null, 4)+"\n")
|
|
85
|
-
} else {
|
|
86
|
-
res.setHeader('content-type','application/json')
|
|
87
|
-
res.send(JSON.stringify(result, null, 4)+"\n")
|
|
88
|
-
}
|
|
89
|
-
}
|
|
72
|
+
let queryWorkerPool = new WorkerPool(maxWorkers, queryWorker, queryWorkerInitTask())
|
|
90
73
|
|
|
91
|
-
server.get('/query/*', (req, res, next) =>
|
|
74
|
+
server.get('/query/*', async (req, res, next) =>
|
|
92
75
|
{
|
|
93
76
|
let start = Date.now()
|
|
94
|
-
|
|
95
77
|
if ( !accept(req,res,
|
|
96
78
|
['application/jsontag','application/json','text/html','text/javascript','image/*'],
|
|
97
79
|
function(req, res, accept) {
|
|
@@ -99,7 +81,7 @@ async function main(options) {
|
|
|
99
81
|
case 'text/html':
|
|
100
82
|
case 'image/*':
|
|
101
83
|
case 'text/javascript':
|
|
102
|
-
handleWebRequest(req,res,
|
|
84
|
+
handleWebRequest(req,res,{root:wwwroot});
|
|
103
85
|
return false
|
|
104
86
|
break
|
|
105
87
|
}
|
|
@@ -109,199 +91,302 @@ async function main(options) {
|
|
|
109
91
|
// done
|
|
110
92
|
return
|
|
111
93
|
}
|
|
112
|
-
let path = req.path.substr(6)
|
|
94
|
+
let path = req.path.substr(6) // cut '/query'
|
|
95
|
+
console.log('query',path)
|
|
113
96
|
let request = {
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
97
|
+
method: req.method,
|
|
98
|
+
url: req.originalUrl,
|
|
99
|
+
query: req.query,
|
|
100
|
+
path: path
|
|
118
101
|
}
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
102
|
+
if (accept(req,res,['application/jsontag'])) {
|
|
103
|
+
request.jsontag = true
|
|
104
|
+
}
|
|
105
|
+
try {
|
|
106
|
+
let result = await queryWorkerPool.run('query', request)
|
|
107
|
+
sendResponse(result, res)
|
|
108
|
+
} catch(error) {
|
|
109
|
+
sendError(error, res)
|
|
110
|
+
}
|
|
111
|
+
let end = Date.now()
|
|
112
|
+
console.log(path, (end-start), process.memoryUsage())
|
|
125
113
|
})
|
|
126
114
|
|
|
127
|
-
|
|
128
|
-
* handle queries, query is the post body
|
|
129
|
-
*/
|
|
130
|
-
server.post('/query/*', (req, res) => {
|
|
115
|
+
server.post('/query/*', async (req,res) => {
|
|
131
116
|
let start = Date.now()
|
|
132
117
|
if ( !accept(req,res,
|
|
133
118
|
['application/jsontag','application/json'])
|
|
134
119
|
) {
|
|
120
|
+
sendError({code:406, message:'Not Acceptable',accept:['application/json','application/jsontag']},res)
|
|
135
121
|
return
|
|
136
122
|
}
|
|
137
|
-
let
|
|
138
|
-
let path = req.path.substr(6); // cut '/query'
|
|
123
|
+
let path = req.path.substr(6) // cut '/query'
|
|
139
124
|
let request = {
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
125
|
+
method: req.method,
|
|
126
|
+
url: req.originalUrl,
|
|
127
|
+
query: req.query,
|
|
128
|
+
path: path,
|
|
129
|
+
body: req.body.toString()
|
|
130
|
+
}
|
|
131
|
+
if (accept(req,res,['application/jsontag'])) {
|
|
132
|
+
request.jsontag = true
|
|
144
133
|
}
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
}
|
|
134
|
+
try {
|
|
135
|
+
let result = await queryWorkerPool.run('query', request)
|
|
136
|
+
sendResponse(result, res)
|
|
137
|
+
} catch(error) {
|
|
138
|
+
sendError(error, res)
|
|
139
|
+
}
|
|
140
|
+
let end = Date.now()
|
|
141
|
+
console.log(path, (end-start), process.memoryUsage())
|
|
142
|
+
// queryWorkerPool.memoryUsage()
|
|
151
143
|
})
|
|
152
144
|
|
|
153
|
-
let status = new Map()
|
|
154
145
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
body: JSON.stringify({"code":404,"message":"Command not found"})
|
|
169
|
-
}, res)
|
|
146
|
+
let status = loadCommandStatus(commandStatus)
|
|
147
|
+
|
|
148
|
+
function loadCommandStatus(commandStatusFile) {
|
|
149
|
+
let status = new Map()
|
|
150
|
+
if (fs.existsSync(commandStatusFile)) {
|
|
151
|
+
let file = fs.readFileSync(commandStatusFile, 'utf-8')
|
|
152
|
+
if (file) {
|
|
153
|
+
let lines = file.split("\n").filter(Boolean) //filter clears empty lines
|
|
154
|
+
for(let line of lines) {
|
|
155
|
+
let command = JSONTag.parse(line)
|
|
156
|
+
status.set(command.id, command.status)
|
|
157
|
+
}
|
|
158
|
+
}
|
|
170
159
|
}
|
|
171
|
-
|
|
160
|
+
return status
|
|
161
|
+
}
|
|
172
162
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
163
|
+
let commandQueue = []
|
|
164
|
+
|
|
165
|
+
function loadCommandLog(commandLog) {
|
|
166
|
+
if (!fs.existsSync(commandLog)) {
|
|
167
|
+
return
|
|
168
|
+
}
|
|
169
|
+
let log = fs.readFileSync(commandLog)
|
|
170
|
+
if (log) {
|
|
171
|
+
let lines = log.split("\n")
|
|
172
|
+
for(let line of lines) {
|
|
173
|
+
let command = JSONTag.parse(line)
|
|
174
|
+
let state = status.get(command.id)
|
|
175
|
+
switch(state) {
|
|
176
|
+
case 'accepted': // enqueue
|
|
177
|
+
commandQueue.push(command)
|
|
178
|
+
break;
|
|
179
|
+
case 'done': // do nothing
|
|
180
|
+
break;
|
|
181
|
+
default: // error, do nothing
|
|
182
|
+
break;
|
|
183
|
+
}
|
|
180
184
|
}
|
|
181
|
-
|
|
185
|
+
}
|
|
186
|
+
}
|
|
182
187
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
return
|
|
193
|
-
} else if (status.has(command.id)) {
|
|
194
|
-
result = "OK"
|
|
195
|
-
sendCommandResponse(result, req, res)
|
|
196
|
-
return
|
|
197
|
-
} else if (!command.name || !commands[command.name]) {
|
|
198
|
-
error = {
|
|
199
|
-
code: 422,
|
|
200
|
-
message: "Command has no name or is unknown"
|
|
188
|
+
loadCommandLog()
|
|
189
|
+
let commandWorkerInstance
|
|
190
|
+
|
|
191
|
+
async function runNextCommand() {
|
|
192
|
+
let command = commandQueue.shift()
|
|
193
|
+
if (command) {
|
|
194
|
+
let start = (resolve, reject) => {
|
|
195
|
+
if (!commandWorkerInstance) {
|
|
196
|
+
commandWorkerInstance = new Worker(commandWorker)
|
|
201
197
|
}
|
|
202
|
-
|
|
203
|
-
|
|
198
|
+
commandWorkerInstance.on('message', result => {
|
|
199
|
+
resolve(result)
|
|
200
|
+
runNextCommand()
|
|
201
|
+
})
|
|
202
|
+
commandWorkerInstance.on('error', error => {
|
|
203
|
+
reject(error)
|
|
204
|
+
runNextCommand()
|
|
205
|
+
})
|
|
206
|
+
commandWorkerInstance.postMessage(command)
|
|
204
207
|
}
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
208
|
+
start(
|
|
209
|
+
// resolve()
|
|
210
|
+
(data) => {
|
|
211
|
+
if (!data || data.error) {
|
|
212
|
+
console.error('ERROR: SimplyStore cannot run command ', command.id, err)
|
|
213
|
+
if (!data.error) {
|
|
214
|
+
status.set(command.id, 'failed')
|
|
215
|
+
throw new Error('Unexpected command failure')
|
|
216
|
+
} else {
|
|
217
|
+
status.set(command.id, data.error)
|
|
218
|
+
throw data.error
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
jsontagBuffer = data.data
|
|
222
|
+
meta = data.meta
|
|
223
|
+
status.set(command.id, 'done')
|
|
224
|
+
appendFile(commandStatus, JSONTag.stringify({command:command.id, status: 'done'}))
|
|
225
|
+
// restart query workers with new data
|
|
226
|
+
let oldPool = queryWorkerPool
|
|
227
|
+
queryWorkerPool = new WorkerPool(maxWorkers, queryWorker, queryWorkerInitTask())
|
|
228
|
+
setTimeout(() => {
|
|
229
|
+
oldPool.close()
|
|
230
|
+
}, 2000)
|
|
231
|
+
},
|
|
232
|
+
//reject()
|
|
233
|
+
(error) => {
|
|
234
|
+
status.set(command.id, error)
|
|
235
|
+
appendFile(commandStatus, JSONTag.stringify({command:command.id, status: error}))
|
|
236
|
+
}
|
|
237
|
+
)
|
|
238
|
+
} else {
|
|
239
|
+
// this code can never be triggered from the post(/command/) route, since it always adds a command to the queue
|
|
240
|
+
// so you can only get here from commandWorkerInstance.on() route
|
|
241
|
+
// which means that the commandWorkerInstance has finished running the previous command
|
|
242
|
+
await commandWorkerInstance.terminate()
|
|
243
|
+
commandWorkerInstance.unref() // @FIXME is this needed?
|
|
244
|
+
commandWorkerInstance = null // @FIXME or this?
|
|
245
|
+
}
|
|
246
|
+
}
|
|
210
247
|
|
|
211
|
-
|
|
212
|
-
|
|
248
|
+
server.post('/command', async (req, res) => {
|
|
249
|
+
let commandId = checkCommand(req, res)
|
|
250
|
+
if (!commandId) {
|
|
251
|
+
return
|
|
252
|
+
}
|
|
253
|
+
let commandStr = req.body.toString()
|
|
254
|
+
try {
|
|
213
255
|
let request = {
|
|
214
256
|
method: req.method,
|
|
215
257
|
url: req.originalUrl,
|
|
216
|
-
query: req.query
|
|
217
|
-
jsontag: req.accepts('application/jsontag')
|
|
258
|
+
query: req.query
|
|
218
259
|
}
|
|
219
260
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
//@TODO: make sure queryWorkerpool is only replaced after
|
|
229
|
-
//workers are initialized, to prevent hickups if initialization takes a long time
|
|
230
|
-
let newQueryWorkerpool = initWorkerPool(queryWorker)
|
|
231
|
-
queryWorkerpool.destroy() // gracefully
|
|
232
|
-
queryWorkerpool = newQueryWorkerpool
|
|
233
|
-
//@TODO: write dataspace to disk
|
|
234
|
-
status.set(command.id, 'done')
|
|
235
|
-
let end = Date.now()
|
|
236
|
-
console.log(command.name, (end-start), process.memoryUsage())
|
|
237
|
-
}
|
|
238
|
-
})
|
|
239
|
-
.catch(err => {
|
|
240
|
-
console.error(err)
|
|
241
|
-
//@TODO: set status for this command to error with this err
|
|
242
|
-
status.set(command.id, err)
|
|
261
|
+
commandQueue.push({
|
|
262
|
+
id:commandId,
|
|
263
|
+
command:commandStr,
|
|
264
|
+
request,
|
|
265
|
+
meta,
|
|
266
|
+
data:jsontagBuffer,
|
|
267
|
+
commandsFile,
|
|
268
|
+
datafile
|
|
243
269
|
})
|
|
270
|
+
|
|
271
|
+
runNextCommand()
|
|
244
272
|
} catch(err) {
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
273
|
+
appendFile(commandStatus, JSONTag.stringify({command:commandId, status: 'ERROR: '+err.message}))
|
|
274
|
+
status.set(commandId, 'ERROR: '+err.message)
|
|
275
|
+
console.error('ERROR: SimplyStore cannot run command ', commandId, err)
|
|
248
276
|
}
|
|
249
277
|
})
|
|
250
278
|
|
|
251
|
-
function
|
|
252
|
-
|
|
253
|
-
let
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
if (
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
279
|
+
function checkCommand(req, res) {
|
|
280
|
+
let commandStr = req.body.toString() // raw body through express.raw()
|
|
281
|
+
let command = JSONTag.parse(commandStr)
|
|
282
|
+
if (!command || !command.id) {
|
|
283
|
+
error = {
|
|
284
|
+
code: 422,
|
|
285
|
+
message: "Command has no id"
|
|
286
|
+
}
|
|
287
|
+
sendResponse({code: 422, body: JSON.stringify(error)}, res)
|
|
288
|
+
return false
|
|
289
|
+
} else if (status.has(command.id)) {
|
|
290
|
+
result = "OK"
|
|
291
|
+
sendResponse({body: JSON.stringify(result)}, res)
|
|
292
|
+
return false
|
|
293
|
+
} else if (!command.name) {
|
|
294
|
+
error = {
|
|
295
|
+
code: 422,
|
|
296
|
+
message: "Command has no name"
|
|
297
|
+
}
|
|
298
|
+
sendResponse({code:422, body: JSON.stringify(error)}, res)
|
|
299
|
+
return false
|
|
269
300
|
}
|
|
301
|
+
appendFile(commandLog, JSONTag.stringify(command))
|
|
302
|
+
appendFile(commandStatus, JSONTag.stringify({
|
|
303
|
+
command: command.id,
|
|
304
|
+
status: 'accepted'
|
|
305
|
+
}))
|
|
306
|
+
status.set(command.id, 'accepted')
|
|
307
|
+
sendResponse({code: 202, body: '"Accepted"'}, res)
|
|
308
|
+
return command.id
|
|
270
309
|
}
|
|
271
310
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
})
|
|
279
|
-
} else
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
data[key] = new JSONTag.Link(baseURL+key+'/')
|
|
286
|
-
} else if (typeof data[key] === 'object') {
|
|
287
|
-
if (JSONTag.getType(data[key])!=='link') {
|
|
288
|
-
let id=JSONTag.getAttribute(data[key], 'id')
|
|
289
|
-
if (!id) {
|
|
290
|
-
id = baseURL+key+'/'
|
|
291
|
-
}
|
|
292
|
-
data[key] = new JSONTag.Link(id)
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
})
|
|
311
|
+
server.get('/command/:id', (req, res) => {
|
|
312
|
+
if (status.has(req.params.id)) {
|
|
313
|
+
let result = status.get(req.params.id)
|
|
314
|
+
sendResponse({
|
|
315
|
+
jsontag: false,
|
|
316
|
+
body: JSON.stringify(result)
|
|
317
|
+
},res)
|
|
318
|
+
} else {
|
|
319
|
+
sendResponse({
|
|
320
|
+
code: 404,
|
|
321
|
+
jsontag: false,
|
|
322
|
+
body: JSON.stringify({code: 404, message: "Command not found"})
|
|
323
|
+
}, res)
|
|
296
324
|
}
|
|
297
|
-
|
|
298
|
-
}
|
|
325
|
+
})
|
|
299
326
|
|
|
300
327
|
server.listen(port, () => {
|
|
301
328
|
console.log('SimplyStore listening on port '+port)
|
|
329
|
+
let used = Math.round(process.memoryUsage().rss / 1024 / 1024);
|
|
330
|
+
console.log(`(${used} MB)`);
|
|
302
331
|
})
|
|
332
|
+
}
|
|
303
333
|
|
|
334
|
+
function sendResponse(response, res) {
|
|
335
|
+
if (response.code && httpStatusCodes[response.code]) {
|
|
336
|
+
res.status(response.code)
|
|
337
|
+
}
|
|
338
|
+
if (response.jsontag) {
|
|
339
|
+
res.setHeader('content-type','application/jsontag')
|
|
340
|
+
} else {
|
|
341
|
+
res.setHeader('content-type','application/json')
|
|
342
|
+
}
|
|
343
|
+
res.send(response.body)+"\n"
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
function sendError(error, res) {
|
|
347
|
+
console.error(error)
|
|
348
|
+
if (error.code && httpStatusCodes[error.code]) {
|
|
349
|
+
res.status(error.code)
|
|
350
|
+
} else {
|
|
351
|
+
res.status(500)
|
|
352
|
+
}
|
|
353
|
+
res.setHeader('content-type','application/json')
|
|
354
|
+
res.send(JSON.stringify(error))
|
|
304
355
|
}
|
|
305
356
|
|
|
306
357
|
server.run = main
|
|
307
|
-
export default server
|
|
358
|
+
export default server
|
|
359
|
+
|
|
360
|
+
function accept(req, res, mimetypes, handler) {
|
|
361
|
+
let accept = req.accepts(mimetypes)
|
|
362
|
+
if (!accept) {
|
|
363
|
+
res.status(406)
|
|
364
|
+
res.send("<h1>406 Unacceptable</h1>\n")
|
|
365
|
+
return false
|
|
366
|
+
}
|
|
367
|
+
if (typeof handler === 'function') {
|
|
368
|
+
return handler(req, res, accept)
|
|
369
|
+
}
|
|
370
|
+
return true
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
function handleWebRequest(req,res,options)
|
|
374
|
+
{
|
|
375
|
+
let path = req.path;
|
|
376
|
+
path = path.replace(/[^a-z0-9_\.\-\/]*/gi, '') // whitelist acceptable file paths
|
|
377
|
+
path = path.replace(/\.+/g, '.') // blacklist '..'
|
|
378
|
+
if (!path) {
|
|
379
|
+
path = '/'
|
|
380
|
+
}
|
|
381
|
+
if (path.substring(path.length-1)==='/') {
|
|
382
|
+
path += 'index.html'
|
|
383
|
+
}
|
|
384
|
+
const fileOptions = {
|
|
385
|
+
root: options.root
|
|
386
|
+
}
|
|
387
|
+
if (fs.existsSync(fileOptions.root+path)) {
|
|
388
|
+
res.sendFile(path, fileOptions)
|
|
389
|
+
} else {
|
|
390
|
+
res.sendFile('/index.html', fileOptions)
|
|
391
|
+
}
|
|
392
|
+
}
|