1. 程式人生 > >java servlet實現上傳檔案程式碼及其原理說明

java servlet實現上傳檔案程式碼及其原理說明

我們做檔案上傳是不是都是使用第三方外掛,雖然知道原理,但想知道是怎麼實現的吧?一起來看看

上傳解析的實現簡單說一下:
    通過ServletRequest類的getInputStream()方法獲得一個客戶端向伺服器發出的資料流、分析上傳的檔案格式,根據分析結果將多個檔案依次輸出伺服器端的目標檔案中。
    格式類似下面:

//檔案分隔符
-----------------------------7d226137250336 
//檔案資訊頭
Content-Disposition: form-data; name="FILE1"; filename="C:\Documents and Settings\Administrator.TIMBER-4O6B0ZZ0\My Documents\tt.sql"
Content-Type: text/plain
//原始檔內容 
create table info(
content image null);
//下一個檔案的分隔符
-----------------------------7d226137250336
Content-Disposition: form-data; name="FILE2"; filename=""
Content-Type: application/octet-stream
-----------------------------7d226137250336 

每個表單提交的元素都有分隔符將其分隔,其提交的表單元素的名稱和對應的輸入值之間也有特殊的字元將其分隔開。

參照了pell中的MultipartRequest類寫了一個上傳元件,程式碼如下:

/*
 * 只支援在windows下上傳檔案
 */
package study.http.upload;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.servlet.ServletException;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class TestServlet extends HttpServlet {
     public final static String DEFAULT_ENCODING = "ISO8859_1";
     public final static String CHINESE_ENCODING = "GBK";
     public final static String SIGN_BOUNDARY = "boundary=";
     public final static String SIGN_FORMELEMENT = "name=";
     public final static String SIGN_FORMFILE = "filename=";
     public final static String SIGN_NOTFILE = "application/octet-stream";
     public final static String SIGN_MULTIDATA = "multipart/form-data";
     public final static String CHINESE_CONTENTTYPE = "text/html; charset=GBK";
     private Hashtable paratable = new Hashtable();
     private Hashtable filetable = new Hashtable();
     private String strBoundary = "";   
     private String strSavePath="";
     private static void println(String s) {
         System.out.println(s);
     }
     /**
      * 增加資料到對應的Hashtable中
      * 說明:如果Hashtable中已存在該鍵值,則將新增加的和原來的都封裝到列表中。
      */
     private static void addElement(Hashtable table, String paraName,
             Object paraValue) {
         ArrayList list = new ArrayList();
         if (table.containsKey(paraName)) {
             Object o = table.get(paraName);
             if (o instanceof List) {
                 ((List) o).add(paraValue);
             } else {
                 list.add(o);
                 list.add(paraValue);
                 o = list;
             }
             table.put(paraName, o);
         } else {
             table.put(paraName, paraValue);
        }
     }
     public static String getHashInfo(Hashtable paratable){
         StringBuffer sb=new StringBuffer();
         Set keySet=paratable.keySet();
         Iterator it=keySet.iterator();
         while(it.hasNext()){
            
            Object keyobj=it.next();
            Object valueobj=paratable.get(keyobj);
            
            sb.append("<tr>");
            sb.append("<td>"+keyobj.toString()+"</td>");
            if(valueobj instanceof List){
                sb.append("<td>");
                int isize=((List)valueobj).size();
                for(int i=0;i<isize;i++){
                    Object tempobj=((List)valueobj).get(i);
                    if(i<isize-1){
                       sb.append(tempobj.toString()+",");
                    }
                    else{
                       sb.append(tempobj.toString());
                    }
                }
                sb.append("</td>");
            }
            else{
                sb.append("<td>"+valueobj.toString()+"</td>");
            }
            sb.append("</tr>");
        }
        return sb.toString();
    }   
    private static byte[] getfileBytes(InputStream is) {
        List byteList = new ArrayList();
        byte[] filebyte = null;
        int readbyte = 0;
        try {
            while ((readbyte = is.read()) != -1) {
                byteList.add(new Byte((byte) readbyte));
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        filebyte = new byte[byteList.size()];
        for (int i = 0; i < byteList.size(); i++) {
            filebyte[i] = ((Byte) byteList.get(i)).byteValue();
        }
        return filebyte;
    }   
    protected void doGet(HttpServletRequest request,
            HttpServletResponse response) throws ServletException, IOException {
        doPost(request, response);
    }
    protected void doPost(HttpServletRequest request,
            HttpServletResponse response) throws ServletException, IOException {
        paratable = new Hashtable();
        filetable = new Hashtable();
        strSavePath=this.getInitParameter("savepath");
        File file=new File(strSavePath);
        if(!file.exists()){
            file.mkdirs();
        }
        String contentType = request.getContentType();    
        strBoundary = getBoundary(contentType);
        ServletInputStream sis = request.getInputStream();
        BufferedInputStream bis = new BufferedInputStream(sis);
        parseInputStream(bis);
        appendPara(request.getParameterMap());  /*追加url對應傳遞的引數*/
        response.setContentType(CHINESE_CONTENTTYPE);        
//        response.getWriter().write(getOutPutInfo());
//        response.getWriter().write(new String(getfileBytes(sis),"GBK"));
        bis.close();
        sis.close();
        request.setAttribute("para",paratable);
        request.setAttribute("file",filetable);        
        this.getServletContext().getRequestDispatcher("/result.jsp").
        forward(request,response);
        
    }    
    /**
     * 不用Hashtable對應的put方法,目的避免覆蓋重複的鍵值
     */
    private void appendPara(Map map){        
        if(map!=null){
            Set keySet=map.keySet();
            Iterator it=keySet.iterator();
            while(it.hasNext()){
                Object keyobj=it.next();
                String[] valueobj=(String[])map.get(keyobj);
                println("keyobj===="+keyobj);
                println("valueobj===="+valueobj);
                for(int i=0;i<valueobj.length;i++){
                    addElement(paratable,(String)keyobj,valueobj[i]);
                }
            }
        }
    }
    /**
     * 輸出上傳表單資訊
     */
    protected String getOutPutInfo() {
        StringBuffer sb = new StringBuffer();
        sb.append("<table width=100% border=1>");
        sb.append("<tr><td>引數名</td><td>引數值</td></tr>");
        sb.append(getHashInfo(paratable));
        sb.append(getHashInfo(filetable));
        sb.append("</table>");
        return sb.toString();
    }
    /**
     * 解析位元組流
     */
    private void parseInputStream(InputStream is) {
        byte[] sizes = getfileBytes(is);
        int icount = 0;
        String s = "";
        int readbyte = 0;
        String reals;
        try {
            reals = new String(sizes, DEFAULT_ENCODING);
            String realsvalue = new String(sizes, CHINESE_ENCODING);
            String[] arrs = reals.split(strBoundary);
            String[] arrsvalue = realsvalue.split(strBoundary);
            for (int i = 0; i < arrs.length; i++) {
                String tempStr = arrs[i];
                String tempStr2 = arrsvalue[i];
                if (tempStr.indexOf(SIGN_FORMFILE) >= 0) {
                    readFile(tempStr, tempStr2);
                } else {
                    readParameter(tempStr2);
                }
            }
        } catch (UnsupportedEncodingException e) {
           e.printStackTrace();
        }
    }
    /**
     * 獲取本次上傳對應的表單元素間的分隔符,注意該分隔符是隨機生成的
     */
    private String getBoundary(String contentType) {
        String tempStr = "";
        if (contentType != null && contentType.startsWith(SIGN_MULTIDATA)
                && contentType.indexOf(SIGN_BOUNDARY) != -1) {
            //獲取表單每個元素的分隔符
           tempStr = contentType
                    .substring(
                            contentType.indexOf(SIGN_BOUNDARY)
                                    + SIGN_BOUNDARY.length()).trim();
        }
        return tempStr;
    }
    /**
     * 解析檔案上傳對應的位元組流。實現演算法<br>
     * 通過解析ISO8859_1編碼方式的字串後轉換成對應上傳檔案的位元組。
     * 通過解析GBK編碼方式的字串後轉換成對應上傳檔案的檔名。
     * 說明:因不清楚位元組在不同編碼方式下的關係,只好使用兩個字串(比較影響效能,以後優化)
     * @param s   以ISO8859_1編碼方式組成的字串
     * @param s2  以GBK編碼方式組成的字串
     */
    private void readFile(String s, String s2) {
        int filepos = -1;
        if ((filepos = s.indexOf(SIGN_FORMFILE)) >= 0) {
            String realName = readFileName(s2);
            //部分確定上傳的是檔案而不是任意輸入的字串
            if(!realName.equals("")&& realName.length()>0 && (realName.indexOf(".")>=0)){
                String filepath = readWriteFile(s, realName);
                addElement(filetable, realName, filepath);
            }
        } 
        else {
            /*上傳的不是檔案*/
            if (s.indexOf(SIGN_NOTFILE) >= 0) {
                return;
            }
        }
    }    
    /**
     * 解析檔案上傳對應的名稱 
     * 實現說明:如果上傳的是檔案對應格式為:<br>filename="檔名"</br> 格式
     * 通過處理可以拆分出對應的檔名  
     * @param s   以GBK編碼方式組成的包含檔名的字串
     * @return    對應上傳檔案的檔名(不包括檔案路徑)
     */
    private String readFileName(String s) {
        int filepos = s.indexOf(SIGN_FORMFILE);
        String tempstr = s.substring(filepos + SIGN_FORMFILE.length() + 1);
        int iendpos = tempstr.indexOf("\"");
        String fileName = tempstr.substring(0, iendpos);
        int ifilenamepos = fileName.lastIndexOf("\\");
        String realName = fileName.substring(ifilenamepos + 1);        
        return realName;
    }
    /**
     * 通過解析ISO8859_1編碼方式的字串後轉換成對應上傳檔案的位元組。
     * 實現演算法說明:檔名轉化後的位元組和具體的檔案位元組中間是以兩個重複的兩個字元隔開,
     * 對應char值為13,10,轉換後的字元對應的最後四個字元也是格式字元,獲取對應中間的位元組即為
     * 上傳檔案的真正的位元組數
     * @param s        以ISO8859_1編碼方式組成的包含檔名和具體檔案位元組的字串
     * @param realName  對應的檔名
     * @return          對應生成的檔名包括全路徑
     */
    private String readWriteFile(String s, String realName) {
        int filepos = s.indexOf(SIGN_FORMFILE);
        String tempstr = s.substring(filepos + SIGN_FORMFILE.length() + 1);
        int icount = 0;
        while (true) {
            int charnum = tempstr.charAt(icount);
            int charnum2 = tempstr.charAt(icount + 1);
            int charnum3 = tempstr.charAt(icount + 2);
            int charnum4 = tempstr.charAt(icount + 3);
            if (charnum == 13 && charnum2 == 10 && charnum3 == 13
                    && charnum4 == 10) {
                break;
            }
            icount++;
        }
        String filevalue = tempstr.substring(icount + 4, tempstr.length() - 4);
        FileOutputStream fos = null;
        String createName=strSavePath + realName;
        File uploadfile = new File(createName);        
        String shortname=realName.substring(0,realName.lastIndexOf("."));
        String filetype=realName.substring(realName.lastIndexOf(".")+1);
        int namecount=1;
        while(uploadfile.exists()){            
            createName=strSavePath+shortname+"["+namecount+"]"+"."+filetype;
            uploadfile=new File(createName);
            namecount++;            
        }
        try {
            byte[] filebytes = filevalue.getBytes(DEFAULT_ENCODING);
            fos = new FileOutputStream(uploadfile);
            fos.write(filebytes);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e1) {
            e1.printStackTrace();
        } finally {
            try {
                fos.close();
            } catch (IOException e2) {
                e2.printStackTrace();
            }
        }
        return createName;
    }    
    /**
     * 解析提交過來的表單元素對應的名稱以及值<br> 
     * 實現說明:如果表單元素的是對應格式為:<br>name="表單元素名"</br> 格式
     * 表單元素名和具體的輸入值中間是以兩個重複的兩個字元隔開,
     * 對應char值為13,10,轉換後的字元對應的最後四個字元也是格式字元,獲取對應中間的字元即為
     * 表單元素的輸入值
     * 通過處理可以拆分出對應的表單元素名以及輸入值  
     * @param s   以GBK編碼方式組成的包含表單元素名和值的字串    
     */    
    private void readParameter(String s) {
        String paraName = "";
        String paraValue = "";
        int istartlen = -1;
        int iendlen = -1;
        if ((istartlen = s.indexOf(SIGN_FORMELEMENT)) >= 0) {
            String tempstr = s.substring(istartlen + SIGN_FORMELEMENT.length()+ 1);
            int nameindex = tempstr.indexOf("\"");
            paraName = tempstr.substring(0, nameindex);
            paraValue = tempstr.substring(nameindex + 5, tempstr.length() - 4);
            addElement(paratable, paraName, paraValue);
        }
    }
}

元件簡單說明:
    上傳路徑在servlet初始引數中設定。

    上傳的表單元素、檔案資料分別封裝在Hashtable中。

做了測試,測試環境說明:
 AppServer: WeblogicSP4
 OS:  WindowXP/ Soloaris 9.0
測試程式:
index.jsp(檔案上傳頁面):

<html>
  <head><title>File Upload</title></head>
  <body>

  <form  name="kkkkkk"     action="test.upload?ssss=bbbbbbbbb&ccccc=eeeeeeee" enctype="multipart/form-data" method="post" >
      <input type=text name="ssss" ><br>
      <input type=text name="ssss" ><br>
      <input type=text name="ssss3" ><br>
      <textarea name="araea"></textarea><br>
      
      <input type=file name="cccc"  ><br>
       <input type=file name="ddddd" ><br>
      <input type=submit value="submit" name="bbbbbbbbb">     
  </form>

  </body>
</html>

result.jsp(檢視提交表單資料)

<%@ page contentType="text/html;charset=GBK"%>
<%@ page import="java.util.*"%> 
<%@ page import="study.http.upload.*"%> 



<%
  Hashtable paratable=(Hashtable)request.getAttribute("para");
  Hashtable filetable=(Hashtable)request.getAttribute("file");
  String parastr=TestServlet.getHashInfo(paratable);
  out.println("<table width=100% border=1>");
  out.println(parastr);
  out.println(TestServlet.getHashInfo(filetable));
  out.println("</table>");
 
%>
<html>
  <head><title>File Upload</title></head>
  <body>

  

  </body>
</html>

測試時對應的web應用已經指定相關字符集,weblogic.xml內容如下:

<weblogic-web-app>
  <charset-params>
        <input-charset>
            <resource-path>/*</resource-path>
            <java-charset-name>GBK</java-charset-name>
        </input-charset>
    </charset-params>
</weblogic-web-app>