这一次,弄明白JS中的文件相关(二):HTTP请求头和响应头
(一)前置知识
开始前,我们先来复习一下HTTP的基础知识。
HTTP请求分为:请求行、请求头、空行、请求体(也叫正文、请求实体、请求主体)。
HTTP响应分为:状态行(也叫响应行)、响应头、空行、响应体(也叫正文、响应实体、响应主体)。
在HTTP请求中,最常见的GET请求是没有请求体的(GET的查询字符串不属于请求体),只有POST、PUT请求有请求体。
在HTTP响应中,大部分时候都会有响应体,什么情况会没有呢?
状态码为1xx、204 No Content、301、302重定向、304 Not Modified的响应。还有一个,就是HEAD方法的响应,HEAD和GET的区别,是只返回头部信息,不返回实体数据。
我们今天要讨论的HTTP中文件相关的字段,就是指的请求体和响应体(统称为实体)相关的字段。
(二)请求体和响应体相关的字段
1. 数据类型:实体的MIME类型。
Accept:请求头字段,标记客户端可理解的MIME类型,写法如:Accept: text/html,application/xml,image/webp。
Content-Type:通用字段,请求头和响应头都可以用,只要有实体数据,就应该设置正确的Content-Type,没有的话就不需要。Content-Type的数据格式是:数据类型+字符集,比如:application/json;charset=UTF-8。作为请求头时,一般不需要后面的字符集。作为响应头时,当响应体为文本类型时,应该指定字符集。
2. 编码类型:实体的压缩格式,比如gzip, deflate, br。服务器可以通过对数据进行压缩,来减小响应数据的大小。
Accept-Encoding:请求头字段,标记客户端支持的压缩格式,写法如:Accept-Encoding: gzip, deflate, br。
Conent-Encoding:响应头字段,响应体的实际压缩格式,写法如:Content-Encoding: gzip。
3. 语言类型,指的是实体的自然语言,比如汉语、英语等。
Accept-Language:请求头字段,标记客户端可理解的自然语言,写法如:Accept-Language: zh-CN, zh, en。
Content-Language:响应头字段,响应体的实际自然语言,写法如:Content-Language: zh-CN。
4. 字符集,指的是实体的字符集。
Accept-Charset:请求头字段,写法如:Accept-Charset: gbk, utf-8。
这儿要注意,没有对应的响应头字段,响应体的数据类型和字符集,都在Content-Type响应头中。
5. 资源处理方式
Content-Disposition:响应头字段,指示客户端应以何种方式处理响应体数据,是显示(inline)还是下载附件(attachment),写法如:Content-Disposition: attachment; filename="example.pdf"。
6. 数据长度
Content-Length:通用字段。不管对于客户端还是服务端,知道明确的Content-Length,都是有好处的。响应头中的Content-Length更为常见,因为它可以帮助客户端优化加载速度、判断响应是否完整以及何时可以安全关闭连接。
7. 分块传输和范围请求
Transfer-Encoding、Range、Accept-Ranges、Content-Range这些暂不讨论。
有的字段,我们在平时的开发中几乎没用过,感知不到它们的存在,原因很可能是:浏览器和现代框架,会帮助我们自动处理。
我们来重点说一下:Content-Type。
(三)Content-Type请求头:
Content-Type请求头最常用的三个值是:application/x-www-form-urlencoded、multipart/form-data、application/json。还有一些其他值,比如text/plain、text/xml、application/octet-stream等,一般是在发送特定数据格式时使用,用的不多。
application/x-www-form-urlencoded和multipart/form-data这两位起源于HTML表单提交,后来在ajax请求中继续沿用。application/json则是从ajax时代开始流行。所以早期的ajax库如jquery,默认的post数据格式是application/x-www-form-urlencoded,而现代ajax库如axios,默认的数据格式就变成了application/json。
1.application/x-www-form-urlencoded:
这是form表单的默认post提交方式,将字段名和值进行URL编码,得到这种格式:a=1&b=2&c=3。你一定感觉很熟悉,没错,和url的查询字符串长一个样。这种提交方式适用于简单的文本数据,同样也是jquery.ajax的默认post请求方式。
2.multipart/form-data:
当需要上传文件时,不管是通过表单提交,还是各种ajax框架,都需要使用这种提交方式。因为这种格式能够携带二进制数据,比如文件。
3.application/json:
现代ajax框架,普遍默认采用这种post方式,比如axios,它post请求的默认数据格式就是这个。
(四)Content-Type响应头:
相比请求头主要是3个值,Content-Type响应头的值就丰富多了,浏览器根据响应体的数据类型,来做出不同的处理。
比如最常见的网页是text/html,api接口数据是application/json,图片是image/jpeg, image/png, image/gif,css文件是text/css等。
这里面比较特别的就是application/octet-stream,意思是不明类型的二进制数据,浏览器对它的处理方式一般是直接下载。
在nodejs中,如果使用原生的http模块,你需要手动设置Content-Type。但如果使用web框架,比如Express、koa2,如果你不设置,它们会根据内容自动推断并设置一个合适的Content-Type。在nginx等HTTP服务器中,也是会自动添加静态文件的Content-Type,它们内部通常有一张mime类型和文件扩展名的映射表。
(五)文件下载
我们来专门说一下文件下载:
我们工作中大多数HTTP请求,浏览器对于文件的处理方式,都是打开或直接使用,而不是下载。前端和后端,分别可以通过各自的方式来让浏览器下载文件。
1. 前端使用<a href="文件URL" download="自定义文件名">下载文件</a>,只要给a标签添加download属性,就无需管响应头的Conent-Type是什么,都可以点击下载文件,还可以指定下载的文件名。
2. 后端把Content-Type响应头设为:application/octet-stream。这种方式,实际上是把文件标记为不明类型的二进制数据,浏览器通常采用下载的方式处理这种数据。
3. 后端设置正确的Content-Type响应头,然后设置Content-Disposition: attachment; filename="example.pdf"。
Content-Disposition是专门指示浏览器如何处理响应体的内容,被浏览器显示(inline)还是作为附件下载(attachment)。可以看出,Content-Disposition是专门处理文件下载的,这才是服务端指定文件下载的最佳实践。
(六)文件上传
再来说一下文件上传,在web开发中,文件上传经历了从传统表单提交到使用AJAX技术的演变。
表单时代,我们通过<form method="post" enctype="multipart/form-data">来上传文件。
在前端,出于安全考虑,JS没有文件系统的权限,所以我们无法通过编程的方式创建文件,<input type="file" />是唯一的文件获取方式。
我们选中的文件,在JS中就是File对象。文件上传,就是把File对象通过表单提交或ajax发送给后端。
那么ajax时代,我们已经很少使用表单了,上传文件应该怎么写呢?
先考一下你,File对象可以作为一个普通对象的属性值吗?
答案是:不可以。因为对象的属性和值都必须是可序列化的数据,比如简单类型或对象,而File或Blob不是简单可序列化的数据。也就是说,JSON和键值对都不支持二进制数据,只有multipart/form-data这种数据格式支持。
我曾经以为,可以通过这种把File挂在普通对象的方式,作为post的请求的参数,来上传文件。由于以上的原因,这样是行不通的。
现在我们一般很少用表单了,如何存放二进制数据呢?此时我们需要一个新的数据结构:FormData。
FormData是HTML5引入的一个API,它允许我们将表单数据以键值对的形式组织起来,并且可以方便地添加文件内容,它可以模拟表单提交。具体这么写:
// XHR上传文件 // 获取文件输入元素 var fileInput = document.getElementById('fileInput'); var file = fileInput.files[0]; // 创建XMLHttpRequest实例 var xhr = new XMLHttpRequest(); xhr.open('POST', '/api/upload', true); xhr.onload = function () { if (xhr.status === 200) { console.log('文件上传成功:', xhr.responseText); } else { console.error('文件上传失败:', xhr.statusText); } }; // 设置请求头,对于文件上传,通常不需要手动设置Content-Type // 因为在使用FormData时,浏览器会自动设置 // xhr.setRequestHeader('Content-Type', 'multipart/form-data'); // 创建FormData对象 var formData = new FormData(); formData.append('file', file); // 发送请求 xhr.send(formData);
// axios上传文件 // 获取文件输入元素 var fileInput = document.getElementById('fileInput'); var file = fileInput.files[0]; // 创建FormData对象 var formData = new FormData(); formData.append('file', file); // 使用axios上传文件 axios.post('/api/upload', formData, { // axios会自动处理FormData的Content-Type // 无需手动设置 }) .then(response => { console.log('文件上传成功:', response.data); }) .catch(error => { console.error('文件上传失败:', error); });
以上是对HTTP请求头和响应头中实体字段的总结,重点说明了Content-Type以及文件下载和上传。