前端文件的上传与下载的区别(前端文件的上传与下载)
简单理解一下前端文件的上传与下载 ,如有错误 ,轻喷 。
http小知识
http协议是建立在tcp 、ip协议的应用层规范 。
http协议规范把http请求分为3个部分:状态行 ,请求头 ,请求体。所有的方法 、实现 ,都是围绕 “如何运用和组织这三部分 ” 来完成的 。
http里没有专门用于文件上传的请求方式 ,文件上传请求是在post请求基础之上定义出来的一种方式 。
http协议规定 POST 请求提交的数据 ,必须放在消息主体(entity-body)中 ,但协议并没有规定消息主体的编码方式 。开发者可以自己决定消息主体的编码格式 ,只要最后发送的 HTTP 请求,满足上面的格式就可以 。
数据发送出去 ,还要服务端解析成功才有意义 。一般服务端语言如 php 、python 等 ,以及它们的 framework,都内置了自动解析常见数据格式的功能 。服务端通常是根据请求头(headers)中的 Content-Type 字段来获知请求中的消息主体是用何种方式编码 ,再对主体进行解析 。
http全名超文本传输协议(Hyper Text Transfer Protocol ,HTTP),顾名思义:用来传输文本的协议 ,那么当我们产生的传输文件的需求时 ,就显得“捉襟见肘 ” 。
在发送表单时 ,输入的都是文本信息 ,但是很多人不理解 ,输出文件时 ,文件的信息根本没有我们想要的数据 ,那到底怎么才能传输?
在原生的html中 ,我们通过form表单进行提交 。而当我们需要对表单数据进行二次处理再进行发送时 ,就需要通过JavaScript脚本进行发送,那么这时候FormData对象就派上了用场。而实际上 ,通过FormData对象提交表单仅仅是form表单提交的一种方式 ,这与通过设置form标签的enctype进行提交是一样的效果 。
FormData允许以二进制binary的方式传递文件,那么 ,既然是二进制数据 ,自然可以通过http进行传输 。
客户端提交文件
通过form标签进行提交
这是最原始的提交方式。但是要注意,http请求头中有一个Content-Type属性 ,该属性指定数据以何种形式进行编码传递 ,默认是“application/x-www-form-urlencoded ” ,以键值对的形式进行传递:key1=val1&key2=val2 。
如果我们默认以“application/x-www-form-urlencoded ”作为请求头 ,那么显然文件不可能变成其中的value进行传递 。
我们可以通过设置form标签的“enctype ”属性来指定“content-type ” ,该属性取值如下:
application/x-www-form-urlencoded: 初始的默认值 multipart/form-data: 适用于使用 标签上传文件 text/plain: HTML5 引入的类型这是简单请求的三种请求头 ,当然现在还可以使用“application/json ”。
给form标签添加“ enctype=“multipart/form-data ” ”属性与属性值 。
<form action="http://localhost:4000/upload/logo" method="post" target="_blank" enctype="multipart/form-data"> <input type="file" name="file"> <input type="submit" value="提交"> </form>此时发送请求 ,请求标头中的“Content-Type”变成 ”“multipart/form-data; boundary=----WebKitFormBoundaryU9sq3PNe4bAowjQG“ 。
其中boundary是一个占位符 ,代表我们规定的分割符 ,可以自己任意规定,但为了避免和正常文本重复了 ,尽量要使用复杂一点的内容 。
通过FormData提交
这可能是最常见的方式了 。
通过FormData构造函数直接从form标签生成 。
<form id="form" action="http://localhost:4000/upload/logo" method="post" target="_blank" enctype="multipart/form-data"> <input id="file" type="file" name="file"> <input id="button" type="button" value="提交"> </form> <script> function submitForm(e) { // 通过表单直接转换 const fd = new FormData(document.querySelector(#form)); const xhr = new XMLHttpRequest(); xhr.open(POST, http://localhost:4000/upload/logo, true); xhr.send(fd); } document.querySelector(#button).addEventListener(click, submitForm) </script>服务端发送文件
当我们将文件发送至服务器之后 ,由服务端自行接收并保存 。
当客户端需要下载文件时,服务端又得将保存的文件进行读取并返回 。
这里通过nodejs的express框架进行文件的读取与返回 。
app.get(/file, (req, res) => { const file = fs.readFileSync(./upload/logo.jpg, ) res.setHeader(content-type,binary); res.send(file) }) app.get(/file1, (req, res) => { res.sendFile(path.resolve(__dirname,./upload/logo.jpg)) })这里res对象返回数据有多种方式 ,如果直接使用sendFile则不需要设置响应头 ,如果是使用send/end等方法需要手动设置响应头为“binary ”表示二进制 。
客户端接收文件
在axios响应拦截器中添加判断,如果返回data类型为“Blob” ,则直接将二进制数据返回 ,注意此处根据具体项目配置。
发送请求 export async function queryFile(){ return await http.get(/file1,{ params: { site_id: 4 }, responseType: blob }) }注意此处的responseType被手动设置成“blob ” ,参考axios文档 ,该值为浏览器专属 。
接收完毕开始下载文件 ,这里用到的技术点最多 ,也是最容易让人头疼的地方 。
const blob = new Blob([res],{ type: image/jpeg }); const url = URL.createObjectURL(blob); const a = document.createElement(a); a.href = url; a.download = logo.jpg document.body.append(a); a.click(); document.body.remove(a); URL.revokeObjectURL(src);首先说说Blob
Blob 对象表示一个二进制文件的数据内容 ,比如一个图片文件的内容就可以通过 Blob 对象读写。它通常用来读写文件 ,它的名字是 Binary Large Object (二进制大型对象)的缩写 。
Blob构造函数接受两个参数 。第一个参数是数组 ,成员是字符串或二进制对象,表示新生成的Blob实例对象的内容;第二个参数是可选的 ,是一个配置对象 ,目前只有一个属性type,它的值是一个字符串 ,表示数据的 MIME 类型 ,默认是空字符串。
第一个参数是一个由ArrayBuffer, ArrayBufferView, Blob, DOMString 等对象构成的 Array ,或者其他类似对象的混合体 。
MIME参考
那么 ,此时使用Blob构造函数生成一个blob对象 ,用来表示二进制文件的数据内容 ,可通过该实例进行读写 ,一般我们不需要对文件进行读写 ,毕竟你也不会去修改图片的二进制信息 。
那这个Blob对象到底有什么用?接着看下面 。
再看看URL.createObjectURL
URL.createObjectURL() 静态方法会创建一个 DOMString ,其中包含一个表示参数中给出的对象的 URL 。
它接收一个参数 ,该参数可以是File对象 、Blob对象或者MediaSource对象 ,返回一个用于指定源object的内容 。
等等!Blob对象?这下就能理解 ,为什么要创建Blob对象了 。因为要生成特定的URL用于下载 。
最后需要注意的是,每次调用 createObjectURL() 方法时 ,都会创建一个新的 URL 对象 ,即使你已经用相同的对象作为参数创建过 。当不再需要这些 URL 对象时,每个对象必须通过调用 URL.revokeObjectURL() 方法来释放 。
这下 ,从发送文件到下载文件就连贯起来了 ,下次不用再去百度“前端怎么下载二进制文件流 ”啦。
其实,也不需要使用Blob
不知道有没有发现 ,在响应拦截器中的判断类型 ,是判断data是否是Blob对象 ,已经是Blob对象了?因为我们设置了responseType为blob ,既然这样 ,那岂不是可以省略代码中new Blob这一步?
待考证实际情况 ,我这里是可以省略的
const url = URL.createObjectURL(data); const a = document.createElement(a); a.href = url; a.download = logo.jpg document.body.append(a); a.click(); document.body.remove(a); URL.revokeObjectURL(src);axios的responseType
如果仔细看看axios文档会发现 ,该属性有多种取值 。
{ // ...... // `responseType` 表示浏览器将要响应的数据类型 // 选项包括: arraybuffer, document, json, text, stream // 浏览器专属:blob responseType: json, // 默认值 }其中有一个“arraybuffer” ,那这个arraybuffer又是什么东西?
在上面服务端发送文件中 ,写到两种方式,第一种是通过文件系统的readFileSync进行文件的读取 ,参考Nodejs官网 ,fs.readFileSync 。
可以发现,这个方法读取文件的返回值默认是Buffer对象:
那么如果把responseType改为arraybuffer是否也可以?
export async function queryFile(){ return await http.get(/file,{ params: { site_id: 4 }, responseType: arraybuffer }) }注意 ,此时responseType改为“arraybuffer“之后 ,拦截器中对返回值类型的判断会发生变化:
那么,在生成URL的时候 ,就必须先通过new Blob进行转换了。
综上
文件上传与下载并不是很难理解的东西 ,关键点在于其中用到的知识点比较多 ,从上传的FormData 、form标签的enctype属性到下载时接收的数据类型等 ,哪怕有一步出现问题都会出错 ,而且无法找到错误位置 ,毕竟 ,下面两个玩意 ,一般人也看不懂 。
参考
http上传文件
常见 MIME 类型列表
Blob对象
axios-请求配置
express-res
创心域SEO版权声明:以上内容作者已申请原创保护,未经允许不得转载,侵权必究!授权事宜、对本内容有异议或投诉,敬请联系网站管理员,我们将尽快回复您,谢谢合作!