文档章节

Java实现文件断点下载。

奔跑的码农
 奔跑的码农
发布于 2017/06/02 15:15
字数 872
阅读 22
收藏 1
点赞 0
评论 0

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

可以看出迅雷不是多个线程同时下载的,自动分块下载。(个人理解).

© 著作权归作者所有

共有 人打赏支持
奔跑的码农
粉丝 10
博文 23
码字总数 28187
作品 0
海淀
程序员
sharding-jdbc源码分析—准备工作

原文作者:阿飞Javaer 原文链接:https://www.jianshu.com/p/7831817c1da8 接下来对sharding-jdbc源码的分析基于tag为源码,根据sharding-jdbc Features深入学习sharding-jdbc的几个主要特性...

飞哥-Javaer ⋅ 05/03 ⋅ 0

Drools7.x环境搭建与简单示例

Drools是个规则引擎的开源实现。官方文档比较长。这里提供一个方法,搭建最基本的Drools运行环境,并提供一个简单示例。 环境准备: JDK 至少需要JDK 1.5以上,JDK去这里下载:http://www.or...

supergao222 ⋅ 01/10 ⋅ 0

SWIG与JAVA 交互最全开发指南一

项目背景 最近开始研究做移动端项目,但是本人基本是做了五六年的c++的底层研发,对C++的研发可以说是驾轻就熟了,但是对于android还是属于刚入门阶段,虽然断断续续做移动端也做了一年,但是...

揽月凡尘 ⋅ 06/16 ⋅ 0

Jenkins 教程(一)实现自动化打包及邮件通知

个人不喜欢装腔作势一堆专业术语放上去,让大多数人看不懂来提升逼格(所谓的专家),所以我简单的介绍jenkins是干啥的。本文使用jenkins,就是让它把git仓库里的东西取出来,然后在jenkins容器...

FantJ ⋅ 05/26 ⋅ 0

Linux下Tomcat的搭建以及开机自启动设置

首先进行下JDK的配置:   1.查看下系统信息,确认是32位还是64位:uname -a   2.下载相应位数的jdk压缩包,传到Linux系统,这里提供一个32位和64位的下载链接:https://pan.baidu.com/s...

武哥_话神 ⋅ 04/27 ⋅ 0

gradle/Groovy语法

Groovy官网的介绍(http://www.groovy-lang.org/download.html#gvm) Gradle API 文档: https://docs.gradle.org/current/dsl/org.gradle.api.invocation.Gradle.html 深入理解Android(一)......

shareus ⋅ 04/27 ⋅ 0

ZK7.0.3中从MongoDB下载文件

问题 在完成Spring从MongoDB中下载文件之GridFS之后,现在需要在ZK7.0.3的ViewModel中下载该文件。 思路 先从MongoDB获取到,以及把转化为ZK的Filedownload能够使用的进行文件下载。 实现 导...

亚林瓜子 ⋅ 06/12 ⋅ 0

Spring-boot+Dubbo应用启停源码分析

背景介绍 Dubbo Spring Boot 工程致力于简化 Dubbo RPC 框架在Spring Boot应用场景的开发。同时也整合了 Spring Boot 特性: 自动装配 (比如: 注解驱动, 自动装配等). Production-Ready (比...

ralf0131 ⋅ 05/29 ⋅ 0

java程序员基础进阶篇,万丈高楼平地起

一.final,finally,finalize 三者区别 Final是一个修饰符: 当final修饰一个变量的时候,变量变成一个常量,它不能被二次赋值 当final修饰的变量为静态变量(即由static修饰)时,必须在声明这...

启示录是真的 ⋅ 05/26 ⋅ 0

ELK 环境准备---jre 1.8

因为使用ELK 版本为6.24 当前环境需要jdk1.8下运行, 一下为安装jdk1.8 linux环境下 1、下载tar.gz格式的软件包至本地服务器上;下载地址为: http://www.oracle.com/technetwork/java/javas...

focus_逸 ⋅ 05/10 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

浅谈springboot Web模式下的线程安全问题

我们在@RestController下,一般都是@AutoWired一些Service,由于这些Service都是单例,所以并不存在线程安全问题。 由于Controller本身是单例模式 (非线程安全的), 这意味着每个request过来,...

算法之名 ⋅ 今天 ⋅ 0

知乎Java数据结构

作者:匿名用户 链接:https://www.zhihu.com/question/35947829/answer/66113038 来源:知乎 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。 感觉知乎上嘲讽题主简...

颖伙虫 ⋅ 今天 ⋅ 0

Confluence 6 恢复一个站点有关使用站点导出为备份的说明

推荐使用生产备份策略。我们推荐你针对你的生产环境中使用的 Confluence 参考 Production Backup Strategy 页面中的内容进行备份和恢复(这个需要你备份你的数据库和 home 目录)。XML 导出备...

honeymose ⋅ 今天 ⋅ 0

JavaScript零基础入门——(九)JavaScript的函数

JavaScript零基础入门——(九)JavaScript的函数 欢迎回到我们的JavaScript零基础入门,上一节课我们了解了有关JS中数组的相关知识点,不知道大家有没有自己去敲一敲,消化一下?这一节课,...

JandenMa ⋅ 今天 ⋅ 0

火狐浏览器各版本下载及插件httprequest

各版本下载地址:http://ftp.mozilla.org/pub/mozilla.org//firefox/releases/ httprequest插件截至57版本可用

xiaoge2016 ⋅ 今天 ⋅ 0

Docker系列教程28-实战:使用Docker Compose运行ELK

原文:http://www.itmuch.com/docker/28-docker-compose-in-action-elk/,转载请说明出处。 ElasticSearch【存储】 Logtash【日志聚合器】 Kibana【界面】 答案: version: '2'services: ...

周立_ITMuch ⋅ 今天 ⋅ 0

使用快嘉sdkg极速搭建接口模拟系统

在具体项目研发过程中,一旦前后端双方约定好接口,前端和app同事就会希望后台同事可以尽快提供可供对接的接口方便调试,而对后台同事来说定好接口还仅是个开始、设计流程,实现业务逻辑,编...

fastjrun ⋅ 今天 ⋅ 0

PXE/KickStart 无人值守安装

导言 作为中小公司的运维,经常会遇到一些机械式的重复工作,例如:有时公司同时上线几十甚至上百台服务器,而且需要我们在短时间内完成系统安装。 常规的办法有什么? 光盘安装系统 ===> 一...

kangvcar ⋅ 昨天 ⋅ 0

使用Puppeteer撸一个爬虫

Puppeteer是什么 puppeteer是谷歌chrome团队官方开发的一个无界面(Headless)chrome工具。Chrome Headless将成为web应用自动化测试的行业标杆。所以我们很有必要来了解一下它。所谓的无头浏...

小草先森 ⋅ 昨天 ⋅ 0

Java Done Right

* 表示难度较大或理论性较强。 ** 表示难度更大或理论性更强。 【Java语言本身】 基础语法,面向对象,顺序编程,并发编程,网络编程,泛型,注解,lambda(Java8),module(Java9),var(...

风华神使 ⋅ 昨天 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部