建网站怎么上传源码分享文件 上传网站源代码用什么程序

大家好,感谢邀请,今天来为大家分享一下建网站怎么上传源码分享文件的问题,以及和上传网站源代码用什么程序的一些困惑,大家要是还不太明白的话,也没有关系,因为接下来将为大家分享,希望可以帮助到大家,解决大家的问题,下面就开始吧!

作者:蓝色的秋风

转发链接:https://mp.weixin.qq.com/s/cruL9JGZNZQFrMSrzJJWiQ

前言

平常在写业务的时候常常会用的到的是GET,POST请求去请求接口,GET相关的接口会比较容易基本不会出错,而对于POST中常用的表单提交,JSON提交也比较容易,但是对于文件上传呢?大家可能对这个步骤会比较害怕,因为可能大家对它并不是怎么熟悉,而浏览器Network对它也没有详细的进行记录,因此它成为了我们心中的一根刺,我们老是无法确定,关于文件上传到底是我写的有问题呢?还是后端有问题,当然,我们一般都比较谦虚,总是会在自己身上找原因,可是往往实事呢?可能就出在后端身上,可能是他接受写的有问题,导致你换了各种请求库去尝试,axios,request,fetch等等。那么我们如何避免这种情况呢?我们自身要对这一块够熟悉,才能不以猜的方式去写代码。如果你觉得我以上说的你有同感,那么你阅读完这篇文章你将收获自信,你将不会质疑自己,不会以猜的方式去写代码。

本文比较长可能需要花点时间去看,需要有耐心,我采用自顶向下的方式,所有示例会先展现出你熟悉的方式,再一层层往下,先从请求端是怎么发送文件的,再到接收端是怎么解析文件的。

前置知识

什么是multipart/form-data?

multipart/form-data最初由《RFC1867:Form-basedFileUploadinHTML》[1]文档提出。

Sincefile-uploadisafeaturethatwillbenefitmanyapplications,thisproposesanextensiontoHTMLtoallowinformationproviderstoexpressfileuploadrequestsuniformly,andaMIMEcompatiblerepresentationforfileuploadresponses.

由于文件上传功能将使许多应用程序受益,因此建议对HTML进行扩展,以允许信息提供者统一表达文件上传请求,并提供文件上传响应的MIME兼容表示。

总结就是原先的规范不满足了,我要扩充规范了。

文件上传为什么要用multipart/form-data?

Theencodingtypeapplication/x-www-form-urlencodedisinefficientforsendinglargequantitiesofbinarydataortextcontainingnon-ASCIIcharacters.Thus,anewmediatype,multipart/form-data,isproposedasawayofefficientlysendingthevaluesassociatedwithafilled-outformfromclienttoserver.

1867文档中也写了为什么要新增一个类型,而不使用旧有的application/x-www-form-urlencoded:因为此类型不适合用于传输大型二进制数据或者包含非ASCII字符的数据。平常我们使用这个类型都是把表单数据使用url编码后传送给后端,二进制文件当然没办法一起编码进去了。所以multipart/form-data就诞生了,专门用于有效的传输文件。

也许你有疑问?那可以用application/json吗?

其实我认为,无论你用什么都可以传,只不过会要综合考虑一些因素的话,multipart/form-data更好。例如我们知道了文件是以二进制的形式存在,application/json是以文本形式进行传输,那么某种意义上我们确实可以将文件转成例如文本形式的Base64形式。但是呢,你转成这样的形式,后端也需要按照你这样传输的形式,做特殊的解析。并且文本在传输过程中是相比二进制效率低的,那么对于我们动辄几十M几百M的文件来说是速度是更慢的。

以上为什么文件传输要用multipart/form-data我还可以举个例子,例如你在中国,你想要去美洲,我们的multipart/form-data相当于是选择飞机,而application/json相当于高铁,但是呢?中国和美洲之间没有高铁啊,你执意要坐高铁去,你可以花昂贵的代价(后端额外解析你的文本)坐高铁去美洲,但是你有更加廉价的方式坐飞机(使用multipart/form-data)去美洲(去传输文件)。你图啥?(如果你有钱有时间,抱歉,打扰了,老子给你道歉)

multipart/form-data规范是什么?

摘自《RFC1867:Form-basedFileUploadinHTML》[2]6.Example

Content-type:multipart/form-data,boundary=AaB03x\n\n–AaB03x\ncontent-disposition:form-data;name=&34;\nJoeBlow\n–AaB03x\ncontent-disposition:form-data;name=&34;;filename=&34;\nContent-Type:text/plain\n\n…contentsoffile1.txt…\n–AaB03x–

可以简单解释一些,首先是请求类型,然后是一个boundary(分割符),这个东西是干啥的呢?其实看名字就知道,分隔符,当时分割作用,因为可能有多文件多字段,每个字段文件之间,我们无法准确地去判断这个文件哪里到哪里为截止状态。因此需要有分隔符来进行划分。然后再接下来就是声明内容的描述是form-data类型,字段名字是啥,如果是文件的话,得知道文件名是啥,还有这个文件的类型是啥,这个也很好理解,我上传一个文件,我总得告诉后端,我传的是个啥,是图片?还是一个txt文本?这些信息肯定得告诉大家,别人才好去进行判断,后面我们也会讲到如果这些没有声明的时候,会发生什么?

好了讲完了这些前置知识,我们接下来要进入我们的主题了。面对File,formData,Blob,Base64,ArrayBuffer,到底怎么做?还有文件上传不仅仅是前端的事。服务端也可以文件上传(例如我们利用某云,把静态资源上传到OSS对象存储)。服务端和客户端也有各种类型,Buffer,Stream,Base64….头秃,怎么搞?不急,就是因为上传文件不单单是前端的事,所以我将以下上传文件的一方称为请求端,接受文件一方称为接收方。我会以请求端各种上传方式,接收端是怎么解析我们的文件以及我们最终的杀手锏调试工具-wireshark来进行讲解。以下是讲解的大纲,我们先从浏览器端上传文件,再到服务端上传文件,然后我们再来解析文件是如何被解析的。

请求端

浏览端

File

首先我们先写下最简单的一个表单提交方式。

<formaction=&34;method=&34;>\n\t<inputname=&34;type=&34;id=&34;>\n\t<inputtype=&34;value=&34;>\n</form>

我们选择文件后上传,发现后端返回了文件不存在。

不用着急,熟悉的同学可能立马知道是啥原因了。嘘,知道了也听我慢慢叨叨。

我们打开控制台,由于表单提交会进行网页跳转,因此我们勾选preservelog来进行日志追踪。

我们可以发现其实FormData中file字段显示的是文件名,并没有将真正的内容进行传输。再看请求头。

发现是请求头和预期不符,也印证了application/x-www-form-urlencoded无法进行文件上传。

我们加上请求头,再次请求。

<formaction=&34;enctype=&34;method=&34;>\n<inputname=&34;type=&34;id=&34;>\n<inputtype=&34;value=&34;>\n</form>

发现文件上传成功,简单的表单上传就是像以上一样简单。但是你得熟记文件上传的格式以及类型。

FormData

formData的方式我随便写了以下几种方式。

<inputtype=&34;id=&34;>\n<buttonid=&34;>上传</button>\n<scriptsrc=&34;></script>\n<script>\nsubmit.onclick=()=>{\nconstfile=document.getElementById(&39;).files[0];\nvarform=newFormData();\nform.append(&39;,file);\n\n\t//type1\naxios.post(&39;,form).then(res=>{\nconsole.log(res.data);\n})\n\t//type2\n\tfetch(&39;,{\nmethod:&39;,\nbody:form\n}).then(res=>res.json()).tehn(res=>{console.log(res)});\n\t//type3\n\tvarxhr=newXMLHttpRequest();\nxhr.open(&39;,&39;,true);\nxhr.onload=function(){\n\tconsole.log(xhr.responseText);\n};\nxhr.send(form);\n}\n</script>

以上几种方式都是可以的。但是呢,请求库这么多,我随便在npm上一搜就有几百个请求相关的库。

因此,掌握请求库的写法并不是我们的目标,目标只有一个还是掌握文件上传的请求头和请求内容。

Blob

Blob对象表示一个不可变、原始数据的类文件对象。Blob表示的不一定是JavaScript原生格式的数据。`File`[3]接口基于Blob,继承了blob的功能并将其扩展使其支持用户系统上的文件。

因此如果我们遇到Blob方式的文件上方式不用害怕,可以用以下两种方式:

1.直接使用blob上传

constjson={hello:&34;};\nconstblob=newBlob([JSON.stringify(json,null,2)],{type:&39;});\n\nconstform=newFormData();\nform.append(&39;,blob,&39;);\naxios.post(&39;,form);

2.使用File对象,再进行一次包装(File兼容性可能会差一些https://caniuse.com/34;world&39;application/json&39;1.json&39;file&39;http://localhost:7787/files&39;image/png&39;file&39;1.png&39;http://localhost:7787/files&39;xxx&39;iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAABlBMVEUAAP+AgIBMbL/VAAAACXBIWXMAAA7EAAAOxAGVKw4bAAAACklEQVQImWNgAAAAAgAB9HFkpgAAAABJRU5ErkJggg==&39;image/png&39;file&39;1.png&39;http://localhost:7787/files&%E6%95%B0%E6%8D%AE%E8%BE%93%E5%85%A5[6])

服务端

讲完了浏览器端,现在我们来讲服务器端,和浏览器不同的是,服务端上传有两个难点。

1.浏览器没有原生formData,也不会想浏览器一样帮我们转成二进制形式。

2.服务端没有可视化的Network调试器。

Buffer

Request

首先我们通过最简单的示例来进行演示,然后一步一步深入。相信文档可以查看https://github.com/request/request39;fs&39;path&39;request&39;../1.png&39;http://localhost:7787/files&34;file&34;1.png&39;../1.png&39;/dev/urandom&39;topsecret.jpg&39;image/jpeg&39;http://localhost:7787/files&39;1.png&L21

就是利用form-data,我们先来看看formData的方式。

constpath=require(&39;);\nconstFormData=require(&39;);\nconstfs=require(&39;);\nconsthttp=require(&39;);\nconstform=newFormData();\nform.append(&39;,fs.readFileSync(path.join(__dirname,&39;)),{\nfilename:&39;,\ncontentType:&39;,\n});\nconstrequest=http.request({\nmethod:&39;,\nhost:&39;,\nport:&39;,\npath:&39;,\nheaders:form.getHeaders()\n});\nform.pipe(request);\nrequest.on(&39;,function(res){\nconsole.log(res.statusCode);\n});

原生Node

看完formData,可能感觉这个封装还是太高层了,于是我打算对照规范手动来构造multipart/form-data请求方式来进行讲解。我们再来回顾一下规范。

Content-type:multipart/form-data,boundary=AaB03x\n\n–AaB03x\ncontent-disposition:form-data;name=&34;\nJoeBlow\n–AaB03x\ncontent-disposition:form-data;name=&34;;filename=&34;\nContent-Type:text/plain\n\n…contentsoffile1.txt…\n–AaB03x–

我模拟上方,我用原生Node写出了一个multipart/form-data请求的方式。

主要分为4个部分

构造请求header构造内容header写入内容写入结束分隔符

constpath=require(&39;);\nconstfs=require(&39;);\nconsthttp=require(&39;);\n//定义一个分隔符,要确保唯一性\nconstboundaryKey=&39;;\nconstrequest=http.request({\nmethod:&39;,\nhost:&39;,\nport:&39;,\npath:&39;,\nheaders:{\n&39;:&39;+boundaryKey,//在请求头上加上分隔符\n&39;:&39;\n}\n});\n//写入内容头部\nrequest.write(\n`–${boundaryKey}\\r\\nContent-Disposition:form-data;name=&34;;filename=&34;\\r\\nContent-Type:image/jpeg\\r\\n\\r\\n`\n);\n//写入内容\nconstfileStream=fs.createReadStream(path.join(__dirname,&39;));\nfileStream.pipe(request,{end:false});\nfileStream.on(&39;,function(){\n//写入尾部\nrequest.end(&39;+boundaryKey+&39;+&39;);\n});\nrequest.on(&39;,function(res){\nconsole.log(res.statusCode);\n});

至此,已经实现服务端上传文件的方式。

Stream、Base64

由于这两块就是和Buffer的转化,比较简单,我就不再重复描述了。可以作为留给大家的作业,感兴趣的可以给我这个示例代码仓库贡献这两个示例。

//base64tobuffer\nconstb64string=/*whatever*/;\nconstbuf=Buffer.from(b64string,&39;);

//streamtobuffer\nfunctionstreamToBuffer(stream){\nreturnnewPromise((resolve,reject)=>{\nconstbuffers=[];\nstream.on(&39;,reject);\nstream.on(&39;,(data)=>buffers.push(data))\nstream.on(&39;,()=>resolve(Buffer.concat(buffers))\n});\n}

小结

由于服务端没有像浏览器那样formData的原生对象,因此服务端核心思路为构造出文件上传的格式(header,filename等),然后写入buffer。然后千万别忘了用wireshark进行验证。

接收端

这一部分是针对Node端进行讲解,对于那些koa-body等用惯了的同学,可能一样不太清楚整个过程发生了什么?可能唯一比较清楚的是ctx.request.files???如果ctx.request.files不存在,就会懵逼了,可能也不太清楚它到底做了什么,文件流又是怎么解析的。

我还是要说到规范…请求端是按照规范来构造请求..那么我们接收端自然是按照规范来解析请求了。

Koa-body

constkoaBody=require(&39;);\n\napp.use(koaBody({multipart:true}));

我们来看看最常用的koa-body,它的使用方式非常简单,短短几行,就能让我们享受到文件上传的简单与快乐(其他源码库一样的思路去寻找问题的本源)可以带着一个问题去阅读,为什么用了它就能解析出文件?

寻求问题的本源,我们当然要打开koa-body的源码,koa-body源码很少只有211行,https://github.com/dlau/koa-body/blob/v4.1.1/index.jsL72

…\nMultipartParser.prototype.write=function(buffer){\n\tconsole.log(buffer);\nvarself=this,\ni=0,\nlen=buffer.length,\nprevIndex=this.index,\nindex=this.index,\nstate=this.state,\n…

我们将它的buffer打印看看.

<Buffer2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d3436313539313038303934313632323531313333363636…>\n144\n<Buffer89504e470d0a1a0a0000000d494844520000000100000001010300000025db56ca00000006504c54450000ff8080804c6cbf…>\n106\n<Buffer0d0a2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d343631353931303830393431363232353131333336…>

我们来看wireshark抓到的包

我用红色进行了分割标记,对应的就是formidable所分割的片段,所以说这个包主要是将大段的buffer进行分割,然后循环处理。

这里我还可以补充一下,可能你对以上表非常陌生。左侧是二进制流,每1个代表1个字节,1字节=8位,上面的2d其实就是16进制的表示形式,用二进制表示就是00101101,右侧是ascii码用来可视化,但是assii分可显和非可显示。有部分是无法可视的。比如你所看到文件中有需要小点,就是不可见字符。

你可以对照,ascii表对照表[7]来看。

我来总结一下formidable对于文件的处理流程。

原生Node

好了,我们已经知道了文件处理的流程,那么我们自己来写一个吧。

constfs=require(&39;);\nconsthttp=require(&39;);\nconstquerystring=require(&39;);\nconstserver=http.createServer((req,res)=>{\nif(req.url===&34;&&req.method.toLowerCase()===&34;){\nparseFile(req,res)\n}\n})\nfunctionparseFile(req,res){\nreq.setEncoding(&34;);\nletbody=&34;;\nletfileName=&34;;\n//边界字符\nletboundary=req.headers[&39;]\n.split(&39;)[1]\n.replace(&34;,&34;)\n\nreq.on(&34;,function(chunk){\nbody+=chunk;\n});\nreq.on(&34;,function(){\n//按照分解符切分\nconstlist=body.split(boundary);\nletcontentType=&39;;\nletfileName=&39;;\nfor(leti=0;i<list.length;i++){\nif(list[i].includes(&39;)){\nconstdata=list[i].split(&39;);\nfor(letj=0;j<data.length;j++){\n//从头部拆分出名字和类型\nif(data[j].includes(&39;)){\nconstinfo=data[j].split(&39;)[1].split(&39;);\nfileName=info[info.length-1].split(&39;)[1].replace(/&39;&39;Content-Type&39;:&34;–&34;–&34;binary&34;sucess”);\n});\n;\n})\n}\n\nserver.listen(7787)

总结

相信有了以上的介绍,你不再对文件上传有所惧怕,对文件上传整个过程都会比较清晰了,还不懂。。。。找我。

再次回顾下我们的重点:

请求端出问题,浏览器端打开network查看格式是否正确(请求头,请求体),如果数据不够详细,打开wireshark,对照我们的规范标准,看下格式(请求头,请求体)。

接收端出问题,情况一就是请求端缺少信息,参考上面请求端出问题的情况,情况二请求体内容错误,如果说请求体内容是请求端自己构造的,那么需要检查请求体是否是正确的二进制流(例如上面的blob构造的时候,我一开始少了一个[],导致内容主体错误)。

推荐JavaScript经典实例学习资料文章

《一行JS代码实现一个简单的模板字符串替换「实践」》

《JS代码是如何被压缩的「前端高级进阶」》

《前端开发规范:命名规范、html规范、css规范、js规范》

《【规范篇】前端团队代码规范最佳实践》

《100个原生JavaScript代码片段知识点详细汇总【实践】》

《关于前端174道JavaScript知识点汇总(一)》

《关于前端174道JavaScript知识点汇总(二)》

《关于前端174道JavaScript知识点汇总(三)》

《几个非常有意思的javascript知识点总结【实践】》

《都2020年了,你还不会JavaScript装饰器?》

《JavaScript实现图片合成下载》

《70个JavaScript知识点详细总结(上)【实践】》

《70个JavaScript知识点详细总结(下)【实践】》

《开源了一个JavaScript版敏感词过滤库》

《送你43道JavaScript面试题》

《3个很棒的小众JavaScript库,你值得拥有》

《手把手教你深入巩固JavaScript知识体系【思维导图】》

《推荐7个很棒的JavaScript产品步骤引导库》

《Echa哥教你彻底弄懂JavaScript执行机制》

《一个合格的中级前端工程师需要掌握的28个JavaScript技巧》

《深入解析高频项目中运用到的知识点汇总【JS篇】》

《JavaScript工具函数大全【新】》

《从JavaScript中看设计模式(总结)》

《身份证号码的正则表达式及验证详解(JavaScript,Regex)》

《浏览器中实现JavaScript计时器的4种创新方式》

《Three.js动效方案》

《手把手教你常用的59个JS类方法》

《127个常用的JS代码片段,每段代码花30秒就能看懂-【上】》

《深入浅出讲解js深拷贝vs浅拷贝》

《手把手教你JS开发H5游戏【消灭星星】》

《深入浅出讲解JS中this/apply/call/bind巧妙用法【实践】》

《手把手教你全方位解读JS中this真正含义【实践】》

《书到用时方恨少,一大波JS开发工具函数来了》

《干货满满!如何优雅简洁地实现时钟翻牌器(支持JS/Vue/React)》

《手把手教你JS异步编程六种方案【实践】》

《让你减少加班的15条高效JS技巧知识点汇总【实践】》

《手把手教你JS开发H5游戏【黄金矿工】》

《手把手教你JS实现监控浏览器上下左右滚动》

《JS经典实例知识点整理汇总【实践】》

《2.6万字JS干货分享,带你领略前端魅力【基础篇】》

《2.6万字JS干货分享,带你领略前端魅力【实践篇】》

《简单几步让你的JS写得更漂亮》

《恭喜你获得治疗JSthis的详细药方》

《谈谈前端关于文件上传下载那些事【实践】》

《面试中教你绕过关于JavaScript作用域的5个坑》

《Jquery插件(常用的插件库)》

《【JS】如何防止重复发送ajax请求》

《JavaScript+Canvas实现自定义画板》

《Continuation在JS中的应用「前端篇」》

作者:蓝色的秋风

转发链接:https://mp.weixin.qq.com/s/cruL9JGZNZQFrMSrzJJWiQ

建网站怎么上传源码分享文件的介绍就聊到这里吧,感谢你花时间阅读本站内容,更多关于上传网站源代码用什么程序、建网站怎么上传源码分享文件的信息别忘了在本站进行查找哦。

Published by

风君子

独自遨游何稽首 揭天掀地慰生平