侧边栏壁纸
博主头像
搭建网站的历程博主等级

生命不息,折腾不止

  • 累计撰写 36 篇文章
  • 累计创建 4 个标签
  • 累计收到 0 条评论

目 录CONTENT

文章目录

SpringBoot支持文件分片下载

甘超良
2023-12-25 / 0 评论 / 0 点赞 / 7 阅读 / 3760 字

简介

当下载大文件时,中途可能会断开,从而导致要重新下载。而此时之前已经下载好的内容会再次被下载,费时费力。

为了不从头下载,浏览器会发送 Range请求头,表示下载文件的哪个部分。

HTTP Range 信息

Range 请求头: Range: bytes=start-end

Range: bytes=0- // 表示从0字节到文件末尾
Range: bytes=100-200 // 表示从100字节开始到200字节结束,共101字节

HTTP Content-Range头

Content-Range头: Content-Range: bytes startBytes-endBytes/filesize

表示返回的数据中,内容所处的位置。跟请求头Range一一对应。

HTTP Content-Length

Content-Length: 101
表示返回的数据长度,要严格跟Range,Content-Range保持一致

HTTP Status 206

当响应流中包含了Content-Range时,需要设置status为206,表示支持内容部分下载

代码

long fileLength = file.length();
long startBytes = 0;
long endBytes = fileLength;

// 读取range
List<HttpRange> rangeList = HttpRange.parseRanges(request.getHeader("Range"));

// 只支持一个文件分片
if (rangeList.size() > 1) {
    response.setStatus(HttpStatus.REQUESTED_RANGE_NOT_SATISFIABLE.value());
    response.setHeader("Content-Range", "bytes */" + fileLength);
    return;
}

// 设置下载文件头
response.setCharacterEncoding(StandardCharsets.UTF_8.name());
response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE);
response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(file.getName(), StandardCharsets.UTF_8));

// 如果有Range头,需要支持断点续传
if (!rangeList.isEmpty()) {
    HttpRange r = rangeList.get(0);
    startBytes = r.getRangeStart(0);
    endBytes = r.getRangeEnd(fileLength);

    long contentLength = endBytes - startBytes + 1;
    String contentRange = "bytes " + startBytes + "-" + endBytes + "/" + fileLength;

    // 写额外的header
    response.setStatus(HttpStatus.PARTIAL_CONTENT.value());
    response.setHeader("Accept-Ranges", "bytes");
    response.setHeader("Content-Range", contentRange);
    response.setHeader("Etag", "W/\"9767057-1323779115364\"");
    response.setContentLengthLong(contentLength);
} else {
    response.setContentLengthLong(fileLength);
}

// 开始写输出流
try {
    try (RandomAccessFile randomAccessFile = new RandomAccessFile(file, "r")) {
        OutputStream out = response.getOutputStream();

        // 跳过已下载内容
        long sent = startBytes;
        randomAccessFile.seek(startBytes);

        int b;
        while ((b = randomAccessFile.read()) != -1) {
            sent++;
            out.write(b);
            if (sent > endBytes) {
                break;
            }
        }

        out.flush();
        response.flushBuffer();
    }
} catch (ClientAbortException cae) {
    log.debug("客户端放弃了下载 clientIp:[{}]", request.getRemoteAddr());
} catch (Exception e) {
    log.error("下载文件失败 {}", e.getMessage());
}
0

评论区