zoukankan      html  css  js  c++  java
  • HTTP上传文件探究

        通常情况下,我们想在网页上上传一个文件的时候,会采用<input type="file">标签,但是你有没有想过,为什么通过这样一个标签,服务器端就能获取到文件数据呢?我们先来写这样一个页面:

     1 <html>
     2 <head>
     3 </head>
     4 <body>
     5    <form action="http://127.0.0.1:8899/upload.do" method="post" enctype="multipart/form-data" >
     6      <input type="file" name="myfile" />
     7           <input name="otherparm" value="aaaaa" />
     8      <input type="submit" value="aa" />
     9    </form>
    10 </body>
    11 </html>

        可以看到这只是一个普通的form表单,里边有两个字段,一个是file类型,另一个是普通的text类型。同时我们还要注意的是,在form的属性里我们设置了提交方式为post,且 enctype=multipart/form-data。那么我们提交后浏览器发送的请求格式是什么样的呢?下边是chrome的开发者工具贴过来的数据:

    Request URL:http://127.0.0.1:8899/upload.do
    Request Headers CAUTION: Provisional headers are shown.
    Accept:text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
    Content-Type:multipart/form-data; boundary=----WebKitFormBoundarykyKevNik4tKTFv0c
    Origin:null
    Referer:
    User-Agent:Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/33.0.1750.146 Safari/537.36
    Request Payload
    ------WebKitFormBoundarykyKevNik4tKTFv0c( )
    Content-Disposition: form-data; name="myfile"; filename="logo.png"( )
    Content-Type: image/png( )
    ( )
    (此处为file数据,chrome下显示为空白)( )
    ------WebKitFormBoundarykyKevNik4tKTFv0c( )
    Content-Disposition: form-data; name="otherparm"( )
    ( )
    aaaaa( )
    ------WebKitFormBoundarykyKevNik4tKTFv0c--


         从中我们可以看出一些不同,一个就是在Content-Type的值为
    multipart/form-data,而一般情况下我们经常看到的为text/html之类的(对照表), 此外就是表单的content内容比较怪异,仔细看下发现content被 ------WebKitFormBoundarykyKevNik4tKTFv0c划分为两部分,而第一部分通过 Content-Disposition可以看出这是一个文件,而且 Content-Type: image/png指定了文件类型,下边跟着文件的数据;而第二部分则对应我们在 表单中提交的otherparm参数。
        这样一分析,input:file上传文件的方式就很明白了,我们后台只要按照此格式对数据进行解析即可。其实这样的格式是遵循 Request For Comments( RFC )1867对文件上传的规定。
        那么我们后台具体要怎么处理呢?对于java web程序而言,我们通常采用COS、apache common-upload组件等。那么如果我们想自己解析呢,下边是一段copy来的代码,仅供参考:

      1 private byte[] parse(HttpServletRequest request) throws IOException {
      2 
      3         final int NONE = 0;
      4         final int DATAHEADER = 1;
      5         final int FILEDATA = 2;
      6         final int FIELDDATA = 3;
      7 
      8         final int MXA_SEGSIZE = 1000 * 1024 * 10;//每批最大的数据量 10M
      9 
     10         String contentType = request.getContentType();// 请求消息类型
     11         String fieldname = ""; // 表单域的名称
     12         String fieldvalue = ""; // 表单域的值
     13         String filename = ""; // 文件名
     14         String boundary = ""; // 分界符
     15         String lastboundary = ""; // 结束符
     16         String filePath = "";
     17         Hashtable<String, String> formfields = new Hashtable<String, String>();
     18         int filesize = 0; // 文件长度
     19 
     20         int pos = contentType.indexOf("boundary=");
     21 
     22         if (pos != -1) { // 取得分界符和结束符
     23             pos += "boundary=".length();
     24             boundary = "--" + contentType.substring(pos);
     25             lastboundary = boundary + "--";
     26         }
     27         int state = NONE;
     28         // 得到数据输入流reqbuf
     29         DataInputStream in = new DataInputStream(request.getInputStream());
     30         // 将请求消息的实体送到b变量中
     31         int totalBytes = request.getContentLength();
     32         String message = "";
     33         if (totalBytes > MXA_SEGSIZE) {//每批大于10m时
     34             message = "Each batch of data can not be larger than " + MXA_SEGSIZE / (1000 * 1024)
     35                     + "M";
     36             return null;
     37         }
     38         byte[] b = new byte[totalBytes];
     39         in.readFully(b);
     40         in.close();
     41         String reqContent = new String(b, "UTF-8");//
     42         BufferedReader reqbuf = new BufferedReader(new StringReader(reqContent));
     43 
     44         boolean flag = true;
     45         int i = 0;
     46         while (flag == true) {
     47             String s = reqbuf.readLine();
     48             if ((s == null) || (s.equals(lastboundary)))
     49                 break;
     50 
     51             switch (state) {
     52                 case NONE:
     53                     if (s.startsWith(boundary)) {
     54                         state = DATAHEADER;
     55                         i += 1;
     56                     }
     57                     break;
     58                 case DATAHEADER:
     59                     pos = s.indexOf("filename=");
     60                     if (pos == -1) { // 将表单域的名字解析出来
     61                         pos = s.indexOf("name=");
     62                         pos += "name=".length() + 1;
     63                         s = s.substring(pos);
     64                         int l = s.length();
     65                         s = s.substring(0, l - 1);
     66                         fieldname = s;
     67                         state = FIELDDATA;
     68                     } else { // 将文件名解析出来
     69                         String temp = s;
     70                         pos = s.indexOf("filename=");
     71                         pos += "filename=".length() + 1;
     72                         s = s.substring(pos);
     73                         int l = s.length();
     74                         s = s.substring(0, l - 1);// 去掉最后那个引号”
     75                         filePath = s;
     76                         pos = s.lastIndexOf("\");
     77                         s = s.substring(pos + 1);
     78                         filename = s;
     79                         // 从字节数组中取出文件数组
     80                         pos = byteIndexOf(b, temp, 0);
     81                         b = subBytes(b, pos + temp.getBytes().length + 2, b.length);// 去掉前面的部分
     82                         int n = 0;
     83                         /**
     84                          * 过滤boundary下形如 Content-Disposition: form-data; name="bin";
     85                          * filename="12.pdf" Content-Type: application/octet-stream
     86                          * Content-Transfer-Encoding: binary 的字符串
     87                          */
     88                         while ((s = reqbuf.readLine()) != null) {
     89                             if (n == 1)
     90                                 break;
     91                             if (s.equals(""))
     92                                 n++;
     93 
     94                             b = subBytes(b, s.getBytes().length + 2, b.length);
     95                         }
     96                         pos = byteIndexOf(b, boundary, 0);
     97                         if (pos != -1)
     98                             b = subBytes(b, 0, pos - 1);
     99 
    100                         filesize = b.length - 1;
    101                         formfields.put("filesize", String.valueOf(filesize));
    102                         state = FILEDATA;
    103                     }
    104                     break;
    105                 case FIELDDATA:
    106                     s = reqbuf.readLine();
    107                     fieldvalue = s;
    108                     formfields.put(fieldname, fieldvalue);
    109                     state = NONE;
    110                     break;
    111                 case FILEDATA:
    112                     while ((!s.startsWith(boundary)) && (!s.startsWith(lastboundary))) {
    113                         s = reqbuf.readLine();
    114                         if (s.startsWith(boundary)) {
    115                             state = DATAHEADER;
    116                             break;
    117                         }
    118                     }
    119                     break;
    120             }
    121         }
    122         return b;
    123 
    124     }
    125 
    126     // 字节数组中的INDEXOF函数,与STRING类中的INDEXOF类似
    127     public static int byteIndexOf(byte[] b, String s, int start) {
    128         return byteIndexOf(b, s.getBytes(), start);
    129     }
    130 
    131     // 字节数组中的INDEXOF函数,与STRING类中的INDEXOF类似
    132     public static int byteIndexOf(byte[] b, byte[] s, int start) {
    133         int i;
    134         if (s.length == 0) {
    135             return 0;
    136         }
    137         int max = b.length - s.length;
    138         if (max < 0)
    139             return -1;
    140         if (start > max)
    141             return -1;
    142         if (start < 0)
    143             start = 0;
    144         search: for (i = start; i <= max; i++) {
    145             if (b[i] == s[0]) {
    146                 int k = 1;
    147                 while (k < s.length) {
    148                     if (b[k + i] != s[k]) {
    149                         continue search;
    150                     }
    151                     k++;
    152                 }
    153                 return i;
    154             }
    155         }
    156         return -1;
    157     }
    158 
    159     // 用于从一个字节数组中提取一个字节数组
    160     public static byte[] subBytes(byte[] b, int from, int end) {
    161         byte[] result = new byte[end - from];
    162         System.arraycopy(b, from, result, 0, end - from);
    163         return result;
    164     }
    165 
    166     // 用于从一个字节数组中提取一个字符串
    167     public static String subBytesString(byte[] b, int from, int end) {
    168         return new String(subBytes(b, from, end));
    169     }
    参考代码

            最后附上自己写的一个测试例子: DEMO


     



  • 相关阅读:
    IP子网掩码格式转换
    错误: symbol lookup error: /usr/local/lib/libreadline.so.6: undefined symbol: PC
    postgresql删除属性
    postgresql 修改属性
    嵌套json的查询
    嵌套json
    关于array_agg 函数
    修改jsonb的属性
    Python中exec的使用
    RHSA-2017:2930-重要: 内核 安全和BUG修复更新(需要重启、存在EXP、本地提权、代码执行)
  • 原文地址:https://www.cnblogs.com/good-temper/p/3592955.html
Copyright © 2011-2022 走看看