Node配合WebSocket做多文件下载以及进度回传
起因
为什么做这个东西,是突然间听一后端同事说起Annie这个东西,发现这个东西下载视频挺方便的,会自动爬取网页中的视频,然后整理成列表。发现用命令执行之后是下面的样子:
心里琢磨了下,整一个界面玩一下吧。然后就做成下面这个样子了。
列表
下载列表
本文地址仓库:https://github.com/Rynxiao/yh-tools,如果喜欢,欢迎star.
涉及技术
- Express后端服务
- Webpack模块化编译工具
- Nginx主要做文件gzip压缩(发现Express添加gzip有点问题,才弃坑nginx)
- Ant-design前端UI库
- React+ReactRouter
- WebSocket进度回传服务
其中还有点小插曲,最开始是使用docker起了一个nginx服务,但是发现内部转发一直有问题,同时获取宿主主机IP也出现了点问题,然后折磨了好久放弃了。(docker研究不深,敬请谅解^_^)
下载部分细节
首先浏览器会连接WebSocket服务器,同时在WebSocket服务器上存在一个所有客户端的Map,浏览器端生成一个uuid作为浏览器客户端id,然后将这个链接作为值存进Map中。
客户端:
//list.jsx awaitWebSocketClient.connect((event)=>{ constdata=JSON.parse(event.data); if(data.event==='close'){ this.updateCloseStatusOfProgressBar(list,data); }else{ this.generateProgressBarList(list,data); } }); //src/utils/websocket.client.js asyncconnect(onmessage,onerror){ constsocket=this.getSocket(); returnnewPromise((resolve)=>{ //... }); } getSocket(){ if(!this.socket){ this.socket=newWebSocket( `ws://localhost:${CONFIG.PORT}?from=client&id=${clientId}`, 'echo-protocol', ); } returnthis.socket; }
服务端:
//public/javascript/websocket/websocket.server.js connectToServer(httpServer){ initWsServer(httpServer); wsServer.on('request',(request)=>{ //uri:ws://localhost:8888?from=client&id=xxxx-xxxx-xxxx-xxxx logger.info('[wsserver]request'); constconnection=request.accept('echo-protocol',request.origin); constqueryStrings=querystring.parse(request.resource.replace(/(^\/|\?)/g,'')); //每有连接连到websocket服务器,就将当前连接保存到map中 setConnectionToMap(connection,queryStrings); connection.on('message',onMessage); connection.on('close',(reasonCode,description)=>{ logger.info(`[wsserver]connectionclosed${reasonCode}${description}`); }); }); wsServer.on('close',(connection,reason,description)=>{ logger.info('[wsserver]someconnectiondisconnect.'); logger.info(reason,description); }); }
然后在浏览器端点击下载的时候,会传递两个主要的字段resourceId(在代码中由parentId和childId组成)和客户端生成的bClientId。这两个id有什么用呢?
每次点击下载,都会在Web服务器中生成一个WebSocket的客户端,那么这个resouceId就是作为在服务器中生成的WebSocket服务器的key值。
bClientId主要是为了区分浏览器的客户端,因为考虑到同时可能会有多个浏览器接入,这样在WebSocket服务器中产生消息的时候,就可以用这个id来区分应该发送给哪个浏览器客户端
客户端:
//list.jsx http.get( 'download', { code, filename, parent_id:row.id, child_id:childId, download_url:url, client_id:clientId, }, ); //routes/api.js router.get('/download',async(req,res)=>{ const{code,filename}=req.query; consturl=req.query.download_url; constclientId=req.query.client_id; constparentId=req.query.parent_id; constchildId=req.query.child_id; constconnectionId=`${parentId}-${childId}`; constparams={ code, url, filename, parent_id:parentId, child_id:childId, client_id:clientId, }; constflag=awaitAnnieDownloader.download(connectionId,params); if(flag){ awaitres.json({code:200}); }else{ awaitres.json({code:500,msg:'downloaderror'}); } }); //public/javascript/annie.js asyncdownload(connectionId,params){ //... //当annie下载时,会进行数据监听,这里会用到节流,防止进度回传太快,websocket服务器无法反应 downloadProcess.stdout.on('data',throttle((chunk)=>{ try{ if(!chunk){ isDownloading=false; } //这里主要做的是解析数据,然后发送进度和速度等信息给websocket服务器 getDownloadInfo(chunk,ws,params); }catch(e){ downloadSuccess=false; WsClient.close(params.client_id,connectionId,'downloaderror'); this.stop(connectionId); logger.error(`[serveranniedownload]error:${e}`); } },500,300)); }
服务端收到进度以及速度的消息后,回传给客户端,如果进度达到了100%,那么就删除掉存在server中的服务器中起的websocket的客户端,并且发送一个客户端被关闭的通知,通知浏览器已经下载完成。
//public/javascript/websocket/websocket.server.js functiononMessage(message){ constdata=JSON.parse(message.utf8Data); constid=data.client_id; if(data.event==='close'){ logger.info('[wsserver]closeevent'); closeConnection(id,data); }else{ getConnectionAndSendProgressToClient(data,id); } } functiongetConnectionAndSendProgressToClient(data,clientId){ constbrowserClient=clientsMap.get(clientId); //logger.info(`[wsserver]send${JSON.stringify(data)}toclient${clientId}`); if(browserClient){ constserverClientId=`${data.parent_id}-${data.child_id}`; constserverClient=clientsMap.get(serverClientId); //发送从web服务器中传过来的进度、速度给浏览器 browserClient.send(JSON.stringify(data)); //如果进度已经达到了100% if(data.progress>=100){ logger.info(`[wsserver]filehasbeendownloadsuccessfully,progressis${data.progress}`); logger.info(`[wsserver]serverclient${serverClientId}readytodisconnect`); //从clientsMap将当前的这个由web服务器创建的websocket客户端移除 //然后关闭当前连接 //同时发送下载完成的消息给浏览器 clientsMap.delete(serverClientId); serverClient.send(JSON.stringify({connectionId:serverClientId,event:'complete'})); serverClient.close('downloadcompleted'); } } }
整体来说就这么多,有一点需要指出,annie在解析的时候有时候可能消息处理不是很稳定,导致我数据解析的时候出现了一些问题,但是我用mock的数据以及mock的进度条回传是不会出现问题的。
声明:本文内容来源于网络,版权归原作者所有,内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:czq8825#qq.com(发邮件时,请将#更换为@)进行举报,并提供相关证据,一经查实,本站将立刻删除涉嫌侵权内容。