最近,为了暑假的实训,在学习Servlet&JSP,其中遇到了文件上传的问题,百度一下,基本上都是说用1) commons-fileupload-1.2.2-bin.zip2) commons-io-2.3-bin.zip包,第三方的API。而且都是相互转载的。这些API都封装了底层的细节,对于如何工作的,我们根本就不知道,这不利于学习。

   废话不多说了!!!在此,通过看书,了解了两种方法实现上传。

方法一:自己解析request.getInputStream()中的信息


   直接看post发送数据的数据格式。截图获取:是由得到字节流(ISO-8895-1编码格式的),然后转成字符形式后的效果。

171551974.png

171551982.png

    从截图可以看到多次出现了--------7dd17……Content-Disposition: form-data;name="**"

???……。对于出现多少次这样的形式,则说明了你在表单中有多少个input这样的标签或其他带数据的标签。其中name表明了标签中的name值,然后接着是它的value。对于<input type="file" />的则还有Content-Type的说明,然后接着是它的字节内容(也可以类比为value吧)。


   那么如何实现文件的上传呢?那么我们需要做的是解析这大块“乱码”,从中分离出文件的字节内容,然后将这些字节写入文件中。步骤如下:

   ENCODING = “ISO-8895-1”

   (1)读取request中的所有数据,并保存在字节数组中


/**

* @param request

* @return

* @throws IOException

*/

private byte[] readBody(HttpServletRequest request) throws IOException{

   int lenth = request.getContentLength();

   DataInputStream in = new DataInputStream(request.getInputStream());

   byte[] body = new byte[lenth];

   int total = 0;

   while(total < lenth) {

       int b = in.read(body, total, lenth);

   total += b;

}

   in.close();

   return body;

}

   (2)转化为ISO-8895-1编码格式的字符串

String textBody = new String(body,"ISO-8859-1");

   (3)分析textBody,获取文件名,是由数据的格式来进行截取

/**

* 获取文件名

* @param reqBody

* @return

*/

private String getFileName(String reqBody) {

   String filename = reqBody.substring(

   reqBody.indexOf("filename=\"") + 10);// 10就是filename="

   filename = filename.substring(0, filename.indexOf("\n"));

   filename = filename.substring(filename.indexOf("\\") + 1,//  

   filename.indexOf("\""));

   try {

       String s = new String(filename.getBytes(ENCODING),"utf-8");

       filename =s;

   } catch (UnsupportedEncodingException e) {

   e.printStackTrace();

   }

   return filename;

}

   (4)截取文件内容

private Position getFilePosition (HttpServletRequest request, String textBody) throws         UnsupportedEncodingException {

   // 取得文件区段边界信息

   String contenttype = request.getContentType();

   String boundarytext = contenttype.substring(

   contenttype.lastIndexOf("=") + 1,

   contenttype.length());

   // 取得实际上传文件的起始与结束位置

   int pos = textBody.indexOf("filename=\"");

   pos = textBody.indexOf("\n", pos) + 1;

   pos = textBody.indexOf("\n", pos) + 1;

   pos = textBody.indexOf("\n", pos) + 1;

   // 文件描述信息后就文件内容,直到为文件边界为止,从pos开始找边界

   int boundaryLoc = textBody.indexOf(boundarytext, pos) -4;

   int begin = ( (textBody.substring(0, pos)).getBytes(ENCODING) ).length;

   int end = ((textBody.substring(begin, boundaryLoc)).getBytes(ENCODING)).length;

   return new Position(begin, end);

}

   (5)写文件

/**

* 将流写入文件中

* @param fileName

* @param body

* @param p

*/

private void writeTo(String fileName, byte[] body, Position p) {

   fileName = PATH + fileName;//PATH="D:/workspace/WebServlet/WebServlet/source/";

   FileOutputStream out = null;

   try {

       out = new FileOutputStream(fileName);

       out.write(body, p.begin, p.end);

       out.flush();

   } catch (FileNotFoundException e) {

       e.printStackTrace();

   } catch (IOException e) {

   e.printStackTrace();

   }finally {

       if(out != null)

       try {

           out.close();

       } catch (IOException e) {

           e.printStackTrace();

       }

   }

}

class Postion{

   int begin;

   int end;

   public Position(int begin, int end){

       this.begin = begin;

       this.end = end;

   }

}

   经过上面5步后就可以实现文件上传了。在此如果想获取某文本框中的数据,则要这样一步一步的分析,对于多文件上传,也是要这样一个个的进行分析。这样真的好麻烦啊。于是有方法二可以简化很多哦。


   方法二:利用javax.servlet.http.Part

   只有两步额,而且很简单。

   (1)获得文件名

/**

* 获得文件名

* @param part

* @return

*/

private String getFileName(Part part) {

   String header = part.getHeader("Content-Disposition");

   String filename = header.substring(header.indexOf("filename=\"") + 10

           , header.lastIndexOf("\""));

   return filename;

}

   (2)写入文件

/**

* 写入文件

* @param filename

* @param part

* @throws IOException

*/

private void writeTo(String filename, Part part) throws IOException {

   InputStream in = part.getInputStream();

   OutputStream out = new FileOutputStream(PATH + filename);//PATH同上面的

   byte[] buffer = new byte[1024];

   int len = -1;

   while((len = in.read(buffer)) != -1) {

   out.write(buffer, 0, len);

   }

   in.close();

   out.close();

}

   在此,你肯定也在关注着如何获取其他控件中的值(比如:文本框)。这个很简单。

要说明的是,对与表单中的每个数据控件、按钮(比如:submit)都会被封装成一个Part,对于label是不会的哦。

   首先:获取Part集合request.getParts(),会返回一个collection集合

   然后:进行遍历,

       如果是文件,则利用上面两步进行,(上面两步其实是由一个public方法来调用的,而这个        方法的返回值为boolean类型,如果这个文件上传成功则返回true,否则false)。故,在每次上        传时,可以判断是否成功,然后可以进行一些说明。

       如果是按钮的话,则continue

       如果是其他数据(比如,一个文本框或选择框等),则利用request.getParameter(str)

   也就是像平常一样获取值。


   总结:


   通过上面两种方法是很容易实现文件上传的,其中第一种方法可以说是最最细节的解析数据,是

很好的一种了解底层方法。当然这样也是很费劲的,很麻烦的。我在写的过程中,就遇到了各种问题,1、乱码,因为忘记文件名的转码了。2、字节数组越界,这真的就是细节了,一个字节的误差都可能会导致越界的问题。不管怎么样,第一种方式就是更具post数据传输的格式进行分析的。

   第二种方法当然是很简单的。毕竟在Part类中已经封装好了这些。


   对于想了解更多细节,写写第一种方式还是不错的哦;对于想实现那些上传等功能,还是直接用第二种方式吧