w-converhp 1.0.39 → 2.0.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.
@@ -1,19 +1,26 @@
1
- // import fs from 'fs'
1
+ import path from 'path'
2
+ import fs from 'fs'
2
3
  import Hapi from '@hapi/hapi'
3
4
  import Inert from '@hapi/inert' //提供靜態檔案
4
- import events from 'events'
5
5
  import stream from 'stream'
6
- import get from 'lodash-es/get'
7
- import map from 'lodash-es/map'
8
- import each from 'lodash-es/each'
9
- import cloneDeep from 'lodash-es/cloneDeep'
6
+ import get from 'lodash-es/get.js'
10
7
  import genPm from 'wsemi/src/genPm.mjs'
11
- import alive from 'wsemi/src/alive.mjs'
12
- import isstr from 'wsemi/src/isstr.mjs'
13
8
  import iseobj from 'wsemi/src/iseobj.mjs'
9
+ import isestr from 'wsemi/src/isestr.mjs'
10
+ import isp0int from 'wsemi/src/isp0int.mjs'
11
+ import ispint from 'wsemi/src/ispint.mjs'
12
+ import isearr from 'wsemi/src/isearr.mjs'
13
+ import isbol from 'wsemi/src/isbol.mjs'
14
+ import isfun from 'wsemi/src/isfun.mjs'
15
+ import ispm from 'wsemi/src/ispm.mjs'
16
+ import cint from 'wsemi/src/cint.mjs'
17
+ import evem from 'wsemi/src/evem.mjs'
14
18
  import obj2u8arr from 'wsemi/src/obj2u8arr.mjs'
15
19
  import u8arr2obj from 'wsemi/src/u8arr2obj.mjs'
16
- import iser from 'wsemi/src/iser.mjs'
20
+ import fsIsFolder from 'wsemi/src/fsIsFolder.mjs'
21
+ import fsCreateFolder from 'wsemi/src/fsCreateFolder.mjs'
22
+ import fsIsFile from 'wsemi/src/fsIsFile.mjs'
23
+ import fsDeleteFile from 'wsemi/src/fsDeleteFile.mjs'
17
24
 
18
25
 
19
26
  /**
@@ -21,78 +28,63 @@ import iser from 'wsemi/src/iser.mjs'
21
28
  *
22
29
  * @class
23
30
  * @param {Object} [opt={}] 輸入設定物件,預設{}
24
- * @param {Integer} [opt.port=8080] 輸入Hapi伺服器所在port,預設8080
25
- * @param {String} [opt.apiName='api'] 輸入http API伺服器網址的api名稱,預設'api'
26
- * @returns {Object} 回傳通訊物件,可監聽事件open、error、clientChange、execute、broadcast、deliver,可使用函數broadcast
31
+ * @param {Integer} [opt.port=8080] 輸入Hapi伺服器所在port正整數,預設8080
32
+ * @param {Boolean} [opt.useInert=true] 輸入是否提供瀏覽pathStaticFiles資料夾內檔案之布林值,預設true
33
+ * @param {String} [opt.pathStaticFiles='dist'] 輸入當useInert=true時提供瀏覽資料夾字串,預設'dist'
34
+ * @param {String} [opt.apiName='api'] 輸入API名稱字串,預設'api'
35
+ * @param {String} [opt.pathUploadTemp='./uploadTemp'] 輸入暫時存放切片上傳檔案資料夾字串,預設'./uploadTemp'
36
+ * @param {Function} [opt.funCheck=()=>{return true}] 輸入呼叫API時檢測函數,預設()=>{return true}
37
+ * @param {Array} [opt.corsOrigins=['*']] 輸入允許跨域網域陣列,若給予['*']代表允許全部,預設['*']
38
+ * @param {Integer} [opt.delayForBasic=0] 輸入基本API用延遲響應時間,單位ms,預設0
39
+ * @param {Integer} [opt.delayForSlice=100] 輸入切片上傳檔案API用延遲響應時間,單位ms,預設100
40
+ * @param {Boolean} [opt.serverHapi=null] 輸入外部提供Hapi伺服器物件,預設null
41
+ * @returns {Object} 回傳通訊物件,可監聽事件execute、upload、handler
27
42
  * @example
28
43
  *
44
+ * import _ from 'lodash-es'
29
45
  * import WConverhpServer from 'w-converhp/dist/w-converhp-server.umd.js'
30
46
  *
31
47
  * let opt = {
32
48
  * port: 8080,
33
49
  * apiName: 'api',
50
+ * pathStaticFiles: '.', //要存取專案資料夾下web.html, 故不能給dist
51
+ * funCheck: () => {
52
+ * return true
53
+ * },
34
54
  * }
35
55
  *
36
56
  * //new
37
57
  * let wo = new WConverhpServer(opt)
38
58
  *
39
- * wo.on('open', function() {
40
- * console.log(`Server[port:${opt.port}]: open`)
41
- *
42
- * //broadcast
43
- * let n = 0
44
- * setInterval(() => {
45
- * n += 1
46
- * let o = {
47
- * text: `server broadcast hi(${n})`,
48
- * data: new Uint8Array([66, 97, 115]), //support Uint8Array data
49
- * }
50
- * wo.broadcast(o, function (prog) {
51
- * console.log('broadcast prog', prog)
52
- * })
53
- * }, 1000)
54
- *
55
- * })
56
- * wo.on('error', function(err) {
57
- * console.log(`Server[port:${opt.port}]: error`, err)
58
- * })
59
- * wo.on('clientChange', function(num) {
60
- * console.log(`Server[port:${opt.port}]: now clients: ${num}`)
61
- * })
62
- * wo.on('clientEnter', function(clientId, data) {
63
- * console.log(`Server[port:${opt.port}]: client enter: ${clientId}`)
64
- *
65
- * //deliver
66
- * wo.deliver(clientId, `server deliver hi(${clientId})`)
67
- *
68
- * })
69
- * wo.on('clientLeave', function(clientId, data) {
70
- * console.log(`Server[port:${opt.port}]: client leave: ${clientId}`)
71
- * })
72
59
  * wo.on('execute', function(func, input, pm) {
73
- * //console.log(`Server[port:${opt.port}]: execute`, func, input)
60
+ * // console.log(`Server[port:${opt.port}]: execute`, func, input)
74
61
  * console.log(`Server[port:${opt.port}]: execute`, func)
75
62
  *
76
63
  * try {
77
64
  *
78
65
  * if (func === 'add') {
79
66
  *
80
- * //save
81
67
  * if (_.get(input, 'p.d.u8a', null)) {
82
- * // fs.writeFileSync(input.p.d.name, Buffer.from(input.p.d.u8a))
83
- * // console.log('writeFileSync input.p.d.name', input.p.d.name)
68
+ * console.log('input.p.d.u8a', input.p.d.u8a)
84
69
  * }
85
70
  *
86
71
  * let r = {
87
- * ab: input.p.a + input.p.b,
88
- * v: [11, 22.22, 'abc', { x: '21', y: 65.43, z: 'test中文' }],
89
- * file: {
72
+ * _add: input.p.a + input.p.b,
73
+ * _data: [11, 22.22, 'abc', { x: '21', y: 65.43, z: 'test中文' }],
74
+ * _bin: {
90
75
  * name: 'zdata.b2',
91
76
  * u8a: new Uint8Array([66, 97, 115]),
92
- * //u8a: new Uint8Array(fs.readFileSync('C:\\Users\\Administrator\\Desktop\\z500mb.7z')),
77
+ * // name: '100mb.7z',
78
+ * // u8a: new Uint8Array(fs.readFileSync('D:\\開源-JS-006-2-w-converhp\\_temp\\100mb.7z')),
79
+ * // name: '500mb.7z',
80
+ * // u8a: new Uint8Array(fs.readFileSync('D:\\開源-JS-006-2-w-converhp\\_temp\\500mb.7z')),
81
+ * // name: '1000mb.7z',
82
+ * // u8a: new Uint8Array(fs.readFileSync('D:\\開源-JS-006-2-w-converhp\\_temp\\1000mb.7z')),
93
83
  * },
94
84
  * }
85
+ *
95
86
  * pm.resolve(r)
87
+ *
96
88
  * }
97
89
  * else {
98
90
  * console.log('invalid func')
@@ -106,72 +98,93 @@ import iser from 'wsemi/src/iser.mjs'
106
98
  * }
107
99
  *
108
100
  * })
109
- * wo.on('broadcast', function(data) {
110
- * console.log(`Server[port:${opt.port}]: broadcast`, data)
111
- * })
112
- * wo.on('deliver', function(data) {
113
- * console.log(`Server[port:${opt.port}]: deliver`, data)
101
+ * wo.on('upload', function(input, pm) {
102
+ * console.log(`Server[port:${opt.port}]: upload`, input)
103
+ *
104
+ * try {
105
+ * let output = input
106
+ * pm.resolve(output)
107
+ * }
108
+ * catch (err) {
109
+ * console.log('execute error', err)
110
+ * pm.reject('execute error')
111
+ * }
112
+ *
114
113
  * })
115
114
  * wo.on('handler', function(data) {
116
115
  * // console.log(`Server[port:${opt.port}]: handler`, data)
117
116
  * })
118
117
  *
119
- *
120
118
  */
121
119
  function WConverhpServer(opt = {}) {
122
- let broadcastMessages = {}
123
120
 
124
-
125
- //default
126
- if (!opt.port) {
127
- opt.port = 8080
128
- }
129
- if (!opt.apiName) {
130
- opt.apiName = 'api'
121
+ //port
122
+ let port = get(opt, 'port')
123
+ if (!ispint(port)) {
124
+ port = 8080
131
125
  }
132
126
 
127
+ //useInert
128
+ let useInert = get(opt, 'useInert')
129
+ if (!isbol(useInert)) {
130
+ useInert = true
131
+ }
133
132
 
134
- //ee
135
- let ee = new events.EventEmitter()
136
-
137
-
138
- //ea
139
- let ea = alive()
140
-
141
-
142
- //ea broadcastMessages
143
- setInterval(() => {
133
+ //pathStaticFiles
134
+ let pathStaticFiles = get(opt, 'pathStaticFiles')
135
+ if (!isestr(pathStaticFiles)) {
136
+ pathStaticFiles = 'dist'
137
+ }
144
138
 
145
- //now alive
146
- let nowAlive = ea.get()
147
- //console.log('nowAlive', nowAlive)
139
+ //apiName
140
+ let apiName = get(opt, 'apiName')
141
+ if (!isestr(apiName)) {
142
+ apiName = 'api'
143
+ }
148
144
 
149
- //clientIds
150
- let clientIds = map(nowAlive, 'key')
151
- //console.log('clientIds', clientIds)
145
+ //apiSliceName
146
+ let apiSliceName = `${apiName}slc`
152
147
 
153
- //pick
154
- let t = {}
155
- each(clientIds, (clientId) => {
156
- t[clientId] = get(broadcastMessages, clientId, [])
157
- })
158
- broadcastMessages = t
159
- //console.log('broadcastMessages', broadcastMessages)
148
+ //pathUploadTemp
149
+ let pathUploadTemp = get(opt, 'pathUploadTemp')
150
+ if (!isestr(pathUploadTemp)) {
151
+ pathUploadTemp = './uploadTemp'
152
+ }
153
+ if (!fsIsFolder(pathUploadTemp)) {
154
+ fsCreateFolder(pathUploadTemp)
155
+ }
160
156
 
161
- }, 1000)
157
+ //funCheck
158
+ let funCheck = get(opt, 'funCheck')
159
+ if (!isfun(funCheck)) {
160
+ funCheck = () => {
161
+ return true
162
+ }
163
+ }
162
164
 
165
+ //corsOrigins
166
+ let corsOrigins = get(opt, 'corsOrigins', [])
167
+ if (!isearr(corsOrigins)) {
168
+ corsOrigins = ['*']
169
+ }
163
170
 
164
- //eeEmit
165
- function eeEmit(name, ...args) {
166
- setTimeout(() => {
167
- ee.emit(name, ...args)
168
- }, 1)
171
+ //delayForBasic
172
+ let delayForBasic = get(opt, 'delayForBasic', '')
173
+ if (!isp0int(delayForBasic)) {
174
+ delayForBasic = 0
169
175
  }
176
+ delayForBasic = cint(delayForBasic)
170
177
 
178
+ //delayForSlice
179
+ let delayForSlice = get(opt, 'delayForSlice', '')
180
+ if (!isp0int(delayForSlice)) {
181
+ delayForSlice = 100
182
+ }
183
+ delayForSlice = cint(delayForSlice)
171
184
 
172
185
  //server
173
- let server
174
- if (opt.serverHapi) {
186
+ let server = null
187
+ if (get(opt, 'serverHapi')) {
175
188
 
176
189
  //use serverHapi
177
190
  server = opt.serverHapi
@@ -181,107 +194,35 @@ function WConverhpServer(opt = {}) {
181
194
 
182
195
  //create server
183
196
  server = Hapi.server({
184
- port: opt.port,
197
+ port,
185
198
  //host: 'localhost',
186
199
  routes: {
187
- // cors: true,
188
200
  cors: {
189
- origin: ['*'], //Access-Control-Allow-Origin
190
- //credentials: true //Access-Control-Allow-Credentials
191
- }
201
+ origin: corsOrigins, //Access-Control-Allow-Origin
202
+ credentials: false, //Access-Control-Allow-Credentials
203
+ },
192
204
  },
193
205
  })
194
206
 
195
207
  }
196
208
 
209
+ //ee
210
+ let ee = evem() //new events.EventEmitter()
197
211
 
198
- /**
199
- * Hapi監聽開啟事件
200
- *
201
- * @memberof WConverhpServer
202
- * @example
203
- * wo.on('open', function() {
204
- * ...
205
- * })
206
- */
207
- function onOpen() {} onOpen()
208
- function open() {
209
- eeEmit('open')
210
- }
211
- open()
212
-
213
-
214
- /**
215
- * Hapi監聽錯誤事件
216
- *
217
- * @memberof WConverhpServer
218
- * @param {*} err 傳入錯誤訊息
219
- * @example
220
- * wo.on('error', function(err) {
221
- * ...
222
- * })
223
- */
224
- function onError() {} onError()
225
- function error(msg, err) {
226
- eeEmit('error', { msg, err })
212
+ //eeEmit
213
+ function eeEmit(name, ...args) {
214
+ setTimeout(() => {
215
+ ee.emit(name, ...args)
216
+ }, 1)
227
217
  }
228
218
 
229
-
230
- /**
231
- * Hapi監聽客戶端上線事件
232
- *
233
- * @memberof WConverhpServer
234
- * @param {String} clientId 識別用使用者主鍵
235
- * @param {*} data 使用者request訊息
236
- * @example
237
- * wo.on('clientEnter', function(clientId, data) {
238
- * ...
239
- * })
240
- */
241
- function onClientEnter() {} onClientEnter()
242
- /**
243
- * Hapi監聽客戶端下線事件
244
- *
245
- * @memberof WConverhpServer
246
- * @example
247
- * wo.on('clientLeave', function(clientId, data) {
248
- * ...
249
- * })
250
- */
251
- function onClientLeave() {} onClientLeave()
252
- /**
253
- * Hapi監聽客戶端變更(上下線)事件
254
- *
255
- * @memberof WConverhpServer
256
- * @example
257
- * wo.on('clientChange', function(num) {
258
- * ...
259
- * })
260
- */
261
- function onClientChange() {} onClientChange()
262
- ea.on('message', function({ eventName, key, data, now }) {
263
- //console.log({ eventName, key, data, now })
264
- if (eventName === 'enter') {
265
- eeEmit('clientEnter', key, data)
266
- }
267
- else if (eventName === 'leave') {
268
- eeEmit('clientLeave', key, data)
269
- }
270
- eeEmit('clientChange', now)
271
- })
272
-
273
-
274
- //dealData
275
- async function dealData(data) {
276
- //console.log('dealData', data)
219
+ //procDeal
220
+ async function procDeal(data) {
277
221
 
278
222
  //pm, pmm
279
223
  let pm = genPm()
280
224
  let pmm = genPm()
281
225
 
282
- //_mode
283
- let _mode = get(data, '_mode', '')
284
-
285
226
  //重新處理回傳結果
286
227
  pmm
287
228
  .then((output) => {
@@ -292,16 +233,13 @@ function WConverhpServer(opt = {}) {
292
233
  //delete input, 因input可能很大故回傳數據不包含原input
293
234
  delete data['input']
294
235
 
295
- //resolve
296
236
  pm.resolve(data)
297
-
298
237
  })
299
238
  .catch((err) => {
300
239
  pm.reject(err)
301
240
  })
302
241
 
303
- //emit
304
- if (_mode === 'execute') {
242
+ if (true) {
305
243
 
306
244
  //func
307
245
  let func = get(data, 'func', '')
@@ -313,291 +251,550 @@ function WConverhpServer(opt = {}) {
313
251
  eeEmit('execute', func, input, pmm)
314
252
 
315
253
  }
316
- else if (_mode === 'broadcast') {
317
-
318
- //input
319
- let input = get(data, 'input', null)
320
-
321
- //broadcast 廣播
322
- eeEmit('broadcast', input)
323
-
324
- //resolve
325
- pmm.resolve('ok')
326
-
327
- }
328
- else if (_mode === 'deliver') {
329
254
 
330
- //input
331
- let input = get(data, 'input', null)
255
+ return pm
256
+ }
332
257
 
333
- //deliver 發送
334
- eeEmit('deliver', input)
258
+ //procUpload
259
+ async function procUpload(input) {
260
+ //console.log('procUpload', data)
335
261
 
336
- //resolve
337
- pmm.resolve('ok')
262
+ //pm, pmm
263
+ let pm = genPm()
264
+ let pmm = genPm()
338
265
 
339
- }
340
- else if (_mode === 'polling') {
266
+ //重新處理回傳結果
267
+ pmm
268
+ .then((output) => {
341
269
 
342
- //clientId
343
- let clientId = get(data, 'clientId', '')
270
+ //resolve
271
+ pm.resolve(output)
344
272
 
345
- //polling messages
346
- let pms = get(broadcastMessages, clientId, [])
347
- pms = cloneDeep(pms)
273
+ })
274
+ .catch((err) => {
275
+ pm.reject(err)
276
+ })
348
277
 
349
- //clear
350
- broadcastMessages[clientId] = []
278
+ if (true) {
351
279
 
352
- //resolve
353
- pmm.resolve(pms)
280
+ //upload 上傳檔案
281
+ eeEmit('upload', input, pmm)
354
282
 
355
283
  }
356
- else {
357
- let msg = 'can not find _mode in data'
358
- error(msg, data)
359
- pm.reject(msg)
360
- pmm.reject()
361
- }
362
284
 
363
285
  return pm
364
286
  }
365
287
 
288
+ //responseU8aStream
289
+ function responseU8aStream(res, u8a) {
290
+
291
+ //stream
292
+ let smr = new stream.Readable()
293
+ smr._read = () => {}
294
+ smr.push(u8a)
295
+ smr.push(null)
296
+
297
+ return res.response(smr)
298
+ .header('Cache-Control', 'no-cache, no-store, must-revalidate')
299
+ .header('Content-Type', 'application/octet-stream')
300
+ .header('Content-Length', smr.readableLength)
301
+ }
366
302
 
367
303
  //apiMain
368
304
  let apiMain = {
369
- path: '/' + opt.apiName,
305
+ path: `/${apiName}`,
370
306
  method: 'POST',
371
- // config: {
372
- // payload: {
373
- // output: 'stream',
374
- // maxBytes: 1024 * 1024 * 1024, //1g
375
- // },
376
- // },
377
307
  options: {
378
308
  payload: {
379
- maxBytes: 1024 * 1024 * 1024, //1g
380
- timeout: 3 * 60 * 1000, //3分鐘, 注意payload timeout必須小於socket timeout
381
- multipart: true, //hapi 19之後修改multipart預設值為false
309
+ maxBytes: 1024 * 1024 * 1024 * 1024, //預設為1mb, 調整至1tb, 也就是給予3次方
310
+ maxParts: 1000 * 1000 * 1000, //預設為1000, 給予3次方
311
+ timeout: false, //避免請求未完成時中斷
312
+ output: 'stream',
313
+ parse: false,
382
314
  },
383
315
  timeout: {
384
- socket: 5 * 60 * 1000, //5分鐘
316
+ socket: false, //避免socket自動關閉
317
+ server: false,
385
318
  },
386
319
  },
387
320
  handler: async function (req, res) {
388
- //console.log(req, res)
389
- //console.log('payload', req.payload)
321
+ // console.log(req, res)
322
+ // console.log('payload', req.payload)
390
323
 
391
- //發送原始接收訊息
324
+ //headers
392
325
  let headers = get(req, 'headers')
393
326
  headers = iseobj(headers) ? headers : ''
327
+ // console.log('headers', headers)
328
+
329
+ //query
394
330
  let query = get(req, 'query')
395
331
  query = iseobj(query) ? query : ''
332
+ // console.log('query', query)
333
+
334
+ //token
335
+ let token = get(query, 'token', '')
336
+
337
+ //check
338
+ if (true) {
339
+
340
+ //funCheck
341
+ let m = funCheck({ token, headers, query })
342
+ if (ispm(m)) {
343
+ m = await m
344
+ }
345
+
346
+ //check
347
+ if (m !== true) {
348
+ return res.response({ error: 'permission denied' }).code(400)
349
+ }
350
+
351
+ }
352
+
353
+ //eeEmit
396
354
  eeEmit('handler', {
355
+ api: 'apiMain',
397
356
  headers,
398
357
  query,
399
358
  })
400
359
 
401
- // if (Math.random() < 0.5) {
402
- // console.log('return code 500: Internal Server Error')
403
- // return res.response('Internal Server Error').code(500)
404
- // }
360
+ //receive
361
+ let receive = () => {
405
362
 
406
- //bbInp
407
- let bbInp = get(req, 'payload.bb', null)
408
- // console.log('bbInp', bbInp)
363
+ //pm
364
+ let pm = genPm()
409
365
 
410
- //check
411
- //console.log('isstr(bbInp)', isstr(bbInp))
412
- if (isstr(bbInp)) {
413
- bbInp = Buffer.from(bbInp, 'utf8') //收nodejs client的buffer會自動解析變成字串
366
+ //totalSize, chunks
367
+ // let totalSize = 0
368
+ let chunks = []
369
+
370
+ //smw
371
+ let smw = new stream.Writable({
372
+ write(chunk, encoding, cb) {
373
+ // console.log('receive payload', chunk)
374
+
375
+ // //totalSize
376
+ // totalSize += chunk.length
377
+
378
+ //push
379
+ chunks.push(chunk)
380
+ // console.log('chunk.length', chunk.length, 'totalSize', totalSize)
381
+
382
+ //setTimeout, 延遲觸發cb
383
+ setTimeout(() => {
384
+ cb()
385
+ }, delayForBasic)
386
+
387
+ }
388
+ })
389
+
390
+ //pipe
391
+ req.payload.pipe(smw)
392
+
393
+ //end
394
+ req.payload.on('end', () => {
395
+ // console.log(`receive payload done`)
396
+
397
+ //concat
398
+ let fileBuffer = Buffer.concat(chunks)
399
+ // console.log('fileBuffer', fileBuffer, fileBuffer.length)
400
+
401
+ //clear, 釋放記憶體
402
+ chunks = []
403
+
404
+ //resolve
405
+ pm.resolve(fileBuffer)
406
+
407
+ })
408
+
409
+ //error
410
+ req.payload.on('error', (err) => {
411
+ console.log(`receive payload err`, err)
412
+ // pm.reject(res.response({ error: err.message }).code(500))
413
+ pm.reject(err.message)
414
+ })
415
+
416
+ return pm
414
417
  }
415
418
 
419
+ //receive
420
+ let bbInp = await receive()
421
+ // console.log('bbInp', bbInp)
422
+
416
423
  //u8aInp
417
424
  let u8aInp = new Uint8Array(bbInp)
418
- //console.log('u8aInp', u8aInp)
425
+ // console.log('u8aInp', u8aInp)
419
426
 
420
427
  //u8arr2obj
421
428
  let inp = u8arr2obj(u8aInp)
422
- //console.log('inp', inp)
429
+ // console.log('inp', inp)
423
430
 
424
- //clientId
425
- let clientId = get(inp, 'clientId')
431
+ //procDeal
432
+ let out = {}
433
+ await procDeal(inp)
434
+ .then(function(msg) {
435
+ out.success = msg
436
+ })
437
+ .catch(function(msg) {
438
+ out.error = msg
439
+ })
440
+ // console.log('out', out)
441
+
442
+ //u8aOut
443
+ let u8aOut = obj2u8arr(out)
444
+ // console.log('u8aOut', u8aOut)
445
+
446
+ return responseU8aStream(res, u8aOut)
447
+ },
448
+ }
449
+
450
+ //apiSlice
451
+ let apiSlice = {
452
+ path: `/${apiSliceName}`,
453
+ method: 'POST',
454
+ options: {
455
+ payload: {
456
+ maxBytes: 1024 * 1024 * 1024 * 1024, //預設為1mb, 調整至1tb, 也就是給予3次方
457
+ maxParts: 1000 * 1000 * 1000, //預設為1000, 給予3次方
458
+ timeout: false, //避免請求未完成時中斷
459
+ output: 'stream',
460
+ parse: false,
461
+ },
462
+ timeout: {
463
+ socket: false, //避免socket自動關閉
464
+ server: false,
465
+ },
466
+ },
467
+ handler: async function (req, res) {
468
+ // console.log(req, res)
469
+ // console.log('payload', req.payload)
470
+
471
+ //headers
472
+ let headers = get(req, 'headers')
473
+ headers = iseobj(headers) ? headers : ''
474
+ // console.log('headers', headers)
475
+
476
+ //query
477
+ let query = get(req, 'query')
478
+ query = iseobj(query) ? query : ''
479
+ // console.log('query', query)
480
+
481
+ //token
482
+ let token = get(query, 'token', '')
483
+
484
+ //check
485
+ if (true) {
486
+
487
+ //funCheck
488
+ let m = funCheck({ token, headers, query })
489
+ if (ispm(m)) {
490
+ m = await m
491
+ }
492
+
493
+ //check
494
+ if (m !== true) {
495
+ return res.response({ error: 'permission denied' }).code(400)
496
+ }
497
+
498
+ }
426
499
 
427
- //client
428
- let client = {
429
- headers: req.headers,
430
- info: req.info,
500
+ //eeEmit
501
+ eeEmit('handler', {
502
+ api: 'apiSlice',
503
+ headers,
504
+ query,
505
+ })
506
+
507
+ //chunkIndex, chunkTotal, packageId, 從headers接收
508
+ let chunkIndex = get(headers, 'chunk-index', '')
509
+ let chunkTotal = get(headers, 'chunk-total', '')
510
+ let packageId = get(headers, 'package-id', '')
511
+
512
+ //check
513
+ if (!isp0int(chunkIndex)) {
514
+ console.log('invalid chunkIndex in headers')
515
+ return res.response({ error: 'invalid chunkIndex in headers' }).code(400)
516
+ }
517
+ chunkIndex = cint(chunkIndex)
518
+ if (!isp0int(chunkTotal)) {
519
+ console.log('invalid chunkTotal in headers')
520
+ return res.response({ error: 'invalid chunkTotal in headers' }).code(400)
431
521
  }
522
+ chunkTotal = cint(chunkTotal)
523
+ if (!isestr(packageId)) {
524
+ console.log('invalid packageId in headers')
525
+ return res.response({ error: 'invalid packageId in headers' }).code(400)
526
+ }
527
+
528
+ //pathFileChunk
529
+ let pathFileChunk = `${pathUploadTemp}/${packageId}_${chunkIndex}` //path.join(pathUploadTemp, `${packageId}_${chunkIndex}`)
530
+ // console.log('pathFileChunk', pathFileChunk)
531
+
532
+ //fileStream
533
+ let fileStream = fs.createWriteStream(pathFileChunk)
534
+
535
+ //receive
536
+ let receive = () => {
432
537
 
433
- //trigger
434
- ea.trigger(clientId, client)
538
+ //pm
539
+ let pm = genPm()
435
540
 
436
- //dealData
541
+ //pipe
542
+ req.payload.pipe(fileStream)
543
+ // console.log(`receiving chunk[${chunkIndex + 1}/${chunkTotal}]...`)
544
+
545
+ //end
546
+ req.payload.on('end', () => {
547
+ // console.log(`receive chunk[${chunkIndex + 1}/${chunkTotal}] done`)
548
+
549
+ //setTimeout, 切片上傳添加延遲處理, 避免佔滿伺服器CPU與流量
550
+ setTimeout(() => {
551
+ pm.resolve(`chunk[${chunkIndex}/${chunkTotal}] of packageId[${packageId}] done`)
552
+ }, delayForSlice)
553
+
554
+ })
555
+
556
+ //error
557
+ req.payload.on('error', (err) => {
558
+ console.log(`receive chunk[${chunkIndex + 1}/${chunkTotal}] err`, err)
559
+ pm.reject(`chunk[${chunkIndex}/${chunkTotal}] of packageId[${packageId}] err`)
560
+ })
561
+
562
+ return pm
563
+ }
564
+
565
+ //receive
437
566
  let out = {}
438
- await dealData(inp)
567
+ await receive()
439
568
  .then(function(msg) {
440
569
  out.success = msg
441
570
  })
442
571
  .catch(function(msg) {
443
572
  out.error = msg
444
573
  })
574
+ // console.log('out', out)
445
575
 
446
576
  //u8aOut
447
577
  let u8aOut = obj2u8arr(out)
578
+ // console.log('u8aOut', u8aOut)
448
579
 
449
- //stream
450
- let sm = new stream.Readable()
451
- sm._read = () => {}
452
- sm.push(u8aOut)
453
- sm.push(null)
454
- // if (out.success._mode !== 'polling') {
455
- // console.log('out', out)
456
- // console.log('u8aOut', u8aOut)
457
- // }
458
-
459
- return res.response(sm)
460
- .header('Cache-Control', 'no-cache, no-store, must-revalidate')
461
- .header('Content-Type', 'application/octet-stream')
462
- .header('Content-Length', sm.readableLength)
580
+ return responseU8aStream(res, u8aOut)
463
581
  },
464
582
  }
465
583
 
584
+ //apiSliceMerge
585
+ let apiSliceMerge = {
586
+ path: `/${apiSliceName}m`,
587
+ method: 'POST',
588
+ options: {
589
+ payload: {
590
+ maxBytes: 1024 * 1024 * 1024 * 1024, //預設為1mb, 調整至1tb, 也就是給予3次方
591
+ maxParts: 1000 * 1000 * 1000, //預設為1000, 給予3次方
592
+ timeout: false, //避免請求未完成時中斷
593
+ parse: true,
594
+ },
595
+ timeout: {
596
+ socket: false, //避免socket自動關閉
597
+ server: false,
598
+ },
599
+ },
600
+ handler: async function (req, res) {
601
+ // console.log(req, res)
602
+ // console.log('payload', req.payload)
466
603
 
467
- /**
468
- * Hapi監聽客戶端執行事件
469
- *
470
- * @memberof WConverhpServer
471
- * @param {String} func 傳入執行函數名稱字串
472
- * @param {*} input 傳入執行函數之輸入數據
473
- * @param {Function} callback 傳入執行函數之回調函數
474
- * @param {Function} sendData 傳入執行函數之強制回傳函數,提供傳送任意訊息給客戶端,供由服器多次回傳數據之用
475
- * @example
476
- * wo.on('execute', function(func, input, callback, sendData) {
477
- * ...
478
- * })
479
- */
480
- function onExecute() {} onExecute()
481
-
482
-
483
- /**
484
- * Hapi監聽客戶端廣播事件
485
- *
486
- * @memberof WConverhpServer
487
- * @param {*} data 傳入廣播訊息
488
- * @example
489
- * wo.on('broadcast', function(data) {
490
- * ...
491
- * })
492
- */
493
- function onBroadcast() {} onBroadcast()
494
-
495
-
496
- /**
497
- * Hapi監聽客戶端發送事件
498
- *
499
- * @memberof WConverhpServer
500
- * @param {*} data 傳入發送訊息
501
- * @example
502
- * wo.on('deliver', function(data) {
503
- * ...
504
- * })
505
- */
506
- function onDeliver() {} onDeliver()
507
-
508
-
509
- /**
510
- * Hapi通訊物件對全客戶端廣播函數
511
- *
512
- * @memberof WConverhpServer
513
- * @function broadcast
514
- * @param {*} data 輸入廣播函數之輸入資訊
515
- * @param {Function} cb 輸入進度函數
516
- * @example
517
- * let data = {...}
518
- * wo.broadcast(data, cb)
519
- */
520
- ee.broadcast = function (data, cbProgress = function () {}) {
521
-
522
- //check, broadcastMessages受ea偵測頻率1s影響, 伺服器初始化後至少需1s才會有有效對象
523
- if (iser(broadcastMessages)) {
524
- //console.log('no client for broadcast')
525
- return
526
- }
604
+ //headers
605
+ let headers = get(req, 'headers')
606
+ headers = iseobj(headers) ? headers : ''
607
+ // console.log('headers', headers)
608
+
609
+ //query
610
+ let query = get(req, 'query')
611
+ query = iseobj(query) ? query : ''
612
+ // console.log('query', query)
613
+
614
+ //token
615
+ let token = get(query, 'token', '')
616
+
617
+ //check
618
+ if (true) {
619
+
620
+ //funCheck
621
+ let m = funCheck({ token, headers, query })
622
+ if (ispm(m)) {
623
+ m = await m
624
+ }
625
+
626
+ //check
627
+ if (m !== true) {
628
+ return res.response({ error: 'permission denied' }).code(400)
629
+ }
527
630
 
528
- //modify broadcast data
529
- let t = {}
530
- each(broadcastMessages, (v, k) => {
631
+ }
531
632
 
532
- //push, 數據為陣列, 加入新廣播數據
533
- v.push({
534
- mode: 'broadcast',
535
- data,
633
+ //eeEmit
634
+ eeEmit('handler', {
635
+ api: 'apiSliceMerge',
636
+ headers,
637
+ query,
536
638
  })
537
639
 
538
- //save
539
- t[k] = v
640
+ //filename, chunkTotal, packageId, 從payload接收
641
+ let filename = get(req, 'payload.filename', '')
642
+ let chunkTotal = get(req, 'payload.chunk-total', '')
643
+ let packageId = get(req, 'payload.package-id', '')
540
644
 
541
- })
542
- broadcastMessages = t
645
+ //check
646
+ if (!isestr(filename)) {
647
+ console.log('invalid filename in payload')
648
+ return res.response({ error: 'invalid filename in payload' }).code(400)
649
+ }
650
+ if (!isp0int(chunkTotal)) {
651
+ console.log('invalid chunkTotal in payload')
652
+ return res.response({ error: 'invalid chunkTotal in payload' }).code(400)
653
+ }
654
+ chunkTotal = cint(chunkTotal)
655
+ if (!isestr(packageId)) {
656
+ console.log('invalid packageId in payload')
657
+ return res.response({ error: 'invalid packageId in payload' }).code(400)
658
+ }
543
659
 
544
- //cbProgress, 無法馬上傳需等待客戶端輪詢接收, 故進度回調只能先回傳100%
545
- cbProgress(100)
660
+ //merge
661
+ let merge = async () => {
662
+ let errTemp = ''
546
663
 
547
- }
664
+ //pathFileMerge
665
+ let pathFileMerge = path.join(pathUploadTemp, `${packageId}`)
666
+ // console.log('pathFileMerge', pathFileMerge)
548
667
 
668
+ //fileStream
669
+ let fileStream = fs.createWriteStream(pathFileMerge)
670
+ // console.log(`merge filename[${filename}] start`)
549
671
 
550
- /**
551
- * Hapi通訊物件對客戶端發送訊息函數
552
- *
553
- * @memberof WConverhpServer
554
- * @function deliver
555
- * @param {String} clientId 輸入識別用使用者主鍵字串
556
- * @param {*} data 輸入發送函數之輸入資訊
557
- * @param {Function} cb 輸入進度函數
558
- * @example
559
- * let clientId = '...'
560
- * let data = {...}
561
- * wo.deliver(clientId, data, cb)
562
- */
563
- ee.deliver = function (clientId, data, cbProgress = function () {}) {
564
-
565
- //bms, 此時有可能ea trigger為非同步, 尚未把
566
- let bms = get(broadcastMessages, clientId, [])
567
-
568
- //push
569
- bms.push({
570
- mode: 'deliver',
571
- data,
572
- })
672
+ //使用try catch攔截,使stream能完整創造與結束
673
+ try {
674
+ for (let i = 0; i < chunkTotal; i++) {
675
+ // console.log(`merge ${i + 1}/${chunkTotal}`)
573
676
 
574
- //modify broadcast data
575
- broadcastMessages[clientId] = bms
677
+ //chunkIndex
678
+ let chunkIndex = i
576
679
 
577
- //cbProgress, 無法馬上傳需等待客戶端輪詢接收, 故進度回調只能先回傳100%
578
- cbProgress(100)
680
+ //pathFileChunk
681
+ let pathFileChunk = path.join(pathUploadTemp, `${packageId}_${chunkIndex}`)
579
682
 
580
- }
683
+ //check
684
+ if (!fsIsFile(pathFileChunk)) {
685
+ // console.log(`pathFileChunk[${pathFileChunk}] is not a file`)
686
+ // return res.response({ error: `Missing chunk ${i} of filename[${filename}]` }).code(400)
687
+ throw new Error(`Missing chunk ${i} of filename[${filename}]`)
688
+ }
689
+
690
+ // console.log(`merging chunk[${chunkIndex + 1}/${chunkTotal}]...`)
581
691
 
692
+ //chunkData
693
+ let chunkData = fs.readFileSync(pathFileChunk)
582
694
 
695
+ //write
696
+ fileStream.write(chunkData)
697
+
698
+ //fsDeleteFile
699
+ fsDeleteFile(pathFileChunk)
700
+
701
+ // console.log(`merge chunk[${chunkIndex + 1}/${chunkTotal}] done`)
702
+ }
703
+ }
704
+ catch (err) {
705
+ errTemp = err.message
706
+ }
707
+
708
+ //end
709
+ fileStream.end()
710
+ // console.log(`merge filename[${filename}] done`)
711
+
712
+ //check
713
+ if (isestr(errTemp)) {
714
+ return Promise.reject(errTemp)
715
+ }
716
+
717
+ //r
718
+ let r = {
719
+ // message: `Merged filename[${filename}] successfully`,
720
+ filename,
721
+ path: pathFileMerge,
722
+ }
723
+
724
+ return r
725
+ }
726
+
727
+ //core
728
+ let core = async() => {
729
+
730
+ //merge
731
+ // console.log('merge start')
732
+ let r = await merge()
733
+ // console.log('merge done', r)
734
+
735
+ //procUpload
736
+ // console.log('procUpload start')
737
+ let rr = await procUpload(r)
738
+ // console.log('procUpload done', rr)
739
+
740
+ return rr
741
+ }
742
+
743
+ //core
744
+ let out = {}
745
+ await core()
746
+ .then(function(msg) {
747
+ out.success = msg
748
+ })
749
+ .catch(function(msg) {
750
+ out.error = msg
751
+ })
752
+ // console.log('out', out)
753
+
754
+ //u8aOut
755
+ let u8aOut = obj2u8arr(out)
756
+ // console.log('u8aOut', u8aOut)
757
+
758
+ return responseU8aStream(res, u8aOut)
759
+ },
760
+ }
761
+
762
+ //startServer
583
763
  async function startServer() {
584
764
 
585
765
  //register inert
586
- await server.register(Inert)
587
-
588
- //api
589
- let api = {
590
- method: 'GET',
591
- path: '/{file*}',
592
- handler: {
593
- directory: {
594
- path: './'
595
- }
596
- },
766
+ if (useInert) {
767
+ await server.register(Inert)
768
+ }
769
+
770
+ //apiRoutes
771
+ let apiRoutes = []
772
+ if (useInert) {
773
+ let api = {
774
+ method: 'GET',
775
+ path: '/{file*}',
776
+ handler: {
777
+ directory: {
778
+ path: `${pathStaticFiles}/`
779
+ }
780
+ },
781
+ }
782
+ apiRoutes = [
783
+ ...apiRoutes,
784
+ api,
785
+ ]
786
+ }
787
+ if (true) {
788
+ apiRoutes = [
789
+ ...apiRoutes,
790
+ apiMain,
791
+ apiSlice,
792
+ apiSliceMerge,
793
+ ]
597
794
  }
598
795
 
599
796
  //route
600
- server.route([api, apiMain])
797
+ server.route(apiRoutes)
601
798
 
602
799
  //start
603
800
  await server.start()
@@ -606,8 +803,9 @@ function WConverhpServer(opt = {}) {
606
803
 
607
804
  }
608
805
 
609
- if (opt.serverHapi) {
610
- opt.serverHapi.route(apiMain)
806
+ //start
807
+ if (get(opt, 'serverHapi')) {
808
+ opt.serverHapi.route([apiMain, apiSlice, apiSliceMerge])
611
809
  }
612
810
  else {
613
811
  startServer()