用 Servlet 进行文件上传的原理和实现.docx

上传人:b****5 文档编号:8026608 上传时间:2023-01-28 格式:DOCX 页数:12 大小:37.74KB
下载 相关 举报
用 Servlet 进行文件上传的原理和实现.docx_第1页
第1页 / 共12页
用 Servlet 进行文件上传的原理和实现.docx_第2页
第2页 / 共12页
用 Servlet 进行文件上传的原理和实现.docx_第3页
第3页 / 共12页
用 Servlet 进行文件上传的原理和实现.docx_第4页
第4页 / 共12页
用 Servlet 进行文件上传的原理和实现.docx_第5页
第5页 / 共12页
点击查看更多>>
下载资源
资源描述

用 Servlet 进行文件上传的原理和实现.docx

《用 Servlet 进行文件上传的原理和实现.docx》由会员分享,可在线阅读,更多相关《用 Servlet 进行文件上传的原理和实现.docx(12页珍藏版)》请在冰豆网上搜索。

用 Servlet 进行文件上传的原理和实现.docx

用Servlet进行文件上传的原理和实现

用Servlet进行文件上传的原理和实现

Servlet是用Java编写的、协议和平台都独立的服务器端组件,使用请求/响应的模式,提供了一个基于Java的服务器解决方案。

使用Servlet可以方便地处理在HTML页面表单中提交的数据,但Servlet的API没有提供对以mutilpart/form-data形式编码的表单进行解码的支持,因而对日常应用中经常涉及到到文件上传等事务无能为力。

如何用Servlet进行文件的上传,必须编程实现。

一、基本原理

通过HTML上载文件的基本流程如下图所示。

浏览器端提供了供用户选择提交内容的界面(通常是一个表单),在用户提交请求后,将文件数据和其他表单信息编码并上传至服务器端,服务器端(通常是一个cgi程序)将上传的内容进行解码了,提取出HTML表单中的信息,将文件数据存入磁盘或数据库。

二、各过程详解

A)填写表单并提交

通过表单提交数据的方法有两种,一种是GET方法,另一种是POST方法,前者通常用于提交少量的数据,而在上传文件或大量数据时,应该选用POST方法。

在HTML代码中,在标签中添加以下代码可以页面上显示一个选择文件的控件。

在页面中显示如下(可能随浏览器不同而不同)

可以直接在文本框中输入文件名,也可以点击按钮后弹出供用户选择文件的对话框。

B)浏览器编码

在向服务器端提交请求时,浏览器需要将大量的数据一同提交给Server端,而提交前,浏览器需要按照Server端可以识别的方式进行编码,对于普通的表单数据,这种编码方式很简单,编码后的结果通常是field1=value2&field2=value2&…的形式,如name=aaaa&Submit=Submit。

这种编码的具体规则可以在rfc2231里查到,通常使用的表单也是采用这种方式编码的,Servlet的API提供了对这种编码方式解码的支持,只需要调用ServletRequest类中的方法就可以得到用户表单中的字段和数据。

这种编码方式(application/x-www-form-urlencoded)虽然简单,但对于传输大块的二进制数据显得力不从心,对于传输这类数据,浏览器采用了另一种编码方式,即"multipart/form-data"的编码方式,采用这种方式,浏览器可以很容易的表单内的数据和文件一起。

这种编码方式先定义好一个不可能在数据中出现的字符串作为分界符,然后用它将各个数据段分开,而对于每个数据段都对应着HTML页面表单中的一个Input区,包括一个content-disposition属性,说明了这个数据段的一些信息,如果这个数据段的内容是一个文件,还会有Content-Type属性,然后就是数据本身。

这里,我们可以编写一个简单的Servlet来看到浏览器到底是怎样编码的。

实现流程:

∙重载HttpServlet中的doPost方法

∙调用request.getContentLength()得到Content-Length,并定义一个与Content-Length大小相等的字节数组buffer。

∙从HttpServletRequest的实例request中得到一个InputStream,并把它读入buffer中。

∙使用FileOutputStream将buffer写入指定文件。

代码清单

//ReceiveServlet.java

importjava.io.*;

importjavax.servlet.*;

importjavax.servlet.http.*;

//示例程序:

记录下Form提交上来的数据,并存储到Log文件中

publicclassReceiveServletextendsHttpServlet

{

publicvoiddoPost(HttpServletRequestrequest,HttpServletResponseresponse)

throwsIOException,ServletException

{

//1

intlen=request.getContentLength();

bytebuffer[]=newbyte[len];

//2

InputStreamin=request.getInputStream();

inttotal=0;

intonce=0;

while((total=0)){

once=in.read(buffer,total,len);

total+=once;

}

//3

OutputStreamout=newBufferedOutputStream(newFileOutputStream("Receive.log",true));

byte[]breaker="\r\nNewLog:

-------------------->\r\n".getBytes();

System.out.println(request.getContentType());

out.write(breaker,0,breaker.length);

out.write(buffer);

out.close();

in.close();

}

}

∙在使用Opera作为浏览器测试时,从指定的文件(Receive.log)中可以看到如下的内容

--_OPERAB__-T/DQLi2fn47+D52OOrpdrz

Content-Disposition:

form-data;name="id"

id00

--_OPERAB__-T/DQLi2fn47+D52OOrpdrz

Content-Disposition:

form-data;name="file3";filename="Autoexec.bat"

Content-Type:

application/octet-stream

@echooff

prompt$d$t[$p]$_$$

--_OPERAB__-T/DQLi2fn47+D52OOrpdrz--

 

这里_OPERAB__-T/DQLi2fn47+D52OOrpdrz就是浏览器指定的分界符,不同的浏览器有不同的确定分界符的方法,但都需要保证分界符不会在文件内容中出现。

下面是用IE进行测试的结果

-----------------------------7d137a26e18

Content-Disposition:

form-data;name="name"

123

-----------------------------7d137a26e18

Content-Disposition:

form-data;name="introduce"

Iam...

Iam..

-----------------------------7d137a26e18

Content-Disposition:

form-data;name="file3";filename="C:

\Autoexec.bat"

Content-Type:

application/octet-stream

@echooff

prompt$d$t[$p]$_$$

SETPATH=d:

\pf\IBMVJava2\eab\bin;%PATH%;D:

\PF\ROSE98I\COMMON

-----------------------------7d137a26e18--

这里---------------------------7d137a26e18作为分界符。

关于分界符的规则可以概况为两条:

∙除了最后一个分界符,每个分界符后面都加一个CRLF即'\u000D'和'\u000A',最后一个分界符后面是两个分隔符"--"

∙每个分界符的开头也要加一个CRLF和两个分隔符("-")。

浏览器采用默认的编码方式是application/x-www-form-urlencoded,可以通过指定form标签中的enctype属性使浏览器知道此表单是用multipart/form-data方式编码如:

C)提交请求

提交请求的过程由浏览器完成的,并且遵循HTTP协议,每一个从浏览器端到服务器端的一个请求,都包含了大量与该请求有关的信息,在Servlet中,HttpServletRequest类将这些信息封装起来,便于我们提取使用。

在文件上载和表单提交的过程中,有两个关心的问题,一是上载的数据是是采用的那种方式的编码,这个问题的可以从Content-Type中得到答案,另一个是问题是上载的数据量有多少即Content-Length,知道了它,就知道了HttpServletRequest的实例中有多少数据可以读取出来。

这两个属性,我们都可以直接从HttpServletRequest的一个实例中获得,具体调用的方法是getContentType()和getContentLength()。

Content-Type是一个字符串,在上面的例子中,增加

System.out.println(request.getContentType());

可以得到这样的一个输出字符串:

multipart/form-data;boundary=---------------------------7d137a26e18

前半段正是编码方式,而后半段正是分界符;

任务分解上述的字符串,取出分界符。

通过String类中的方法,我们可以把这个字符串分解,提取出分界符。

StringcontentType=request.getContentType();

intstart=contentType.indexOf("boundary=");

intboundaryLen=newString("boundary=").length();

Stringboundary=contentType.substring(start+boundaryLen);

boundary="--"+boundary;

判断编码方式可以直接用String类中的startsWith方法判断。

if(contentType==null||!

contentType.startsWith("multipart/form-data"))

这样,我们在解码前可以知道:

编码的方式是否是multipart/form-data

数据内容的分界符

数据的长度

我们可以用类似于ReceiveServlet中的方式将这个请求的输入流读入一个长度为Content-Length的字节数组,接下来就是将这个字节数组里的内容全部提取出来了。

D)解码

解码对我们来说是整个上载过程最繁琐的一个步骤,经过以上的流程,我们可以得到一个包含有所有上载数据的一个字节数组和一个分界符,通过对Receive.log分析,还可以得到每个数据段中的分界符。

而我们要得到以下内容:

∙提交的表单中的各个字段以及对应的值

∙如果表单中有file控件,并且用户选择了上载文件,则需要分析出字段的名称、文件在浏览器端的名字、文件的Content-Type和文件的内容。

字节数组的内容可以分解如下:

具体解码过程也可以分为两个步骤:

∙将上载的数据分解成数据段,每个数据段对应着表单中的一个Input区。

∙对每个数据段,再进行分解,提出上述要求得到的内容。

这两个步骤主要的操作有两个,一个是从一个数组中找出另一个数组的位置,类似于String类中的indexOf的功能,另一个是从一个数组中提取出另一个数组,类似于String类中的substring的功能,为此我们可以专门写两个方法,实现这种功能。

intbyteIndexOf(byte[]source,byte[]search,intstart)

byte[]subBytes(byte[]source,intfrom,intend)

为了便于使用,可以从这两个方法中衍生出下列方法

intbyteIndexOf(byte[]source,Stringsearch,intstart)以一个String作为搜索对象参数

StringsubBytesString(byte[]source,intfrom,intend)直接返回一个String

intbytesLen(Strings)返回字符串转化为字节数组后,字节数组的长度

这样,从一个字节数组中,根据标记提取出另一个字节数组可以表示如下:

假设我们已经将数据存入字节数组buffer中,分界符存入Stringboundary中

intpos1=0;//pos1记录在buffer中下一个boundary的位置

//pos0,pos1用于subBytes的两个参数

intpos0=byteIndexOf(buffer,boundary,0);

//pos0记录boundary的第一个字节在buffer中的位置

do

{

pos0+=boundaryLen;

//记录boundary后面第一个字节的下标

pos1=byteIndexOf(buffer,boundary,pos0);

if(pos1==-1)

break;

pos0+=2;//考虑到boundary后面的\r\n

PARSE[(subBytes(buffer,pos0,pos1-2));]

//考虑到boundary后面的\r\n

pos0=pos1;

}while(true);

其中PARSE部分是对每一个数据段进行解码的方法,考虑到Content-Disposition等属性,首先定义一个String数组

String[]tokens={"name=\"",

"\";filename=\"",

"\"\r\n",

"Content-Type:

",

"\r\n\r\n"

};

对于一个不是文件的数据段,只可能有tokens中的第一个元素和最后一个元素,如果是一个文件数据段,则包含所有的元素。

第一步先得到tokens中每个元素在这个数据段中的位置

int[]position=newint[tokens.length];

for(inti=0;i

{

position[i]=byteIndexOf(buffer,tokens[i],0);

}

第二步判断是否是一个文件数据段,如果是一个文件数据段则position[1]应该大于0,并且postion[1]应该小于postion[2]即position[1]>0&&position[1]

1.得到字段名

Stringname=subBytesString(buffer,position[0]+bytesLen(tokens[0]),position[1]);

2.得到文件名

Stringfile=subBytesString(buffer,position[1]+bytesLen(tokens[1]),position[2]);

3.得到Content-Type

StringcontentType=subBytesString(buffer,position[3]+bytesLen(tokens[3]),position[4]);

4.得到文件内容

byte[]b=subBytes(buffer,position[4]+bytesLen(tokens[4]),buffer.length);

否则,说明数据段是一个name/value型的数据段,

且name在tokens[0]和tokens[2]之间,value在tokens[4]之后

//1.得到name

Stringname=subBytesString(buffer,position[0]+bytesLen(tokens[0]),position[2]);

//2.得到value

Stringvalue=subBytesString(buffer,position[4]+bytesLen(tokens[4]),buffer.length);

回页首

三、具体实现

为便于使用,定义upload包,包括以下类:

ContentFactory

对从client中传来的数据进行解码,并提供一系列get方法,从中得到上传的各种信息。

具体接口如下

staticContentFactory

getContentFactory(javax.servlet.http.HttpServletRequestrequest)

返回根据当前请求生成的一个ContentFactory实例

staticContentFactory

getContentFactory(javax.servlet.http.HttpServletRequestrequest,intmaxLength)

返回根据当前请求生成的一个ContentFactory实例

FileHolder

getFileParameter(java.lang.Stringname)

返回一个FileHolder实例,该实例包含了通过字段名为name的file控件上载的文件信息,如果不存在这个字段或者提交页面时,没有选择上载的文件,则返回null。

java.util.Enumeration

getFileParameterNames()

返回一个由String对象构成的Enumeration,包含了Html页面窗体中所有file控件的name属性。

FileHolder[]

getFileParameterValues(java.lang.Stringname)

返回一个FileHolder数组,该数组包含了所有通过字段名为name的file控件上载的文件信息,如果不存在这个字段或者提交页面时,没有选择任何上载的文件,则返回一个零元素的数组(不是null)。

java.lang.String

getParameter(java.lang.Stringname)

以String类型返回请求的参数的值,如果该参数不存在,则返回为null。

参数存于提交的表单数据中。

java.util.Enumeration

getParameterNames()

返回一个String类型的Enumeration对象,该对象包含了所有提交请求的参数名称。

java.lang.String[]

getParameterValues(java.lang.Stringname)

返回String类型的数组,该数组包含了指定名称的参数对应的所有的值,如果参数不存在,则返回为null。

FileHolder

封装一个文件数据段,可以从中提取文件名,Content-Type和文件内容等属性。

接口如下:

byte[]

getBytes()

返回一个文件内容的字节数组

java.lang.String

getContentType()返回该文件的Content-Type

java.lang.String

getFileName()

返回该文件在文件上载前在客户端的名称

java.lang.String

getParameterName()

返回上载该文件时,Html页面窗体中file控件的name属性

void

saveTo(java.io.Filefile)

把文件的内容存到指定的文件中

void

saveTo(java.lang.Stringname)

把文件的内容存到指定的文件中

ContentFactoryException

在ContentFactory.getContentFactory方法中可能抛出。

各类的源文件详解代码清单。

回页首

四、使用示例

附录中包含了一个Servlet示例,该示例重载了HttpServlet的两个方法(doGet,doPost),在浏览器发送GET请求时,产生一个表单,在用户提交表单时,将文件和数据上载,并在浏览器端显示出上载文件存盘后的URL,以及页面中的各字段的name和value。

该示例及各类在Windows98、jdk1.3和tomcat3.1,浏览器为IE5和Opera3.6的环境下调试通过。

回页首

五、附录

∙代码清单

o1、ContentFactory.java

o2、FileHolder.java

o3、ContentFactoryException.java

o4、FormUpload.java

∙示例及整个upload包,以及javadoc生成的API文档(source.zip)

参考资料

∙RFC1867Form-basedFileUploadinHTML

∙RFC2045/2046MIME(MultipurposeInternetMailExtensions)

∙RFC1806TheContent-DispositionHeader

∙RFC2388ReturningValuesfromForms:

multipart/form-data

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > PPT模板 > 商务科技

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1