java模擬post傳輸文件到tomcat服務器端servlet接收

網絡上一篇介紹這個的文章,如下文章1其實是有問題的。實際上它的模擬http請求的格式有點問題,如果傳輸.txt文件就會發現.txt文件裏面多出了一些字符,而這些字符是我們的傳輸文件頭內容。爲什麼會這樣的?

參考文章2我們對比發現文章1的傳輸文件頭和文件內容的組合格式有問題的,正確的應該是文章2的樣子,但是文章2又缺少了文件尾。最後綜合之後,正確的格式應該是:


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

Patrick
------------HV2ymHFg03ehbqgZCaKO6jyH
Content-Disposition: form-data; name="password"

HELLOPATRICK
------------HV2ymHFg03ehbqgZCaKO6jyH
Content-Disposition: form-data; name="hobby"

Computer programming
------------HV2ymHFg03ehbqgZCaKO6jyH

Content-Disposition:form-data; name="upload1"; filename="C:\123.txt"
Content-Type:application/octet-stream


------------HV2ymHFg03ehbqgZCaKO6jyH--

------------HV2ymHFg03ehbqgZCaKO6jyH

Content-Disposition:form-data; name="upload2"; filename="C:\Asturias.mp3"
Content-Type:application/octet-stream


------------HV2ymHFg03ehbqgZCaKO6jyH--

------------HV2ymHFg03ehbqgZCaKO6jyH

Content-Disposition:form-data; name="upload3"; filename="C:\dyz.txt"
Content-Type:application/octet-stream


------------HV2ymHFg03ehbqgZCaKO6jyH--

------------HV2ymHFg03ehbqgZCaKO6jyH

Content-Disposition:form-data; name="upload4"; filename="C:\full.jpg"
Content-Type:application/octet-stream


------------HV2ymHFg03ehbqgZCaKO6jyH--

------------HV2ymHFg03ehbqgZCaKO6jyH

Content-Disposition:form-data; name="upload5"; filename="C:\Recovery.txt"
Content-Type:application/octet-stream


------------HV2ymHFg03ehbqgZCaKO6jyH--



文章1

在某些情況下,需要用Java applicatioin來模擬form,向服務器(本文以servlet爲例)發送http post請求,包括提交表單域中的數據以及上傳文件。如果僅僅是傳遞form中的數據,而不包含上傳文件,那是很簡單的,比如Java application可以這麼寫:

package com.pat.postrequestemulator;

importjava.io.BufferedReader;

importjava.io.InputStream;

importjava.io.InputStreamReader;

importjava.io.OutputStreamWriter;

importjava.net.HttpURLConnection;

importjava.net.URL;

 

public class PostRequestEmulator

{

         public static void main(String[] args)throws Exception

         {

                   // 服務地址

                   URL url = newURL("http://127.0.0.1:8080/test/upload");

 

                   // 設定連接的相關參數

                   HttpURLConnection connection= (HttpURLConnection) url.openConnection();

                   connection.setDoOutput(true);

                   connection.setRequestMethod("POST");

                   OutputStreamWriter out = newOutputStreamWriter(connection.getOutputStream(), "UTF-8");

                  

                   // 向服務端發送key = value對

                   out.write("username=kevin&password=pass");

                   out.flush();

                   out.close();

                  

                   // 獲取服務端的反饋

                   String strLine="";

                   String strResponse ="";

                   InputStream in =connection.getInputStream();

                   BufferedReader reader = newBufferedReader(new InputStreamReader(in));

                   while((strLine =reader.readLine()) != null)

                   {

                            strResponse +=strLine +"\n";

                   }

                   System.out.print(strResponse);

         }

}

 

服務端的servlet可以這麼寫:

packagecom.pat.handlinghttprequestservlet;

importjava.io.IOException;

importjava.io.PrintWriter;

importjavax.servlet.ServletException;

importjavax.servlet.http.HttpServlet;

importjavax.servlet.http.HttpServletRequest;

importjavax.servlet.http.HttpServletResponse;

 

public class HandlingHttpRequestServlet extends HttpServlet

{

         private static final longserialVersionUID = 1L;

 

         @Override

         protected void doGet(HttpServletRequestreq, HttpServletResponse resp)

         throws ServletException, IOException

         {

                   super.doGet(req, resp);

         }

 

         @Override

         protected void doPost(HttpServletRequest req, HttpServletResponse resp)

         throwsServletException, IOException

         {

                   String username =req.getParameter("username");          //獲取username所對應的value

                   String password =req.getParameter("password");           //獲取password所對應的value

                   System.out.println("Thereceived username and password is: " + username + "/" +password);

                  

                   // 向請求端發回反饋信息

                   PrintWriter out =resp.getWriter();

                   out.print("OK");

                   out.flush();

                   out.close();

                  

                   super.doPost(req, resp);

         }

}

 

一切看起來都不復雜。但是如果要模擬的表單,除了要向服務器傳遞如上面的“key = value”這樣的普通信息,同時還要上傳文件,事情就複雜得多。下面詳解如下:

 

1. 準備

玄機逸士很久沒有開發web方面的應用了,所以機器上沒有現成的環境,爲此先要進行這方面的準備。

a)  到http://tomcat.apache.org 上下載tomcat壓縮包apache-tomcat-6.0.33.zip,將其解壓到指定目錄即可,

     如:D:\Tomcat6

b)  到http://commons.apache.org上下載用於文件上傳的兩個包:commons-fileupload-1.2.2-bin.zip

     和commons-io-2.1-bin.zip, commons-fileupload依賴於commons-io,但在編程的過程中,

     不會直接用到commons-io

c)  檢查Tomcat的安裝是否成功。雙擊D:\Tomcat6\bin目錄中的startup.bat文件,就可以啓動tomcat。

     打開瀏覽器,訪問http://localhost:8080/,如果出現tomcat相關的頁面,則說明tomcat安裝成功。

d)  在D:\Tomcat6\webapps目錄下創建一個test子目錄,我們等會開發的servlet就將部署在這裏。在

     test目錄下再建立兩個目錄WEB-INF(必須大寫)和upload,在WEB-INF下面 創建兩個目錄classes和lib,

     同時新建一個web.xml文件;在upload目錄下,創建一個temp子目錄,這些工作做完以後,test目錄

     下面的目錄文件結構如下圖所示。


其中的classes目錄,用來存放將要開發的servlet,lib用來存放commons-fileupload和commons-io相關的jar包,web.xml是一些應用描述信息;upload用於存放客戶端(即我們要開發的Java application)上傳過來的文件,temp則用於存放上傳過程中可能產生的一些臨時文件。

e)  將commons-fileupload-1.2.2-bin.zip和commons-io-2.1-bin.zip解壓,可以得到commons-fileupload-

     1.2.2.jar和commons-io-2.1.jar,我們將這兩個文件拷貝到d)中創建的lib目錄中。

f)   編輯web.xml使之如下:

<?xmlversion="1.0" encoding="ISO-8859-1"?>

<!--

 Licensed to the Apache Software Foundation(ASF) under one or more

  contributor license agreements.  See the NOTICE file distributed with

  this work for additional informationregarding copyright ownership.

  The ASF licenses this file to You under theApache License, Version 2.0

  (the "License"); you may not usethis file except in compliance with

  the License. You may obtain a copy of the License at

 

     http://www.apache.org/licenses/LICENSE-2.0

 

  Unless required by applicable law or agreedto in writing, software

  distributed under the License is distributedon an "AS IS" BASIS,

  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,either express or implied.

  See the License for the specific languagegoverning permissions and

  limitations under the License.

-->

 

<web-appxmlns="http://java.sun.com/xml/ns/javaee"

  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

  xsi:schemaLocation="http://java.sun.com/xml/ns/javaeehttp://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"

   version="2.5">

 

   <servlet>

         <servlet-name>Hello</servlet-name>

          <servlet-class>com.pat.handlinghttprequestservlet.HandlingHttpRequestServlet</servlet-class>

    </servlet>

 

    <servlet-mapping>  

         <servlet-name>Hello</servlet-name>

         <url-pattern>/upload</url-pattern> 

    </servlet-mapping>

</web-app>

 

這個web.xml可以先從D:\Tomcat6\webapps\examples\WEB-INF下面拷貝過來,再進行如上黑體字所示的修改即可。<servlet>標籤描述的是servlet代碼方面的資料,其中<servlet-name>Hello</servlet-name>是給這個servlet起一個名字Hello,它必須和下面的<servlet-mapping>中的<servlet-name>一致,該名字具體是什麼可以隨意寫定。

<servlet-class>com.pat.handlinghttprequestservlet.HandlingHttpRequestServlet</servlet-class>說明了完整的servlet類名,即servlet的類名爲HandlingHttpRequestServlet,它位於包com.pat.handlinghttprequestservlet中。

<url-pattern>/upload</url-pattern>,說明了如果客戶端向“http://Web服務器的URL地址:端口號/test/upload”發出了請求,則該請求將由位於包com.pat.handlinghttprequestservlet中的HandlingHttpRequestServlet進行處理。

到此,前期準備工作結束。下面準備寫代碼。

 

2.       Servlet的代碼

packagecom.pat.handlinghttprequestservlet;

 

importjava.io.File;

importjava.io.IOException;

importjava.io.PrintWriter;

importjava.util.ArrayList;

importjavax.servlet.ServletException;

importjavax.servlet.http.HttpServlet;

importjavax.servlet.http.HttpServletRequest;

importjavax.servlet.http.HttpServletResponse;

importorg.apache.commons.fileupload.FileItem;

importorg.apache.commons.fileupload.disk.DiskFileItemFactory;

importorg.apache.commons.fileupload.servlet.ServletFileUpload;

 

public class HandlingHttpRequestServlet extends HttpServlet

{

 

         private static final longserialVersionUID = 1L;

 

         @Override

         protected void doGet(HttpServletRequestreq, HttpServletResponse resp)

         throws ServletException, IOException

         {

                   super.doGet(req, resp);

         }

 

         @SuppressWarnings({"unchecked", "deprecation" })

         @Override

         protected void doPost(HttpServletRequest req, HttpServletResponse resp)

         throwsServletException, IOException

         {       

                   DiskFileItemFactory factory =new DiskFileItemFactory();

 

                   //得到絕對文件夾路徑,比如"D:\\Tomcat6\\webapps\\test\\upload"

                   String path = req.getRealPath("/upload");

                   //臨時文件夾路徑

                   String repositoryPath =req.getRealPath("/upload/temp");        

                   // 設定臨時文件夾爲repositoryPath

                   factory.setRepository(newFile(repositoryPath)); 

                   // 設定上傳文件的閾值,如果上傳文件大於1M,就可能在repository

                   // 所代 表的文件夾中產生臨時文件,否則直接在內存中進行處理

                   factory.setSizeThreshold(1024* 1024);

                  

                   //System.out.println("----"+ req.getContextPath());  // 得到相對文件夾路徑,比如 "/test"

                  

                   // 創建一個ServletFileUpload對象

                   ServletFileUpload uploader =new ServletFileUpload(factory);

                   try

                   {

                            // 調用uploader中的parseRequest方法,可以獲得請求中的相關內容,

                            // 即一個FileItem類型的ArrayList。FileItem是在

                            // org.apache.commons.fileupload中定義的,它可以代表一個文件,

                            // 也可以代表一個普通的form field

                            ArrayList<FileItem>list = (ArrayList<FileItem>)uploader.parseRequest(req);

                            System.out.println(list.size());

                            for(FileItemfileItem : list)

                            {

                                     if(fileItem.isFormField())      // 如果是普通的form field

                                     {

                                               Stringname = fileItem.getFieldName();

                                               Stringvalue = fileItem.getString();

                                               System.out.println(name+ " = " + value);

                                     }

                                     else   // 如果是文件

                                     {

                                               Stringvalue = fileItem.getName();

                                               intstart = value.lastIndexOf("\\");

                                               StringfileName = value.substring(start + 1);

                                               // 將其中包含的內容寫到path(即upload目錄)下,

                                               // 名爲fileName的文件中

                                               fileItem.write(newFile(path, fileName));

                                     }

                            }

                   }

                   catch(Exception e)

                   {

                            e.printStackTrace();

                   }

                  

                   // 向客戶端反饋結果

                   PrintWriter out =resp.getWriter();

                   out.print("OK");

                   out.flush();

                   out.close();

                  

                   super.doPost(req, resp);

         }

}

再在classes目錄建立如下目錄結構com\pat\handlinghttprequestservlet,這用來代表HandlingHttpRequestServlet這個servlet所在的包名,將編譯好的HandlingHttpRequestServlet.class,拷貝到這個目錄下,然後啓動(或者重新啓動)tomcat

 

3.       Java application的代碼

package com.pat.postrequestemulator;

importjava.io.BufferedReader;

importjava.io.DataInputStream;

importjava.io.File;

import java.io.FileInputStream;

importjava.io.InputStream;

importjava.io.InputStreamReader;

importjava.io.OutputStream;

importjava.io.Serializable;

importjava.net.HttpURLConnection;

importjava.net.URL;

importjava.util.ArrayList;

 

public classPostRequestEmulator

{

         public static void main(String[] args)throws Exception

         {

                   // 設定服務地址

                   String serverUrl ="http://127.0.0.1:8080/test/upload";

                  

                   // 設定要上傳的普通Form Field及其對應的value

                   // 類FormFieldKeyValuePair的定義見後面的代碼

                   ArrayList<FormFieldKeyValuePair> ffkvp = new ArrayList<FormFieldKeyValuePair>();

                   ffkvp.add(newFormFieldKeyValuePair("username", "Patrick"));

                   ffkvp.add(newFormFieldKeyValuePair("password", "HELLOPATRICK"));

                   ffkvp.add(newFormFieldKeyValuePair("hobby", "Computer programming"));

                  

                   // 設定要上傳的文件。UploadFileItem見後面的代碼

                   ArrayList<UploadFileItem> ufi = new ArrayList<UploadFileItem>();

                   ufi.add(newUploadFileItem("upload1", "E:\\Asturias.mp3"));

                   ufi.add(newUploadFileItem("upload2", "E:\\full.jpg"));

                   ufi.add(newUploadFileItem("upload3", "E:\\dyz.txt"));

                   

                   // 類HttpPostEmulator的定義,見後面的代碼

                   HttpPostEmulator hpe = new HttpPostEmulator();

                   String response =hpe.sendHttpPostRequest(serverUrl, ffkvp, ufi);

                   System.out.println("Responsefrom server is: " + response);

         }

}

 

classHttpPostEmulator

{

         //每個post參數之間的分隔。隨意設定,只要不會和其他的字符串重複即可。

         private static final String BOUNDARY ="----------HV2ymHFg03ehbqgZCaKO6jyH";

   

         public StringsendHttpPostRequest(String serverUrl,

                                                ArrayList<FormFieldKeyValuePair>generalFormFields,

                                                ArrayList<UploadFileItem>filesToBeUploaded) throws Exception

         {

                   // 向服務器發送post請求

                   URL url = newURL(serverUrl/*"http://127.0.0.1:8080/test/upload"*/);

                   HttpURLConnection connection= (HttpURLConnection) url.openConnection();

                  

                   // 發送POST請求必須設置如下兩行

                   connection.setDoOutput(true);

                   connection.setDoInput(true);

                   connection.setUseCaches(false);

                   connection.setRequestMethod("POST");

                   connection.setRequestProperty("Connection","Keep-Alive");

                   connection.setRequestProperty("Charset","UTF-8");

                   connection.setRequestProperty("Content-Type","multipart/form-data; boundary=" + BOUNDARY);

                  

                   // 頭

                   String boundary = BOUNDARY;

                   // 傳輸內容

                   StringBuffer contentBody =new StringBuffer("--" + BOUNDARY);

                   // 尾

                   String endBoundary ="\r\n--" + boundary + "--\r\n";

                  

                   OutputStream out =connection.getOutputStream();

                  

                   // 1. 處理普通表單域(即形如key = value對)的POST請求

                   for(FormFieldKeyValuePairffkvp : generalFormFields)

                   {

                            contentBody.append("\r\n")

                           .append("Content-Disposition: form-data; name=\"")

                           .append(ffkvp.getKey() + "\"")

                           .append("\r\n")

                           .append("\r\n")

                           .append(ffkvp.getValue())

                           .append("\r\n")

                           .append("--")

                           .append(boundary);

                   }

                   String boundaryMessage1 =contentBody.toString();

                   out.write(boundaryMessage1.getBytes("utf-8"));

                  

                   // 2. 處理文件上傳

                   for(UploadFileItem ufi :filesToBeUploaded)

                   {

                            contentBody = newStringBuffer();

                            contentBody.append("\r\n")

                          .append("Content-Disposition:form-data; name=\"")

                          .append(ufi.getFormFieldName() +"\"; ")   // form中field的名稱

                          .append("filename=\"")

                          .append(ufi.getFileName() +"\"")   //上傳文件的文件名,包括目錄

                          .append("\r\n")

                          .append("Content-Type:application/octet-stream")

                          .append("\r\n\r\n");

                           

                            StringboundaryMessage2 = contentBody.toString();

                            out.write(boundaryMessage2.getBytes("utf-8"));

                           

                            // 開始真正向服務器寫文件

                            File file = newFile(ufi.getFileName());

                            DataInputStream dis= new DataInputStream(new FileInputStream(file));

                            int bytes = 0;

                            byte[] bufferOut =new byte[(int) file.length()];

                            bytes =dis.read(bufferOut);

                            out.write(bufferOut,0, bytes);

                            dis.close();

                            contentBody.append("------------HV2ymHFg03ehbqgZCaKO6jyH");

                           

                            StringboundaryMessage = contentBody.toString();

                            out.write(boundaryMessage.getBytes("utf-8"));

                            //System.out.println(boundaryMessage);

                   }

                   out.write("------------HV2ymHFg03ehbqgZCaKO6jyH--\r\n".getBytes("UTF-8"));

                  

                   // 3. 寫結尾

                   out.write(endBoundary.getBytes("utf-8"));

                   out.flush();

                   out.close();

                  

                   // 4. 從服務器獲得回答的內容

                   String strLine="";

                   String strResponse ="";

                  

                   InputStream in =connection.getInputStream();

                   BufferedReader reader = newBufferedReader(new InputStreamReader(in));

                   while((strLine =reader.readLine()) != null)

                   {

                            strResponse +=strLine +"\n";

                   }

                   //System.out.print(strResponse);

                  

                   return strResponse;

         }

}

 

// 一個POJO。用於處理普通表單域形如key = value對的數據

classFormFieldKeyValuePair implements Serializable

{

         private static final longserialVersionUID = 1L;

        

         // The form field used for receivinguser's input,

         // such as "username" in "<inputtype="text" name="username"/>"

         private String key;

         // The value entered by user in thecorresponding form field,

         // such as "Patrick" the abovementioned formfield "username"

         private String value;

        

         public FormFieldKeyValuePair(Stringkey, String value)

         {

                   this.key = key;

                   this.value = value;

         }

        

         public String getKey()

         {

                   return key;

         }

        

         public void setKey(String key)

         {

                   this.key = key;

         }

        

         public String getValue()

         {

                   return value;

         }

        

         public void setValue(String value)

         {

                   this.value = value;

         }

}

 

// 一個POJO。用於保存上傳文件的相關信息

classUploadFileItem implements Serializable

{

         private static final longserialVersionUID = 1L;

        

         // The form field name in a form used foruploading a file,

         // such as "upload1" in "<inputtype="file" name="upload1"/>"

         private String formFieldName;

        

         // File name to be uploaded, thefileName contains path,

         // such as "E:\\some_file.jpg"

         private String fileName;

        

         public UploadFileItem(StringformFieldName, String fileName)

         {

                   this.formFieldName =formFieldName;

                   this.fileName = fileName;

         }

 

         public String getFormFieldName()

         {

                   return formFieldName;

         }

 

         public void setFormFieldName(StringformFieldName)

         {

                   this.formFieldName =formFieldName;

         }

 

         public String getFileName()

         {

                   return fileName;

         }

 

         public void setFileName(StringfileName)

         {

                   this.fileName = fileName;

         }

}

 

4.       運行結果

運行PostRequestEmulator之前,服務器的upload目錄下的情況:

 

運行PostRequestEmulator後,服務器的upload目錄下的情況:

 

PostRequestEmulator從服務端得到的反饋情況:

Response fromserver is: OK

 

Tomcat控制檯輸出:

 

如果上傳的文件中有大於1M的情況,第二次執行PostRequestEmulator的時候,就會在temp目錄中產生臨時文件。

 

本文參考材料:

http://v.youku.com/v_show/id_XMjc0ODMxMTA4.html

http://www.iteye.com/topic/1116110(在Android中用Application模擬http post請求)

http://apps.hi.baidu.com/share/detail/46728849

http://www.eoeandroid.com/thread-22679-1-1.html

http://blog.zhaojie.me/2011/03/html-form-file-uploading-programming.html

 

文章2

 在最初的 http 協議中,沒有上傳文件方面的功能。RFC1867("Form-based File Upload in HTML".)
 http 協議添加了這個功能。客戶端的瀏覽器,如 Microsoft IE, Mozila, Opera 等,按照此規範將用
戶指定的文件發送到服務器。服務器端的網頁程序,如
 php, asp, jsp 等,可以按照此規範,解析出用戶
發送來的文件。

2.1客戶端

簡單來說,RFC1867規範要求http協議增加了file類型的input標籤,用於瀏覽需要上傳的文件。同時
要求
FORM表單的enctype屬性設置爲“multipart/form-data”,method屬性設置爲“post”即可,下面是我們文
件上傳頁面的表單代碼:

<form action="<%=request.getContextPath()%>/servlet/SimpleUpload" enctype="multipart/form-data" 
method
="post">

文本1<input type="text" name="text1" value="文本1"><br>

文件2<input type="text" name="text2" value="文本2"><br>

文件1<input type="file" name="file1"><br>

文件2<input type="file" name="file2"><br>

文件2<input type="file" name="file3"><br>

<input type="submit" value="開始上傳">

</form>

2.2 服務器端

一個文件上傳請求的消息實體由一系列根據 RFC1867"Form-based File Upload in HTML".)編碼的項目
(文本參數和文件參數)組成。自己編程來解析獲取這些數據是非常麻煩的,還需要了解
RFC1867規範對請
求數據編碼的相關知識。
FileUpload 可以幫助我們解析這樣的請求,將每一個項目封裝成一個實現了FileItem
接口的對象,並以列表的形式返回。所以,我們只需要瞭解FileUploadAPI如何使用即可,不用管它們的底
層實現。讓我們來看一個簡單文件上傳處理代碼:

DiskFileItemFactory factory = new DiskFileItemFactory();

ServletFileUpload uploader = new ServletFileUpload(factory);

List<FileItem> list = uploader.parseRequest(request);

if (item.isFormField()){

// 處理普通表單域

String field = item.getFieldName();//表單域名

String value = item.getString("GBK");

else {

//將臨時文件保存到指定目錄

String fileName = item.getName();//文件名稱

String filepath = "您希望保存的目錄/" + fileName;

item.write(new File(filepath));//執行保存

}

    怎麼樣?簡單吧!下面我們來繼續瞭解一些必須瞭解的API

FileItem接口

org.apache.commons.fileupload.disk.DiskFileItem實現了FileItem接口,用來封裝單個表單字段元素的
數據。通過調用
FileItem 定義的方法可以獲得相關表單字段元素的數據。我們不需要關心DiskFileItem的具
體實現,在程序中可以採用
FileItem接口類型來對DiskFileItem對象進行引用和訪問。FileItem類還實現了
Serializable接口,以支持序列化操作。

下圖是一個文件上傳表單:



上圖表單提交的http數據包的內容:

POST /demo/servlet/SimpleUpload HTTP/1.1

Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/x-shockwave-flash, application/msword, 
application/vnd.ms-excel, application/vnd.ms-powerpoint, */*

Referer: http://127.0.0.1:8080/demo/simpleUpload.jsp

Accept-Language: zh-cn

Content-Type: multipart/form-data; boundary=---------------------------7da1772c5504c6

UA-CPU: x86

Accept-Encoding: gzip, deflate

User-Agent: Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; .NET CLR 2.0.50727)

Host: 127.0.0.1:8080

Content-Length: 184423

Connection: Keep-Alive

Cache-Control: no-cache

Cookie: JSESSIONID=BD8E58E5BAD9B559C0262077FB5E0B4E

-----------------------------7da1772c5504c6

Content-Disposition: form-data; name="text1"

鄭州蜂鳥科技有限公司

-----------------------------7da1772c5504c6

Content-Disposition: form-data; name="text2"

申林

-----------------------------7da1772c5504c6

Content-Disposition: form-data; name="file1"; filename="C:\Documents and Settings\All Users\
Documents\My Pictures\示例圖片\Blue hills.jpg"

Content-Type: image/pjpeg

大量二進制數據內容,無法複製 …….

-----------------------------7da1772c5504c6

Content-Disposition: form-data; name="file2"; filename="C:\Documents and Settings\All Users\
Documents\My Pictures\示例圖片\
Sunset.jpg"

Content-Type: image/pjpeg

大量二進制數據內容,無法複製 …….

-----------------------------7da1772c5504c6

Content-Disposition: form-data; name="file3"; filename="C:\Documents and Settings\All Users\
Documents\My Pictures\示例圖片\
Water lilies.jpg"

Content-Type: image/pjpeg

大量二進制數據內容,無法複製 …….

從第一行,也就是請求行,我們可以看出這是一個post請求。在請求頭部部分,我們可以看到這樣
一個頭部信息:

Content-Type: multipart/form-data; boundary=---------------------------7da1772c5504c6

其中紅色部分說明該請求是一個multipart/form-data類型即多媒體類型的請求。藍色部分boundary的值
定義了一個字段分隔界線。在消息體部分可以看出每個表單字段元素數據之間採用字段分隔界線進行分
割,兩個分隔界線間的內容稱爲一個分區,每個分區中的內容包括兩部分,一部分是對錶單字段元素進
行描述的描述頭,另外一部分是表單字段元素的主體內容。 

通過對比描述頭,我們可以很容易區分文本字段和文件字段。不管是文件字段還是文本字段,都有
name屬性,即該字段作爲一個表單域的名字。而文件字段還有filename,即上傳文件本身的名字。另外,
還有
conten-type屬性用於指明文件的類型。

每一個表單字段,不管它是文本還是文件,都被封裝成 FileItem 對象,我們稱之爲文件項,當文件
項數據內容尺寸小於
DiskFileItemFactory 的sizeThreshold 屬性設置的臨界值時,直接保存在內存中;否則,
將數據流以臨時文件的形式,保存在 
DiskFileItemFactory 的 repository 屬性指定的臨時目錄中。臨時文件
名形如“
upload_00000005(八位或八位以上的數字).tmp”。
FileItem類內部提供了維護臨時文件名中的
數值不重複的機制,以保證了臨時文件名的唯一性。另外,如何保證臨時文件能被及時清除,釋放寶貴
的系統資源,是非常重要的,我們將在後面講解。 


FileItem類常用的方法:

1.  boolean isFormField()方法

isFormField方法用於判斷FileItem類對象封裝的數據是一個普通文本表單字段,還是一個文件表單字
段,如果是普通表單字段則返回true,否則返回false

2.  String getName()方法 

getName方法用於獲得文件上傳字段中的文件名,即表單字段元素描述頭中的filename屬性值,如“C:\Documents and Settings\All Users\Documents\My Pictures\示例圖片\Sunset.jpg”。如果FileItem類對象對
應的是普通表單字段,
getName方法將返回null即使用戶沒有通過網頁表單中的文件字段傳遞任何
文件,但只要設置了文件表單字段的name屬性,瀏覽器也會將文件字段的信息傳遞給服務器,
只是文件名和文件內容部分都爲空,但這個表單字段仍然對應一個
FileItem對象,此時,
getName
方法返回結果爲空字符串
"",讀者在調用Apache文件上傳組件時要注意考慮這個情況。

注意:上面的數據包是通過IE提交,所以是完整的路徑和名稱。如 
C:\Documents and Settings\All Users\Documents\My Pictures\示例圖片\
Sunset.jpg。如果是其它瀏覽
器,如火狐和Chromium,則僅僅是名字,沒有路徑,如
Sunset.jpg

3.  String getFieldName()方法

getFieldName方法用於返回表單字段元素描述頭的name屬性值,也是表單標籤name屬性的值。例
如“
name=file1”中的“file1”。

4.  void write(File file)方法

write方法用於將FileItem對象中保存的主體內容保存到某個指定的文件中。如果FileItem對象中的主
體內容是保存在某個臨時文件中,該方法順利完成後,臨時文件有可能會被清除。該方法也可將普通
表單字段內容寫入到一個文件中,但它主要用途是將上傳的文件內容保存在本地文件系統中。

5.  String getString()方法

getString方法用於將FileItem對象中保存的數據流內容以一個字符串返回,它有兩個重載的定義形式:

public java.lang.String getString()

public java.lang.String getString(java.lang.String encoding)

throws java.io.UnsupportedEncodingException

前者使用缺省的字符集編碼將主體內容轉換成字符串,後者使用參數指定的字符集編碼將主體內容
轉換成字符串。
如果在讀取普通表單字段元素的內容時出現了中文亂碼現象,請調用第二個
getString方法,併爲之傳遞正確的字符集編碼名稱。

6.  String getContentType()方法

getContentType 方法用於獲得上傳文件的類型,即表單字段元素描述頭屬性“Content-Type”的值,
如“
image/jpeg”。如果FileItem類對象對應的是普通表單字段,該方法將返回null

7.  boolean isInMemory()方法

isInMemory方法用來判斷FileItem對象封裝的數據內容是存儲在內存中,還是存儲在臨時文件中,
如果存儲在內存中則返回true,否則返回false

8.  void delete()方法

delete方法用來清空FileItem類對象中存放的主體內容,如果主體內容被保存在臨時文件中,
delete方法將刪除該臨時文件。

儘管當FileItem對象被垃圾收集器收集時會自動清除臨時文件,但及時調用delete方法可以更早的
清除臨時文件,釋放系統存儲資源。另外,當系統出現異常時,仍有可能造成有的臨時文件被永久
保存在了硬盤中。

9.  InputStream getInputStream()方法

    以流的形式返回上傳文件的數據內容。

10. long getSize()方法

返回該上傳文件的大小(以字節爲單位)。

DiskFileItemFactory

將請求消息實體中的每一個項目封裝成單獨的DiskFileItem (FileItem接口的實現對象的任務
由 
org.apache.commons.fileupload.FileItemFactory 接口的默認實現 
org.apache.commons.fileupload.disk.DiskFileItemFactory 來完成。當上傳的文件項目比較小時,直接保
存在內存中
(速度比較快)比較大時,以臨時文件的形式,保存在磁盤臨時文件夾(雖然速度
慢些,但是內存資源是有限的)。

屬性

1) public static final int DEFAULT_SIZE_THRESHOLD :將文件保存在內存還是
磁盤臨時文件夾的默認臨界值,值爲10240,即10kb

2) private File repository:用於配置在創建文件項目時,當文件項目大於臨界值時使
用的臨時文件夾,默認採用系統
默認的臨時文件路徑,可以通過系統屬性 java.io.tmpdir 
獲取。如下代碼:

System.getProperty("java.io.tmpdir");

3) private int sizeThreshold用於保存將文件保存在內存還是磁盤臨時文件夾的臨界值

構造方法

1) public DiskFileItemFactory():採用默認臨界值和系統臨時文件夾構造文件項工廠對象。

2) public DiskFileItemFactory(int sizeThreshold,File repository):採用參數指定臨界值和系統臨時
文件夾構造文件項工廠對象。

FileItem createItem() 方法

根據DiskFileItemFactory相關配置將每一個請求消息實體項目創建 成DiskFileItem 實例,並返回。
該方法從來不需要我們親自調用,FileUpload組件在解析請求時內部使用。

void setSizeThreshold(int sizeThreshold)

Apache文件上傳組件在解析上傳數據中的每個字段內容時,需要臨時保存解析出的數據,以便
在後面進行數據的進一步處理(保存在磁盤特定位置或插入數據庫)。因爲
Java虛擬機默認可以使
用的內存空間是有限的,超出限制時將會拋出“
java.lang.OutOfMemoryError”錯誤。如果上傳的文件
很大,例如800M的文件,在內存中將無法臨時保存該文件內容,Apache文件上傳組件轉而採用臨時
文件來保存這些數據;但如果上傳的文件很小,例如
600個字節的文件,顯然將其直接保存在內存中
性能會更加好些。

setSizeThreshold方法用於設置是否將上傳文件已臨時文件的形式保存在磁盤的臨界值(以字節
爲單位的
int值),如果從沒有調用該方法設置此臨界值,將會採用系統默認值10KB。對應的
getSizeThreshold() 方法用來獲取此臨界值。

void setRepository(File repository)

setRepositoryPath方法用於設置當上傳文件尺寸大於setSizeThreshold方法設置的臨界值時,將文件以
臨時文件形式保存在磁盤上的存放目錄。有一個對應的獲得臨時文件夾的 File getRespository() 方法。

注意:當從沒有調用此方法設置臨時文件存儲目錄時,默認採用系統默認的臨時文件路徑,可以
通過系統屬性 
java.io.tmpdir 獲取。如下代碼:

System.getProperty("java.io.tmpdir");

Tomcat系統默認臨時目錄爲“<tomcat安裝目錄>/temp/”。

ServletFileUpload 類

org.apache.commons.fileupload.servlet.ServletFileUpload類是Apache文件上傳組件處理文件上傳的
核心高級類(所謂高級就是不需要管底層實現,暴露給用戶的簡單易用的接口)。

使用其 parseRequest(HttpServletRequest) 方法可以將通過表單中每一個HTML標籤提交的數據封裝
成一個
FileItem對象,然後以List列表的形式返回。使用該方法處理上傳文件簡單易用。

如果你希望進一步提高新能,你可以採用 getItemIterator 方法,直接獲得每一個文件項的數據輸
入流,對數據做直接處理。

在使用ServletFileUpload對象解析請求時需要根據DiskFileItemFactory對象的屬性 sizeThreshold(臨
界值)和
repository(臨時目錄) 來決定將解析得到的數據保存在內存還是臨時文件中,如果是臨時
文件,保存在哪個臨時目錄中?。所以,我們需要在進行解析工作前構造好
DiskFileItemFactory對象,
通過ServletFileUpload對象的構造方法或
setFileItemFactory()方法設置 ServletFileUpload對象的
fileItemFactory屬性。

ServletFileUpload繼承結構:

java.lang.Object

|org.apache.commons.fileupload.FileUploadBase

     |org.apache.commons.fileupload.FileUpload

|org.apache.commons.fileupload.servlet.ServletFileUpload

構造方法:

1) public ServletFileUpload():構造一個未初始化的實例,需要在解析請求之前先調用
setFileItemFactory()方法設置 fileItemFactory屬性。

2) public ServletFileUpload(FileItemFactory fileItemFactory):構造一個實例,並根據參數
指定的
FileItemFactory 對象,設置 fileItemFactory屬性。

ServletFileUpload類常用方法:

1. public void setSizeMax(long sizeMax)方法

setSizeMax方法繼承自FileUploadBase類,用於設置請求消息實體內容(即所有上傳數據)的最大
尺寸限制,
以防止客戶端惡意上傳超大文件來浪費服務器端的存儲空間。其參數以字節爲單位的
long型數字

在請求解析的過程中,如果請求消息體內容的大小超過了setSizeMax方法的設置值,將會拋出
FileUploadBase內部定義的SizeLimitExceededException異常(FileUploadException的子類)。如:

org.apache.commons.fileupload.FileUploadBase$SizeLimitExceededException
the request was rejected because its size (1649104) exceeds the configured 
maximum (153600)

該方法有一個對應的讀方法:public long getSizeMax()方法。

2. public void setFileSizeMax(long fileSizeMax)方法

setFileSizeMax方法繼承自FileUploadBase類,用於設置單個上傳文件的最大尺寸限制,以防止客戶
端惡意上傳超大文件來浪費服務器端的存儲空間。其
參數以字節爲單位的long型數字。該方法有一個
對應的讀方法:public long geFileSizeMax()方法。

在請求解析的過程中,如果單個上傳文件的大小超過了setFileSizeMax方法的設置值,將會拋出
FileUploadBase內部定義的FileSizeLimitExceededException異常(FileUploadException的子類)。如:

org.apache.commons.fileupload.FileUploadBase$FileSizeLimitExceededException: The field file1 exceeds its
 maximum permitted size of 51200 characters.

3. public List parseRequest(javax.servlet.http.HttpServletRequest req)

parseRequest 方法是ServletFileUpload類的重要方法,它是對HTTP請求消息體內容進行解析的入口
方法。
解析出FORM表單中的每個字段的數據,並將它們分別包裝成獨立的FileItem對象,然後將這
FileItem對象加入進一個List類型的集合對象中返回。

該方法拋出FileUploadException異常來處理諸如文件尺寸過大、請求消息中的實體內容的類型不
是“
multipart/form-data”、IO異常、請求消息體長度信息丟失等各種異常。每一種異常都是
FileUploadException的一個子類型。

4. public FileItemIterator getItemIterator(HttpServletRequest request)

getItemIterator方法和parseRequest 方法基本相同。但是getItemIterator方法返回的是一個迭代
器,該迭代器中保存的不是
FileItem對象,而是FileItemStream 對象,如果你希望進一步提高新能,
你可以採用 
getItemIterator 方法,直接獲得每一個文件項的數據輸入流,做底層處理;如果性能不
是問題,你希望代碼簡單,則採用
parseRequest方法即可。
 

5. public stiatc boolean isMultipartContent(HttpServletRequest req)

isMultipartContent方法方法用於判斷請求消息中的內容是否是“multipart/form-data”類型,是則返
true,否則返回false
isMultipartContent方法是一個靜態方法,不用創建ServletFileUpload類的實例對
象即可被調用。

6. getFileItemFactory()setFileItemFactory(FileItemFactory)方法

方法繼承自FileUpload類,用於設置和讀取fileItemFactory屬性。

7. public void setProgressListener(ProgressListener pListener)

設置文件上傳進度監聽器。關於監聽器的具體內容,將在後面學習。該方法有一個對應的讀取
方法:ProgressListener getProgressListener()

8.public void setHeaderEncoding()方法

在文件上傳請求的消息體中,除了普通表單域的值是文本內容以外,文件上傳字段中的文件路
徑名也是文本,在內存中保存的是它們的某種字符集編碼的字節數組,Apache文件上傳組件在讀取
這些內容時,必須知道它們所採用的字符集編碼,才能將它們轉換成正確的字符文本返回。

setHeaderEncoding方法繼承自FileUploadBase類,用於設置上面提到的字符編碼。如果沒有設置,
則對應的讀方法g
etHeaderEncoding()方法返回null,將採用HttpServletRequest設置的字符編碼,如果
HttpServletRequest的字符編碼也爲null,則採用系統默認字符編碼。可以通過一下語句獲得系統默認
字符編碼:

System.getProperty("file.encoding"));

好,到這裏我們學習了主要的一些API,足夠我們來完成一個簡單文件上傳的功能了,下一章,
我們將一起來編寫一個文件上傳應用程序。


   


發佈了54 篇原創文章 · 獲贊 38 · 訪問量 64萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章