5.1.8 实现动态文件下载
在Web服务器上实现文件下载功能很容易。只要将URL指向要下载的文件即可。但是这要有一个前提,就是要下载的文件必须位于在Web服务器中部署的Web目录中。但有时需要在下载文件之前做一些其他的事,如验证用户是否有权限下载该文件。在这种情况下,就必须通过动态下载的方式(也就是通过程序来读取待下载的文件,而不是直接由Web服务器负责下载)来实现。
下面的例子演示了如何通过Servlet实现动态下载文件的功能。
例子 : 实现动态下载文件
1. 实例说明
在本例中将待下载的文件放到了非Web目录中(在web.xml中设置),使客户端无法直接访问待下载的文件。然后通过一个Servlet进行中转,如果待下载的文件存在,通过FileInputStream对象打开这个文件,并通过ServletOutputStream对象将待下载的文件按字节流的方式输出到客户端,如果待下载的文件扩展名是“.jpg”,则直接在浏览器中显示该图象。该程序还有一个功能,就是列出在web.xml文件中指定的目录中的所有文件(带链接)。只需要直接点击相应的文件就可下载或显示该文件的内容。
2. 编写Download类
该类负责列目录和下载文件,实现代码如下:
package chapter5;
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
import java.net.*;
public class Download extends HttpServlet
{
private void download(File file, HttpServletResponse response)
throws IOException
{
if (file.exists())
{
// 当扩展名是.jpg时,在浏览器中显示图象,而不是下载这个图象文件
if ((file.getName().length() - file.getName().lastIndexOf(".jpg")) == 4)
{
response.setContentType("image/jpeg");
response.addHeader("Content-Disposition",
"filename=" + URLEncoder.encode(file.getName(), "UTF-8"));
}
// 设置要下载的文件的名称、Content-Type和长度
else
{
response.setContentType("application/octet-stream");
response.addHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(file.getName(), "UTF-8"));
}
response.addHeader("Content-Length", String.valueOf(file.length()));
InputStream is = new FileInputStream(file);
byte[] buffer = new byte[8192]; // 每次向客户端发送8K字节
int count = 0;
ServletOutputStream sos = response.getOutputStream();
// 向客户端输出下载文件的内容,每次输出8K字节
while ((count = is.read(buffer)) > 0)
sos.write(buffer, 0, count);
is.close();
sos.close();
}
}
// 输出path初始化参数指定的目录中的文件
private void listDir(File dir, HttpServletResponse response)
throws IOException
{
response.setContentType("text/html;charset=UTF-8");
PrintWriter out = response.getWriter();
// 扫描目录的子目录和文件
for (File file : dir.listFiles())
{
// 如果是文件,输出文件名和其对应的URL
if (file.isFile())
{
out.print("<a href='Download?filename=" + URLEncoder.encode(file.getName(), "UTF-8") + "'>");
out.println(file.getName() + "</a><br/>");
}
}
}
public void service(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException
{
// 读取path初始化参数的值
String path = this.getServletConfig().getInitParameter("path");
String filename = request.getParameter("filename");
File dir = new File(path);
if (dir.exists())
{
if (filename != null)
{
filename = dir.getPath() + File.separator + filename;
File downloadFile = new File(filename);
download(downloadFile, response); // 下载文件
}
else
{
listDir(dir, response); // 列出文件目录
}
}
}
}
3. 配置Download类和path参数
path是Download类的初始化参数,需要在<servlet>元素中配置,代码如下:
<servlet>
<servlet-name>Download</servlet-name>
<servlet-class>chapter5.Download</servlet-class>
<!-- 配置path初始化参数 -->
<init-param>
<param-name>path</param-name>
<param-value>D:\download\</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>Download</servlet-name>
<url-pattern>/Download</url-pattern>
</servlet-mapping>
其中path表示要下载文件所在的目录,读者也可以指定其他存在的目录。
4. 测试程序
在浏览器地址栏中输入如下的URL:
http://localhost:8080/demo/servlet/Download
在浏览器中会列出D:\download目录中的所有文件,如图5-5所示。
图5.5 列出待下载文件所在的目录
当单击“我的文章.doc”时,就会弹出如图5-6所示的下载对话框。
图5.6 下载对话框
5. 程序总结
在编写上面的代码时,应注意如下几点:
(1)在下载文件时必须设置Content-Type和Content-Disposition字段。其中Content-Type字段的值是application/octet-stream,表示下载的是二进制字节流。而Content-Disposition字段的值有两部分组成,其中attachment表示下载的是附件,也就是说,浏览器会弹出一个下载对话框。而后面的filename部分设置了下载对话框中显示的默认文件名。如果不设置Content-Disposition字段,要下载的文件将直接在浏览器中打开。
(2)如果要在浏览器中显示某些类型的文件,需要将Content-Type字段值设成相应的MIME类型,如本例中要显示jpg格式的图象,则该字段的值为image/jpeg。但要注意, Content-Disposition字段中不能有attachment,否则浏览器会下载这个jpg文件,而不会显示它。
(3)如果下载的文件名中包含中文,在设置Content-Disposition中的filename时,应使用java.net.URLEncoder.encode方法将文件名按UTF-8格式编码,否则,在下载对话框中无法正确显示中文名。