This article is participating in “Java Theme Month – Java Debug Notes Event”, see < Event link > for more details.

preface

In the case of large files or network bandwidth is not very good, fragment breakpoint download will be very necessary, at present the major download tools, such as: Thunderbolt, are very good support for fragment breakpoint download function. This article through HTTP file fragmentation breakpoint download, actual combat.

The Range of HTTP

Before we begin, it is necessary to understand the concepts and principles, namely, HTTP Range, in order to better understand the principles of fragmented download.

What is the Range

Range is an HTTP request header that tells the server what part of the file to return, that is, what Range of bytes of data to return. In Range, multiple parts can be requested at once, and the server will return them as a multipart file. If the server is returning a range response, use the 206 Partial Content status code. If the Range requested is invalid, the server returns the 416 Range Not Satisfiable status code, indicating that the client has failed. The server allows you to ignore the Range header and return the entire file with a status code of 200.

Fragmented breakpoints are much easier to download because of the presence of Range headers in HTTP.

If the file server does not support breakpoints, you will have to wait to download the movie again before continuing to watch it. If Range is supported, the client will record the Range of video files it has watched before. After the network is restored, it will send a request to the server to read the remaining Range. The server only needs to send the content requested by the client instead of sending the whole video file back to the client, so as to save network bandwidth.

Range specifications

Range: <unit>=<range-start>-
Range: <unit>=<range-start>-<range-end>
Range: <unit>=<range-start>-<range-end>, <range-start>-<range-end>
Range: <unit>=<range-start>-<range-end>, <range-start>-<range-end>, <range-start>-<range-end>
Copy the code

: The unit used for the scope, usually bytes

: an integer representing the start of a range in a specific unit

: An integer representing the end value of a range in a specific unit. This value is optional; if it does not exist, the scope extends to the end of the document.

Range: bytes=1024-2048
Copy the code

Fragment breakpoint download implementation

In the way of Java Spring Boot, the core code is as follows:

  • Serivce layer

    package com.xcbeyond.common.file.chunk.service.impl;

    import com.xcbeyond.common.file.chunk.service.FileService; import org.springframework.stereotype.Service;

    import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.BufferedOutputStream; import java.io.File; import java.io.IOException; import java.io.RandomAccessFile;

    / * *

    • Service is used to fragment files
    • @Auther: xcbeyond
    • @Date: 2019/5/9 23:02

    */ @Service public class FileServiceImpl implements FileService {

    @param range Indicates the range of HTTP request headers. This range is used to indicate the content of the specified part of the request. * Format: Range: Bytes =start-end [start,end] @param Request @param response */ public void fileChunkDownload(String range, HttpServletRequest Request, HttpServletResponse Response) {// The file to download, using the project pom.xml file as an example. File File = new File(system.getProperty ("user.dir") + "\\pom.xml"); Long startByte = 0; Long endByte = file.length() -1; // range if (range! = null && range.contains("bytes=") && range.contains("-")) { range = range.substring(range.lastIndexOf("=") + 1).trim();  String ranges[] = range.split("-"); If (ranges. Length == 1) {// If (ranges. Length == 1) { Bytes =-1024 Data from the start byte to the 1024th byte if (range.startswith ("-")) {endByte = long.parselong (ranges[0]); } // Case 2, e.g. : bytes=1024- 1024th byte to last byte else if (range.endswith ("-")) {startByte = long.parselong (ranges[0]); Else if (ranges. Length == 2) {startByte = long.parselong (ranges[0]); endByte = Long.parseLong(ranges[1]); } } catch (NumberFormatException e) { startByte = 0; endByte = file.length() - 1; }} // Long contentLength = endbyte-startByte + 1; // File name String fileName = file.getName(); / / file type String contentType = request. GetServletContext () getMimeType (fileName); / / response headers set / / https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Accept-Ranges response. SetHeader (" Accept - Ranges ", "bytes"); // Content-type Indicates the resource Type, for example, file Type response.setHeader(" content-type ", contentType); Content-disposition indicates whether the response is presented inline (i.e. as a web page or part of a page) or downloaded and saved locally as an attachment. Responsetheader (" Content-disposition ", "inline; filename=pom.xml"); Responsetheader (" content-length ", string.valueof (contentLength)); // Content-range indicates how much data was responded in the format: Response. setHeader(" content-range ", "bytes " + startByte + "-" + endByte + "/" + file.length()); response.setStatus(response.SC_OK); response.setContentType(contentType); BufferedOutputStream outputStream = null; RandomAccessFile randomAccessFile = null; // Transmitted data size long transmitted data flag = 0; try { randomAccessFile = new RandomAccessFile(file, "r"); outputStream = new BufferedOutputStream(response.getOutputStream()); byte[] buff = new byte[2048]; int len = 0; randomAccessFile.seek(startByte); / / determine if less than 2048 (buff length) at the end of a byte while (+ len (transmitted) < = contentLength && (len = randomAccessFile. Read (buff))! = -1) { outputStream.write(buff, 0, len); transmitted += len; If (transmitted < contentLength) {len = randomAccessfile. read(buff, 0, 0); (int) (contentLength - transmitted)); outputStream.write(buff, 0, len); transmitted += len; } outputStream.flush(); response.flushBuffer(); randomAccessFile.close(); } catch (IOException e) { e.printStackTrace(); } finally { try { if (randomAccessFile ! = null) { randomAccessFile.close(); } } catch (IOException e) { e.printStackTrace(); }}}Copy the code

    }

  • The controller layer

    package com.xcbeyond.common.file.chunk.controller;

    import com.xcbeyond.common.file.chunk.service.FileService; import org.springframework.web.bind.annotation.RequestHeader; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController;

    import javax.annotation.Resource; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse;

    / * *

    • File fragmentation operation Controller
    • @Auther: xcbeyond
    • @Date: 2019/5/9 22:56

    */ @RestController public class FileController { @Resource private FileService fileService;

    @param range Indicates the range of HTTP request headers. This range is used to indicate the content of the specified part of the request. * Format: Range: Bytes =start-end [start,end] * @param Request HTTP request * @Param Response HTTP response */ @requestMapping (value = "/file/chunk/download", method = RequestMethod.GET) public void fileChunkDownload(@RequestHeader(value = "Range") String range, HttpServletRequest request, HttpServletResponse response) { fileService.fileChunkDownload(range,request,response); }Copy the code

    }

After Spring Boot is started, for example, download the first 1024 bytes of the file (Range:bytes=0-1023). The following information is displayed:

Note: The client is not provided in this implementation. The client can call the download interface in this example in a loop, specifying the actual download offset range each time.

Note the accept-ranges, content-range headers

Accept-ranges: indicates that the response identifier supports range requests. The specific value of the field is used to define the unit of range requests, for example, bytes. When the Accept-range header is found, you can try to continue the previously interrupted download rather than restart it.

Content-range: indicates how much data was received. The format is: [start location to download]-[end location]/[total file size], for example, bytes 0-1023/2185

Source: github.com/xcbeyond/co…

(If you like it, leave it behind and give it a Star on GitHub.)

Reference: developer.mozilla.org/zh-CN/docs/…