需求背景动态创建的文件下载的时候希望浏览器显示下载进度
动态创建的文件希望能够分段下载
http断点续传报文要实现http断点续传必须要简单了解以下几个报文。
accept-ranges 告诉客户端(浏览器..)服务器端支持断点续传 服务器端返回
range 客户端告诉服务器端从指定的的位置/范围(这里值字节数)下载资源 客户端发出
content-range 服务器端告诉客户端响应的数据信息,在整个返回体中本部分的字节位置 服务器端返回
etag 资源标识 非必须 服务器端返回
last-modified 资源最后一次更新的时间 非必须 服务器端返回
range 的范围格式
表示0-499个字节范围:range: bytes=0-499
表示最后500个字节范围:range: bytes=-500
表示500字节开始到结束范围:range: bytes=500-
表示第一个和最后一个字节:range: bytes=0-0,-1
表示同时指定几个范围:range: bytes=500-600,601-999
content-range 的数据格式
content-range: bytes 0-499/22036 :表示返回0-499字节范围数据 资源一共22036个字节
原理
客户端发起请求 设置range指定开始字节数或结束字节数 如果是从0开始也可以不用设置。
服务器端检查到客户端range头 解析开始字节数以及结束字节数 并返回报文头 accept-ranges表示支持断点续传,content-range记录该次向客户端写入流的位置信息,然后再写入流到客户端。
服务端可以使用etag last-modified 标记一下资源是否被修改。作一些验证工作,如果验证不通过则返回错误,非必须项。
java实现outputstream os=null;
inputstream inputstream =null;
file zipfile=null;
try{
long zipstart=system.currenttimemillis();
zipfile=createfile();//动态根据业务创建文件
if(logger.isinfoenabled()){
logger.info(string.format("压缩zip 花费时间 %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 {
// 暂时只处理这2种range格式 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];//每次以512kb 0.5mb的流量下载
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("下载 花费时间 %s(s) ",
(system.currenttimemillis()-downloadstart)/1000));
}
}
}catch (exception e){
if(stringutils.endswithignorecase(e.getmessage(),"broken pipe")){
logger.error("用户取消下载");
}
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){}
}
}
}
比如google浏览器下载的时候就能看到下载进度以及暂停下载和恢复下载操作,也可以设置range测试分段下载。
以上就是java实现断点续传下载原理的示例分享的详细内容。