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.