简介
当下载大文件时,中途可能会断开,从而导致要重新下载。而此时之前已经下载好的内容会再次被下载,费时费力。
为了不从头下载,浏览器会发送 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());
}
评论区