1. 程式人生 > >Tomcat下檔案下載與上傳的簡單實現

Tomcat下檔案下載與上傳的簡單實現

實現下載

實現下載需要
- 修改Tomcat中的server.xml
- 修改web.xml

修改server.xml

<Host> </Host>中加入(一般在檔案末尾可以找到)

<Context docBase="C://Download" path="/download" reloadable="true" />

其中docBase項是本地目錄,path項是訪問目錄

修改web.xml

   <servlet>
        <servlet-name>default</servlet-name
>
<servlet-class>org.apache.catalina.servlets.DefaultServlet</servlet-class> <init-param> <param-name>debug</param-name> <param-value>0</param-value> </init-param> <init-param> <param-name
>
listings</param-name> <param-value>true</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet>

其中listings下確保值是true

這時在C://Download下存放檔案,並開啟相應的地址,就能看見檔案目錄了

實現上傳

實現上傳也是分兩步

  • 實現客戶端的上傳
  • 實現服務端的處理

實現客戶端的上傳

post請求

我們都知道http常用的請求方式有getpost等,其中我們實現檔案上傳,就是模擬表單採用post方式上傳檔案

首先來看一下post報文的格式,以上傳一張圖片為例

POST/logsys/home/uploadIspeedLog!doDefault.html HTTP/1.1 

Accept: text/plain, */* 
Accept-Language: zh-cn 
Host: 192.168.24.56
Content-Type:multipart/form-data;boundary=【這裡隨意設定】
User-Agent: WinHttpClient 
Content-Length: 3693
Connection: Keep-Alive

以上部分是請求的引數,不需要我們完全實現,通過方法傳參的方式自動構建

--【這裡隨意設定】
Content-Disposition: form-data;name="file1";filename="C://E//a.png"
Content-Type:application/octet-stream


<檔案的二進位制資訊>
--【這裡隨意設定】--

這一部分是報文的內容,需要我們通過字串或者位元組流自行拼接,並且格式不能出錯

注意,三處【這裡隨意設定】的部分必須完全相同

程式碼實現
public static void uploadFile(String fileName) {  
    try {  

        // 換行符  
        final String newLine = "\r\n";  
        //資料分隔線  
        final String BOUNDARY = "【這裡隨意設定】";//可以隨意設定,一般是用  ---------------加一堆隨機字元
        //檔案結束標識
        final String boundaryPrefix = "--";  

        // 伺服器的域名  
        URL url = new URL("http://localhost:8070/secondary/HandleFile");  
        HttpURLConnection conn = (HttpURLConnection) url.openConnection();  
        // 設定為POST情  
        conn.setRequestMethod("POST");  
        // 傳送POST請求必須設定如下兩行  
        conn.setDoOutput(true);  
        //conn.setDoInput(true);/不必加,預設為true  
        //conn.setUseCaches(false);//用於設定快取,預設為true,不改也沒有影響(至少在傳輸單個檔案這裡沒有)  

        // 設定請求頭引數  
        //關於keep-alive的說明:https://www.kafan.cn/edu/5110681.html
        //conn.setRequestProperty("connection", "Keep-Alive");//現在的預設設定一般即為keep-Alive,因此此項為強呼叫,可以不加
        //conn.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows Nt 5.1; SV1)");//用於模擬瀏覽器,非必須

        //用於表示上傳形式,必須
        conn.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + BOUNDARY);  
        //這裡是Charset,網上大多都是Charsert???我的天,笑哭。不過好像沒什麼影響...不知道哪位大佬解釋一下
        conn.setRequestProperty("Charset", "UTF-8");

        //獲取conn的輸出流用於向伺服器輸出資訊
        OutputStream out = new DataOutputStream(conn.getOutputStream());  

        //構造檔案的結構  
        //寫引數頭
        StringBuilder sb = new StringBuilder();  
        sb.append(boundaryPrefix)//表示報文開始
          .append(BOUNDARY)//新增檔案分界線
          .append(newLine);//換行,換行方式必須嚴格約束
        //固定格式,其中name的引數名可以隨意修改,只需要在後臺有相應的識別就可以,filename填你想要被後臺識別的檔名,可以包含路徑
        sb.append("Content-Disposition: form-data;name=\"file\";")
            .append("filename=\"").append(fileName)
            .append("\"")
            .append(newLine);  
        sb.append("Content-Type:application/octet-stream");  
        //換行,為必須格式
        sb.append(newLine);  
        sb.append(newLine);  

        //將引數頭的資料寫入到輸出流中  
        out.write(sb.toString().getBytes());  
        System.out.print(sb);

        //寫檔案資料(通過資料輸入流)  
        File file = new File(fileName);
        DataInputStream in = new DataInputStream(new FileInputStream(  
                file));  
        byte[] bufferOut = new byte[1024];  
        int bytes = 0;  
        //每次讀1KB資料,並且將檔案資料寫入到輸出流中  
        while ((bytes = in.read(bufferOut)) != -1) {  
            out.write(bufferOut, 0, bytes);  
        }  
        in.close();

        //寫引數尾  
        out.write(newLine.getBytes());  
        System.out.print(new String(newLine.getBytes()));

        // 定義最後資料分隔線,即--加上BOUNDARY再加上--。
        sb = new StringBuilder();
        sb.append(newLine)
            .append(boundaryPrefix)
            .append(BOUNDARY)
            .append(boundaryPrefix)
            .append(newLine);
        // 寫上結尾標識  
        out.write(sb.toString().getBytes());  
        System.out.println(sb);
        //輸出結束,關閉輸出流
        out.flush();  
        out.close();  

        //定義BufferedReader輸入流來讀取URL的響應 ,注意必須接受來自伺服器的返回,否則伺服器不會對傳送的post請求做處理!!這裡坑了我好久
        BufferedReader reader = new BufferedReader(new InputStreamReader(  
                conn.getInputStream()));  
        String line = null;  
        while ((line = reader.readLine()) != null) {  
            System.out.println(line);  
        }  

    } catch (Exception e) {  
        System.out.println("傳送POST請求出現異常!" + e);  
        e.printStackTrace();  
    }  
}  

註釋很清楚,結合post請求的格式理解一下,就不一一解釋了

實現服務端的處理

上述的程式碼會形成一個post請求到伺服器,於是相應的介面的dopost()方法就會相應

dopost()中做了什麼呢?

  • 獲取報文中的檔案資訊
  • 處理檔案資訊,包括識別檔名,識別檔案型別(由使用者定義,而不是檔案字尾)
  • 儲存到本地(伺服器端硬碟)

程式碼如下:

import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
//注意導的包

/**
 * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
 */
public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    response.setCharacterEncoding("UTF-8");
    request.setCharacterEncoding("UTF-8");
    response.setContentType("text/html");
    PrintWriter out = response.getWriter();
    //輸出到客戶端瀏覽器
    DiskFileItemFactory factory = new DiskFileItemFactory();
    ServletFileUpload sup = new ServletFileUpload(factory);//這裡要將factory傳入,否則會報NullPointerException: No FileItemFactory has been set.
    try{
        List<FileItem> list = sup.parseRequest(request);
        for(FileItem fileItem:list){
            System.out.println(fileItem.getFieldName()+"--"+fileItem.getName());
            if(!fileItem.isFormField()){
                if("file".equals(fileItem.getFieldName())){
                    //獲取遠端檔名
                    String remoteFilename = new String(fileItem.getName().getBytes(),"UTF-8");
                    File remoteFile = new File(remoteFilename);

                    //設定伺服器端存放檔案的位置
                    File locate = new File("C://E//download/",remoteFile.getName());

//                      locate.getParentFile().mkdirs();//用於確保檔案目錄存在,如果為單級目錄可以去掉
                    locate.createNewFile(); //建立新檔案

                    InputStream ins = fileItem.getInputStream();   //FileItem的內容
                    OutputStream ous = new FileOutputStream(locate); //輸出
                    try{
                        byte[] buffer = new byte[1024]; //緩衝位元組
                        int len = 0;
                        while((len = ins.read(buffer))>-1)
                            ous.write(buffer, 0, len);
                    }finally{
                        ous.close();
                        ins.close();
                    }
                }
            }       
        }
    }catch (FileUploadException e){}

    out.print("everything is ok");
    out.flush();
    out.close();

}

同樣註釋很清楚,不再解釋了

小結

將上述過程理解清楚後,再結合post報文的格式,那麼是否就可以處理多個檔案同時上傳了呢?如果對客戶端的流做出一些大小限制,是不是就可以限制上傳大小了?…一個小demo,幫助理解一下基礎的實現。