回到了北京,过的第一个十一,但是房子太贵,买不起。租房子也负担有点困难,只能搞个蜗居,女朋友辞掉了工作跟我来北京了,但是一时还找不到合适的工作,我也知道她有点苦,但是不对我说,她知道我的压力大,不过没有关系,会好起来的。今天下午学习。
SmartUpload是个小的工具包,原来是使用Servlet来实现文件的上传(可能又点不合适)
Servlet实现文件上传的原理:
<1>在用户提交请求以后,将文件和其他的表单信息编码并上传到服务器,服务器端将上传的内容进行解码,提取HTML中的信息,将文件数据存入到磁盘或者是数据库中
<2>浏览器编码
在向服务器提交请求的时候,浏览器要将大量的数据一同提交给Server端,而提交前,浏览器需要按照服务器端可以识别的方法对提交的内容进行编码,对于普通的表单,这钟编码很简单,通常是: field1=value2&field2=value2&… ,同时Servlet的API提供了对对这种编码方式解码的支持,只需要调用ServletRequest的方法就可以获取
这种编码方式( application/x-www-form-urlencoded )虽然简单,但对于传输大块的二进制数据显得力不从心,对于传输这类数据,浏览器采用了另一种编码方式,即 "multipart/form-data" 的编码方式,采用这种方式,浏览器可以很容易的表单内的数据和文件一起。这种编码方式先定义好一个不可能在数据中出现的字符串作为分界符,然后用它将各个数据段分开,而对于每个数据段都对应着 HTML 页面表单中的一个 Input 区,包括一个 content-disposition 属性,说明了这个数据段的一些信息,如果这个数据段的内容是一个文件,还会有 Content-Type 属性,然后就是数据本身。 这里,我们可以编写一个简单的 JSP(servlet) 来看到浏览器到底是怎样编码的。
<page contentType="text/html; charset=GBK"%>
<%@page import="java.io.*"%>
<%
System.out.println("-----");
int length = request.getContentLength();
byte[] buffer = new byte[length];
InputStream in = request.getInputStream();
int total = 0;
int once = 0;
while ((total < length) && (once >=0)) {
once = in.read(buffer,total,length);
total += once;
}
//ServletOutputStream os = response.getOutputStream();
//os.write(buffer);
//os.flush();
//os.close();
OutputStream os=new BufferedOutputStream(new FileOutputStream("F:\\Receive.log",true));
os.write(buffer,0,buffer.length);
os.flush();
os.close();
%>
下面是用IE来获取的文件头:
引用
-----------------------------7dada34409ca
Content-Disposition: form-data; name="uploadFile"; filename="C:\Documents and Settings\HP Pavilion\桌面\item.xml"
Content-Type: text/xml
这里 ---------------------------7d137a26e18 作为分界符。关于分界符的规则可以概况为两条:
除了最后一个分界符,每个分界符后面都加一个 CRLF 即 '\u000D' 和 '\u000A', 最后一个分界符后面是两个分隔符"--"
每个分界符的开头也要加一个 CRLF 和两个分隔符("-")。
浏览器采用默认的编码方式是 application/x-www-form-urlencoded ,可以通过指定 form 标签中的 enctype 属性使浏览器知道此表单是用 multipart/form-data 方式编码如:
< form action="/servlet/ReceiveServlet" ENCTYPE="multipart/form-data" method=post >
<3>提交请求
提交请求的过程由浏览器完成的,并且遵循 HTTP 协议,每一个从浏览器端到服务器端的一个请求,都包含了大量与该请求有关的信息, 在 Servlet 中,HttpServletRequest 类将这些信息封装起来,便于我们提取使用。在文件上载和表单提交的过程中,有两个指的关心的问题,一是上载的数据是是采用的那种方式的编码,这个问题的可以从 Content-Type 中得到答案,另一个是问题是上载的数据量有多少即 Content-Length ,知道了它,就知道了 HttpServletRequest 的实例中有多少数据可以读取出来。这两个属性,我们都可以直接从 HttpServletRequest 的一个实例中获得,具体调用的方法是 getContentType() 和 getContentLength() 。
Content-Type 是一个字符串,在上面的例子中,增加:
System.out.println(request.getContentType());
可以得到这样的一个输出字符串:
引用
multipart/form-data; boundary=---------------------------7d137a26e18
前半段正是编码方式,而后半段正是分界符,通过 String 类中的方法,我们可以把这个字符串分解,提取出分界符。
String contentType=request.getContentType();
int start=contentType.indexOf("boundary=");
int boundaryLen=new String("boundary=").length();
String boundary=contentType.substring(start+boundaryLen);
boundary="--"+boundary;
判断编码方式可以直接用 String 类中的 startsWith 方法判断。
if(contentType==null || !contentType.startsWith("multipart/form-data"))
这样,我们在解码前可以知道:
编码的方式是否是multipart/form-data
数据内容的分界符
数据的长度
<4>解码
解码对我们来说是整个上载过程最繁琐的一个步骤,经过以上的流程,我们可以得到一个包含有所有上载数据的一个字节数组和一个分界符,通过对 Receive.log 分析,还可以得到每个数据段中的分界符。而我们要得到以下内容:
提交的表单中的各个字段以及对应的值
如果表单中有 file 控件,并且用户选择了上载文件,则需要分析出字段的名称、文件在浏览器端的名字、文件的 Content-Type 和文件的内容。
具体解码过程也可以分为两个步骤:
- 将上载的数据分解成数据段,每个数据段对应着表单中的一个 Input 区。
- 对每个数据段,再进行分解,提出上述要求得到的内容。
有5个类:
SmartUpload.java ---上传核心类
File.java ---包装了上传的文件的一些基本信息,包括文件名称了,后缀名了,大小等
Files.java ---表示上传文件的集合,用它可以来获得上传文件的个数等
Request.java ---等同于HttpServletRequest,因为文件上传是form必须有两个固定的属性:
method="post"
enctype="miltipart/form-date"
对于enctype="miltipart/form-date"的表格,form中的input值是以二进制的形式传过去。是无法用request.getParameter();来获取表格的属性的,所以提供了这个方法来获取表格属性
SmartUploadException.java ---定义的异常类
介绍一下SmartUpload.java
初始化方法:主要是用当前页面的request,response,ServletContext来初始化SmartUpLoad类的类成员变量
有一下的几个初始化方法:
1.使用ServletConfig,HttpServletRequest,HttpServletResponse
public final void initialize(ServletConfig servletconfig, HttpServletRequest httpservletrequest, HttpServletResponse httpservletresponse)
throws ServletException
{
m_application = servletconfig.getServletContext();
m_request = httpservletrequest;
m_response = httpservletresponse;
}
2.使用PageContext
public final void initialize(PageContext pagecontext)
throws ServletException
{
m_application = pagecontext.getServletContext();
m_request = (HttpServletRequest)pagecontext.getRequest();
m_response = (HttpServletResponse)pagecontext.getResponse();
}
3.还有其他一个已经过时的初始化方法
可以看出上面的初始化方法没有很大的区别,异曲同工
使用上面的方法之一初始化之后,需要文件上传,smartUpload会把所有上传到的内容存放到一个字节数组中,可能是对上传小的文件有很搞的效率。
获取request的请求流的长度:
m_totalBytes = m_request.getContentLength();
使用request.getContentLength,对contentLength的解释:
Returns the length,in bytes, of the request body and made available by the input stream, or -1 if the length is not known.