几乎所有人的人都下载过文件,如音频、视频、软件等等,在各类论坛、网站也会有相应信息导出的功能,那么这种下载的功能是如何分类的呢?又是如何实现的呢?
对于第一个问题,我认为文件的下载分为两种:其一,信息动态生成,可能通过如时间节点、操作类型,而后导出相应的报表如txt、excel等;第二种,是下载现有文件(音视频、软件),这些文件通常会存储在相应的存储上,通过地址获得其字节流,而后下载。
不管是第一种,还是第二种,都有一个特征,就是需要将信息转化为字节流,而后输出,这里阐述使用浏览器下载,即通过HTTP的相应报文进行下载。
一、下载的基本流程
Request->File->Stream->Response
下载的流程如上所述,用户发出一个下载的请求,而后,后台接收这个请求,对请求的文件或数据进行包装,在通过response返回给用户。那么按照此流程,来看个真实的例子:
1.1 Baidu云盘
下面的截图,是在百度云空间下载文件时的request和response,可以看下其具体都有哪些信息:
Content-Disposition:这是最重要的一个字段,意思是,当客户需要请求的信息以文件的形式返回时,设置此字 段,在rfc2616以及rfc1806中有详细的论述,其中一段e文原文为:
The Content-Disposition response-header field has been proposed as a
means for the origin server to suggest a default filename if the user
requests that the content is saved to a file. This usage is derived
from the definition of Content-Disposition in RFC 1806 [35].
means for the origin server to suggest a default filename if the user
requests that the content is saved to a file. This usage is derived
from the definition of Content-Disposition in RFC 1806 [35].
An example is:
Content-Disposition:attachment; filename="fname.txt"
Content-Length:此字段,表示返回内容的长度
Content-Type: 此字段,表示返回的是何种类型的内容即MIME(媒体类型)
这样,根据这三个字段,我们知道了文件的大小、文件的类型、以及何种操作,就可以将这个文件保存下来了。
1.2 HTTP方式文件下载的根基
HTTP是应用层协议,并且HTTP协议选择了TCP协议作为其传输层协议,这样,也就保证了数据的可靠性。同时,这也是HTTP方式下载的根基,客户端与Server端建立了一条endpoint to endpoint的通道,用于传输信息,这条通道也是双向的。
二、Struts下载的实现
注:本文不叙述Struts的搭建过程,文章中,使用的版本为,2.3.9;
1th.查看Struts的帮助文档查看如下信息
在Struts中每个action会对应一个result,上图是result type的类型,可以看出,对应file dowload的设置是Stream,看下Stream类型的详细配置为:
可以看出,contentType、contentLength赫然在列,下面看下详细的操作
注:以上文档信息,在struts2的官方doc中均可找到,有需要可在附件中下载
2.1 Struts文件下载的相关配置
struts.xml
- <?xml version="1.0" encoding="UTF-8"?>
- <!DOCTYPE struts PUBLIC
- "-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
- "http://struts.apache.org/dtds/struts-2.0.dtd">
- <struts>
- <package name="a" extends="struts-default">
- <action name="download" class="com.hzy.struts2.download.FileDownLoad">
- <result name="success" type="stream">
- <param name="contentLength">${fileLength}</param>
- <param name="contentType">text/plain;charset=UTF-8</param>
- <param name="contentDisposition">attachment;filename="${fileName}"</param>
- <param name="inputName">inputStream</param>
- </result>
- </action>
- <action name="downloadFile" class="com.hzy.struts2.download.FileDownLoadFile">
- <result name="success" type="stream">
- <param name="contentType">application/octet-stream</param>
- <param name="contentDisposition">attachment;filename="${fileName}"</param>
- <param name="inputName">inputStream</param>
- <param name="buffserSize">4096</param>
- </result>
- </action>
- </package>
- </struts>
- package com.hzy.struts2.download;
- import java.io.InputStream;
- import org.apache.struts2.ServletActionContext;
- import com.opensymphony.xwork2.ActionSupport;
- public class FileDownLoadFile extends ActionSupport
- {
- private String fileName;
- private InputStream inputStream;
- public void setFileName( String fileName )
- {
- this.fileName = fileName + ".exe";
- }
- public String getFileName( )
- {
- return this.fileName;
- }
- public InputStream getInputStream()
- {
- inputStream = ServletActionContext.getServletContext().getResourceAsStream( "/" + fileName );
- if( inputStream == null )
- {
- System.out.println( "getResource error!" );
- }
- System.out.println( "FileName : " + fileName );
- return inputStream;
- }
- @Override
- public String execute() throws Exception
- {
- return SUCCESS;
- }
- }
- package com.hzy.struts2.download;
- import java.io.ByteArrayInputStream;
- import java.io.InputStream;
- import com.opensymphony.xwork2.ActionSupport;
- public class FileDownLoad extends ActionSupport
- {
- private String fileName;
- private int fileLength;
- public void setFileName( String start )
- {
- this.fileName = start;
- }
- public void setFileLength( int fileLength )
- {
- this.fileLength = fileLength;
- }
- public String getFileName( )
- {
- return this.fileName;
- }
- public int getFileLength()
- {
- return this.fileLength;
- }
- public InputStream getInputStream()
- {
- String content = "Hahahahaha : " + fileName;
- this.setFileLength( content.getBytes().length );
- System.out.println( "The File Size : " + content.getBytes().length );
- return new ByteArrayInputStream( content.getBytes() );
- }
- @Override
- public String execute() throws Exception
- {
- return SUCCESS;
- }
- }
以上是,structs单文件下载的一个demo,download.acion模拟的是动态生成一些信息,然后通过stream流输出到前台,并以文本形式报文;downloadFile.action则是,下载服务器端的一个文件,这里下载的是Firefox.exe(20MB)的一个安装包。
无论是哪种形式,都需要指定类型、长度大小等信息;在structs中这些关键的配置信息已经被固化到了struts.xml中,并且配置比较灵活。
注:这里当下载Firxfox.exe时,并为指定content-Length,这里涉及到另一个协议,HTTP TRUNKED协议,该协议当content-Length未指定的时候,会对数据进行分片传输,并以0结尾,标示文件传输结束。对于该协议的具体细节将会单独开一篇讨论!
同时,当文件大小不可知或未能明确给出时,会在http的响应报文中,给出Transfer-Encoding chunked字段;同时content-Length与Transfer-Encoding:chunked只能存在其一。
下图是按上述demo做的截图,两个文件下载的访问方式如下(根据实际情况,酌情修改):
txt:
exe:
下载文本:http://localhost:8080/Struts_DownFile_/download?fileName=abc
下载文件:http://localhost:8080/Struts_DownFile_/downloadFile?fileName=Firefox
三、小结
本文初次形成,比较粗糙,如struts缓冲区大小配置等并没有详细论述,而且文件下载一些拓展功能也并没叙述:批量文件下载、上传、断点下载、上传等;这些在后续的文章中会逐步补充,同时,每篇文章都会维护更新的,欢迎大家提出意见。
诚挚感谢温大的文章,解决了我的困惑:http://my.oschina.net/u/866190/blog/170243
四、附件下载
Struts2(源码、框架、doc):http://pan.baidu.com/s/1gP1ct