Foreword
?
Internet connections are slow and unstable, and there is a possibility of disconnection due to network failures.
When the client downloads a large object, the probability of upload and download failure due to network disconnection becomes non-negligible.
?
When requesting a GET
object, the client tells the interface service where to start outputting the data of the object by setting the Range
header.
Determine whether to support breakpoint download, according to the document: 14.35.1 Byte Rangeshttps://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
// directly judge whether there is Accept-Ranges = bytes boolean support = urlConnection.getHeaderField("Accept-Ranges").equals("bytes"); System.out.println("Partial content retrieval support = " + (support ? "Yes" : "No));
For example:
<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="acc8c3c2cdc0c8ecc8c3c2cdc0c881dcdec3">[email protected]</a>:~$ curl -i --range 0-9 http://localhost:8080/file/chunk/download HTTP/1.1 206 Accept-Ranges: bytes Content-Disposition: inline;filename=pom.xml Content-Range: bytes 0-9/13485 Content-Length: 10 Date: Mon, 01 Nov 2021 09:53:25 GMT
Directly judge the header HEAD
, for example:
?
The
HeadObject
interface is used to obtain the meta information of a file (Object
). Using this interface will not return the file content.?
HEAD /ObjectName HTTP/1.1 Host: BucketName.oss-cn-hangzhou.aliyuncs.com Date: GMT Date Authorization: SignatureValue
It should be noted that the corresponding HTTP
status code:
-
206 Partial Content
:HTTP Range
request succeeded -
416 Requested Range Not Satisfiable status
:HTTP Range
request out of bounds -
200 OK
: range request not supported
The summary is as follows:
-
HTTP
range request:HTTP/1.1
and above are required. If a segment of the double-end is lower than this version, it is considered unsupported. -
Whether range requests are supported is determined by
Accept-Ranges
in the response header. -
Specify the byte range of the requested content entity by adding the request header
Range
to the request header. -
In the response header, use
Content-Range
to identify the range of the currently returned content entity, and use Content-Length to identify the length of the currently returned content entity range. -
During the request process,
If-Range
can be used to distinguish whether the resource file has changed, and its value comes fromETag
orLast-Modifled
. If the resource file is changed, the download process will be repeated.
Practical production
Development also has to rely on evidence and set boundaries in order to control the overall situation.
There are ready-made documents, see Alibaba Cloud Documentation https://help.aliyun.com/document_detail/39571.html
-
Range: bytes=0-499
: Indicates the content in the range of 0~499 bytes. -
Range: bytes=500-999
: Indicates the content of the 500th~999th byte range. -
Range: bytes=-500
: indicates the content of the last 500 bytes. -
Range: bytes=500-
: Indicates the content from the 500th byte to the end of the file. -
Range: bytes=0-
: Indicates the first byte to the last byte, that is, the complete file content.
Whether HTTP Range
is valid or not:
-
If the
HTTP Range
request is valid, the response returns 206 and contains theContent-Range
in the response header -
If the
HTTP Range
request is illegal, or the specified range is not in the valid range, theRange
will not take effect, the response return value will be 200, and the entireObject
content.
?
The following are examples and error descriptions of illegal
HTTP Range
requests: Assume that the resource size ofObject
is 1000 bytes, and the valid range ofRange
is 0~999
Range: byte=0-499
: wrong format,byte
should bebytes
.
Range: bytes=0-1000
: The last byte1000
exceeds the valid range.
Range: bytes=1000-2000
: The specified range exceeds the valid range.
Range: bytes=1000-
: The first byte exceeds the valid range.
Range: bytes=-2000
: The specified range exceeds the valid range.?
Take some chestnuts:
> Background management system + user applet based on Spring Cloud Alibaba + Gateway + Nacos + RocketMQ + Vue & amp; Element, supporting RBAC dynamic permissions, multi-tenancy, data permissions, workflow, three-party login, payment, SMS , shopping mall and other functions > > * Project address: <https://github.com/YunaiV/yudao-cloud> > * Video tutorial: <https://doc.iocoder.cn/video/> # normal range download <a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="8aeee5e4ebe6eecaeee5e4ebe6eea7faf8e5">[email protected]</a>:~$ curl -i --range 0-9 http://localhost:8080/file/chunk/download HTTP/1.1 206 Accept-Ranges: bytes Content-Disposition: inline; filename=Screen_Recording_20211101-162729_Settings.mp4 Content-Range: bytes 0-9 Content-Type: application/force-download;charset=UTF-8 Content-Length: 16241985 Date: Wed, 03 Nov 2021 09:50:50 GMT
Server – Business Development
Here we take SpringBoot
as a chestnut:
-
External support
range
download -
Underlying storage: use
ceph
-
The
Controller
is as follows:
@Slf4j @RestController public class Controller { @Autowired private FileService fileService; /** * download file * * Provided externally * * @param fileId fileId * @param token token * @param accountId Account Id * @param response response */ @GetMapping("/oceanfile/download") public void downloadOceanfile(@RequestParam String fileId, @RequestHeader(value = "Range") String range, HttpServletResponse response) { this.fileService.downloadFile(fileId, response, range); } }
-
The
Service
is as follows:
@Slf4j @Service public class FileService { @Autowired private CephUtils cephUtils; /** * Download files directly * * Tips: Support breakpoint download * @param fileId fileId * @param response return * @param range range */ public void downloadFile(String fileId, HttpServletResponse response, String range) { // Get file information according to fileId FileInfo fileInfo = getFileInfo(fileId); String bucketName = fileInfo. getBucketName(); String relativePath = fileInfo. getRelativePath(); // process range, range information RangeDTO rangeInfo = executeRangeInfo(range, fileInfo. getFileSize()); // rangeInfo = null, download the entire file directly if (Objects. isNull(rangeInfo)) { cephUtils.downloadFile(response, bucketName, relativePath); return; } // download some files cephUtils.downloadFileWithRange(response, bucketName, relativePath, rangeInfo); } private RangeDTO executeRangeInfo(String range, Long fileSize) { if (StringUtils.isEmpty(range) || !range.contains("bytes=") || !range.contains("-")) { return null; } long startByte = 0; long endByte = fileSize - 1; range = range.substring(range.lastIndexOf("=") + 1).trim(); String[] ranges = range. split("-"); if (ranges. length <= 0 || ranges. length > 2) { return null; } try { if (ranges. length == 1) { if (range. startsWith("-")) { //1. For example: bytes=-1024 data from the start byte to the 1024th byte endByte = Long. parseLong(ranges[0]); } else if (range. endsWith("-")) { //2. For example: bytes=1024- the data from the 1024th byte to the last byte startByte = Long. parseLong(ranges[0]); } } else { //3. For example: bytes=1024-2048 The data from the 1024th byte to the 2048th byte startByte = Long. parseLong(ranges[0]); endByte = Long. parseLong(ranges[1]); } } catch (NumberFormatException e) { startByte = 0; endByte = fileSize - 1; } if (startByte >= fileSize) { log.error("range error, startByte >= fileSize." + "startByte: {}, fileSize: {}", startByte, fileSize); return null; } return new RangeDTO(startByte, endByte); } }
You can collect the above content, if you encounter such a scene in the future, you can get it done in minutes!