使用HEAD请求判断文件是否存在

1,497 阅读3分钟

1、需求背景

点击“下载到本地”按钮,将文件下载到本地,但是有的下载链接,当文件是不存在时,需要给出“文件不存在,无法下载”的提示信息。 image.png

2、实现方案

2.1、后端判断

前端调取接口,让后端判断文件是否存在,如果文件存在,则返回文件下载链接。

但是后端不想另外提供接口了,因为其他项目都是直接给下载链接,由前端自己判断文件是否存在。
image.png

2.2、前端判断

看了其他项目的文件下载方式,基本上是使用一个GET异步请求,通过返回结果判断文件是否存在。

如果下载的是小文件,这样做是没有太大问题;如果下载的是大文件,那就不OK了。如下载1G大小的文件,需要长时间等待文件下载完成后,js才能执行异步请求的回调函数。

那有没有优雅的方案可以解决这个问题,答案是有的,可以通过HEAD请求来判断服务端文件是否存在。

HEAD和GET请求本质是一样的,区别在于HEAD不含有呈现数据,而仅仅是HTTP头信息,可以通过HTTP(HTTPS)响应头信息来判断文件是否存在。

3、开整

通过判断响应头中的content-type属性值是否存在octet-stream字符串,如果存在,说明服务端的文件是存在,否则服务端的文件不存在,并弹出提示信息,代码如下:

// 下载文件到本地前,使用HEAD请求,根据响应报文头,判断文件是否存在(不会下载文件)
const url = `${BASEAPI.poc}/api/poc/algorithm-platform/deviceData/downloadLocal/${record?.id}`;
const x = new XMLHttpRequest();
x.open('HEAD', url, false);
x.withCredentials = true; // 跨域时是否在请求中协带cookie
x.onreadystatechange = () => {
  if (x.readyState === 4) {
    if (x.status === 200) {
      // octet-stream,意味着消息体是文件流,可以下载到本地
      if (x.getResponseHeader('content-type')?.includes('octet-stream')) {
        window.location.href = url;
      } else {
        messageInform('文件不存在,无法下载', 'error');
      }
    }
  }
};
x.send();

image.png

image.png

4、遇坑填坑

  1. 这里不用项目中封装好的axios,是因为当文件不存在时,封装好的axios响应拦截器会直接拦截掉。
  2. 要设置请求withCredentials属性为true,不然跨域时是不会带cookie,会导致请求响应头不一致。
  3. 刚开始的时候,我是通过响应头中content-disposition属性值来判断的,但是会报错:Refused to get unsafe header "Content-Disposition",查阅相关资料后发现,需要后端给该属性设置白名单("Access-Control-Expose-Headers":"content-disposition"),不然浏览器是不会给你读取的。后面发现可以直接读取content-type属性,因为有些属性浏览器是没有做限制的(这些属性浏览器没有做限制:Cache-Control,Content-Language,Content-Type,Expires,Last-Modified,Pragma)。

image.png

image.png

5、总结

  1. HEAD和GET本质是一样的,区别在于HEAD不含有呈现数据,而仅仅是请求和响应头信息,通过HTTP(HTTPS)响应头信息来判断文件是否存在。
  2. 有的人可能觉得HEAD请求这个方法没什么用,其实不是这样的。想象一个业务情景:欲判断某个资源是否存在,我们通常使用GET,但这里用HEAD则意义更加明确,还可以节约带宽资源。