Servlet 进行上传文件的原理
来源:互联网 发布:软件设计师下午试题 编辑:程序博客网 时间:2024/05/18 22:51
一、基本原理
通过 HTML 上载文件的基本流程如下图所示。浏览器端提供了供用户选择提交内容的界面(通常是一个表单),在用户提交请求后,将文件数据和其他表单信息编码并上传至服务器端,服务器端(通常是一个 cgi 程序)将上传的内容进行解 码了,提取出 HTML 表单中的信息,将文件数据存入磁盘或数据库。
二、各过程详解
A)填写表单并提交
通过表单提交数据的方法有两种,一种是 GET 方法,另一种是 POST 方法,前者通常用于提交少量的数据,而在上传文件或大量数据时,应该选用 POST 方法。在 HTML 代码中,在 <form> 标签中添加以下代码可以页面上显示一个选择文件的控件。
<input type="file" name="file01">
可以直接在文本框中输入文件名,也可以点击按钮后弹出供用户选择文件的对话框。
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 写入指定文件。
代码清单
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class ReceiveServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doPost(request, response);
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 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(
"E:/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();
}
}
在使用IE作为浏览器测试时,从指定的文件( Receive.log
)中可以看到如下的内容
-----------------------------7d802110e4c
Content-Disposition: form-data; name="file01"; filename=""
Content-Type: application/octet-stream
-----------------------------7d802110e4c--
这里 ----------------------------7d802110e4c 作为分界符。关于分界符的规则可以概况为两条:
- 除了最后一个分界符,每个分界符后面都加一个 CRLF 即 '/u000D' 和 '/u000A', 最后一个分界符后面是两个分隔符"--"
- 每个分界符的开头也要加一个 CRLF 和两个分隔符("-")。
浏览器采用默认的编码方式是 application/x-www-form-urlencoded ,可以通过指定 form 标签中的 enctype 属性使浏览器知道此表单是用 multipart/form-data 方式编码如:
< form action="ReceiveServlet.do" ENCTYPE="multipart/form-data" method=post >
C)提交请求
提交请求的过程由浏览器完成的,并且遵循 HTTP 协议,每一个从浏览器端到服务器端的一个请求,都包含了大量与该请求有关的信息, 在 Servlet 中,HttpServletRequest 类将这些信息封装起来,便于我们提取使用。在文件上载和表单提交的过程中,有两个指的关心的问题,一是上载的数据是是采用的那种方式的编码,这个问题的可以从 Content-Type 中得到答案,另一个是问题是上载的数据量有多少即 Content-Length ,知道了它,就知道了 HttpServletRequest 的实例中有多少数据可以读取出来。这两个属性,我们都可以直接从 HttpServletRequest 的一个实例中获得,具体调用的方法是 getContentType() 和 getContentLength() 。
Content-Type 是一个字符串,在上面的例子中,增加
System.out.println(request.getContentType());
可以得到这样的一个输出字符串:
multipart/form-data; boundary=---------------------------7d802110e4c
前半段正是编码方式,而后半段正是分界符,通过 String 类中的方法,我们可以把这个字符串分解,提取出分界符。
int start=contentType.indexOf("boundary=");
int boundaryLen=new String("boundary=").length();
String boundary=contentType.substring(start+boundaryLen);
boundary="--"+boundary;
判断编码方式可以直接用 String 类中的 startsWith 方法判断。
这样,我们在解码前可以知道:
编码的方式是否是multipart/form-data
数据内容的分界符
数据的长度
我们可以用类似于 ReceiveServlet 中的方式将这个请求的输入流读入一个长度为 Content-Length 的字节数组,接下来就是将这个字节数组里的内容全部提取出来了。
D)解码
解码对我们来说是整个上载过程最繁琐的一个步骤,经过以上的流程,我们可以得到一个包含有所有上载数据的一个字节数组和一个分界符,通过对 Receive.log 分析,还可以得到每个数据段中的分界符。而我们要得到以下内容:
- 提交的表单中的各个字段以及对应的值
- 如果表单中有 file 控件,并且用户选择了上载文件,则需要分析出字段的名称、文件在浏览器端的名字、文件的 Content-Type 和文件的内容。
字节数组的内容可以分解如下:
具体解码过程也可以分为两个步骤:
- 将上载的数据分解成数据段,每个数据段对应着表单中的一个 Input 区。
- 对每个数据段,再进行分解,提出上述要求得到的内容。
这两个步骤主要的操作有两个,一个是从一个数组中找出另一个数组的位置,类似于 String 类中的 indexOf 的功能,另一个是从一个数组中提取出另一个数组, 类似于 String 类中的 substring 的功能,为此我们可以专门写两个方法,实现这种功能。
byte[] subBytes(byte[] source,int from,int end)
String subBytesString(byte[] source,int from,int end) 直接返回一个 String
int bytesLen(String s) 返回字符串转化为字节数组后,字节数组的长度
这样,从一个字节数组中,根据标记提取出另一个字节数组可以表示如下:
假设我们已经将数据存入字节数组 buffer 中,分界符存入 String 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 数组
"/"; filename=/"",
"/"/r/n",
"Content-Type: ",
"/r/n/r/n"
};
对于一个不是文件的数据段,只可能有 tokens 中的第一个元素和最后一个元素,如果是一个文件数据段,则包含所有的元素。第一步先得到 tokens 中每个元素在这个数据段中的位置
for (int i=0;i < tokens.length ;i++ )
{
position[i]=byteIndexOf(buffer,tokens[i],0);
}
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);
- Servlet 进行上传文件的原理
- servlet进行文件的上传和下载
- 使用servlet进行多文件的上传
- Servlet实现文件上传的原理
- Servlet实现文件上传的原理
- 关于Servlet与Applet的澄清,进行大文件上传
- 关于Servlet与Applet的澄清,进行大文件上传
- 关于Servlet与Applet的澄清,进行大文件上传
- java文件的上传与下载原理(使用Servlet)
- servlet的上传文件
- Servlet的文件上传
- 文件的上传servlet
- servlet下用SmartUpload进行上传文件
- 文件上传的原理
- 文件上传的原理
- 文件上传的原理
- 文件上传的原理
- Servlet 实现文件的上传
- Open API分析、实践和思索(后半篇)
- UBUNTU 下 JDK 的安装
- 每日IN语(2009-01-11)什么是友情?
- nth prime number contain n as their substring within 100000000
- 生活杂记(01-11)
- Servlet 进行上传文件的原理
- equals相等而hashCode是否一定相等的分析
- VC的若干实用小技巧[转]
- 金融危机,既是危也是机!
- CSocket在多线程环境下使用Static库出错的解决办法
- 多媒体交互应用基础(10)
- Unable to load DLL (oci.dll)的解决方法
- 80后的 我们 依然单身(csdn里看到的贴子,觉得还不错)
- 基于JRobin的磁盘IO监控