w-converhp 2.0.14 → 2.0.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +58 -104
- package/dist/w-converhp-client.umd.js +2 -2
- package/dist/w-converhp-client.umd.js.map +1 -1
- package/dist/w-converhp-server.umd.js +2 -2
- package/dist/w-converhp-server.umd.js.map +1 -1
- package/docs/WConverhpClient.html +3 -3
- package/docs/WConverhpClient.mjs.html +102 -119
- package/docs/WConverhpServer.html +43 -3
- package/docs/WConverhpServer.mjs.html +330 -35
- package/docs/index.html +1 -1
- package/package.json +2 -2
- package/scld.mjs +4 -4
- package/src/WConverhpClient.mjs +101 -118
- package/src/WConverhpServer.mjs +329 -34
- package/srv.mjs +15 -7
- package/test/downloadLargeFile.test.mjs +24 -19
- package/test/executeWithFile.test.mjs +10 -12
- package/test/executeWithU8a.test.mjs +11 -13
- package/test/uploadLargeFile.test.mjs +10 -14
package/src/WConverhpServer.mjs
CHANGED
|
@@ -26,6 +26,11 @@ import mergeFiles from './mergeFiles.wk.umd.js'
|
|
|
26
26
|
// import mergeFiles from './mergeFiles.mjs'
|
|
27
27
|
|
|
28
28
|
|
|
29
|
+
//回傳前端stream時(POST或GET皆可), 前端會須等stream傳完才能判斷是否為大檔或錯誤訊息, 此會導致若回傳超大檔, 會需要對超大檔進行解析會有記憶體上限問題, 故需要通過header提供基本成功或失敗訊息, 讓前端能進行解析判斷
|
|
30
|
+
//回傳前端(nodejs)時, 針對超大檔, 只能用POST並用stream回傳
|
|
31
|
+
//回傳前端(browser)時, 針對超大檔, 可用POST並用stream回傳但還要處理進度條, 若要交由瀏覽器下載器處理, 只能用GET並用stream回傳, 且前端只能用window.location.href或a.href+a.click()下載
|
|
32
|
+
|
|
33
|
+
|
|
29
34
|
/**
|
|
30
35
|
* 建立Hapi伺服器
|
|
31
36
|
*
|
|
@@ -36,6 +41,7 @@ import mergeFiles from './mergeFiles.wk.umd.js'
|
|
|
36
41
|
* @param {String} [opt.pathStaticFiles='dist'] 輸入當useInert=true時提供瀏覽資料夾字串,預設'dist'
|
|
37
42
|
* @param {String} [opt.apiName='api'] 輸入API名稱字串,預設'api'
|
|
38
43
|
* @param {String} [opt.pathUploadTemp='./uploadTemp'] 輸入暫時存放切片上傳檔案資料夾字串,預設'./uploadTemp'
|
|
44
|
+
* @param {String} [opt.tokenType='Bearer'] 輸入token類型字串,預設'Bearer'
|
|
39
45
|
* @param {Function} [opt.verifyConn=()=>{return true}] 輸入呼叫API時檢測函數,預設()=>{return true}
|
|
40
46
|
* @param {Array} [opt.corsOrigins=['*']] 輸入允許跨域網域陣列,若給予['*']代表允許全部,預設['*']
|
|
41
47
|
* @param {Integer} [opt.delayForBasic=0] 輸入基本API用延遲響應時間,單位ms,預設0
|
|
@@ -44,14 +50,24 @@ import mergeFiles from './mergeFiles.wk.umd.js'
|
|
|
44
50
|
* @returns {Object} 回傳事件物件,可監聽事件execute、upload、handler
|
|
45
51
|
* @example
|
|
46
52
|
*
|
|
53
|
+
* import fs from 'fs'
|
|
47
54
|
* import _ from 'lodash-es'
|
|
48
|
-
* import
|
|
55
|
+
* import w from 'wsemi'
|
|
56
|
+
* import WConverhpServer from './src/WConverhpServer.mjs'
|
|
57
|
+
*
|
|
58
|
+
* let ms = []
|
|
49
59
|
*
|
|
50
60
|
* let opt = {
|
|
51
61
|
* port: 8080,
|
|
52
62
|
* apiName: 'api',
|
|
53
63
|
* pathStaticFiles: '.', //要存取專案資料夾下web.html, 故不能給dist
|
|
54
|
-
* verifyConn: () => {
|
|
64
|
+
* verifyConn: async ({ apiType, authorization, headers, query }) => {
|
|
65
|
+
* console.log('verifyConn', `apiType[${apiType}]`, `authorization[${authorization}]`)
|
|
66
|
+
* let token = w.strdelleft(authorization, 7) //刪除Bearer
|
|
67
|
+
* if (!w.isestr(token)) {
|
|
68
|
+
* return false
|
|
69
|
+
* }
|
|
70
|
+
* // await w.delay(3000)
|
|
55
71
|
* return true
|
|
56
72
|
* },
|
|
57
73
|
* }
|
|
@@ -69,6 +85,7 @@ import mergeFiles from './mergeFiles.wk.umd.js'
|
|
|
69
85
|
*
|
|
70
86
|
* if (_.get(input, 'p.d.u8a', null)) {
|
|
71
87
|
* console.log('input.p.d.u8a', input.p.d.u8a)
|
|
88
|
+
* ms.push({ 'input.p.d.u8a': input.p.d.u8a })
|
|
72
89
|
* }
|
|
73
90
|
*
|
|
74
91
|
* let r = {
|
|
@@ -76,13 +93,7 @@ import mergeFiles from './mergeFiles.wk.umd.js'
|
|
|
76
93
|
* _data: [11, 22.22, 'abc', { x: '21', y: 65.43, z: 'test中文' }],
|
|
77
94
|
* _bin: {
|
|
78
95
|
* name: 'zdata.b2',
|
|
79
|
-
* u8a: new Uint8Array([66, 97, 115]),
|
|
80
|
-
* // name: '100mb.7z',
|
|
81
|
-
* // u8a: new Uint8Array(fs.readFileSync('D:\\開源-JS-006-2-w-converhp\\_temp\\100mb.7z')),
|
|
82
|
-
* // name: '500mb.7z',
|
|
83
|
-
* // u8a: new Uint8Array(fs.readFileSync('D:\\開源-JS-006-2-w-converhp\\_temp\\500mb.7z')),
|
|
84
|
-
* // name: '1000mb.7z',
|
|
85
|
-
* // u8a: new Uint8Array(fs.readFileSync('D:\\開源-JS-006-2-w-converhp\\_temp\\1000mb.7z')),
|
|
96
|
+
* u8a: new Uint8Array([52, 66, 97, 115]),
|
|
86
97
|
* },
|
|
87
98
|
* }
|
|
88
99
|
*
|
|
@@ -105,6 +116,7 @@ import mergeFiles from './mergeFiles.wk.umd.js'
|
|
|
105
116
|
* console.log(`Server[port:${opt.port}]: upload`, input)
|
|
106
117
|
*
|
|
107
118
|
* try {
|
|
119
|
+
* ms.push({ 'receive and return': input })
|
|
108
120
|
* let output = input
|
|
109
121
|
* pm.resolve(output)
|
|
110
122
|
* }
|
|
@@ -117,29 +129,32 @@ import mergeFiles from './mergeFiles.wk.umd.js'
|
|
|
117
129
|
* wo.on('download', (input, pm) => {
|
|
118
130
|
* console.log(`Server[port:${opt.port}]: download`, input)
|
|
119
131
|
*
|
|
132
|
+
* let streamRead = null
|
|
120
133
|
* try {
|
|
121
134
|
* ms.push({ 'download': input })
|
|
122
135
|
*
|
|
123
136
|
* //fp
|
|
124
137
|
* let fp = `./test/1mb.7z`
|
|
125
138
|
*
|
|
126
|
-
* //
|
|
127
|
-
* let streamRead = fs.createReadStream(fp)
|
|
128
|
-
*
|
|
129
|
-
* //fileName
|
|
130
|
-
* let fileName = `1mb.7z`
|
|
139
|
+
* //check, 檔案存在才往下
|
|
131
140
|
*
|
|
132
141
|
* //fileSize
|
|
133
142
|
* let stats = fs.statSync(fp)
|
|
134
143
|
* let fileSize = stats.size
|
|
135
144
|
*
|
|
145
|
+
* //streamRead
|
|
146
|
+
* streamRead = fs.createReadStream(fp)
|
|
147
|
+
*
|
|
148
|
+
* //filename
|
|
149
|
+
* let filename = `1mb中文.7z` //測試支援中文
|
|
150
|
+
*
|
|
136
151
|
* //fileType
|
|
137
152
|
* let fileType = 'application/x-7z-compressed'
|
|
138
153
|
*
|
|
139
154
|
* //output
|
|
140
155
|
* let output = {
|
|
141
156
|
* streamRead,
|
|
142
|
-
*
|
|
157
|
+
* filename,
|
|
143
158
|
* fileSize,
|
|
144
159
|
* fileType,
|
|
145
160
|
* }
|
|
@@ -148,14 +163,27 @@ import mergeFiles from './mergeFiles.wk.umd.js'
|
|
|
148
163
|
* }
|
|
149
164
|
* catch (err) {
|
|
150
165
|
* console.log('download error', err)
|
|
166
|
+
* // try {
|
|
167
|
+
* // streamRead.destroy() //若fs.createReadStream早於fs.statSync執行, 但fs.statSync發生錯誤時, stream得要destroy
|
|
168
|
+
* // }
|
|
169
|
+
* // catch (err) {}
|
|
151
170
|
* pm.reject('download error')
|
|
152
171
|
* }
|
|
153
172
|
*
|
|
154
173
|
* })
|
|
174
|
+
* wo.on('error', (err) => {
|
|
175
|
+
* console.log(`Server[port:${opt.port}]: error`, err)
|
|
176
|
+
* })
|
|
155
177
|
* wo.on('handler', (data) => {
|
|
156
178
|
* // console.log(`Server[port:${opt.port}]: handler`, data)
|
|
157
179
|
* })
|
|
158
180
|
*
|
|
181
|
+
* setTimeout(() => {
|
|
182
|
+
* console.log('ms', ms)
|
|
183
|
+
* // console.log('ms', JSON.stringify(ms))
|
|
184
|
+
* wo.stop()
|
|
185
|
+
* }, 3000)
|
|
186
|
+
*
|
|
159
187
|
*/
|
|
160
188
|
function WConverhpServer(opt = {}) {
|
|
161
189
|
|
|
@@ -183,12 +211,6 @@ function WConverhpServer(opt = {}) {
|
|
|
183
211
|
apiName = 'api'
|
|
184
212
|
}
|
|
185
213
|
|
|
186
|
-
//apiUploadSliceName
|
|
187
|
-
let apiUploadSliceName = `${apiName}slc`
|
|
188
|
-
|
|
189
|
-
//apiDownloadName
|
|
190
|
-
let apiDownloadName = `${apiName}dw`
|
|
191
|
-
|
|
192
214
|
//pathUploadTemp
|
|
193
215
|
let pathUploadTemp = get(opt, 'pathUploadTemp')
|
|
194
216
|
if (!isestr(pathUploadTemp)) {
|
|
@@ -198,6 +220,12 @@ function WConverhpServer(opt = {}) {
|
|
|
198
220
|
fsCreateFolder(pathUploadTemp)
|
|
199
221
|
}
|
|
200
222
|
|
|
223
|
+
//tokenType
|
|
224
|
+
let tokenType = get(opt, 'tokenType')
|
|
225
|
+
if (!isestr(tokenType)) {
|
|
226
|
+
tokenType = 'Bearer'
|
|
227
|
+
}
|
|
228
|
+
|
|
201
229
|
//verifyConn
|
|
202
230
|
let verifyConn = get(opt, 'verifyConn')
|
|
203
231
|
if (!isfun(verifyConn)) {
|
|
@@ -558,7 +586,7 @@ function WConverhpServer(opt = {}) {
|
|
|
558
586
|
|
|
559
587
|
//apiUploadSlice
|
|
560
588
|
let apiUploadSlice = {
|
|
561
|
-
path: `/${
|
|
589
|
+
path: `/${apiName}slc`,
|
|
562
590
|
method: 'POST',
|
|
563
591
|
options: {
|
|
564
592
|
payload: {
|
|
@@ -700,7 +728,7 @@ function WConverhpServer(opt = {}) {
|
|
|
700
728
|
|
|
701
729
|
//apiUploadSliceMerge
|
|
702
730
|
let apiUploadSliceMerge = {
|
|
703
|
-
path: `/${
|
|
731
|
+
path: `/${apiName}slcm`,
|
|
704
732
|
method: 'POST',
|
|
705
733
|
options: {
|
|
706
734
|
payload: {
|
|
@@ -708,7 +736,7 @@ function WConverhpServer(opt = {}) {
|
|
|
708
736
|
maxParts: 1000 * 1000 * 1000, //預設為1000, 給予3次方
|
|
709
737
|
timeout: false, //避免請求未完成時中斷
|
|
710
738
|
// output: 'stream',
|
|
711
|
-
parse: true, //前端送obj
|
|
739
|
+
parse: true, //前端送obj過來須自動解析
|
|
712
740
|
},
|
|
713
741
|
timeout: {
|
|
714
742
|
server: false, //關閉伺服器超時
|
|
@@ -818,9 +846,263 @@ function WConverhpServer(opt = {}) {
|
|
|
818
846
|
},
|
|
819
847
|
}
|
|
820
848
|
|
|
849
|
+
//apiDownloadGetFilename
|
|
850
|
+
let apiDownloadGetFilename = {
|
|
851
|
+
path: `/${apiName}dwgfn`,
|
|
852
|
+
method: 'POST',
|
|
853
|
+
options: {
|
|
854
|
+
payload: {
|
|
855
|
+
maxBytes: 1024 * 1024 * 1024 * 1024, //預設為1mb, 調整至1tb, 也就是給予3次方
|
|
856
|
+
maxParts: 1000 * 1000 * 1000, //預設為1000, 給予3次方
|
|
857
|
+
timeout: false, //避免請求未完成時中斷
|
|
858
|
+
// output: 'stream',
|
|
859
|
+
parse: true, //前端送obj過來須自動解析
|
|
860
|
+
},
|
|
861
|
+
timeout: {
|
|
862
|
+
server: false, //關閉伺服器超時
|
|
863
|
+
socket: false, //關閉socket超時
|
|
864
|
+
},
|
|
865
|
+
},
|
|
866
|
+
handler: async function (req, res) {
|
|
867
|
+
// console.log(req, res)
|
|
868
|
+
// console.log('payload', req.payload)
|
|
869
|
+
|
|
870
|
+
//headers
|
|
871
|
+
let headers = get(req, 'headers')
|
|
872
|
+
headers = iseobj(headers) ? headers : ''
|
|
873
|
+
// console.log('headers', headers)
|
|
874
|
+
|
|
875
|
+
//query
|
|
876
|
+
let query = get(req, 'query')
|
|
877
|
+
query = iseobj(query) ? query : ''
|
|
878
|
+
// console.log('query', query)
|
|
879
|
+
|
|
880
|
+
//authorization
|
|
881
|
+
let authorization = get(headers, 'authorization', '')
|
|
882
|
+
authorization = isestr(authorization) ? authorization : ''
|
|
883
|
+
|
|
884
|
+
//check
|
|
885
|
+
if (true) {
|
|
886
|
+
|
|
887
|
+
//verifyConn
|
|
888
|
+
let m = verifyConn({ apiType: 'download-get-filename', authorization, headers, query })
|
|
889
|
+
if (ispm(m)) {
|
|
890
|
+
m = await m
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
//check
|
|
894
|
+
if (m !== true) {
|
|
895
|
+
return responseU8aStreamWithError(res, 'permission denied')
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
//eeEmit
|
|
901
|
+
eeEmit('handler', {
|
|
902
|
+
api: 'apiDownloadGetFilename',
|
|
903
|
+
headers,
|
|
904
|
+
query,
|
|
905
|
+
})
|
|
906
|
+
|
|
907
|
+
//fileId, 從payload接收
|
|
908
|
+
let fileId = get(req, 'payload.fileId', '')
|
|
909
|
+
// console.log('fileId', fileId)
|
|
910
|
+
|
|
911
|
+
//check
|
|
912
|
+
if (!isestr(fileId)) {
|
|
913
|
+
// console.log('invalid fileId in payload')
|
|
914
|
+
return responseU8aStreamWithError(res, 'invalid fileId in payload')
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
//inp
|
|
918
|
+
let inp = { fileId }
|
|
919
|
+
|
|
920
|
+
//procDownloadGetFilename
|
|
921
|
+
let out = {}
|
|
922
|
+
let returnType = ''
|
|
923
|
+
let returnMsg = ''
|
|
924
|
+
await procDownload(inp)
|
|
925
|
+
.then(function(msg) {
|
|
926
|
+
out.success = msg
|
|
927
|
+
returnType = 'success'
|
|
928
|
+
returnMsg = 'need to parse'
|
|
929
|
+
})
|
|
930
|
+
.catch(function(msg) {
|
|
931
|
+
out.error = msg
|
|
932
|
+
returnType = 'error'
|
|
933
|
+
returnMsg = 'need to parse'
|
|
934
|
+
})
|
|
935
|
+
// console.log('out', out)
|
|
936
|
+
|
|
937
|
+
//r
|
|
938
|
+
let r = get(out, 'success')
|
|
939
|
+
|
|
940
|
+
//streamRead
|
|
941
|
+
let streamRead = get(r, 'streamRead')
|
|
942
|
+
|
|
943
|
+
//destroy, 不提供stream故須預先destroy
|
|
944
|
+
try {
|
|
945
|
+
streamRead.destroy()
|
|
946
|
+
}
|
|
947
|
+
catch (err) {}
|
|
948
|
+
|
|
949
|
+
//filename
|
|
950
|
+
let filename = get(r, 'filename')
|
|
951
|
+
if (!isestr(filename)) {
|
|
952
|
+
//已於前面destroy
|
|
953
|
+
return responseU8aStreamWithError(res, 'invalid filename')
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
//重新提供out
|
|
957
|
+
out = {
|
|
958
|
+
success: {
|
|
959
|
+
filename,
|
|
960
|
+
},
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
//u8aOut
|
|
964
|
+
let u8aOut = obj2u8arr(out)
|
|
965
|
+
// console.log('u8aOut', u8aOut)
|
|
966
|
+
|
|
967
|
+
return responseU8aStream(res, u8aOut, { returnType, returnMsg })
|
|
968
|
+
},
|
|
969
|
+
}
|
|
970
|
+
|
|
971
|
+
//apiDownloadGetFile
|
|
972
|
+
let apiDownloadGetFile = {
|
|
973
|
+
path: `/${apiName}dwgf`,
|
|
974
|
+
method: 'GET',
|
|
975
|
+
options: {
|
|
976
|
+
timeout: {
|
|
977
|
+
server: false, //關閉伺服器超時
|
|
978
|
+
socket: false, //關閉socket超時
|
|
979
|
+
},
|
|
980
|
+
},
|
|
981
|
+
handler: async function (req, res) {
|
|
982
|
+
// console.log(req, res)
|
|
983
|
+
// console.log('payload', req.payload)
|
|
984
|
+
|
|
985
|
+
//headers
|
|
986
|
+
let headers = get(req, 'headers')
|
|
987
|
+
headers = iseobj(headers) ? headers : ''
|
|
988
|
+
// console.log('headers', headers)
|
|
989
|
+
|
|
990
|
+
//query
|
|
991
|
+
let query = get(req, 'query')
|
|
992
|
+
query = iseobj(query) ? query : ''
|
|
993
|
+
// console.log('query', query)
|
|
994
|
+
|
|
995
|
+
//token
|
|
996
|
+
let token = get(query, 'token', '')
|
|
997
|
+
token = isestr(token) ? token : ''
|
|
998
|
+
// console.log('token', token)
|
|
999
|
+
|
|
1000
|
+
//authorization
|
|
1001
|
+
let authorization = ''
|
|
1002
|
+
if (isestr(token)) {
|
|
1003
|
+
authorization = `${tokenType} ${token}`
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
//check
|
|
1007
|
+
if (true) {
|
|
1008
|
+
|
|
1009
|
+
//verifyConn
|
|
1010
|
+
let m = verifyConn({ apiType: 'download-get-file', authorization, headers, query })
|
|
1011
|
+
if (ispm(m)) {
|
|
1012
|
+
m = await m
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
//check
|
|
1016
|
+
if (m !== true) {
|
|
1017
|
+
return responseU8aStreamWithError(res, 'permission denied')
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
}
|
|
1021
|
+
|
|
1022
|
+
//eeEmit
|
|
1023
|
+
eeEmit('handler', {
|
|
1024
|
+
api: 'apiDownloadGetFilename',
|
|
1025
|
+
headers,
|
|
1026
|
+
query,
|
|
1027
|
+
})
|
|
1028
|
+
|
|
1029
|
+
//fileId
|
|
1030
|
+
let fileId = get(query, 'fileId', '')
|
|
1031
|
+
fileId = isestr(fileId) ? fileId : ''
|
|
1032
|
+
// console.log('fileId', fileId)
|
|
1033
|
+
|
|
1034
|
+
//check
|
|
1035
|
+
if (!isestr(fileId)) {
|
|
1036
|
+
// console.log('invalid fileId in query')
|
|
1037
|
+
return responseU8aStreamWithError(res, 'invalid fileId in query')
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
//inp
|
|
1041
|
+
let inp = { fileId }
|
|
1042
|
+
|
|
1043
|
+
//procDownload
|
|
1044
|
+
let out = {}
|
|
1045
|
+
await procDownload(inp)
|
|
1046
|
+
.then(function(msg) {
|
|
1047
|
+
out.success = msg
|
|
1048
|
+
})
|
|
1049
|
+
.catch(function(msg) {
|
|
1050
|
+
out.error = msg
|
|
1051
|
+
})
|
|
1052
|
+
// console.log('out', out)
|
|
1053
|
+
|
|
1054
|
+
//return
|
|
1055
|
+
if (haskey(out, 'error')) {
|
|
1056
|
+
// console.log('out.error', out.error)
|
|
1057
|
+
return responseU8aStreamWithError(res, `can not get file from fileId`)
|
|
1058
|
+
}
|
|
1059
|
+
|
|
1060
|
+
//r
|
|
1061
|
+
let r = get(out, 'success')
|
|
1062
|
+
|
|
1063
|
+
//streamRead
|
|
1064
|
+
let streamRead = get(r, 'streamRead')
|
|
1065
|
+
|
|
1066
|
+
// //filename
|
|
1067
|
+
// let filename = get(r, 'filename')
|
|
1068
|
+
// if (!isestr(filename)) {
|
|
1069
|
+
// return responseU8aStreamWithError(res, 'invalid filename')
|
|
1070
|
+
// }
|
|
1071
|
+
// filename = str2b64(filename) //headers內對中文支援度不佳須用base64傳
|
|
1072
|
+
// console.log('filename', filename)
|
|
1073
|
+
|
|
1074
|
+
//fileSize
|
|
1075
|
+
let fileSize = get(r, 'fileSize')
|
|
1076
|
+
if (!isNumber(fileSize)) {
|
|
1077
|
+
try {
|
|
1078
|
+
streamRead.destroy() //提供stream前發生錯誤, 得強制destroy
|
|
1079
|
+
}
|
|
1080
|
+
catch (err) {}
|
|
1081
|
+
return responseU8aStreamWithError(res, 'invalid fileSize')
|
|
1082
|
+
}
|
|
1083
|
+
// fileSize = cstr(fileSize)
|
|
1084
|
+
|
|
1085
|
+
//fileType
|
|
1086
|
+
let fileType = get(r, 'fileType')
|
|
1087
|
+
if (!isestr(fileType)) {
|
|
1088
|
+
try {
|
|
1089
|
+
streamRead.destroy() //提供stream前發生錯誤, 得強制destroy
|
|
1090
|
+
}
|
|
1091
|
+
catch (err) {}
|
|
1092
|
+
return responseU8aStreamWithError(res, 'invalid fileType')
|
|
1093
|
+
}
|
|
1094
|
+
fileType = cstr(fileType)
|
|
1095
|
+
|
|
1096
|
+
return res.response(streamRead)
|
|
1097
|
+
.type(fileType)
|
|
1098
|
+
// .header('Content-Disposition', `attachment; filename="${filename}"`) //chrome會優先使用header內filename, 但header內支援中文度很差須用base64, 此導致chrome下載檔名只能為base64, 故一律改由前端(browser)先取得真實檔名後直接給予下載檔名, 避免用header提供真實檔名
|
|
1099
|
+
.header('Content-Length', fileSize)
|
|
1100
|
+
},
|
|
1101
|
+
}
|
|
1102
|
+
|
|
821
1103
|
//apiDownload
|
|
822
1104
|
let apiDownload = {
|
|
823
|
-
path: `/${
|
|
1105
|
+
path: `/${apiName}dw`,
|
|
824
1106
|
method: 'POST',
|
|
825
1107
|
options: {
|
|
826
1108
|
payload: {
|
|
@@ -828,7 +1110,7 @@ function WConverhpServer(opt = {}) {
|
|
|
828
1110
|
maxParts: 1000 * 1000 * 1000, //預設為1000, 給予3次方
|
|
829
1111
|
timeout: false, //避免請求未完成時中斷
|
|
830
1112
|
// output: 'stream',
|
|
831
|
-
parse: true, //前端送obj
|
|
1113
|
+
parse: true, //前端送obj過來須自動解析
|
|
832
1114
|
},
|
|
833
1115
|
timeout: {
|
|
834
1116
|
server: false, //關閉伺服器超時
|
|
@@ -912,16 +1194,24 @@ function WConverhpServer(opt = {}) {
|
|
|
912
1194
|
//streamRead
|
|
913
1195
|
let streamRead = get(r, 'streamRead')
|
|
914
1196
|
|
|
915
|
-
//
|
|
916
|
-
let
|
|
917
|
-
if (!isestr(
|
|
918
|
-
|
|
1197
|
+
//filename
|
|
1198
|
+
let filename = get(r, 'filename')
|
|
1199
|
+
if (!isestr(filename)) {
|
|
1200
|
+
try {
|
|
1201
|
+
streamRead.destroy() //提供stream前發生錯誤, 得強制destroy
|
|
1202
|
+
}
|
|
1203
|
+
catch (err) {}
|
|
1204
|
+
return responseU8aStreamWithError(res, 'invalid filename')
|
|
919
1205
|
}
|
|
920
|
-
|
|
1206
|
+
filename = str2b64(filename) //headers內對中文支援度不佳須用base64傳
|
|
921
1207
|
|
|
922
1208
|
//fileSize
|
|
923
1209
|
let fileSize = get(r, 'fileSize')
|
|
924
1210
|
if (!isNumber(fileSize)) {
|
|
1211
|
+
try {
|
|
1212
|
+
streamRead.destroy() //提供stream前發生錯誤, 得強制destroy
|
|
1213
|
+
}
|
|
1214
|
+
catch (err) {}
|
|
925
1215
|
return responseU8aStreamWithError(res, 'invalid fileSize')
|
|
926
1216
|
}
|
|
927
1217
|
// fileSize = cstr(fileSize)
|
|
@@ -929,13 +1219,17 @@ function WConverhpServer(opt = {}) {
|
|
|
929
1219
|
//fileType
|
|
930
1220
|
let fileType = get(r, 'fileType')
|
|
931
1221
|
if (!isestr(fileType)) {
|
|
1222
|
+
try {
|
|
1223
|
+
streamRead.destroy() //提供stream前發生錯誤, 得強制destroy
|
|
1224
|
+
}
|
|
1225
|
+
catch (err) {}
|
|
932
1226
|
return responseU8aStreamWithError(res, 'invalid fileType')
|
|
933
1227
|
}
|
|
934
1228
|
fileType = cstr(fileType)
|
|
935
1229
|
|
|
936
1230
|
return res.response(streamRead)
|
|
937
1231
|
.type(fileType)
|
|
938
|
-
.header('Content-Disposition', `attachment; filename="${
|
|
1232
|
+
.header('Content-Disposition', `attachment; filename="${filename}"`) //針對前端(nodejs)用POST下載, 可基於header內base64檔名解析出並直接給予檔名, 不用預先取得檔名
|
|
939
1233
|
.header('Content-Length', fileSize)
|
|
940
1234
|
},
|
|
941
1235
|
}
|
|
@@ -971,6 +1265,8 @@ function WConverhpServer(opt = {}) {
|
|
|
971
1265
|
apiMain,
|
|
972
1266
|
apiUploadSlice,
|
|
973
1267
|
apiUploadSliceMerge,
|
|
1268
|
+
apiDownloadGetFilename,
|
|
1269
|
+
apiDownloadGetFile,
|
|
974
1270
|
apiDownload,
|
|
975
1271
|
]
|
|
976
1272
|
}
|
|
@@ -987,8 +1283,7 @@ function WConverhpServer(opt = {}) {
|
|
|
987
1283
|
|
|
988
1284
|
//start
|
|
989
1285
|
if (get(opt, 'serverHapi')) {
|
|
990
|
-
|
|
991
|
-
server.route([apiMain, apiUploadSlice, apiUploadSliceMerge, apiDownload])
|
|
1286
|
+
server.route([apiMain, apiUploadSlice, apiUploadSliceMerge, apiDownloadGetFilename, apiDownloadGetFile, apiDownload])
|
|
992
1287
|
}
|
|
993
1288
|
else {
|
|
994
1289
|
startServer()
|
package/srv.mjs
CHANGED
|
@@ -3,6 +3,7 @@ import _ from 'lodash-es'
|
|
|
3
3
|
import w from 'wsemi'
|
|
4
4
|
import WConverhpServer from './src/WConverhpServer.mjs'
|
|
5
5
|
|
|
6
|
+
|
|
6
7
|
let ms = []
|
|
7
8
|
|
|
8
9
|
let opt = {
|
|
@@ -77,29 +78,32 @@ wo.on('upload', (input, pm) => {
|
|
|
77
78
|
wo.on('download', (input, pm) => {
|
|
78
79
|
console.log(`Server[port:${opt.port}]: download`, input)
|
|
79
80
|
|
|
81
|
+
let streamRead = null
|
|
80
82
|
try {
|
|
81
83
|
ms.push({ 'download': input })
|
|
82
84
|
|
|
83
85
|
//fp
|
|
84
86
|
let fp = `./test/1mb.7z`
|
|
85
87
|
|
|
86
|
-
//
|
|
87
|
-
let streamRead = fs.createReadStream(fp)
|
|
88
|
-
|
|
89
|
-
//fileName
|
|
90
|
-
let fileName = `1mb中文.7z` //測試支援中文
|
|
88
|
+
//check, 檔案存在才往下
|
|
91
89
|
|
|
92
90
|
//fileSize
|
|
93
91
|
let stats = fs.statSync(fp)
|
|
94
92
|
let fileSize = stats.size
|
|
95
93
|
|
|
94
|
+
//streamRead
|
|
95
|
+
streamRead = fs.createReadStream(fp)
|
|
96
|
+
|
|
97
|
+
//filename
|
|
98
|
+
let filename = `1mb中文.7z` //測試支援中文
|
|
99
|
+
|
|
96
100
|
//fileType
|
|
97
101
|
let fileType = 'application/x-7z-compressed'
|
|
98
102
|
|
|
99
103
|
//output
|
|
100
104
|
let output = {
|
|
101
105
|
streamRead,
|
|
102
|
-
|
|
106
|
+
filename,
|
|
103
107
|
fileSize,
|
|
104
108
|
fileType,
|
|
105
109
|
}
|
|
@@ -108,6 +112,10 @@ wo.on('download', (input, pm) => {
|
|
|
108
112
|
}
|
|
109
113
|
catch (err) {
|
|
110
114
|
console.log('download error', err)
|
|
115
|
+
// try {
|
|
116
|
+
// streamRead.destroy() //若fs.createReadStream早於fs.statSync執行, 但fs.statSync發生錯誤時, stream得要destroy
|
|
117
|
+
// }
|
|
118
|
+
// catch (err) {}
|
|
111
119
|
pm.reject('download error')
|
|
112
120
|
}
|
|
113
121
|
|
|
@@ -123,7 +131,7 @@ setTimeout(() => {
|
|
|
123
131
|
console.log('ms', ms)
|
|
124
132
|
// console.log('ms', JSON.stringify(ms))
|
|
125
133
|
wo.stop()
|
|
126
|
-
},
|
|
134
|
+
}, 3000)
|
|
127
135
|
|
|
128
136
|
|
|
129
137
|
//node --experimental-modules srv.mjs
|