天天看点

CORS中MIME协议传输文件问题

最近在写一个系统的时候需要导出后台的统计数据到一个csv文件中。按照正常的公司框架,使用axios发送一个post请求给后台之后就应该会返回数据并触发下载了。但是这次成功的返回了数据却没有触发浏览器的文件下载。后来发现是一个隐藏了跨域问题外加一个mime协议格式的问题,下面来仔细分析一下。

CORS

CORS也就是跨域资源共享,这里就不详细讲解,想进一步学习的可以自己去看一下。

在这里我们主要要清楚,对不同的CORS请求浏览器的处理方法是不一样的:第一种是简单请求,包括HEAD、GET、POST或者是请求头部只有Accept、Accept-Language、Content-Language、Last-Event-ID、Content-Type:只限于三个值application/x-www-form-urlencoded、multipart/form-data、text/plain这些字段的请求;第二种是非简单请求,包括PUT或DELETE,或者Content-Type字段的类型是application/json的请求。

浏览器在处理第二种请求的时候首先会发送一个option请求来判断即将发送的请求能否被浏览器所接受,不可以的话就会报错,当然这种请求方式并不是我们这次讨论的重点。

第一种请求,也就是我们在这次涉及到的简单请求(POST),浏览器在处理的时候不会预先发送option请求而是直接自动在头信息之中,添加一个Origin字段。Origin字段用来说明,本次请求来自哪个源(协议 + 域名 + 端口)。服务器根据这个值,决定是否同意这次请求。如果Origin指定的源,不在许可范围内,服务器会返回一个正常的HTTP回应。浏览器发现,这个回应的头信息没有包含Access-Control-Allow-Origin字段,就知道出错了,从而抛出一个错误。如果Origin指定的源,在许可范围内,服务器返回的响应头会多出几个头信息字段:

重点

(1)Access-Control-Allow-Origin

必须。它的值要么是请求时Origin字段的值,要么是一个*,表示接受任意域名的请求。

(2)Access-Control-Allow-Credentials

可选。它的值是一个布尔值,表示是否允许发送Cookie。

(3)Access-Control-Expose-Headers

可选。CORS请求时,XMLHttpRequest对象的getResponseHeader()方法只能拿到6个基本字段:Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma。如果想拿到其他字段,就必须在Access-Control-Expose-Headers里面指定。

MIME

MIME原本是一种用于邮件拓展的协议,后来被添加到http协议中来扩展http协议对不同类型的文件的传输支持。具体的协议细节我们在这里也不再讨论,我们主要就说一下MIME协议在http头部添加的那些字段:

重点

MIME-Version:

   这个头提供了所用MIME的版本号。这个值习惯上为1.0。

  Content-Type:

   它定义了数据的类型,以便数据能被适当的处理。

  Content-Transfer-Encoding:

   说明了对数据所执行的编码方式。

  Content-ID:

   超纲不提

  Content-Description:

   这是一个可选的头。它是任何信息段内容的自由文本描述。描述必须使用us-ascii码。

  Content-Disposition:

   一个试验性的头,它用于给客户程序/MUA提供提示,来决定是否在行内显示附件或作为单独的附件。当这个头被设置为类似于下面这样的格式的时候就会触发浏览器的下载。

Content-Disposition: attachment; filename=%E6%9C%88%E7%B8F%8D%E9%A6%88%E6%95%B0%E6%8D%AE20190820162303.csv

问题所在

我们来稍微总结一下,因为本次请求需要跨域,所以我们使用CORS技术来解决跨域问题,然后再通过设置返回头的字段:

Content-Disposition

来实现唤起浏览器下载。

这样看起来完全不会出问题呀,所以我们的后台大概是这样的一个代码:(省略CORS处理部分)

String csvFileName = "abc.csv";
response.setContentType("text/csv;charset=utf-8");
String headerKey = "Content-Disposition";
try {
	csvFileName = URLEncoder.encode(csvFileName, "UTF-8");
} catch (UnsupportedEncodingException e) {
	logger.error(e.getLocalizedMessage(), e);
}
String headerValue = String.format("attachment; filename=\"%s\"", csvFileName);
response.setHeader(headerKey, headerValue);
           

然后?然后就出现了我在文章开头所说的问题,数据成功返回了,但是没有触发下载。说到这里可能很多人都发现了问题所在:

Content-Disposition

头字段并不属于我们之前说的CORS的六个基本字段,所以就算我们设置并且成功返回给了客户端,浏览器也是完全无法读取到这一个字段的,所以我们需要加上下面这一行代码:

这样这个问题就解决啦,当然上面的代码还有一个问题,大家自己找找看吧。