多媒体处理类

import java.io.*; import java.util.*; import javax.servlet.http.*;

/**  * <p>&nbsp;&nbsp;&nbsp; 该类用于在Servlet或JSP中处理以“multipart/form-data”  * 格式提交的请求数据。通过传入一个HttpServletRequest对象构造该对象,然后即可用  * 该对象取得请求中的普通参数及上传文件。如果请求不是该格式,构造时将抛出  * IllegalArgumentException异常。  * </p>  * <p>&nbsp;&nbsp;&nbsp; 由于普通请求对象HttpServletRequest不能处理该编码类型的  * 请求,即使没有上传文件,也不能通过getParameter()等方法得到参数。该类的一部分  * 功能模仿HttpServletRequest,提供同名的方法用来获取请求中所带的参数。包括:  * getPamameterNames()、getPamameter()、getPamameterValues()。如果某个参数代表的  * 是一个上传文件,则参数值是指该文件的文件名。另外提供upload()方法用于将一个上  * 传文件的内容写入一个输出流,可选指定文件大小限制;如果有多个上传文件,则应该  * 按照文件上传的顺序,依次调用upload()来保存。可以通过getFilename()方法用来得到  * 一个需上传的文件的文件名,也可以根据文件参数的参数名通过getParameter()来获得  * 文件名。upload()方法使用输出流来接收数据,因此不限制必须用文件形式保存,可根  * 据具体应用以各种方式保存或处理上传数据。upload()的第二个参数可选,用来定义最  * 大上传文件长度,若不定义这个参数则没有限制。若上传的文件长度超过设定限额,则  * upload()方法在写入正好最大限额那么多数据后返回-1。当upload()因文件超大而返回  * 时,可以再次调用upload()方法,将继续上传剩余部分。如果文件长度小于或等于最大  * 限额,返回文件真实长度。若用户在输入页面的文件输入框里随便输入一个不存在的文  * 件,upload()在上传时会返回长度0,调用者可根据返回值的不同做相应处理。  * </p>  * <p>&nbsp;&nbsp;&nbsp; <b>注意:</b>有一个重要的限制是多个文件只能按参数次序依  * 次上传,不能颠倒;并且在一个文件完成上传之前将不能得到在这个文件数据之后的参  * 数,因为该类为支持大文件上传,并不保存上传的数据。因此比较好的解决方案是依参  * 数次序处理,或者先上传文件,再处理普通参数(普通参数会保存)。为防止受限制,最  * 好将文件域放在HTML表单的后面。另外由于HTTP协议的特点,在上传文件之前最好不要  * 向客户端输出页面,因为浏览器在上传完数据之前不会接收页面数据,写页面的操作可  * 能被阻塞。若必须这样做可将页面缓冲设置足够大:在JSP中如下设置缓冲:  * &lt;%@ page buffer="16k" %&gt;。  * </p>  * <p>&nbsp;&nbsp;&nbsp; 代码举例:在JSP页面或Servlet的doPost方法中写如下代码即  * 可实现将请求中带的全部文件上传至upload目录:  * <pre>  * ......                                                                  <br>  * MultiRequest mr = new MultiRequest(request);                            <br>  * while (mr.getFilename()!=null && mr.getFilename().length()>0) {         <br>  *   OutputStream os = new FileOutputStream("upload/"+mr.getFilename());   <br>  *   mr.upload(os);                                                        <br>  *   os.close();                                                           <br>  * }                                                                       <br>  * ......  * </pre>  * </p>  * <p>&nbsp;&nbsp;&nbsp; 本类功能在以下环境测试通过:<br>  * 服务器:Resin1.3、Tomcat3.1、Apache+Tomcat3.1;JDK1.2.2、1.3.1、1.4beta <br>  * 浏览器:NT4.0+IE4.0、Win98+IE5.0、Win98+Netscape4.5、SunOS5.7+Netscape6 <br>  * 该类使用了Java2平台的集合类,在JDK1.1及1.0下编译和运行需要集合类扩展包。本程  * 序只有这一个文件,不依赖于其他内容,可单独编译,编译时需要servlet接口。  * </p>  * <p>&nbsp;&nbsp;&nbsp; 如果在使用过程中发现Bug,或有什么建议请发邮件至  * <a href="mailto:ldv@huawei.com">ldv@huawei.com</a> 谢谢。  * </p>  * @author sunlen  * @version 1.0  */ public class MultiRequest {

  /** 从ServletRequest得到的输入流 */   private InputStream requestStream;

  /** 保存参数值,文件类型的参数值是其在客户端的文件名(不包括路径)。*/   private HashMap parameters = new HashMap();

  /** 保存可以上传的文件的文件名,若当前位置不是一个文件则为空。*/   private String filename;

  /** 保存可以上传的文件的文件全名(带路径),若当前位置不是一个文件则为空。by 黎新朝*/   private String fileFullName;

  /** 缓冲区长度。2048是实践值,再大并不会提高性能。*/   private final int BUFFER_SIZE = 2048;

  /** 流解析过程中的使用缓冲区。*/   private byte[] buffer = new byte[BUFFER_SIZE];

  /** 协议头和正文的分隔符。*/   private final byte[] HEAD_END = new byte[]{0x0d,0x0a,0x0d,0x0a};

  /** 各参数域的分隔符。*/   private byte[] boundary;

  /** 输入流已经结束。*/   private boolean inputFinished = false;

  /** 缓冲区中保存的有效数据长度。*/   private int dataLength = 2;

  /** request 使用的字符集。--黎新朝增加,用于支持中文。   *   影响MultiRequest(HttpServletRequest request)和readString(byte[] endFlag)   */   private String charset;

  /**    * 用一般请求对象构造一个能处理multipart/form-data格式请求的对象。由于普通请求    * 对象HttpServletRequest不能处理该编码类型的请求,即使没有上传文件,也不能通    * 过getParameter()等方法得到参数。因此需要用该对象同名的方法来替代。另外提供    * upload()方法用于上传文件。其它功能请还是使用原来的HttpServletRequest对象,    * 例如得到协议头中的属性、获取Servlet上下文等。    * @param request JSP内部变量request,或Servlet的doPost()方法传入的请求对象。    * @throws IOException 读取请求数据流出现异常时。    * @exception IllegalArgumentException 解析数据出错:如请求数据格式不是    *            multipart/form-data格式,或找不到应该存在的协议头等。    */   public MultiRequest(HttpServletRequest request) throws IOException {

    //缓冲区中先放入回车换行     buffer[0]=(byte)'/r';     buffer[1]=(byte)'/n';

    //保存request使用的字符编码 -- add by 黎新朝     this.charset = request.getCharacterEncoding() ;     requestStream = request.getInputStream();     String contentType = request.getContentType();     if (contentType == null)       throw new IllegalArgumentException("Can not determine content type");     int i = contentType.indexOf("boundary=");     if (i<=0) //该请求不是multipart/form-data格式       throw new IllegalArgumentException("Not multipart/form-data format");     try {       boundary = ("/r/n--"+contentType.substring(i+9)).getBytes("ASCII");     } catch (Exception ex) {       throw new IllegalArgumentException("Can not find boundary: " + ex);     }     while (parseNext()); //解析普通参数,直到流结束或遇到该上传文件。   }

  /**    * 根据参数名取得参数值。若当前有文件需要上传,则该文件后面的参数将不能获得,    * (当前需要上传的文件的参数值可以获得,就是其文件名),若没有文件需要上传了则    * 取得的参数就是完整的了。如果有多个同名参数,该方法只返回第一个参数值,可以    * 用getParameterValues()方法获得所有同名参数的参数值数组。    * @param paramName 参数名。    * @return 参数值。如果参数不存在,或者在尚未上传的文件之后,返回null。    * @    */   public String getParameter(String paramName) {     List values = (List) parameters.get(paramName);     if (values ==null)       return null;     else       return (String) values.get(0);   }

  /**    * 取得多值参数的所有参数值数组。若当前有文件需要上传,则该文件后面的参数将不    * 能获得,(当前需要上传的文件的参数值可以获得,就是其文件名),若没有文件需要    * 上传了则取得的参数就是完整的了。    * @param paramName 参数名。    * @return 若参数存在,返回参数值数组;若参数不存在返回null。    */   public String[] getParameterValues(String paramName) {     List values = (List) parameters.get(paramName);     if (values == null)       return null;     else       return (String[]) values.toArray(new String[0]);   }

  /**    * 取得当前已经获得的所有参数名。若当前有文件需要上传,则该文件后面的参数将不    * 能获得,(当前需要上传的文件的参数值可以获得,就是其文件名),若没有文件需要    * 上传了则取得的参数就是完整的了。    * @return 参数名枚举。    */   public Enumeration getPamameterNames() {     return Collections.enumeration(parameters.keySet());   }

  /**    * 取得当前已经准备好上传的文件的文件名。如果明确知道文件域的名字(多数情况是这    * 样),也可以通过getParameter()方法获得文件名。该方法一般用在不考虑传来的参数    * 和文件有哪些,一股脑成批处理。同时也可用来判断当前是否应该进行上传操作若返    * 回null,那一定是已经没有文件需要上传了。    * @return 准备好上传的文件的文件名,若当前没有文件可上传则返回null。    */   public String getFilename() {     return filename;   }

  /**    * 取得当前已经准备好上传的文件的全路径名。如果明确知道文件域的名字(多数情况是这    * 样),也可以通过getParameter()方法获得文件名。该方法一般用在不考虑传来的参数    * 和文件有哪些,一股脑成批处理。同时也可用来判断当前是否应该进行上传操作若返    * 回null,那一定是已经没有文件需要上传了。    * @return 准备好上传的文件的文件名,若当前没有文件可上传则返回null。    * @add by 黎新朝.    */   public String getFullFilename() {     return fileFullName ;   }

  /**    * 将上传的一个文件的内容写入到一个输出流中。输出流的打开和关闭和缓冲清洗    * (flush)由调用者负责。该方法对上传文件的大小没有限制。    * @param os 数据输出的目的地。    * @return 成功返回文件大小,出错返回小于0的错误号,其中流结束返回-2。    * @throws IOException 从请求中读数据或往输出流里写数据时出现异常。    */   public int upload(OutputStream os) throws IOException {     return upload(os,Integer.MAX_VALUE);   }

  /**    * 带最大文件长度限制上传。如果因文件超长,本方法确保此时写入输出流的数据长度    * 刚好为maxFileSize个字节。允许再次调用upload()方法继续超长部分的数据。超长    * 文件不上传完,将获得不了后续参数。    * @param os 文件写入的流。    * @param maxFileSize 最大文件长度,整数最大值大约是2G,应该足够了。    * @return 成功返回文件大小,出错返回小于0的错误号,其中:    *         文件超过最大限制返回-1;上传过程中流结束返回-2;    *         上传文件前流已经结束返回-3。    * @throws IOException 从请求中读数据或往输出流里写数据时出现异常。    */   public int upload(OutputStream os,int maxFileSize) throws IOException {     int fileLength = 0;     int safeLength = BUFFER_SIZE - boundary.length;     if (inputFinished)       return -3;     for (;;) { //循环从输入流中读,往输出流里写,直到读到边界为止       int n = fillTo(boundary);       if (n==-1) { //缓冲区被读满         if (fileLength+safeLength > maxFileSize) { //将会超大           safeLength = maxFileSize - fileLength;           os.write(buffer,0,safeLength);           moveFront(safeLength);           return -1;         } else { //不会超大           os.write(buffer,0,safeLength);           moveFront(safeLength);           fileLength += safeLength;         }       } else if (n==-2) { //输入流已经结束         return -2;       } else { //读到边界         if (fileLength+safeLength > maxFileSize) { //将会超大           safeLength = maxFileSize - fileLength;           os.write(buffer,0,safeLength);           moveFront(safeLength);           return -1;         } else { //不会超大           os.write(buffer,0,n);           moveFront(n+boundary.length);           fileLength += n;           filename = null;           fileFullName = null ;           while (parseNext()); //解析下一个参数直到不能解析为止         }         return fileLength;       }     } //for(;;)结束   }

  /**    * 解析下一个参数片段,并将解析出的参数名、参数值放到参数哈希表中,若当前位置    * 指向一个上传文件的内容,则什么也不做,直接返回。也就是说必须处理完上传文件    * 才能继续对其后面的内容进行解析。    * @return 成功解析出下一个片段,则返回true;    *         输入流结束或遇到一个需要上传的文件,则不做任何处理,直接返回false。    * @throws IOException 读取请求输入流出现异常。    * @exception IndexOutOfBoundsException 协议格式不符合约定导致解析错误。    */   private boolean parseNext() throws IOException{     if (inputFinished || (filename!=null && !filename.equals(""))) //已经不能解析了       return false;     String head = readString(HEAD_END); //读取片段头     if (head==null)       return false;

    //从片段头中获得参数名     int nameBegin = head.indexOf("name=/"");     int nameEnd = head.indexOf((int)'/"',nameBegin+6);     String name = head.substring(nameBegin+6,nameEnd);     String value; //用来存放参数值

    //判断是否文件类型     int fileBegin = head.indexOf("filename=/"");     if (fileBegin<0) { //不是文件类型,取出片段体作为参数值       value = readString(boundary);     } else { //是文件类型的参数,将文件名作为参数值       int fileEnd = head.indexOf((int)'/"',fileBegin+10);       value = head.substring(fileBegin+10,fileEnd);

      //保存文件的全路径名       this.fileFullName = value ;       //去掉文件名中的路径       int f = value.lastIndexOf((int)'/');       if (f<0)         f = value.lastIndexOf((int)'//');       if (f<0)         f = value.lastIndexOf((int)':');       if (f>=0)         value = value.substring(f+1);       filename = value;     }

    //将参数加入列表     List values = (List) parameters.get(name);     if (values==null) { //如果还没有该名称的参数,则先建立一个空列表       values = new ArrayList();       parameters.put(name,values);     }     values.add(value);     return true;   }

  /**    * 读输入流中的数据直到填满缓冲区,或者遇到指定的边界标记,或者输入流结束。    * 如果读取前在当前缓冲中找到边界标记,则不做任何事情,直接返回标记的位置。    * @param endFlag边界标记。    * return -1 缓冲区满;-2 输入流结束;遇到的第一个边界的位置。    */   private int fillTo(byte[] endFlag) throws IOException{     boolean notFind = true;     int flagLength = endFlag.length;     int position = 0; //已经搜索过的位置     for(;;) { //循环查找

      //从position到数据结束前范围内寻找边界       for (; position<dataLength-flagLength; position ++) {         notFind=false;

        //和边界进行匹配         for (int i=flagLength-1;i>=0;i--) { //倒着找比较快

          //只要有一个字节不相等position就不是匹配位置,进入position的下一个循环           if (buffer[position+i]!=endFlag[i]) {             notFind=true;             break;           }         }         if (!notFind) break; //找到了       }       //当前缓冲区已经搜索完成

      if (notFind) { //如果没找到         if (dataLength<BUFFER_SIZE) { //没找到,并且缓冲区没满了

          //读一段数据到缓冲区           int n = requestStream.read(buffer,dataLength,BUFFER_SIZE-dataLength);           if (n<0) {//没找到并且缓冲区没满,读时发现输入流结束了             inputFinished = true;             return -2;           }           dataLength += n;           continue; //读入了新数据,重新查找         } else //缓冲区满了并且没找到           return -1;       } else //如果找到了,则返回找到的位置         return position;     }   }

  /**    * 将缓冲区中数据前移n字节,若n>=dataLength则将dataLength设置为0。    * @param n 前移多少字节。    */   private void moveFront(int n) {     if (n>=dataLength)       dataLength = 0;     else {       System.arraycopy(buffer,n,buffer,0,dataLength-n);       dataLength -= n;     }   }

  /**    * 读取以endFlag结尾的数据转换成字符串(不包括结束标记本身),    * 并将缓冲中内容连同结束标记全部清除。    * @param endFlag 结束标记。    * @return 返回读到的字符串,如果流结束仍没读到endFlag则返回null。    * @throws IOException 读取请求数据流时出现异常。    */   private String readString(byte[] endFlag) throws IOException {     List result = new LinkedList(); //保存读出的字节     int flagLen = endFlag.length;     int safeLen = BUFFER_SIZE - flagLen;     int end; //结束标记在缓冲区中的位置     for(;;){ //循环读       end = fillTo(endFlag); //申请读到结束标记       if (end<=-2) { //没读到结束标记输入流就已经结束         return null;       } else { //读到结束标记或缓冲区满         byte[] oldBuffer = buffer; //保存旧缓冲区         buffer = new byte[BUFFER_SIZE];//创建新缓冲区         result.add(oldBuffer);         if (end < 0) { //读到缓冲区满           System.arraycopy(oldBuffer,safeLen,buffer,0,flagLen);           dataLength=flagLen;         } else { //读到结束标记           int remainLength = dataLength-end-flagLen;           System.arraycopy(oldBuffer,end + flagLen,buffer,0,remainLength);           dataLength=remainLength;           break;         }       }     } //for(;;)结束

    //将列表中的多个数组合并成单个数组,并转换成字符串     byte[] single = new byte[(result.size()-1)*(safeLen)+end];     int index = 0;     for (Iterator i = result.iterator(); i.hasNext();index++) {       byte[] item = (byte[]) i.next();       if (i.hasNext()) //不是最后一个         System.arraycopy(item,0,single,index*safeLen,safeLen);       else         System.arraycopy(item,0,single,index*safeLen,end);     }

    //使用request的编码字符集转换  -- modify by 黎新朝     return new String(single ,this.charset); //转换成字符串   } }

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Golang语言社区

【Golang语言社区】 Go语言中使用 Protobuf

安装 goprotobuf 1.从 https://github.com/google/protobuf/releases 获取 Protobuf 编译器 pr...

3253
来自专栏封碎

Java多线程参考手册 博客分类: 经典文章转载

http://blog.csdn.net/ring0hx/article/details/6858582

742
来自专栏精讲JAVA

OutOfMemoryError异常系列之Java堆溢出

OOM异常是一种很常见的错误,但是更多的程序员对其更多的是一种迷惑,今天我就在这给大家讲讲OOM的几种情景。 Java堆溢出。 虚拟机栈和本地方法栈溢出。 方...

1985
来自专栏武培轩的专栏

京东面经汇总

一、Java Java的优势 平台无关性、垃圾回收 Java有哪些特性,举个多态的例子。 封装、继承、多态 abstract interface区别 含有abs...

5756
来自专栏玄魂工作室

怎样学Python 第二十三课 模块化处理用户输入基础

大家好,今天让我们来了解一个非常有用的模块,我很久以前就没有意识到这一点,这个模块允许我们简单而有效地使用命令行参数,它不仅会为我们处理这些争论,而且如果事情不...

30610
来自专栏云瓣

Node.js 异步异闻录

提到 Node.js, 我们脑海就会浮现异步、非阻塞、单线程等关键词,进一步我们还会想到 buffer、模块机制、事件循环、进程、V8、libuv 等知识点。本...

4098
来自专栏Dawnzhang的开发者手册

Spring中的@Transactional(rollbackFor = Exception.class)属性详解

今天我在写代码的时候,看到了。一个注解@Transactional(rollbackFor = Exception.class),今天就和大家分享一下,这个注解...

2001
来自专栏西安-晁州

struts2随笔

1、struts.properties配置常量等同于struts.xml中配置(置于类加载路径下面) struts.multipart.maxSize文件上传最...

2180
来自专栏前端杂货铺

AngularJS源码分析之依赖注入$injector

开篇 随着javaEE的spring框架的兴起,依赖注入(IoC)的概念彻底深入人心,它彻底改变了我们的编码模式和思维。在IoC之前,我们在程序中需要创建一个...

3415
来自专栏Java学习之路

03 Spring框架 bean的属性以及bean前处理和bean后处理

整理了一下之前学习spring框架时候的一点笔记。如有错误欢迎指正,不喜勿喷。 上一节我们给出了三个小demo,具体的流程是这样的: 1.首先在aplicat...

3246

扫码关注云+社区

领取腾讯云代金券