import path from 'path'
import fs from 'fs'
import stream from 'stream'
import Hapi from '@hapi/hapi'
import Inert from '@hapi/inert' //提供靜態檔案
import get from 'lodash-es/get.js'
import isNumber from 'lodash-es/isNumber.js'
import genPm from 'wsemi/src/genPm.mjs'
import evem from 'wsemi/src/evem.mjs'
import iseobj from 'wsemi/src/iseobj.mjs'
import isestr from 'wsemi/src/isestr.mjs'
import isp0int from 'wsemi/src/isp0int.mjs'
import ispint from 'wsemi/src/ispint.mjs'
import isearr from 'wsemi/src/isearr.mjs'
import isbol from 'wsemi/src/isbol.mjs'
import isfun from 'wsemi/src/isfun.mjs'
import ispm from 'wsemi/src/ispm.mjs'
import cint from 'wsemi/src/cint.mjs'
import cstr from 'wsemi/src/cstr.mjs'
import str2b64 from 'wsemi/src/str2b64.mjs'
import haskey from 'wsemi/src/haskey.mjs'
import obj2u8arr from 'wsemi/src/obj2u8arr.mjs'
import u8arr2obj from 'wsemi/src/u8arr2obj.mjs'
import fsIsFolder from 'wsemi/src/fsIsFolder.mjs'
import fsCreateFolder from 'wsemi/src/fsCreateFolder.mjs'
import mmg from './managerMergeSlices.mjs'
// import checkTotalHash from './checkTotalHash.mjs'
import checkTotalHash from './checkTotalHash.wk.umd.js'
// import checkSlicesHash from './checkSlicesHash.mjs'
import checkSlicesHash from './checkSlicesHash.wk.umd.js'
//回傳前端stream時(POST或GET皆可), 前端會須等stream傳完才能判斷是否為大檔或錯誤訊息, 此會導致若回傳超大檔, 會需要對超大檔進行解析會有記憶體上限問題, 故需要通過header提供基本成功或失敗訊息, 讓前端能進行解析判斷
//回傳前端(nodejs)時, 針對超大檔, 只能用POST並用stream回傳
//回傳前端(browser)時, 針對超大檔, 可用POST並用stream回傳但還要處理進度條, 若要交由瀏覽器下載器處理, 只能用GET並用stream回傳, 且前端只能用window.location.href或a.href+a.click()下載
/**
* 建立Hapi伺服器
*
* @class
* @param {Object} [opt={}] 輸入設定物件,預設{}
* @param {Integer} [opt.port=8080] 輸入Hapi伺服器所在port正整數,預設8080
* @param {Boolean} [opt.useInert=true] 輸入是否提供瀏覽pathStaticFiles資料夾內檔案之布林值,預設true
* @param {String} [opt.pathStaticFiles='dist'] 輸入當useInert=true時提供瀏覽資料夾字串,預設'dist'
* @param {String} [opt.apiName='api'] 輸入API名稱字串,預設'api'
* @param {String} [opt.pathUploadTemp='./uploadTemp'] 輸入暫時存放切片上傳檔案資料夾字串,預設'./uploadTemp'
* @param {String} [opt.tokenType='Bearer'] 輸入token類型字串,預設'Bearer'
* @param {Integer} [opt.sizeSlice=1024*1024] 輸入切片上傳檔案之切片檔案大小整數,單位為Byte,預設為1024*1024
* @param {Function} [opt.verifyConn=()=>{return true}] 輸入呼叫API時檢測函數,預設()=>{return true}
* @param {Array} [opt.corsOrigins=['*']] 輸入允許跨域網域陣列,若給予['*']代表允許全部,預設['*']
* @param {Integer} [opt.delayForSlice=100] 輸入切片上傳檔案API用延遲響應時間,單位ms,預設100
* @param {Boolean} [opt.serverHapi=null] 輸入外部提供Hapi伺服器物件,預設null
* @returns {Object} 回傳事件物件,可監聽事件execute、upload、handler
* @example
*
* import fs from 'fs'
* import _ from 'lodash-es'
* import w from 'wsemi'
* import WConverhpServer from './src/WConverhpServer.mjs'
*
* let ms = []
*
* let opt = {
* port: 8080,
* apiName: 'api',
* pathStaticFiles: '.', //要存取專案資料夾下web.html, 故不能給dist
* verifyConn: async ({ apiType, authorization, headers, query }) => {
* console.log('verifyConn', `apiType[${apiType}]`, `authorization[${authorization}]`)
* let token = w.strdelleft(authorization, 7) //刪除Bearer
* if (!w.isestr(token)) {
* return false
* }
* // await w.delay(3000)
* return true
* },
* }
*
* //new
* let wo = new WConverhpServer(opt)
*
* wo.on('execute', (func, input, pm) => {
* // console.log(`Server[port:${opt.port}]: execute`, func, input)
* console.log(`Server[port:${opt.port}]: execute`, func)
*
* try {
*
* if (func === 'add') {
*
* if (_.get(input, 'p.d.u8a', null)) {
* console.log('input.p.d.u8a', input.p.d.u8a)
* ms.push({ 'input.p.d.u8a': input.p.d.u8a })
* }
*
* let r = {
* _add: input.p.a + input.p.b,
* _data: [11, 22.22, 'abc', { x: '21', y: 65.43, z: 'test中文' }],
* _bin: {
* name: 'zdata.b2',
* u8a: new Uint8Array([52, 66, 97, 115]),
* },
* }
*
* pm.resolve(r)
*
* }
* else {
* console.log('invalid func')
* pm.reject('invalid func')
* }
*
* }
* catch (err) {
* console.log('execute error', err)
* pm.reject('execute error')
* }
*
* })
* wo.on('upload', (input, pm) => {
* console.log(`Server[port:${opt.port}]: upload`, input)
*
* try {
* ms.push({ 'receive and return': input })
* let output = input
* pm.resolve(output)
* }
* catch (err) {
* console.log('upload error', err)
* pm.reject('upload error')
* }
*
* })
* wo.on('download', (input, pm) => {
* console.log(`Server[port:${opt.port}]: download`, input)
*
* let streamRead = null
* try {
* ms.push({ 'download': input })
*
* //fp
* let fp = `./test/1mb.7z`
*
* //check, 檔案存在才往下
*
* //fileSize
* let stats = fs.statSync(fp)
* let fileSize = stats.size
*
* //streamRead
* streamRead = fs.createReadStream(fp)
*
* //filename
* let filename = `1mb中文.7z` //測試支援中文
*
* //fileType
* let fileType = 'application/x-7z-compressed'
*
* //output
* let output = {
* streamRead,
* filename,
* fileSize,
* fileType,
* }
*
* pm.resolve(output)
* }
* catch (err) {
* console.log('download error', err)
* // try {
* // streamRead.destroy() //若fs.createReadStream早於fs.statSync執行, 但fs.statSync發生錯誤時, stream得要destroy
* // }
* // catch (err) {}
* pm.reject('download error')
* }
*
* })
* wo.on('error', (err) => {
* console.log(`Server[port:${opt.port}]: error`, err)
* })
* wo.on('handler', (data) => {
* // console.log(`Server[port:${opt.port}]: handler`, data)
* })
*
* setTimeout(() => {
* console.log('ms', ms)
* // console.log('ms', JSON.stringify(ms))
* wo.stop()
* }, 3000)
*
*/
function WConverhpServer(opt = {}) {
//port
let port = get(opt, 'port')
if (!ispint(port)) {
port = 8080
}
//useInert
let useInert = get(opt, 'useInert')
if (!isbol(useInert)) {
useInert = true
}
//pathStaticFiles
let pathStaticFiles = get(opt, 'pathStaticFiles')
if (!isestr(pathStaticFiles)) {
pathStaticFiles = 'dist'
}
//apiName
let apiName = get(opt, 'apiName')
if (!isestr(apiName)) {
apiName = 'api'
}
//pathUploadTemp
let pathUploadTemp = get(opt, 'pathUploadTemp')
if (!isestr(pathUploadTemp)) {
pathUploadTemp = './uploadTemp'
}
if (!fsIsFolder(pathUploadTemp)) {
fsCreateFolder(pathUploadTemp)
}
//tokenType
let tokenType = get(opt, 'tokenType')
if (!isestr(tokenType)) {
tokenType = 'Bearer'
}
//sizeSlice
let sizeSlice = get(opt, 'sizeSlice')
if (!ispint(sizeSlice)) {
sizeSlice = 1024 * 1024 //1m
}
//verifyConn
let verifyConn = get(opt, 'verifyConn')
if (!isfun(verifyConn)) {
verifyConn = () => {
return true
}
}
//corsOrigins
let corsOrigins = get(opt, 'corsOrigins', [])
if (!isearr(corsOrigins)) {
corsOrigins = ['*']
}
//delayForSlice
let delayForSlice = get(opt, 'delayForSlice', '')
if (!isp0int(delayForSlice)) {
delayForSlice = 100
}
delayForSlice = cint(delayForSlice)
//server
let server = null
if (get(opt, 'serverHapi')) {
//use serverHapi
server = opt.serverHapi
}
else {
//create server
server = Hapi.server({
//host: 'localhost',
port,
routes: {
timeout: {
server: false, //關閉伺服器超時
socket: false, //關閉socket超時
},
cors: {
origin: corsOrigins, //Access-Control-Allow-Origin
credentials: false, //Access-Control-Allow-Credentials
},
},
})
}
//ee
let ee = evem() //new events.EventEmitter()
//eeEmit
let eeEmit = (name, ...args) => {
setTimeout(() => {
ee.emit(name, ...args)
}, 1)
}
//procDeal
async function procDeal(data) {
//pm, pmm
let pm = genPm()
let pmm = genPm()
//重新處理回傳結果
pmm
.then((output) => {
//add output
data['output'] = output
//delete input, 因input可能很大故回傳數據不包含原input
delete data['input']
pm.resolve(data)
})
.catch((err) => {
pm.reject(err)
})
if (true) {
//func
let func = get(data, 'func', '')
//input
let input = get(data, 'input', null)
//execute 執行
eeEmit('execute', func, input, pmm)
}
return pm
}
//procUpload
async function procUpload(input) {
// console.log('procUpload', input)
//pm, pmm
let pm = genPm()
let pmm = genPm()
//重新處理回傳結果
pmm
.then((output) => {
//resolve
pm.resolve(output)
})
.catch((err) => {
pm.reject(err)
})
if (true) {
//upload, 上傳檔案
eeEmit('upload', input, pmm)
}
return pm
}
//procDownload
async function procDownload(input) {
// console.log('procDownload', input)
//pm, pmm
let pm = genPm()
let pmm = genPm()
//重新處理回傳結果
pmm
.then((output) => {
//resolve
pm.resolve(output)
})
.catch((err) => {
pm.reject(err)
})
if (true) {
//download, 下載檔案
eeEmit('download', input, pmm)
}
return pm
}
//responseU8aStream
function responseU8aStream(res, u8a, opt = {}) {
//stream
let smr = new stream.Readable()
smr._read = () => {}
smr.push(u8a)
smr.push(null)
//returnType
let returnType = get(opt, 'returnType', '')
//returnMsg
let returnMsg = get(opt, 'returnMsg', '')
//r
let r = res.response(smr)
.header('Cache-Control', 'no-cache, no-store, must-revalidate')
.header('Content-Type', 'application/octet-stream')
.header('Content-Length', smr.readableLength)
if (isestr(returnType)) {
r.header('Return-Type', returnType)
}
if (isestr(returnMsg)) {
r.header('Return-Msg', returnMsg)
}
return r
}
//responseU8aStreamWithError
function responseU8aStreamWithError(res, msg) {
//check
if (!isestr(msg)) {
console.log('msg', msg)
console.log(`msg is not an effective string, set msg=''`)
msg = ''
}
//u8aOut
let u8aOut = obj2u8arr({
error: msg,
})
// console.log('download u8aOut', u8aOut)
//str2b64
// msg = str2b64(msg) //預期程式內調用皆為英文, 不須轉base64來支援中文
return responseU8aStream(res, u8aOut, { returnType: 'error', returnMsg: msg })
}
//apiMain
let apiMain = {
path: `/${apiName}`,
method: 'POST',
options: {
payload: {
maxBytes: 1024 * 1024 * 1024 * 1024, //預設為1mb, 調整至1tb, 也就是給予3次方
maxParts: 1000 * 1000 * 1000, //預設為1000, 給予3次方
timeout: false, //避免請求未完成時中斷
output: 'stream', //代表前端用stream傳至伺服器(Content-Type為application/octet-stream)
parse: false,
},
timeout: {
server: false, //關閉伺服器超時
socket: false, //關閉socket超時
},
},
handler: async function (req, res) {
// console.log(req, res)
// console.log('payload', req.payload)
//headers
let headers = get(req, 'headers')
headers = iseobj(headers) ? headers : ''
// console.log('headers', headers)
//query
let query = get(req, 'query')
query = iseobj(query) ? query : ''
// console.log('query', query)
//authorization
let authorization = get(headers, 'authorization', '')
authorization = isestr(authorization) ? authorization : ''
//check
if (true) {
//verifyConn
let m = verifyConn({ apiType: 'main', authorization, headers, query })
if (ispm(m)) {
m = await m
}
//check
if (m !== true) {
return responseU8aStreamWithError(res, 'permission denied')
}
}
//eeEmit
eeEmit('handler', {
api: 'apiMain',
headers,
query,
})
//receive
let receive = () => {
//pm
let pm = genPm()
//chunks
let chunks = []
let smw = new stream.Writable({
write(chunk, encoding, cb) {
// console.log('smw receive payload', chunk)
//push
chunks.push(chunk)
// console.log('chunk.length', chunk.length)
//cb
cb()
}
})
//finish
smw.on('finish', () => {
// console.log(`smw finish`)
})
//pipe
req.payload.pipe(smw)
//end
req.payload.on('end', () => {
// console.log(`req.payload end`)
//bb
let bb = Buffer.concat(chunks)
// console.log('bb', bb, bb.length)
//clear, 釋放記憶體
chunks = []
//resolve
pm.resolve(bb)
})
//close
req.payload.on('close', () => {
// console.log(`req.payload close`)
})
//error
req.payload.on('error', (err) => {
// console.log(`req.payload error`, err)
eeEmit('error', `receive payload err`)
pm.reject(err.message)
})
return pm
}
//receive
let bbInp = await receive()
// console.log('bbInp', bbInp)
//u8aInp
let u8aInp = new Uint8Array(bbInp)
// console.log('u8aInp', u8aInp)
//u8arr2obj
let inp = u8arr2obj(u8aInp)
// console.log('inp', inp)
//procDeal
let out = {}
let returnType = ''
let returnMsg = ''
await procDeal(inp)
.then((res) => {
out.success = res
returnType = 'success'
returnMsg = 'need to parse'
})
.catch((err) => {
out.error = err
returnType = 'error'
returnMsg = 'need to parse'
})
// console.log('out', out)
//u8aOut
let u8aOut = obj2u8arr(out)
// console.log('u8aOut', u8aOut)
return responseU8aStream(res, u8aOut, { returnType, returnMsg })
},
}
//apiUploadCheck
let apiUploadCheck = {
path: `/${apiName}ulctr`,
method: 'POST',
options: {
payload: {
maxBytes: 1024 * 1024 * 1024 * 1024, //預設為1mb, 調整至1tb, 也就是給予3次方
maxParts: 1000 * 1000 * 1000, //預設為1000, 給予3次方
timeout: false, //避免請求未完成時中斷
// output: 'stream',
parse: true, //前端送obj過來須自動解析
},
timeout: {
server: false, //關閉伺服器超時
socket: false, //關閉socket超時
},
},
handler: async function (req, res) {
// console.log(req, res)
// console.log('payload', req.payload)
//headers
let headers = get(req, 'headers')
headers = iseobj(headers) ? headers : ''
// console.log('headers', headers)
//query
let query = get(req, 'query')
query = iseobj(query) ? query : ''
// console.log('query', query)
//authorization
let authorization = get(headers, 'authorization', '')
authorization = isestr(authorization) ? authorization : ''
//check
if (true) {
//verifyConn
let m = verifyConn({ apiType: 'upload-controller', authorization, headers, query })
if (ispm(m)) {
m = await m
}
//check
if (m !== true) {
return responseU8aStreamWithError(res, 'permission denied')
}
}
//eeEmit
eeEmit('handler', {
api: 'apiUploadCheck',
headers,
query,
})
//mode, 從payload接收
let mode = get(req, 'payload.mode', '')
//check
if (mode !== 'check-total-hash' && mode !== 'check-slices-hash' && mode !== 'merge-slices-push' && mode !== 'merge-slices-get') {
// console.log('invalid mode in payload')
return responseU8aStreamWithError(res, `invalid mode[${mode}] in payload`)
}
//fileHash, 從payload接收
let fileHash = get(req, 'payload.fileHash', '')
// console.log(mode, 'fileHash', fileHash)
//check
if (!isestr(fileHash)) {
// console.log('invalid fileHash in payload')
return responseU8aStreamWithError(res, 'invalid fileHash in payload')
}
//procCore
let procCore = async() => {
let out = null
if (mode === 'check-total-hash') {
//filename, 從payload接收
let filename = get(req, 'payload.filename', '')
// console.log(mode, 'filename', filename)
//fileSize, 從payload接收
let fileSize = get(req, 'payload.fileSize', '')
// console.log(mode, 'fileSize', fileSize)
//checkTotalHash
out = await checkTotalHash(fileSize, sizeSlice, fileHash, pathUploadTemp)
// console.log(mode, 'out', out)
//check, 因合併大檔後可能非預期中斷而重傳, 每次偵測有合併完成大檔, 就得調用procUpload讓伺服器攔截函數處理
if (out.bAllHash) {
//ri
let ri = {
filename,
path: out.path,
}
//procUpload
// console.log('procUpload start')
let ro = await procUpload(ri)
// console.log('procUpload done', ro)
//out merge, 直接添加ro就回傳, 此處不使用msg儲存
out = {
...out,
...ro,
}
}
}
else if (mode === 'check-slices-hash') {
//fileSliceHashs, 從payload接收
let fileSliceHashs = get(req, 'payload.fileSliceHashs', [])
// console.log(mode, 'fileSliceHashs', fileSliceHashs)
//checkSlicesHash
out = await checkSlicesHash(fileSliceHashs, fileHash, pathUploadTemp)
// console.log(mode, 'out', out)
}
else if (mode === 'merge-slices-push') {
//chunkTotal, 從payload接收
let chunkTotal = get(req, 'payload.chunkTotal', '')
// console.log(mode, 'chunkTotal', chunkTotal)
//mmg.push
let queueId = mmg.push(fileHash, chunkTotal, pathUploadTemp)
//out
out = {
queueId,
}
}
else if (mode === 'merge-slices-get') {
//filename, 從payload接收
let filename = get(req, 'payload.filename', '')
// console.log(mode, 'filename', filename)
//queueId, 從payload接收
let queueId = get(req, 'payload.queueId', '')
// console.log(mode, 'queueId', queueId)
//mmg.get
let r = mmg.get(queueId, pathUploadTemp)
//out
out = {
state: r.state,
msg: r.msg, //state為'error'時會於msg提供錯誤訊息
queueId,
filename,
path: r.path,
}
//check
if (r.state === 'success') {
//ri
let ri = {
filename,
path: r.path,
}
//procUpload, 偵測有合併完成大檔, 得調用procUpload讓伺服器攔截函數處理
// console.log('procUpload start')
let ro = await procUpload(ri)
// console.log('procUpload done', ro)
//out merge, ro須附加至msg, 才供前端偵測state為'success'時提取msg顯示
out = {
...out, //state為'success'時msg為空字串, 直接被ro複寫至msg即可
msg: ro,
}
}
}
// console.log('out', out)
return out
}
//procCore
let out = {}
let returnType = ''
let returnMsg = ''
await procCore()
.then((res) => {
out.success = res
returnType = 'success'
returnMsg = 'need to parse'
})
.catch((err) => {
out.error = err
returnType = 'error'
returnMsg = 'need to parse'
})
// console.log('out', out)
//u8aOut
let u8aOut = obj2u8arr(out)
// console.log('u8aOut', u8aOut)
return responseU8aStream(res, u8aOut, { returnType, returnMsg })
},
}
//apiUploadSlice
let apiUploadSlice = {
path: `/${apiName}slc`,
method: 'POST',
options: {
payload: {
maxBytes: 1024 * 1024 * 1024 * 1024, //預設為1mb, 調整至1tb, 也就是給予3次方
maxParts: 1000 * 1000 * 1000, //預設為1000, 給予3次方
timeout: false, //避免請求未完成時中斷
output: 'stream', //代表前端用stream傳至伺服器(Content-Type為application/octet-stream)
parse: false,
},
timeout: {
server: false, //關閉伺服器超時
socket: false, //關閉socket超時
},
},
handler: async function (req, res) {
// console.log(req, res)
// console.log('payload', req.payload)
//headers
let headers = get(req, 'headers')
headers = iseobj(headers) ? headers : ''
// console.log('headers', headers)
//query
let query = get(req, 'query')
query = iseobj(query) ? query : ''
// console.log('query', query)
//authorization
let authorization = get(headers, 'authorization', '')
authorization = isestr(authorization) ? authorization : ''
//check
if (true) {
//verifyConn
let m = verifyConn({ apiType: 'upload-slice', authorization, headers, query })
if (ispm(m)) {
m = await m
}
//check
if (m !== true) {
return responseU8aStreamWithError(res, 'permission denied')
}
}
//eeEmit
eeEmit('handler', {
api: 'apiUploadSlice',
headers,
query,
})
//chunkIndex, chunkTotal, packageId, 從headers接收
let chunkIndex = get(headers, 'chunk-index', '')
let chunkTotal = get(headers, 'chunk-total', '')
let packageId = get(headers, 'package-id', '')
//check
if (!isp0int(chunkIndex)) {
// console.log('invalid chunkIndex in headers')
return responseU8aStreamWithError(res, 'invalid chunkIndex in headers')
}
chunkIndex = cint(chunkIndex)
if (!isp0int(chunkTotal)) {
// console.log('invalid chunkTotal in headers')
return responseU8aStreamWithError(res, 'invalid chunkTotal in headers')
}
chunkTotal = cint(chunkTotal)
if (!isestr(packageId)) {
// console.log('invalid packageId in headers')
return responseU8aStreamWithError(res, 'invalid packageId in headers')
}
//pathFileChunk
let pathFileChunk = path.join(pathUploadTemp, `${packageId}_${chunkIndex}`)
// console.log('pathFileChunk', pathFileChunk)
//streamWrite
let streamWrite = fs.createWriteStream(pathFileChunk)
//receive
let receive = () => {
//pm
let pm = genPm()
//pipe
req.payload.pipe(streamWrite)
// console.log(`receiving chunk[${chunkIndex + 1}/${chunkTotal}]...`)
//end
req.payload.on('end', () => {
// console.log(`receive chunk[${chunkIndex + 1}/${chunkTotal}] done`)
//setTimeout, 切片上傳添加延遲處理, 避免佔滿伺服器CPU與流量
setTimeout(() => {
pm.resolve(`chunk[${chunkIndex}/${chunkTotal}] of packageId[${packageId}] done`)
}, delayForSlice)
})
//error
req.payload.on('error', (err) => {
// console.log(`receive chunk[${chunkIndex + 1}/${chunkTotal}] of packageId[${packageId}] err`, err)
eeEmit('error', `receive chunk[${chunkIndex}/${chunkTotal}] of packageId[${packageId}] err`)
pm.reject(`apiUploadSlice receive error: ${err.message}`)
})
return pm
}
//receive
let out = {}
let returnType = ''
let returnMsg = ''
await receive()
.then((res) => {
out.success = res
returnType = 'success'
returnMsg = 'need to parse'
})
.catch((err) => {
out.error = err
returnType = 'error'
returnMsg = 'need to parse'
eeEmit('error', err)
})
// console.log('out', out)
//u8aOut
let u8aOut = obj2u8arr(out)
// console.log('u8aOut', u8aOut)
return responseU8aStream(res, u8aOut, { returnType, returnMsg })
},
}
//apiDownloadGetFilename
let apiDownloadGetFilename = {
path: `/${apiName}dwgfn`,
method: 'POST',
options: {
payload: {
maxBytes: 1024 * 1024 * 1024 * 1024, //預設為1mb, 調整至1tb, 也就是給予3次方
maxParts: 1000 * 1000 * 1000, //預設為1000, 給予3次方
timeout: false, //避免請求未完成時中斷
// output: 'stream',
parse: true, //前端送obj過來須自動解析
},
timeout: {
server: false, //關閉伺服器超時
socket: false, //關閉socket超時
},
},
handler: async function (req, res) {
// console.log(req, res)
// console.log('payload', req.payload)
//headers
let headers = get(req, 'headers')
headers = iseobj(headers) ? headers : ''
// console.log('headers', headers)
//query
let query = get(req, 'query')
query = iseobj(query) ? query : ''
// console.log('query', query)
//authorization
let authorization = get(headers, 'authorization', '')
authorization = isestr(authorization) ? authorization : ''
//check
if (true) {
//verifyConn
let m = verifyConn({ apiType: 'download-get-filename', authorization, headers, query })
if (ispm(m)) {
m = await m
}
//check
if (m !== true) {
return responseU8aStreamWithError(res, 'permission denied')
}
}
//eeEmit
eeEmit('handler', {
api: 'apiDownloadGetFilename',
headers,
query,
})
//fileId, 從payload接收
let fileId = get(req, 'payload.fileId', '')
// console.log('fileId', fileId)
//check
if (!isestr(fileId)) {
// console.log('invalid fileId in payload')
return responseU8aStreamWithError(res, 'invalid fileId in payload')
}
//inp
let inp = { fileId }
//procDownload
let out = {}
let returnType = ''
let returnMsg = ''
await procDownload(inp)
.then((res) => {
out.success = res
returnType = 'success'
returnMsg = 'need to parse'
})
.catch((err) => {
out.error = err
returnType = 'error'
returnMsg = 'need to parse'
})
// console.log('out', out)
//r
let r = get(out, 'success')
//streamRead
let streamRead = get(r, 'streamRead')
//destroy, 不提供stream故須預先destroy
try {
streamRead.destroy()
}
catch (err) {}
//filename
let filename = get(r, 'filename')
if (!isestr(filename)) {
//已於前面destroy
return responseU8aStreamWithError(res, 'invalid filename')
}
//重新提供out
out = {
success: {
filename,
},
}
//u8aOut
let u8aOut = obj2u8arr(out)
// console.log('u8aOut', u8aOut)
return responseU8aStream(res, u8aOut, { returnType, returnMsg })
},
}
//apiDownloadGetFile
let apiDownloadGetFile = {
path: `/${apiName}dwgf`,
method: 'GET',
options: {
timeout: {
server: false, //關閉伺服器超時
socket: false, //關閉socket超時
},
},
handler: async function (req, res) {
// console.log(req, res)
// console.log('payload', req.payload)
//headers
let headers = get(req, 'headers')
headers = iseobj(headers) ? headers : ''
// console.log('headers', headers)
//query
let query = get(req, 'query')
query = iseobj(query) ? query : ''
// console.log('query', query)
//token
let token = get(query, 'token', '')
token = isestr(token) ? token : ''
// console.log('token', token)
//authorization
let authorization = ''
if (isestr(token)) {
authorization = `${tokenType} ${token}`
}
//check
if (true) {
//verifyConn
let m = verifyConn({ apiType: 'download-get-file', authorization, headers, query })
if (ispm(m)) {
m = await m
}
//check
if (m !== true) {
return responseU8aStreamWithError(res, 'permission denied')
}
}
//eeEmit
eeEmit('handler', {
api: 'apiDownloadGetFilename',
headers,
query,
})
//fileId
let fileId = get(query, 'fileId', '')
fileId = isestr(fileId) ? fileId : ''
// console.log('fileId', fileId)
//check
if (!isestr(fileId)) {
// console.log('invalid fileId in query')
return responseU8aStreamWithError(res, 'invalid fileId in query')
}
//inp
let inp = { fileId }
//procDownload
let out = {}
await procDownload(inp)
.then((res) => {
out.success = res
})
.catch((err) => {
out.error = err
})
// console.log('out', out)
//return
if (haskey(out, 'error')) {
// console.log('out.error', out.error)
return responseU8aStreamWithError(res, `can not get file from fileId`)
}
//r
let r = get(out, 'success')
//streamRead
let streamRead = get(r, 'streamRead')
//fileSize
let fileSize = get(r, 'fileSize')
if (!isNumber(fileSize)) {
try {
streamRead.destroy() //提供stream前發生錯誤, 得強制destroy
}
catch (err) {}
return responseU8aStreamWithError(res, 'invalid fileSize')
}
// fileSize = cstr(fileSize)
//fileType
let fileType = get(r, 'fileType')
if (!isestr(fileType)) {
try {
streamRead.destroy() //提供stream前發生錯誤, 得強制destroy
}
catch (err) {}
return responseU8aStreamWithError(res, 'invalid fileType')
}
fileType = cstr(fileType)
return res.response(streamRead)
.type(fileType)
// .header('Content-Disposition', `attachment; filename="${filename}"`) //chrome會優先使用header內filename, 但header內支援中文度很差須用base64, 此導致chrome下載檔名只能為base64, 故一律改由前端(browser)先取得真實檔名後直接給予下載檔名, 避免用header提供真實檔名
.header('Content-Length', fileSize)
},
}
//apiDownload
let apiDownload = {
path: `/${apiName}dw`,
method: 'POST',
options: {
payload: {
maxBytes: 1024 * 1024 * 1024 * 1024, //預設為1mb, 調整至1tb, 也就是給予3次方
maxParts: 1000 * 1000 * 1000, //預設為1000, 給予3次方
timeout: false, //避免請求未完成時中斷
// output: 'stream',
parse: true, //前端送obj過來須自動解析
},
timeout: {
server: false, //關閉伺服器超時
socket: false, //關閉socket超時
},
},
handler: async function (req, res) {
// console.log(req, res)
// console.log('payload', req.payload)
//headers
let headers = get(req, 'headers')
headers = iseobj(headers) ? headers : ''
// console.log('headers', headers)
//query
let query = get(req, 'query')
query = iseobj(query) ? query : ''
// console.log('query', query)
//authorization
let authorization = get(headers, 'authorization', '')
authorization = isestr(authorization) ? authorization : ''
//check
if (true) {
//verifyConn
let m = verifyConn({ apiType: 'download', authorization, headers, query })
if (ispm(m)) {
m = await m
}
//check
if (m !== true) {
return responseU8aStreamWithError(res, 'permission denied')
}
}
//eeEmit
eeEmit('handler', {
api: 'apiDownload',
headers,
query,
})
//fileId, 從payload接收
let fileId = get(req, 'payload.fileId', '')
// console.log('fileId', fileId)
//check
if (!isestr(fileId)) {
// console.log('invalid fileId in payload')
return responseU8aStreamWithError(res, 'invalid fileId in payload')
}
//inp
let inp = { fileId }
//procDownload
let out = {}
await procDownload(inp)
.then((res) => {
out.success = res
})
.catch((err) => {
out.error = err
})
// console.log('out', out)
//return
if (haskey(out, 'error')) {
// console.log('out.error', out.error)
return responseU8aStreamWithError(res, `can not get file from fileId`)
}
//r
let r = get(out, 'success')
//streamRead
let streamRead = get(r, 'streamRead')
//filename
let filename = get(r, 'filename')
if (!isestr(filename)) {
try {
streamRead.destroy() //提供stream前發生錯誤, 得強制destroy
}
catch (err) {}
return responseU8aStreamWithError(res, 'invalid filename')
}
filename = str2b64(filename) //headers內對中文支援度不佳須用base64傳
//fileSize
let fileSize = get(r, 'fileSize')
if (!isNumber(fileSize)) {
try {
streamRead.destroy() //提供stream前發生錯誤, 得強制destroy
}
catch (err) {}
return responseU8aStreamWithError(res, 'invalid fileSize')
}
// fileSize = cstr(fileSize)
//fileType
let fileType = get(r, 'fileType')
if (!isestr(fileType)) {
try {
streamRead.destroy() //提供stream前發生錯誤, 得強制destroy
}
catch (err) {}
return responseU8aStreamWithError(res, 'invalid fileType')
}
fileType = cstr(fileType)
return res.response(streamRead)
.type(fileType)
.header('Content-Disposition', `attachment; filename="${filename}"`) //針對前端(nodejs)用POST下載, 可基於header內base64檔名解析出並直接給予檔名, 不用預先取得檔名
.header('Content-Length', fileSize)
},
}
//startServer
async function startServer() {
//register inert
if (useInert) {
await server.register(Inert)
}
//apiRoutes
let apiRoutes = []
if (useInert) {
let api = {
method: 'GET',
path: '/{file*}',
handler: {
directory: {
path: `${pathStaticFiles}/`
}
},
}
apiRoutes = [
...apiRoutes,
api,
]
}
if (true) {
apiRoutes = [
...apiRoutes,
apiMain,
apiUploadCheck,
apiUploadSlice,
// apiUploadSliceMerge,
apiDownloadGetFilename,
apiDownloadGetFile,
apiDownload,
]
}
//route
server.route(apiRoutes)
//start
await server.start()
console.log(`Server running at: ${server.info.uri}`)
}
//start
if (get(opt, 'serverHapi')) {
// server.route([apiMain, apiUploadCheck, apiUploadSlice, apiUploadSliceMerge, apiDownloadGetFilename, apiDownloadGetFile, apiDownload])
server.route([apiMain, apiUploadCheck, apiUploadSlice, apiDownloadGetFilename, apiDownloadGetFile, apiDownload])
}
else {
startServer()
}
//stop
let stop = () => {
server.stop()
}
//save
ee.stop = stop
return ee
}
export default WConverhpServer