Mystery0の小站

Mystery0の小站

记一次排查SpringBoot项目下载文件数据为空的问题

2020-09-17
记一次排查SpringBoot项目下载文件数据为空的问题

问题描述

Snipaste_2020-09-17_11-36-08

因为一些特殊的原因,我是自己写了一个SpringBoot项目用来处理文件的上传和下载,其中文件下载是通过 ResourceHttpRequestHandler + FileSystemResource 的形式交给SpringBoot进行处理。但是QA一直给我开一个前端无法展示图片的bug,我自己打开控制台刷新页面却又是正常的,后来我发现只要打开控制台,文件就加载正常,但是不打开控制台就无法展示。

SpringBoot 版本 1.5.9.RELEASE

此处应有图如下: image.png

猜测原因

起初我以为是客户端的缓存问题导致的加载失败,前段时间请别人帮忙测试视频播放也是出现过不开控制台无法播放的情况,所以也没太关注。 但是这两天频繁出现这个问题,就开始关注出现的原因。

排查问题

尝试搜索 SpringBoot 空数据 控制台 等关键字,都没有找到相关的信息,也没发现别人有这个问题。然后在我随便点点的时候,我发现只要我打开控制台就加载正常,想到之前为了调试本地网页方便就开了禁用缓存的选项。会不会……

Snipaste_2020-09-17_11-42-39

多次尝试后发现,打开控制台能正确加载是因为这个 禁用缓存 ,如果不禁用缓存,打开控制台也无法加载。

返回数据的请求Response Header如下:

  • 返回空数据的请求

Snipaste_2020-09-17_11-46-39

  • 返回正常数据的请求

Snipaste_2020-09-17_11-47-12

Nginx日志:

image.png

为了截图方便,时间点可能对不上,表示意思是一样的

于是开始在后台接收请求的过滤器中查看客户端的Header,看到如下的两个请求:

image.png

image.png

已打码重要信息

进行对比

image.png

很明显是缓存相关的不同参数,导致的一个会返回数据,一个不会返回数据。

相关代码

if (!dir.endsWith("/")) {
    dir += "/";
}
String filePath = path;
if (filePath.startsWith("/")) {
    filePath = filePath.substring(1);
}
File file = new File(dir + filePath);
if (file.exists()) {
    response.setBufferSize(64 * 1024);
    request.setAttribute(NonStaticResourceHttpRequestHandler.ATTR_FILE, file.getAbsolutePath());
    nonStaticResourceHttpRequestHandler.handleRequest(request, response);
} else {
    log.warn("file not exist, path: [{}]", path);
    response.setStatus(HttpServletResponse.SC_NOT_FOUND);
    response.setCharacterEncoding(StandardCharsets.UTF_8.toString());
}
@Component
public class NonStaticResourceHttpRequestHandler extends ResourceHttpRequestHandler {
    public static final String ATTR_FILE = "ATTR_FILE";

    @Override
    protected Resource getResource(HttpServletRequest request) {
        final String filePath = (String) request.getAttribute(ATTR_FILE);
        return new FileSystemResource(new File(filePath));
    }
}

问题解决

既然知道是缓存的原因了,只要随便搜一下缓存相关的配置,加上本地运行调试就得出以下的配置代码:

response.addHeader(HttpHeaders.CACHE_CONTROL, "no-cache,no-store");

在返回数据的时候,添加一个 Cache-ControlHeader ,这样子客户端在发起请求的时候就不会带 if-modified-sinceHeader ,也不会缓存这个请求了。

应该有更加标准的解决办法,为了时间,我这里暂时就这样子处理了,如果有其他解决办法,欢迎进行评论。