- 浏览: 29142 次
- 性别:
- 来自: 厦门
-
文章分类
最新评论
-
gentlesong88:
赞一个 基础的操作都有了
条形码/二维码之开源利器ZXing图文介绍(转) -
苇间风语:
...
条形码/二维码之开源利器ZXing图文介绍(转) -
songfantasy:
学习了,今天刚好遇到这样的困惑。3Q
将变量置入循环
用 Servlet 进行上载的原理和实现
(引用:http://www.ibm.com/developerworks/cn/java/fileup/index.html)
通过 HTML 上载文件的基本流程如下图所示。浏览器端提供了供用户选择提交内 容的界面(通常是一个表单),在用户提交请求后,将文件数据和其他表单信息 编码并上传至服务器端,服务器端(通常是一个 cgi 程序)将上传的内容进行解 码了,提取出 HTML 表单中的信息,将文件数据存入磁盘或数据库。
通过表单提交数据的方法有两种,一种是 GET 方法,另一种是 POST 方法, 前者通常用于提交少量的数据,而在上传文件或大量数据时,应该选用 POST 方法。在 HTML 代码中,在 <form> 标签中添加以下代码可以 页面上显示一个选择文件的控件。
<input type="file" name="file01"> |
在页面中显示如下(可能随浏览器不同而不同)
可以直接在文本框中输入文件名,也可以点击按钮后弹出供用户选择文件的对话框。
在向服务器端提交请求时,浏览器需要将大量的数据一同提交给 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 import java.io.*; import javax.servlet.*; import javax.servlet.http.*; //示例程序:记录下Form提交上来的数据,并存储到Log文件中 public class ReceiveServlet extends HttpServlet { public void doPost(HttpServletRequest request,HttpServletResponse response) throws IOException, ServletException { //1 int len = request.getContentLength(); byte buffer[] = new byte[len]; //2 InputStream in = request.getInputStream(); int total = 0; int once = 0; while ((total < len) && (once >=0)) { once = in.read(buffer,total,len); total += once; } //3 OutputStream out=new BufferedOutputStream( new FileOutputStream("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(); } } |
在使用
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 @echo off 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" I am... I am.. -----------------------------7d137a26e18 Content-Disposition: form-data; name="file3"; filename="C:\Autoexec.bat" Content-Type: application/octet-stream @echo off prompt $d $t [ $p ]$_$$ SET PATH=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 方式编码如:
< form action="/servlet/ReceiveServlet" ENCTYPE="multipart/form-data" method=post > |
提交请求的过程由浏览器完成的,并且遵循 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
数据内容的分界符
数据的长度
我们可以用类似于 ReceiveServlet 中的方式将这个请求的输入流读 入一个长度为 Content-Length 的字节数组,接下来就是将这个字节数组里 的内容全部提取出来了。
解码对我们来说是整个上载过程最繁琐的一个步骤,经过以上的流程, 我们可以得到一个包含有所有上载数据的一个字节数组和一个分界符, 通过对 Receive.log 分析,还可以得到每个数据段中的分界符。 而我们要得到以下内容:
- 提交的表单中的各个字段以及对应的值
- 如果表单中有 file 控件,并且用户选择了上载文件, 则需要分析出字段的名称、文件在浏览器端的名字、文件的 Content-Type 和文件的内容。
字节数组的内容可以分解如下:
具体解码过程也可以分为两个步骤:
- 将上载的数据分解成数据段,每个数据段对应着表单中的一个 Input 区。
- 对每个数据段,再进行分解,提出上述要求得到的内容。
这两个步骤主要的操作有两个,一个是从一个数组中找出另一个数组的位置,类似于 String 类中的 indexOf 的功能,另一个是从一个数组中提取出另一个数组, 类似于 String 类中的 substring 的功能,为此我们可以专门写两个方法,实现这种功能。
int byteIndexOf (byte[] source,byte[] search,int start) byte[] subBytes(byte[] source,int from,int end) |
为了便于使用,可以从这两个方法中衍生出下列方法
int byteIndexOf (byte[] source,String search,int start) 以一个 String 作为搜索对象参数 String subBytesString(byte[] source,int from,int end) 直接返回一个 String int bytesLen(String s) 返回字符串转化为字节数组后,字节数组的长度 |
这样,从一个字节数组中,根据标记提取出另一个字节数组可以表示如下:
假设我们已经将数据存入字节数组 buffer 中,分界符存入 String boundary 中
int pos1=0; //pos1 记录 在buffer 中下一个 boundary 的位置 //pos0,pos1 用于 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后面的 \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=new int[tokens.length]; for (int i=0;i < tokens.length ;i++ ) { position[i]=byteIndexOf(buffer,tokens[i],0); } |
第二步判断是否是一个文件数据段,如果是一个文件 数据段则 position[1] 应该大于0,并且 postion[1] 应该小于 postion[2] 即 position[1] > 0 && position[1] < position[2] 如果为真,则为一个文件数据段,
1.得到字段名 String name =subBytesString(buffer,position[0]+bytesLen(tokens[0]),position[1]); 2.得到文件名 String file= subBytesString(buffer,position[1]+bytesLen(tokens[1]),position[2]); 3.得到 Content-Type String contentType=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 String name =subBytesString(buffer,position[0]+bytesLen(tokens[0]),position[2]); //2.得到 value String value= 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的环境下调试通过。
- source.zip (29.5 KB)
- 下载次数: 2
发表评论
-
SMSLib实现Java短信收发的功能(转)
2012-05-03 11:31 2045摘选:http://sjsky.iteye.com/blog/ ... -
条形码/二维码之开源利器ZXing图文介绍(转)
2012-05-03 11:18 7798摘选:http://sjsky.iteye.com/blog/ ... -
log4j使用详解
2012-04-25 13:30 553log4j使用详解 ... -
读取条形码
2012-04-23 10:54 647(摘选: http://www.ibm.com/deve ... -
用 Java 实现断点续传 (HTTP)
2012-04-23 10:23 479断点续传的原理 其实断点续传的原理很简单,就是在 H ... -
尽可能使用堆栈变量
2012-04-20 13:58 419如果您频繁存取变量,就需要考虑从何处存取这些变量。变量是 ... -
finally 块
2012-04-20 11:29 490finally 块必须与 try 或 ... -
mx参数
2012-04-20 11:25 516在大多数情况下,如果 ...
相关推荐
本项目"jsp+servlet小项目(实现图片上传功能)"旨在教授如何利用这两者创建一个简单的图片上传功能。下面将详细介绍这个项目涉及的关键知识点。 1. JSP基础:JSP是一种动态网页技术,允许开发者在HTML页面中嵌入...
在本文中,我们将深入探讨如何使用Java的JSP(JavaServer Pages)和Servlet技术来实现一个图片上传功能。这是一个常见的Web开发任务,特别是在构建交互性强、动态内容丰富的网站时。以下是一些关键知识点: 1. **...
本教程将详细讲解如何使用Servlet实现文件的上传和下载功能,其中涉及到的主要技术点包括Servlet API、Multipart解析以及文件流操作。 首先,我们需要了解Servlet在文件上传中的作用。Servlet在接收到客户端(通常...
总的来说,这个项目展示了如何结合Flex的前端交互性和Java Servlet的强大处理能力,实现图片的上传与预览功能。Flex处理用户界面和文件选择,而Java Servlet处理文件的接收和存储,两者协同工作,为用户提供了一种...
本教程将详细讲解如何使用Servlet实现文件的上传和下载功能,这对于任何Web应用程序来说都是至关重要的。 首先,我们来了解一下Servlet的基本概念。Servlet是Java编程语言中一个用于扩展服务器功能的接口,主要应用...
在给定的压缩包文件中,我们有与Servlet实现相关的源码,包括`FormUpload.class`和`FormUpload.java`,这可能是实现文件上传功能的一个Servlet类。另外,还有`upload`目录,可能包含了与文件上传相关的静态资源,如...
以上就是使用JQuery和Servlet实现上传进度条的基本原理和实现流程。这个过程涉及到的技术点包括前端的JQuery交互、HTML5 File API的使用,以及后端的Servlet文件处理,这些都是现代Web开发中的基础技能。通过掌握...
例如,当用户提交上传头像的表单时,对应的Servlet会接收到这个请求,读取上传的文件,进行必要的验证(如文件类型、大小检查),然后将新头像保存到服务器的指定位置。 AJAX技术则用于实现页面无刷新更新,提升...
总的来说,这个项目展示了如何使用Servlet、MySQL和JDBC实现基本的用户管理系统。这包括了用户注册、登录验证以及文件上传(如头像)的核心功能。对于初学者来说,这是一个很好的学习资源,可以帮助理解Web开发的...
Servlet上传文件原理: Servlet在Java Web开发中是服务器端处理HTTP请求的核心组件。在文件上传场景下,Servlet接收客户端通过POST请求发送的多部分/形式数据(multipart/form-data)。Servlet API提供`...
Servlet技术是Java Web开发中的重要组成部分,主要用于处理HTTP请求并...通过运行和研究这个项目,开发者不仅可以深入了解Servlet和SpringMVC的工作原理,还能掌握如何高效地处理数据库查询,以及增强用户界面的功能。
"基于Servlet实现文件的上传与下载"是一个常见的实战任务,它涵盖了网络编程、多线程和文件I/O等多个核心概念。这里我们将深入探讨如何利用Servlet来实现这两个功能。 首先,让我们了解Servlet的工作原理。Servlet...
在本文中,我们将深入探讨如何使用Java的Servlet技术来实现一个简易的文件上传功能,并且在上传过程中添加进度条的显示。Servlet是Java Web开发中不可或缺的一部分,它提供了服务器端处理HTTP请求的能力,使得我们...
在本例中,我们将深入探讨如何使用Servlet来实现文件上传和下载功能。 首先,让我们了解文件上传的基本流程。当用户通过HTML表单提交包含文件的请求时,Servlet接收到这个请求,并使用`Part`接口(来自Java Servlet...
2. **文件上传原理**: 文件上传是通过HTTP协议的POST请求实现的,通常包含一个或多个名为`multipart/form-data`的表单字段。在Java Servlet中,我们使用`javax.servlet.http.Part`接口来处理这些多部分数据。 3. ...
在本文中,我们将深入探讨如何使用Servlet来理解Struts2中的文件上传原理。Servlet是Java Web应用程序中的一个组件,用于处理HTTP请求和响应。而Struts2是一个基于MVC(模型-视图-控制器)设计模式的开源Web应用框架...
文件上传下载是 Web 应用中的一种常见需求,使用 jsp 页面和 servlet 实现文件上传下载是其中的一种解决方案。下面将详细介绍 jsp、servlet 文件上传下载技术。 文件上传下载原理 文件上传下载的原理是将客户端的...
总之,"用Servlet实现人力资源管理系统"是一个综合性的Java Web项目,涵盖了Servlet的基础应用和人力资源管理领域的业务逻辑。通过这个项目,开发者可以深入理解Servlet的工作原理,学习如何构建实际的企业级应用,...
本教程将探讨如何使用纯JSP(JavaServer Pages)和Servlet技术来实现文件上传,而不依赖Apache的文件上传组件,如Commons FileUpload。 首先,我们需要了解JSP和Servlet的基本概念。JSP是用于创建动态网页的Java...
在本项目中,它被用来实现类似QQ空间的多张图片上传体验,后端处理服务使用的是Java的Servlet技术。这里我们将深入探讨SwfUpload与Servlet结合实现图片上传的相关知识点。 1. **SwfUpload组件介绍** SwfUpload 是...