HTTP头字段(英语:HTTP header fields)是指在超文本传输协议(HTTP)的请求和响应消息中的消息头部分。它们定义了一个超文本传输协议事务中的操作参数。(维基百科)
本篇文章用到的HTTP头字段有:
Range: 仅请求某个实体的一部分。字节偏移以0开始。参考 字节服务 。
Range: bytes=500-999 (500-)(-500)分别表示从文件的第500字节开始下载,到999字节(500之后的字节)(最后500个字节)。
Accept-Ranges: 这个服务器支持哪些种类的部分内容范围。
Accept-Ranges: bytes: 表示服务器支持bytes的的分段方式。
Content-Length: 回应消息体的长度,以 字节 (8位为一字节)为单位.
Content-Length: 348: 文件大小为348字节。
Content-Range: 这条部分消息是属于某条完整消息的哪个部分。
Content-Range: bytes 21010-47021/47022 表示服务器输出的文件是21010字节到47021字节,文件总大小47022字节。
首先需要一个文件随机读取的通用类,代码如下:
/** * 根据文件的起始未知和要读的字节书 * @param downloadFileName 要下载的文件名 * @param start 起始位置 * @param size 要读取的文件大小 * @param outputStream 文件输出的位置 */
public static void download(String downloadFileName,long start,long size,OutputStream outputStream) {
try{
//缓冲字节输出流
BufferedOutputStream out=new BufferedOutputStream(outputStream);
//注意FILEDIR为文件的根目录
RandomAccessFile raf=new RandomAccessFile(FILEDIR+"/"+new String(downloadFileName.getBytes("utf-8")), "r");
write(start, size, raf, out);
}catch (Exception e) {
e.printStackTrace();
}
}
/** * 将文件写入输出流 * @param start 文件起始位置(例如:RandomAccessFile.seek(5)是在第六的字节开始读的) * @param size 文件的大小 * @param raf 随机输入流 * @param out 输出流 */
private static void write(long start,long size,RandomAccessFile raf,BufferedOutputStream out){
try {
byte[] buffer=new byte[1024];
int byteRead=-1;
long readLength=0;
raf.seek(start);
while(readLength<size-1024){
byteRead=raf.read(buffer, 0, 1024);
readLength+=1024;
out.write(buffer,0,byteRead);
}
if (readLength<=size) {
byteRead=raf.read(buffer, 0, (int)(size-readLength));
out.write(buffer,0,byteRead);
}
out.flush();
} catch (IOException e) {
e.printStackTrace();
}finally {
try{
if (raf!=null) {
raf.close();
}
}catch (IOException ex) {
}
try {
if (out!=null) {
out.close();
}
} catch (IOException ex) {
}
}
}
接下来编写servlet
@Override
protected void doGet(HttpServletRequest request,HttpServletResponse response) throws ServletException,IOException{
//要下下载的文件名
String downloadFileName=request.getParameter("filename");
response.reset();
//告诉客户端支持的下载方式
response.setHeader("Accept-Ranges", "bytes");
String[] sizes=getLength(request, response);
File file=new File(FileUtil.FILEDIR+"/"+downloadFileName);
long start=0L;
long size=file.length();
if (sizes==null) {
//如果为空则Range的值为空,即没有指定下载哪一段文件
}else if (sizes.length==1) {
start=Long.parseLong(sizes[0])-1L;
size=size-Long.parseLong(sizes[0]+1L);
String contentRange = new StringBuffer(request.getHeader("Range")).append("/").append(size).toString();
contentRange = contentRange.replaceAll("=", " ");
response.setHeader("Content-Range", contentRange);
}else if (sizes.length==2) {
//因为RandomAccessFile.seek()是从start的下一个字节开始读的,所以需要-1
start = Long.parseLong(sizes[0]) - 1L;
String contentRange = new StringBuffer(request.getHeader("Range")).append("/").append(size).toString();;
contentRange = contentRange.replaceAll("=", " ");
response.setHeader("Content-Range", contentRange);
size = Long.parseLong(sizes[1]) - start + 1L;
}else {
return;
}
//为了方便测试,下载的文件名指定为a.jpg
response.setHeader("Content-disposition", String.format("attachment; filename=\"%s\"", "a.jpg"));
//告诉客户端文件的大小
response.addHeader("Content-Length", String.valueOf(size));
FileUtil.download(downloadFileName, start, size, response.getOutputStream());
}
private String[] getLength(HttpServletRequest request, HttpServletResponse response){
String[] sizes = null;
String range = request.getHeader("Range");
System.out.println("客户端请求: "+range);
if (range != null && !"".equals(range.trim())) {
response.setStatus(javax.servlet.http.HttpServletResponse.SC_PARTIAL_CONTENT);
range = range.replaceAll("bytes=", "").trim();
sizes = range.split("-");
}
return sizes;
}
使用迅雷精简版可以实现断点下载:下载时输出Range的数据
客户端请求: bytes=4103856-7108546
客户端请求: bytes=1850337-2601509
客户端请求: bytes=5606201-7108546
客户端请求: bytes=2601510-4103855
可以看出迅雷不是多个线程同时下载的,自动分块下载。(个人理解).