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