5.1.8 实现动态文件下载

优质
小牛编辑
132浏览
2023-12-01

在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所示。

05

图5.5 列出待下载文件所在的目录

当单击“我的文章.doc”时,就会弹出如图5-6所示的下载对话框。

06

图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格式编码,否则,在下载对话框中无法正确显示中文名。