Based on breakpoint continuation download principle of the implementation

  • 2020-10-23 20:58:46
  • OfStack

Demand background

When dynamically created files are downloaded, you want the browser to show the download progress

Dynamically created files want to be downloaded in segments

HTTP break point to resume message

To implement the HTTP breakpoint continuation, you must simply understand the following messages.

Accept-Ranges tells the client (browser..) Server side supports breakpoint continuation server side return

The Range client tells the server to download the resource from the specified location/range (here the number of bytes is valued)

The ES20en-ES21en server side tells the client the data information for the response, and the server side returns the byte position of this section throughout the return body

ETag resource identity is not required to be returned on the server side

The time of the last update of the ES27en-ES28en resource is not required to be returned on the server side

Range range format

Represents a range of 0-499 bytes: Range: bytes=0-499

Represents the last 500 byte range: Range: bytes=-500

Represents the start to end range of 500 bytes: Range: bytes=500-

Represents the first and last byte: Range: bytes=0-0,-1

Indicates that several ranges are specified simultaneously: Range: bytes=500-600,601-999

Content-Range data format

Content-Range: bytes 0-499/22036: represents the data resource in the range of 0-499 bytes returned 1, a total of 22036 bytes

The principle of

The client initiates a request to set Range to specify the number of start bytes or end bytes if starting from 0.

The server checks the client's Range header to parse the starting and ending bytes and returns the header ES69en-ES70en to indicate support for breakpoint continuation. Content-Range records the location of this write to the client, and then writes to the client.

The server can use ETag ES76en-ES77en to mark whether the resource under 1 has been modified. Do some validation work, if the validation does not pass the return error, not required items.

java implementation


OutputStream os=null;
 InputStream inputStream =null;
 File zipFile=null;
 try{
  long zipStart=System.currentTimeMillis();
  zipFile=createFile();// Create files dynamically based on the business 
  if(logger.isInfoEnabled()){
   logger.info(String.format(" The compression ZIP  Spend time  %s(s) ",
  (System.currentTimeMillis()-zipStart)/1000));
  }
  if (zipFile.exists()) {
   long downloadStart=System.currentTimeMillis();
   inputStream= new BufferedInputStream(new FileInputStream(zipFile));
   response.reset();
   os=new BufferedOutputStream(response.getOutputStream());
   String userAgent = request.getHeader("USER-AGENT");
   String fileName=zipFile.getName();
   if (null != userAgent && -1 != userAgent.indexOf("MSIE")) {
    fileName = URLEncoder.encode(fileName, "UTF8");
   } else if (null != userAgent && -1 != userAgent.indexOf("Mozilla")) {
    fileName = new String(fileName.getBytes("utf-8"), "ISO-8859-1");
   }
   response.setHeader("Accept-Ranges", "bytes");
   response.setHeader("Content-Disposition", 
  "attachment;filename="+ fileName);
   response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE);
   long pos = 0, fileSize=zipFile.length(),
 last=fileSize-1;
   response.setHeader("ETag",zipFile.getName().
   concat(Objects.toString(fileSize))
     .concat("_").concat(Objects.toString(zipFile.lastModified())));
   response.setDateHeader("Last-Modified",zipFile.lastModified());
   response.setDateHeader("Expires",
   System.currentTimeMillis()+1000*60*60*24);
   if (null != request.getHeader("Range")) {
    response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
    try {
     //  Just deal with this for now 2 Kind of range format  1 , RANGE: bytes=111- 2 , Range: bytes=0-499
     String numRang = request.getHeader("Range")
   .replaceAll("bytes=", "");
     String[] strRange = numRang.split("-");
     if (strRange.length == 2) {
      pos = Long.parseLong(strRange[0].trim());
      last = Long.parseLong(strRange[1].trim());
     } else {
      pos = Long.parseLong(numRang.replaceAll("-", "").trim());
     }
    } catch (NumberFormatException e) {
     logger.error(request.getHeader("Range") + " error");
     pos = 0;
    }
   }
   long rangLength = last - pos + 1;
   String contentRange = new StringBuffer("bytes ").
   append(String.valueOf(pos)).
   append("-").append(last).append("/").
   append(String.valueOf(fileSize)).toString();
   response.setHeader("Content-Range", contentRange);
   response.addHeader("Content-Length",Objects.toString(rangLength));
   if(pos>0){
    inputStream.skip(pos);
   }
   byte[] buffer = new byte[1024*512];// Every time to 512KB 0.5MB Traffic downloads 
   int length = 0,sendTotal=0;
   while (sendTotal < rangLength && length!=-1) {
    length = inputStream.read(buffer, 0,
  ((rangLength - sendTotal) <= buffer.length ?
      ((int) (rangLength - sendTotal)) : buffer.length));
    sendTotal = sendTotal + length;
    os.write(buffer, 0, length);
   }
   if(os!=null){
    os.flush();
   }
   if(logger.isInfoEnabled()){
    logger.info(String.format(" download   Spend time  %s(s) ",
  (System.currentTimeMillis()-downloadStart)/1000));
   }
  }
 }catch (Exception e){
  if(StringUtils.endsWithIgnoreCase(e.getMessage(),"Broken pipe")){
   logger.error(" User cancels download ");
  }
  logger.error(e.getMessage(),e);
 }finally {
  if(os!=null){
   try{
    os.close();
   }catch (Exception e){}
  }
  if(inputStream!=null){
   try{
    IOUtils.closeQuietly(inputStream);
   }catch (Exception e){}
  }
 }
}

For example, when the google browser downloads, you can see the progress of the download and suspend and resume the download. You can also set the Range test block download.


Related articles: