Form表单的要求
1.提供from表单,method必须是post(post方式可以提交大量数据)
2.提供input type=”file”标签
3.form表单的enctype属性必须是multipart/form-data(就是在设置请求消息头的Content-Type)
- application/x-www-form-urlencoded(默认)。请求报文内容的格式为name=admin&password=123。服务器通过request.getParameter(“name”);来获取数据
- multipart/form-data。请求报文内容的格式为
Content-Type: multipart/form-data; boundary=---------------------------28512195225210
Content-Length: 295
-----------------------------28512195225210
Content-Disposition: form-data; name="a"
q
-----------------------------28512195225210
Content-Disposition: form-data; name="b"; filename="a.txt"
Content-Type: text/plain
eaglezsx
-----------------------------28512195225210--
数据被分隔线分成一段一段的,其中每两个分隔线之间的数据称为一个item。request.getParameter(“name”)方法获取指定的表单字段字符内容,但文件上传表单已经不再是字符内容,而是字节内容,所以失效。用request.getInputStream();方法可以获取字节流。
例子
新建一个a.txt,里边随便写点儿东西,eaglezsx。
新建一个upload.jsp
<form enctype="multipart/form-data" action="${pageContext.request.contextPath}/servlet/uploadServlet" method="post">
<input type="text" name="a" />
<input type="file" name="b"/></br>
<input type="submit" value="提交"/>
</form>
新建一个Servlet,UploadServlet.java
public class UploadServlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
ServletInputStream sis=request.getInputStream();//获取请求报文中的内容
int len=-1;
byte[] b=new byte[1024];
while ((len=sis.read(b))!=-1) {
System.out.println(new String(b,0,len));//将请求报文中的内容转化为字符串,打印到控制台
}
sis.close();
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}
运行结果为
-----------------------------17157105472171
Content-Disposition: form-data; name="a"
hello
-----------------------------17157105472171
Content-Disposition: form-data; name="b"; filename="a.txt"
Content-Type: text/plain
eaglezsx
-----------------------------17157105472171--
这就是从浏览器接收到的那些东西。把这些东西解析了,就可以获得需要的内容了。自己解析起来比较麻烦。有现成的组件commons fileupload来解析这些请求。Apache的官网有它的用户指南http://commons.apache.org/proper/commons-fileupload/using.html里边有个小例子。
在使用FileUpload组件时,需要导入commons-fileupload和commons-io两个jar包。
通过这个例子,可以看出主要用这几样东西
DiskFileItemFactory类
如果上传的文件比较小,将保存在内存中。如果上传的文件比较大,则会以临时文件的形式保存在磁盘的临时文件夹中。默认情况下,文件保存在内存还是硬盘临时文件夹的临界值是10240,即10K。
构造方法
- DiskFileItemFactory():采用默认临界值10240,默认临时文件夹为D:\Java\apache-tomcat-7.0.52\temp,Tomcat安装目录中的temp文件夹中。
- DiskFileItemFactory(int sizeThreshold,File repository):采用参数指定临界值和临时文件的存储位置
方法
- setSizeThreshold(int sizeThreshold):设置临界值
- setRepository(File repository):设置临时文件的存储位置
ServletFileUpload类
构造方法:
- ServletFileUpload(FileItemFactory fileItemFactory)
方法:
- parseRequest(HttpServletRequest req):解析出Form表单中的每个字段的数据,并将它们分别包装成独立的FileItem对象,然后将这些FileItem对象加入进一个List类型的集合对象中返回。
- isMultipartContent():是个静态方法,判断请求消息中的内容是否是multipart/form-data类型,如果是则返回true。
FileItem接口
- boolean isFormFiled():用于判断FileItem类对象封装的数据是一个普通文本表单字段,还是一个文件表单字段,如果是普通表单字段则返回true。
- String getFiledName():获取表单字段name属性的值。
- String getName():获取文件上传字段中的文件名
- String getString():获取普通字段的value属性的值。
- String getString(String encoding):可以设置编码,这样普通标签的value为中文时就不会乱码了
void write(File file):将FileItem对象中保存的主体内容保存到某个指定的文件中。如果内容是在临时文件中,那么DiskFileItem被垃圾回收后,临时文件会被自动删除。
String getContentType():获取上传文件的类型,即表单字段描述头属性Content-Type的值,如“image/jpeg”。
- boolean isInMemory():判断FileItem对象封装的数据内容是存储在内存中,还是存储在临时文件中,在内存中则返回true。
- long getSize():返回该上传文件的大小(以字节为单位)
- InputStream getInputStream():以流的形式返回上传文件的内容
public class UploadServlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
if (ServletFileUpload.isMultipartContent(request)) {
File repository=new File("G:/");
DiskFileItemFactory factory=new DiskFileItemFactory(1024*1024, repository);
ServletFileUpload upload=new ServletFileUpload(factory);
try {
List<FileItem> items=upload.parseRequest(request);//这句话执行就会开始产生临时文件,这句话执行完后,临时文件也就产生了,临时文件和上传的文件一样大,后面的操作都是对临时文件的操作
for (FileItem item : items) {
if (item.isFormField()) {
processFormField(item);
}else {
try {
processUploadedFile(item);
} catch (Exception e) {
e.printStackTrace();
}
}
}
} catch (FileUploadException e) {
e.printStackTrace();
}
}else {
System.out.println("这不是文件上传表单");
}
}
private void processUploadedFile(FileItem item) throws Exception {
String fieldName=item.getFieldName();
String fileName=item.getName();
String contentType=item.getContentType();
boolean isInMemory=item.isInMemory();
long sizeInBytes=item.getSize();
System.out.println(fieldName+" "+fileName+" "+contentType+" "+" "+isInMemory+" "+sizeInBytes);
//把上传的文件保存d盘
File uploadedFile=new File("D:/"+fileName);
item.write(uploadedFile);//这种方式临时文件会自动删除
//或者采用流的形式操作数据,这种方式需要调用delete方法来删除临时文件
/*
InputStream uploadedStream=item.getInputStream();
FileOutputStream fos=new FileOutputStream(uploadedFile);
int len=0;
byte[] b=new byte[1024];
while((len=uploadedStream.read(b))!=-1){
fos.write(b,0,len);
}
uploadedStream.close();
fos.close();
item.delete();
*/
}
private void processFormField(FileItem item) {
String name=item.getFieldName();
String value=item.getString();
System.out.println("name:"+name+"---"+"value:"+value);
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}
显示结果为
name:a---value:hello
b a.txt text/plain true 8
需要注意的问题
1.getName()在有的浏览器中获取的是文件名(a.txt),但在有些浏览器中获取的就是完整的路径(C:\Users\YZ\Desktop\a.txt)。
解决方法
String filename=fileitem.getName();
filename=filename.substring(filename.lastIndexOf(File.separator)+1);
针对这个问题官网也给出了解决办法http://commons.apache.org/proper/commons-fileupload/faq.html
filename=FilenameUtils.getName(filename);
2.如果客户端上传了一个jsp,上面写着一段让服务器关机的代码。之后客户端访问这个jsp,jsp里边的代码就会执行。
为了保证服务器的安全,把上传文件的目录放在用户访问不到的地方。WEB-INF下
File uploadedFile=new File(this.getServletContext().getRealPath("/WEB-INF/uploadedfile"));
3.如果上传的文件名称相同,以前的文件会被覆盖
让文件名唯一即可
filename=UUID.randomUUID()+"_"+filename;
4.把文件都放到一个文件夹里到时候检索的时候会很慢
把文件打散,弄一些子目录
- 通过日期打散
File uploadedFile=new File(this.getServletContext().getRealPath("/WEB-INF/uploadedfile"));
SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd");
String date=sdf.format(new Date());
uploadedFile=new File(uploadedFile,date);
- 通过哈希值打散
int hashcode=fileName.hashCode();//返回此对象的哈希值
String code=Integer.toHexString(hashcode);//将其转换为16进制的字符,se3343ie
String childDirectory=code.charAt(0)+File.separator+code.charAt(1);// s/e
uploadedFile=new File(uploadedFile,childDirectory);
5.限制文件的大小
ServletFileUpload中的
- setFileSizeMax(字节):设置单个文件的大小
- setSizeMax(字节):总文件大小(多文件上传)
6.用户没有选择文件,就点击了上传
判断文件名是否为空即可
7.文件名为中文时会乱码。
用ServletFileUpload的setHeaderEncoding方法设置一下。
8.普通字段的value为中文时会乱码
String value=item.getString("utf-8");
用Streaming API实现上传
前面用的是传统的API实现上传,commons-fileupload还有一套Streaming API。官方文档http://commons.apache.org/proper/commons-fileupload/streaming.html说这种方法没有传统方法便利,但性能更好。传统方法有多便利我没看出来(有知道的,麻烦告诉我一下),但Streaming API在性能上的提升确可以明显的看出来。传统方法中,上传的文件都要先存储到内存或临时文件中,然后在对内存或临时文件进行操作。这样的话,如果我上传的大文件,相当于操作了文件两遍,先把上传文件弄到缓存文件中,再把缓存文件复制一份到最终目录。如果用Streaming API的话,直接把上传的文件传到最终的目录。
public class UploadServlet2 extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
if (ServletFileUpload.isMultipartContent(request)) {
ServletFileUpload upload=new ServletFileUpload();
try {
FileItemIterator iter = upload.getItemIterator(request);
while(iter.hasNext()){
FileItemStream item=iter.next();
String name=item.getFieldName();//获取name属性的值
if(item.isFormField()){
processFormField(item);
}else{
processUploadedFile(item);
}
}
} catch (FileUploadException e) {
e.printStackTrace();
}
}else {
System.out.println("这不是文件上传表单");
}
}
private void processUploadedFile(FileItemStream item) throws IOException{
InputStream is=item.openStream();
FileOutputStream fos=new FileOutputStream(new File("D:/"+item.getName()));
int len=-1;
byte[] b=new byte[1024*1024];
while((len=is.read(b))!=-1){
fos.write(b, 0, len);
}
is.close();
fos.close();
}
private void processFormField(FileItemStream item) throws IOException {
String name=item.getFieldName();
InputStream is=item.openStream();
String value=Streams.asString(is);
System.out.println("name:"+name+"---"+"value:"+value);
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}