




版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
1、用 Servlet 进行文件上传的原理和实现Servlet 是用 Java 编写的、协议和平台都独立的服务器端组件,使用请求 / 响应的模式,提供了一个基于 Java 的服务器解决方案。 使用 Servlet 可以方便地处理在 HTML 页面表单中提交的数据,但 Servlet 的 API 没有提供对以 mutilpart/form-data 形式编码 的表单进行解码的支持,因而对日常应用中经常涉及到到文件上传等事务无能为力。如何用 Servlet 进行文件的上传,必须编程实现。一、基本原理 通过 HTML 上载文件的基本流程如下图所示。浏览器端提供了供用户选择提交内容的界面(通常是一个表单)
2、,在用户提交请求后,将文件数据和其他表单信息编码并上传至服务 器端,服务器端(通常是一个 cgi 程序)将上传的内容进行解码了,提取出 HTML 表单中的信息,将文件数据存入磁盘或数据库、各过程详解A)填写表单并提交通过表单提交数据的方法有两种,一种是 GET 方法,另一种是 POST 方法,前者通常用于提交少量的数据,而在上传文件或大量 数据时,应该选用 POST 方法。在 HTML 代码中,在 标签中添加以下代码可以页面上显示一个选择文件的控件。在页面中显示如下(可能随浏览器不同而不同)可以直接在文本框中输入文件名,也可以点击按钮后弹出供用户选择文件的对话框。B)浏览器编码在向服务器端提交
3、请求时,浏览器需要将大量的数据一同提交给 Server 端, 而提交前, 浏览器需要按照 Server 端可以识别的 方式进行编码,对于普通的表单数据,这种编码方式很简单,编码后的结果通常是 field1=value2&field2=value2& 的形式,如 name=aaaa&Submit=Submi。t这种编码的具体规则可以在 rfc2231 里查到, 通常使用的表单也是采用这种方式编码的, Servlet 的 API 提供了对这种编码方 式解码的支持,只需要调用 ServletRequest 类中的方法就可以得到用户表单中的字段和数据。这种编码方式( application/x-www
4、-form-urlencoded )虽然简单,但对于传输大块的二进制数据显得力不从心,对于传输这类 数据,浏览器采用了另一种编码方式,即 multipart/form-data 的编码方式,采用这种方式,浏览器可以很容易的表单内的数据和 文件一起。这种编码方式先定义好一个不可能在数据中出现的字符串作为分界符,然后用它将各个数据段分开,而对于每个数据段都 对应着 HTML 页面表单中的一个 Input 区,包括一个 content-disposition 属性,说明了这个数据段的一些信息,如果这个数据段 的内容是一个文件,还会有 Content-Type 属性,然后就是数据本身。这里,我们可以编
5、写一个简单的 Servlet 来看到浏览器到底是怎样编码的 实现流程:重载 HttpServlet 中的 doPost 方法buffer 。调用 request.getContentLength() 得到 Content-Length ,并定义一个与 Content-Length 大小相等的字节数组 从 HttpServletRequest 的实例 request 中得到一个 InputStream, 并把它读入 buffer 中。使用 FileOutputStream 将 buffer 写入指定文件。代码清单/ ReceiveServlet.javaimport java.io.*;impo
6、rt javax.servlet.*;import javax.servlet.http.*;/ 示例程序:记录下 Form 提交上来的数据,并存储到 Log 文件中public class ReceiveServlet extends HttpServletpublic void doPost(HttpServletRequest request,HttpServletResponse response)throws IOException, ServletException/1int len = request.getContentLength();byte buffer = new by
7、telen;/2InputStream in = request.getInputStream();int total = 0;int once = 0;while (total =0) once = in.read(buffer,total,len);total += once;/3OutputStream out=new BufferedOutputStream( new FileOutputStream(Receive.log,true); byte breaker=rnNewLog: rn.getBytes();System.out.println(request.getContent
8、Type(); out.write(breaker,0,breaker.length);out.write(buffer);out.close();in.close();在使用 Opera 作为浏览器测试时,从指定的文件( Receive.log )中可以看到如下的内容 -_OPERAB_-T/DQLi2fn47+D52OOrpdrzContent-Disposition: form-data; name=id id00-_OPERAB_-T/DQLi2fn47+D52OOrpdrzContent-Disposition: form-data; name=file3; filename=Aut
9、oexec.bat Content-Type: application/octet-streamecho offprompt $d $t $p $_$-_OPERAB_-T/DQLi2fn47+D52OOrpdrz-这里 _OPERAB_-T/DQLi2fn47+D52OOrpdrz就 是浏览器指定的分界符,不同的浏览器有不同的确定分界符的方法,但都需要保证分界 符不会在文件内容中出现。下面是用 IE 进行测试的结果7d137a26e18Content-Disposition: form-data; name=name123 7d137a26e18Content-Disposition: fo
10、rm-data; name=introduce I am.I am. 7d137a26e18Content-Disposition: form-data; name=file3; filename=C:Autoexec.bat Content-Type: application/octet-streamecho offprompt $d $t $p $_$SET PATH=d:pfIBMVJava2eabbin;%PATH%;D:PFROSE98ICOMMON 7d137a26e18-这里 7d137a26e18 作为分界符。关于分界符的规则可以概况为两条: 除了最后一个分界符,每个分界符后面
11、都加一个 CRLF 即 u000D 和 u000A, 最后一个分界符后面是两个分隔符 - 每个分界符的开头也要加一个 CRLF 和两个分隔符( - )。浏览器采用默认的编码方式是 application/x-www-form-urlencoded ,可以通过指定 form 标签中的 enctype 属性使浏览器知道此 表单是用 multipart/form-data 方式编码如:C) 提交请求提交请求的过程由浏览器完成的,并且遵循 HTTP 协议,每一个从浏览器端到服务器端的一个请求,都包含了大量与该请求有关 的信息, 在 Servlet 中, HttpServletRequest 类将这些信
12、息封装起来,便于我们提取使用。在文件上载和表单提交的过程中,有两个关心的问题,一是上载的数据是是采用的那种方式的编码,这个问题的可以从Content-Type 中得到答案,另一个是问题是上载的数据量有多少即 Content-Length ,知道了它,就知道了 HttpServletRequest 的 实例中有多少数据可以读取出来。这两个属性,我们都可以直接从 HttpServletRequest 的一个实例中获得,具体调用的方法是 getContentType() 和 getContentLength() 。Content-Type 是一个字符串,在上面的例子中,增加System.out.pr
13、intln(request.getContentType();可以得到这样的一个输出字符串:7d137a26e18multipart/form-data ; boundary =-前半段正是编码方式,而后半段正是分界符 ;任务分解上述的字符串 , 取出分界符。通过 String 类中的方法,我们可以把这个字符串分解,提取出分界符。String contentType = request .getContentType( );int start = contentType .indexOf(boundary=);int boundaryLen = new String(boundary=).le
14、ngth();String boundary = contentType .substring(start+boundaryLen); boundary = - + boundary;判断编码方式可以直接用 String 类中的 startsWith 方法判断。if(contentType=null | !contentType.startsWith(multipart/form-data)这样,我们在解码前可以知道:编码的方式是否是 multipart/form-data 数据内容的分界符数据的长度我们可以用类似于 ReceiveServlet 中的方式将这个请求的输入流读入一个长度为 Co
15、ntent-Length 的字节数组,接下来就是将这个 字节数组里的内容全部提取出来了。D)解码 解码对我们来说是整个上载过程最繁琐的一个步骤,经过以上的流程,我们可以得到一个包含有所有上载数据的一个字节数组和一个 分界符,通过对 Receive.log 分析,还可以得到每个数据段中的分界符。而我们要得到以下内容:提交的表单中的各个字段以及对应的值如果表单中有 file 控件,并且用户选择了上载文件, 则需要分析出字段的名称、 文件在浏览器端的名字、 文件的 Content-Type 和文件的内容。字节数组的内容可以分解如下:具体解码过程也可以分为两个步骤:将上载的数据分解成数据段,每个数据段
16、对应着表单中的一个 Input 区 对每个数据段,再进行分解,提出上述要求得到的内容。这两个步骤主要的操作有两个,一个是从一个数组中找出另一个数组的位置,类似于 String 类中的 indexOf 的功能,另一个是从一 个数组中提取出另一个数组, 类似于 String 类中的 substring 的功能,为此我们可以专门写两个方法,实现这种功能。int byteIndexOf (byte source,byte search,int start) byte subBytes(byte source,int from,int end)为了便于使用,可以从这两个方法中衍生出下列方法int byt
17、eIndexOf (byte source,String search,int start)以一个 String 作为搜索对象参数String subBytesString(byte source,int from,int end) 直接返回一个 Stringint bytesLen(String s)返回字符串转化为字节数组后,字节数组的长度这样,从一个字节数组中,根据标记提取出另一个字节数组可以表示如下:假设我们已经将数据存入字节数组 buffer 中,分界符存入 String boundary 中int pos1=0;/pos1/pos0,pos1记录 在 buffer 中下一个 bou
18、ndary 的位置用于 subBytes 的两个参数int pos0=byteIndexOf(buffer,boundary,0);/pos0记录 boundary 的第一个字节在 buffer 中的位置do pos0+=boundaryLen;/ 记录 boundary 后面第一个字节的下标 pos1=byteIndexOf(buffer,boundary,pos0);if (pos1=-1)break;pos0+=2; / 考虑到 boundary 后面的 rn PARSE(subBytes(buffer,pos0,pos1-2);/ 考虑到 boundary 后面的 rn pos0=po
19、s1;数组while(true);其中 PARSE 部分是对每一个数据段进行解码的方法,考虑到 Content-Disposition 等属性,首先定义一个 StringString tokens= name= ,; filename= , rn , Content-Type: , rnrn ;个文件数据段,则包含所有的元素。第0,并且 postion1 应该小于 postion2 即对于一个不是文件的数据段,只可能有 tokens 中的第一个元素和最后一个元素,如果是 步先得到 tokens 中每个元素在这个数据段中的位置int position=new inttokens.length;f
20、or (int i=0;i 0 & position1 position2如果为真,则为一个文件数据段,1. 得到字段名String name =subBytesString(buffer,position0+bytesLen(tokens0),position1);2. 得到文件名String file= subBytesString(buffer,position1+bytesLen(tokens1),position2);3. 得到 Content-TypeString contentType=subBytesString(buffer,position3+bytesLen(tokens
21、3),position4);4. 得到文件内容byte b=subBytes(buffer,position4+bytesLen(tokens4),buffer.length); 否则,说明数据段是一个 name/value 型的数据段,且 name 在 tokens0 和 tokens2 之间, value 在 tokens4 之后/1. 得到 nameString name =subBytesString(buffer,position0+bytesLen(tokens0),position2);/2. 得到 valueString value= subBytesString(buffer
22、,position4+bytesLen(tokens4),buffer.length);回页首三、具体实现为便于使用,定义 upload 包,包括以下类:ContentFactory对从 client 中传来的数据进行解码,并提供一系列 get 方法,从中得到上传的各种信息 具体接口如下staticContentFactorygetContentFactory (javax.servlet.http.HttpServletRequestrequest)返回根据当前请求生成的一个 ContentFactory 实例staticContentFactorygetContentFactory (ja
23、vax.servlet.http.HttpServletRequestrequest, intmaxLength) 返回根据当前请求生成的一个 ContentFactory 实例FileHoldergetFileParameter (java.lang.Stringname)返回一个 FileHolder 实例,该实例包含了通过字段名为 name 的 file 控件上载的文件信息,如果不存在 这个字段或者提交页面时,没有选择上载的文件,则返回 null 。java.util.EnumerationgetFileParameterNames ()返回一个 由 String 对象构成的 Enume
24、ration ,包含了 Html 页面窗体中所有 file 控件的 name 属性。FileHoldergetFileParameterValues (java.lang.Stringname)返回一个 FileHolder 数组,该数组包含了所有通过字段名为 name 的 file 控件上载的文件信息,如果不 存在这个字段或者提交页面时,没有选择任何上载的文件,则返回一个零元素的数组(不是 null ) 。java.lang.StringgetParameter (java.lang.Stringname)以 String 类型返回请求的参数的值 , 如果该参数不存在,则返回为 null 。
25、参数存于提交的表单数据中。java.util.EnumerationgetParameterNames ()返回一个 String 类型的 Enumeration 对象,该对象包含了所有提交请求的参数名称。java.lang.StringgetParameterValues (java.lang.Stringname)返回 String 类型的数组,该数组包含了指定名称的参数对应的所有的值, 如果参数不存在, 则返回为 null 。FileHolder封装一个文件数据段,可以从中提取文件名, Content-Type 和文件内容等属性。 接口如下 :bytegetBytes ()返回一个文件内容的字节数组java.lang.StringgetContentType () 返回该文件的 Content-Typejava.lang.StringgetFileName
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 专业医院陪护管理办法
- 萧山景观草坪管理办法
- 专利转化资金管理办法
- 中小企业收入管理办法
- 网络云维护管理办法
- 个人授信业务管理办法
- 社区团支部管理办法
- 社会性监考管理办法
- 粉末原材料管理办法
- 仓储智能配送管理办法
- 2025年理赔专业技术职务任职资格考试(理赔员·车险理赔)历年参考题库含答案详解(5套)
- 广东省东莞市2024-2025学年八年级下学期7月期末考试英语试卷(含答案)
- 医院设备采购培训课件
- 2025年科创板开通考试试题及答案
- 保护患者合法权益培训课件
- 2025年湖南省中考语文试卷
- 2025年汽车修理工(高级)实操考试题带答案
- 乡村文化振兴培训课件
- 2025年秋沪科版八年级数学上册 第11章综合测试卷
- 《产品三维建模与结构设计(UG)》技工全套教学课件
- 昭阳区生活垃圾管理办法
评论
0/150
提交评论