servlet获取form表单提交的文件

不知不觉,感觉我已经开始走向全栈的道路了,这两天学到了在后端接收前端传递过来的文件数据,由于水平太菜,我准备先使用servlet接收数据。
这篇博客由浅入深记录了我如何实现获取上传的文件、在html协议内容来看如何获取文件、我读Apache Commons FileUpload源码的一点理解。

How

html form

<form action="../form_ajax" method="post" enctype="multipart/form-data">
    用户名:<input type="text" name="username"/><br/>
    选择要上传的文件<br/>
    请选择文件:<input type="file" name="myfile"/><br/>
    <input type="submit" value="submit"/><br/>
form>
  • 在form标签里面添加属性 enctype=”multipart/form-data”

用servlet api获取文件内容

@MultipartConfig
public class FileUpload1Servlet extends HttpServlet {

    private static Logger LOGGER = Logger.getLogger(FileUpload1Servlet.class);

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //文件上传
        //首先要在Servlet上用 @MultipartConfig 标识支持文件上传
        //获取part对象 参数为name属性的值
        Part part = req.getPart("myfile");
        //Servlet3没有提供直接获取文件名的方法,需要从请求头中解析出来
        //获取文件名
        String fileName = part.getSubmittedFileName();
        //获取数据的流
        InputStream inputStream = part.getInputStream();

        BufferedReader br = new BufferedReader(new InputStreamReader(inputStream));
        StringBuffer sb = new StringBuffer();
        String line = br.readLine();
        while(line != null){
            sb.append("\n").append(line);
            line = br.readLine();
        }
        LOGGER.info("content:"+sb.toString());

        PrintWriter printWriter = resp.getWriter();
        printWriter.print("皮皮虾,我们走。");
        printWriter.flush();
        printWriter.close();
    }

}

含有 enctype=”multipart/form-data” 属性的表单提交上来的数据,用ServletRequest.getParameter 方法是获取不到参数的。要按照下面的步骤获取参数:

  • 1、Servlet类要加上注释@MultipartConfig
  • 2、使用ServletRequest.getPart方法获取Part对象,Part对象就是对应的你的表单里面的每一个input。

缺点:

  • 如果我们想获取传递过来的表单里的value,Part只给我们提供了InputStream getInputStream() throws IOException;方法,对于普通的文本类型,我们还得自己处理。

用Apache Commons FileUpload获取文件内容

首先添加Apache Commons FileUpload依赖。

public class FileUpload2Servlet extends HttpServlet {

    private static Logger LOGGER = Logger.getLogger(FileUpload2Servlet.class);

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

        // Configure a repository (to ensure a secure temp location is used)
        ServletContext servletContext = this.getServletConfig().getServletContext();
        File repository = (File) servletContext.getAttribute("javax.servlet.context.tempdir");
        factory.setRepository(repository);

        // Create a new file upload handler
        ServletFileUpload upload = new ServletFileUpload(factory);

        try {
            // Parse the request
            List items = upload.parseRequest(req);

            // Process the uploaded items
            Iterator iter = items.iterator();
            while (iter.hasNext()) {
                FileItem item = iter.next();

                if (item.isFormField()) {
                    processFormField(item);
                } else {
                    processUploadedFile(item);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        PrintWriter printWriter = resp.getWriter();
        printWriter.print("皮皮虾,我们走。");
        printWriter.flush();
        printWriter.close();
    }

    private void processFormField(FileItem item) {
    }

    private void processUploadedFile(FileItem item) {
    }

}

如上面代码,获取到的FileItem就是对应的表单里的input,通过FileItem可以获取流或者值等,比原生的servlet api要方便。

Apache Commons FileUpload获取非file属性乱码

FileItem.getString是获取表单传递过来的属性值的方法,该方法默认是用 ISO-8859-1 解码的。该方法有一个重载的方法 String getString(String encoding) throws UnsupportedEncodingException; 来指定解码字符集。

注意

如果form表单里没有添加enctype=”multipart/form-data”(或者multipart/mixed等相似的)属性,我们就使用 ServletRequest.getParameter 来获取参数,不能使用上面的介绍的两种方式处理,否则会报错。

如果form表单里添加了enctype=”multipart/form-data”(或者multipart/mixed等相似的)属性,就需要使用上述两种方法或类似的方法处理请求参数,因为 ServletRequest.getParameter 方法无法获取到值。

因此,对于form表单里是否添加了enctype=”multipart/form-data”(或者multipart/mixed等相似的)属性,会有不同的处理。当然,这本来就是已知的,因为那form表单都是我们自己写的。我们也可以从传递过来的http请求头里判断出来,下面的博客中就会提现出来再http请求发送的数据里到底是如何不同的。Apache Commons FileUpload库给我们提供了简单的方法判断,如:

boolean isMultipart = ServletFileUpload.isMultipartContent(req);

why

socket实现简单web服务器,可查看http请求信息 这是我以前写的一篇博客,通过它可以直观的观察到http请求发送的内容。

enctype=”multipart/form-data”:这个属性影响的是当post数据的时候,是以一种什么样的形式来编码数据。该属性默认值是application/x-www-form-urlencoded。我们只讨论post,enctype为这两个值是的情况。

enctype=”application/x-www-form-urlencoded”

POST / HTTP/1.1
Host: 127.0.0.1:8000
Connection: keep-alive
Content-Length: 44
Cache-Control: max-age=0
Origin: http://localhost:8080
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36
Content-Type: application/x-www-form-urlencoded
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Referer: http://localhost:8080/jquery/jquery_easy_ui_demo/index2.html?
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.8,en;q=0.6
Cookie: _ga=GA1.1.2113425928.1460981708

username=%E5%BC%A0%E4%B8%89&myfile=test1.sql

enctype=”multipart/form-data”

POST / HTTP/1.1
Host: 127.0.0.1:8000
Connection: keep-alive
Content-Length: 1043
Cache-Control: max-age=0
Origin: http://localhost:8080
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary2vmXA3pgPdCoiZjv
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Referer: http://localhost:8080/jquery/jquery_easy_ui_demo/index2.html?
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.8,en;q=0.6
Cookie: _ga=GA1.1.2113425928.1460981708


------WebKitFormBoundary2vmXA3pgPdCoiZjv
Content-Disposition: form-data; name="username"

张三
------WebKitFormBoundary2vmXA3pgPdCoiZjv
Content-Disposition: form-data; name="myfile"; filename="test1.sql"
Content-Type: application/octet-stream

/*
 Navicat MySQL Data Transfer

 Source Server         : localhost
 Source Server Version : 50716
 Source Host           : localhost
 Source Database       : test1

 Target Server Version : 50716
 File Encoding         : utf-8

 Date: 11/18/2016 00:54:30 AM
*/

SET NAMES utf8;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
--  Table structure for `obj1`
-- ----------------------------
DROP TABLE IF EXISTS `obj1`;
CREATE TABLE `obj1` (
  `name` varchar(20) NOT NULL,
  `id` int(11) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

-- ----------------------------
--  Records of `obj1`
-- ----------------------------
BEGIN;
INSERT INTO `obj1` VALUES ('张三', '1');
COMMIT;

SET FOREIGN_KEY_CHECKS = 1;

------WebKitFormBoundary2vmXA3pgPdCoiZjv--

区别

在这里我们只需要注意两个地方:请求头里的Content-Type属性和请求体。

  • 请求头里的Content-Type属性属性不同,服务器端可以从这个属性判断用那种方式去处理请求参数。当Content-Type为multipart/form-data时,会有boundary=—-WebKitFormBoundary2vmXA3pgPdCoiZjv这么一个值,它就是区分请求体里不同的属性的。
  • enctype=”application/x-www-form-urlencoded”时,请求体里就是urlencoded过拼接二层的查询字符串。当enctype=”multipart/form-data”时,表单被boundary分成不同的部分,每一部分都包含header和数据,如上面的例子一样。

解析

当get请求或post且enctype=”application/x-www-form-urlencoded”时,直接使用ServletRequest.getParameter方法就能得到参数,这里就不讨论了。

当post且enctype=”multipart/form-data”时,数据就在请求体里,我们也知道请求体的格式了,就是按照这个格式把需要的数据解析出来。首先得到请求体力的内容,我们知道ServletRequest.getInputStream方法,该方法返回的输入流包含整个请求体力的内容。

对于真正解析上述的post请求体数据,还是比较复杂的一件事情,本着负责任(懒)的态度,我就没有自己动手写,下面内容是我阅读Apache Commons FileUpload源码的一点理解。

Apache Commons FileUpload理解

代码有点多,就不贴了,写点我的理解。

  • 1、把请求体先解析成FileItemIterator,该对象是一个迭代器,可以获取所有的FileItemStream对象,FileItemStream对象可以对应一个传递过来的input的内容,可以获取fieldName、流等数据。
  • 2、遍历FileItemIterator,通过FileItemStream生成FileItem,真正生成的是FileItem的子类DiskFileItem。
  • 3、解析时使用这样两层的结构为了方便拓展,FileItemStream对象能代表原始的数据,通过它生成FileItem,现在生成的是DiskFileItem,以后可以通过数据库缓存的FileItem也未可知。官方文档里也有介绍说使用FileItemStream获取数据,叫Streaming API.
  • 4、为了节省内存,DiskFileItem会根据它包含内容的大小决定是否把它包含的数据缓存在文件系统里。如下:
➜  jquery pwd
/Users/mabinbin/Library/Caches/IntelliJIdea2016.1/tomcat/Unnamed_blog_backend/work/Catalina/localhost/jquery
➜  jquery ls -lh
total 249792
-rw-r--r--  1 mabinbin  staff   122M  4 26 14:18 upload_08e39bf7_614a_4684_aee1_6d44138ff8f4_00000001.tmp

参考

HTML

标签的 enctype 属性
socket实现简单web服务器,可查看http请求信息
FileUpload – Using FileUpload

你可能感兴趣的:(♚java♚)