前面我们刚刚学完文件上传,那么我们就接着来看和文件上传相对应的文件下载吧。对于文件上传而言,文件下载实现起来要简单的多。通常我们可以直接将一个超链接的地址指向我们想要给用户下载的资源即可。但是如果这些资源是浏览器能够解析的文件类型,比如html文件等,那么浏览器将不会提示用户下载这些文件了,而是会直接在浏览器中打开。这样做还有一个问题,那就是我们的资源文件的地址就直接暴露给用户了,它可以在其他的页面中被轻易的引用,这往往让我们觉得不太安全。那么就需要通过某种手段来告诉浏览器,让它提示用户下载我们的资源文件,并且将这些资源文件的真实地址隐藏起来。
使用 Jsp/Servlet 实现文件下载
首先我们使用Jsp和Servlet来完成我们上面手的文件下载功能。
public class FileDownServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
doPost(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
File file = new File(getServletContext().getRealPath("/down")
+ "/AdobeReader8.rar");
FileInputStream in = new FileInputStream(file);
OutputStream out = resp.getOutputStream();
resp.setContentType("Application/Octet-stream;charset=utf-8");
resp.addHeader("Content-Disposition",
"attachment; filename="+file.getName());
resp.addHeader("Content-Length::", file.length() + "");
byte[] b = new byte[1024];
int len = 0;
while ((len = in.read(b)) != -1) {
out.write(b, 0, len);
}
out.close();
in.close();
}
}
Input.jsp
< body >
< a href = "FileDownServlet" > 点击下载 </ a >
</ body >
测试(打开浏览器,点击链接):
可以看到,这样一来,即使是文本文件,也可以提供给用户下载,并且很好的隐藏了文件资源的真实地址。因为文件的真实地址是我们在文件中通过某种方式获得的,并且是以流的形式输出到浏览器,那么浏览器看到的就只有当前jsp文件的地址。我们可以通过在链接上传一些参数(比如文件的id号),然后在Servlet中通过该Id号到数据库中去查询文件的真实的值和名字,也可通过算法直接算出真实地址。
这样虽然可以对文件进行下载了,但是它不支持断点续传功能,即下载的过程不能中断,一旦中断就需要重新下载了。
支持断点续传
实现断点续传的基本原理就是在请求头中加上一个Range参数,这个参数告诉流从什么地方开始下载,如果Rang参数为空或者0那么表示从头开始下载,如果参数有值,表示从流中跳过这么多字节再下载。如果服务端查询到是从头开始下载,那么就应该将响应头状态设置为200,如果是续传,那么应该将响应头设置为206.然后设置头参数Accept-Ranges的值为bytes,说明以字节流的方式传输,是下载文件。接着设置响应字节跳转值,Content-Range属性的值 这个值的格式为 "请求参数Range-流总长度减去1/流总长度"
最后开始向客户端传输响应流,这个流用OutputStream.skip(intp)方法跳过请求参数Range数值进行传输。我们将上面的例子改为支持断点续的服务端:
public class FileDownServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
doPost(req, resp);
}
@Override
protected void doPost(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
File file = new File(getServletContext().getRealPath("/down")
+ "/Access2003.exe");
if (file.exists()) {
long p = 0;
long fileLength;
fileLength = file.length();
InputStream in = new FileInputStream(file);
response.reset();
response.setHeader("Accept-Ranges", "bytes");
// 查看是否头信息中包含Range参数
if (request.getHeader("Range") != null) {
// 设置相应头状态(206)
response
.setStatus(javax.servlet.http.HttpServletResponse.SC_PARTIAL_CONTENT);
// 通过头信息获取Range参数的(整数)值
p = Long.parseLong(request.getHeader("Range").replaceAll(
"bytes=", "").replaceAll("-", ""));
}
response.setHeader("Content-Length", new Long(fileLength - p)
.toString());
if (p != 0) {
// 断点开始
// 响应的格式是:
// Content-Range: bytes [文件块的开始字节]-[文件的总大小 - 1]/[文件的总大小]
String contentRange = new StringBuffer("bytes ").append(
new Long(p).toString()).append("-").append(
new Long(fileLength - 1).toString()).append("/")
.append(new Long(fileLength).toString()).toString();
response.setHeader("Content-Range", contentRange);
// 跳过指定数量的字节
in.skip(p);
}
response.addHeader("Content-Disposition", "attachment;filename="
+ file.getName());
byte[] buf = new byte[1024];
int len = 0;
// 开始写入数据
while ((len = in.read(buf)) != -1) {
response.getOutputStream().write(buf, 0, len);
response.getOutputStream().flush();
}
in.close();
}
}
Input.jsp页面保持不变。
测试(下载工具需要支持断点续传,这里我使用快车):
通过从上面的两张图中可以看出,从头下载和续传下载在请求头上的区别。
使用 Struts2 实现文件下载
Struts2中提供了一种叫做stream的结果类型,通过它可以直接将一个InputStream传送给它,它就会自动的提供给客户端下载。原来一些我们在程序中使用代码设置的头信息,现在可以在Action的配置文件中配置了(当然也可以通过代码设置)。下面我们就动手做一个实例吧:
public class DownAction extends ActionSupport implements ServletContextAware {
private int id;
private InputStream inputStream;
private ServletContext context;
private String fileName;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getFileName() {
return fileName;
}
public void setFileName(String fileName) {
this.fileName = fileName;
}
public InputStream getInputStream() {
fileName = id + ".exe";
return context.getResourceAsStream("/down/" + id + ".exe");
}
public void setInputStream(InputStream inputStream) {
this.inputStream = inputStream;
}
public void setServletContext(ServletContext context) {
this.context = context;
}
}
down.jsp
< body >
< a href = "down.action?id=1" > 下载地址1 </ a >< br />
< a href = "down.action?id=2" > 下载地址2 </ a >
</ body >
struts.xml
< action name = "down" class = "action.DownAction" >
< result name = "success" type = "stream" >
< param name = "contentType" > application/octet-stream </ param >
< param name = "contentDisposition" > filename=${fileName} </ param >
</ result >
</ action >
测试:
配置文件中这些关于响应参数的设置也可以通过StreamRsult类提供的方法来设置。
void | setAllowCaching (boolean allowCaching) |
void | setBufferSize (int bufferSize) |
void | setContentCharSet ( String contentCharSet) |
void | setContentDisposition ( String contentDisposition) |
void | setContentLength ( String contentLength) |
void | setContentType ( String contentType) |
void | setInputName ( String inputName) |
Struts2文件下载要实现断点续传,思路和我们前面使用jsp/servlet实现断点断点续传的思路相同。主要就是请求头参数和响应头参数的处理。断点续传的时候,下载位置由客户端来提供的,那么我们在服务器端只要具有能够根据客户端请求参数来移动输入流指针位置的功能,就能够实现断点续传。当然,上面只是我自己一些理解和看法,如有错误还希望路过的老鸟指正,感激不尽!