上传文件的基本流程以下图所示。浏览器端提供了一个表单,在用户提交请求后,将文件数据和其余表单信息 编码并上传至服务器端,服务器端将上传的内容进行解码了,提取出 HTML 表单中的信息,将文件数据存入磁盘或数据库。html
<form enctype="application/x-www-form-urlencoded" action="/upload" method="post" >
<input type="text" name="name"/><br/>
<input type="text" name="age"/><br/>
<input type="submit" value="上传"/><br/>
</form>复制代码
在向服务器端提交请求时,浏览器须要将大量的数据一同提交给 Server 端, 而提交前,浏览器须要按照 Server 端能够识别的方式进行编码,对于普通 的表单数据,这种编码方式很简单,编码后的结果一般是 field1=value2&field2=value2&… 的形式,如 name=ltq&age=18。一般使用的表单也是采用这种方式编码的,Servlet 的 API 提供了对这种 编码方式解码的支持,只须要调用 ServletRequest 类中的方法就能够获得 用户表单中的字段和数据。前端
这种编码方式( application/x-www-form-urlencoded )虽然简单,但对于 传输大块的二进制数据显得力不从心,对于传输这类数据,浏览器采用 了另外一种编码方式,即 "multipart/form-data" 的编码方式,采用这种方式, 浏览器能够很容易的表单内的数据和文件一块儿。这种编码方式先定义好 一个不可能在数据中出现的字符串做为分界符,而后用它将各个数据段 分开,而对于每一个数据段都对应着 HTML 页面表单中的一个 Input 区,包 括一个 content-disposition 属性,说明了这个数据段的一些信息,若是这个 数据段的内容是一个文件,还会有 Content-Type 属性,而后就是数据自己。 这里,咱们能够编写一个简单的 Servlet 来看到浏览器究竟是怎样编码的。
java
实现流程:数据库
<html><head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title></head>
<body>
<form enctype="multipart/form-data" action="${pageContext.request.contextPath }/servlet/uploadServlet2" method="post" >
<input type="text" name="name"/><br/>
<input type="file" name="photo"/><br/>
<input type="submit" value="上传"/><br/>
</form>
</body>
</html>复制代码
package com.itheima.upload;
import java.io.IOException;
import java.io.InputStream;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class UploadServlet1 extends HttpServlet {
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
int len = request.getContentLength();
byte buffer[] = new byte[len];
InputStream in = request.getInputStream();
int total = 0;
int once = 0;
while ((total < len) && (once >=0)) {
once = in.read(buffer,total,len);
total += once;
}
System.out.println(new String(buffer,0,len));
}
}复制代码
在使用 数组
这里 ------WebKitFormBoundaryMsG9gupKbNVAw2Dn
就是浏览器指定的分界符,不一样的浏览器有不一样的肯定分界符的方法,但都须要保证分界符不会在文件内容中出现.
浏览器
------WebKitFormBoundaryMsG9gupKbNVAw2Dnbash
Content-Disposition: form-data; name="name"服务器
李同钱app
------WebKitFormBoundaryMsG9gupKbNVAw2Dnpost
Content-Disposition: form-data; name="photo"; filename="robots.txt"
Content-Type: text/plain
# www.robotstxt.org/
# Allow crawling of all content
User-agent: *
Disallow:
------WebKitFormBoundaryMsG9gupKbNVAw2Dn--
以上是上传输出内容
浏览器采用默认的编码方式是 application/x-www-form-urlencoded , 能够经过指定 form 标签中的 enctype 属性使浏览器知道此表单是用 multipart/form-data 方式编码如:
<form enctype="multipart/form-data" action="${pageContext.request.contextPath}/servlet/uploadServlet2" method="post" > |
提交请求
提交请求的过程由浏览器完成的,而且遵循 HTTP 协议,每个从浏览 器端到服务器端的一个请求,都包含了大量与该请求有关的信息, 在 Servlet 中,HttpServletRequest 类将这些信息封装起来,便于咱们提取 使用。在文件上载和表单提交的过程当中,有两个指的关心的问题,一是 上载的数据是是采用的那种方式的编码,这个问题的能够从 Content-Type 中获得答案,另外一个是问题是上载的数据量有多少即 Content-Length , 知道了它,就知道了 HttpServletRequest 的实例中有多少数据能够读取 出来。这两个属性,咱们均可以直接从 HttpServletRequest 的一个实例 中得到,具体调用的方法是 getContentType() 和 getContentLength() 。
Content-Type 是一个字符串,在上面的例子中,增长
System.out.println(request.getContentType());
|
能够获得这样的一个输出字符串:
multipart/form-data; boundary=----WebKitFormBoundaryLJzBFw0CbuD1LLFn
|
前半段正是编码方式,然后半段正是分界符,经过 String 类中的方法, 咱们能够把这个字符串分解,提取出分界符。
String contentType=request.getContentType();
int start=contentType.indexOf("boundary=");
int boundaryLen=new String("boundary=").length();
String boundary=contentType.substring(start+boundaryLen);
boundary="--"+boundary;复制代码
通过以上的流程, 咱们能够获得一个包含有全部上载数据的一个字节数组和一个分界符, 而咱们要获得如下内容:
字节数组的内容能够分解以下:
字节中上传的内容可能包括file类型的和普通表单类型,两种类型DATA存在的字段存在一些差别
经过分界符boundary,提出DATA,
实现将boundary转换成buffer,与传输的Buffer循环比较找到相同的块,切割出DATA,
在递归DATA比较其中的字段,提出对应的字段,保存在集合中
https://www.ibm.com/developerworks/cn/java/fileup