從原理角度解析Android (Java) http 文件上傳

jopen 8年前發布 | 10K 次閱讀 Java開發

轉載請標明出處:http://blog.csdn.net/lmj623565791/article/details/23781773

文件上傳是我們項目中經常使用的功能,一般我們的服務器可能都是web服務器,當我們使用非瀏覽器客戶端上傳文件時,比如手機(Android)等上傳,可能就需要對傳輸的數據進行規范化的拼接,說白了,就是我們得自己完成瀏覽器幫我們做的事。

我首先寫了服務器端代碼,用來接收我們的數據,一會會貼出源碼。然后寫了個web頁面用于上次,便于我們看其中的原理。


當點擊了上傳以后,這里我使用了firefox的firebug來觀察網絡信息,可以看到發出了一個POST請求,下面我框出的是請求頭信息。里面包含一些請求的配置數據。


接下來看這張圖:

我們可以看到我們發送的數據,一個是name為username的普通表單數據,一個為name為uploadFile的一個文件數據,可以看得出來,瀏覽器把文件數據轉化成了2進制然后按特定的格式發給服務器了。


好了,下面開始實現上傳,模擬瀏覽器的操作。

1、使用HttpUrlConnection

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

    /**
     * 
     * @param params
     *            傳遞的普通參數
     * @param uploadFile
     *            需要上傳的文件名
     * @param fileFormName
     *            需要上傳文件表單中的名字
     * @param newFileName
     *            上傳的文件名稱,不填寫將為uploadFile的名稱
     * @param urlStr
     *            上傳的服務器的路徑
     * @throws IOException
     */
    public void uploadForm(Map<String, String> params, String fileFormName,
            File uploadFile, String newFileName, String urlStr)
            throws IOException {
        if (newFileName == null || newFileName.trim().equals("")) {
            newFileName = uploadFile.getName();
        }

        StringBuilder sb = new StringBuilder();
        /**
         * 普通的表單數據
         */
        for (String key : params.keySet()) {
            sb.append("--" + BOUNDARY + "\r\n");
            sb.append("Content-Disposition: form-data; name=\"" + key + "\""
                    + "\r\n");
            sb.append("\r\n");
            sb.append(params.get(key) + "\r\n");
        }
        /**
         * 上傳文件的頭
         */
        sb.append("--" + BOUNDARY + "\r\n");
        sb.append("Content-Disposition: form-data; name=\"" + fileFormName
                + "\"; filename=\"" + newFileName + "\"" + "\r\n");
        sb.append("Content-Type: image/jpeg" + "\r\n");// 如果服務器端有文件類型的校驗,必須明確指定ContentType
        sb.append("\r\n");

        byte[] headerInfo = sb.toString().getBytes("UTF-8");
        byte[] endInfo = ("\r\n--" + BOUNDARY + "--\r\n").getBytes("UTF-8");
        System.out.println(sb.toString());
        URL url = new URL(urlStr);
        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
        conn.setRequestMethod("POST");
        conn.setRequestProperty("Content-Type",
                "multipart/form-data; boundary=" + BOUNDARY);
        conn.setRequestProperty("Content-Length", String
                .valueOf(headerInfo.length + uploadFile.length()
                        + endInfo.length));
        conn.setDoOutput(true);

        OutputStream out = conn.getOutputStream();
        InputStream in = new FileInputStream(uploadFile);
        out.write(headerInfo);

        byte[] buf = new byte[1024];
        int len;
        while ((len = in.read(buf)) != -1)
            out.write(buf, 0, len);

        out.write(endInfo);
        in.close();
        out.close();
        if (conn.getResponseCode() == 200) {
            System.out.println("上傳成功");
        }

    }
我詳細解釋一下,首先我拼接了需要發送的數據,其實就是咱們在圖三中看到的數據,然后使用HttpUrlConnetion設置了一系列屬性其實就是在設置圖二中看到的請求頭信息。

于是,我們完成了請求頭的設置,以及需要上傳數據的拼接,所以我們完成了瀏覽器的工作,自然就實現文件上傳了。

2、使用Socket實現文件上傳,參數基本一致,使用HttpUrlConnection上傳有一個很致命的問題就是,當上傳文件很大時,會發生內存溢出,手機分配給我們app的內存更小,所以就更需要解決這個問題,于是我們可以使用Socket模擬POST進行HTTP文件上傳。

/**
     * 
     * @param params
     *            傳遞的普通參數
     * @param uploadFile
     *            需要上傳的文件名
     * @param fileFormName
     *            需要上傳文件表單中的名字
     * @param newFileName
     *            上傳的文件名稱,不填寫將為uploadFile的名稱
     * @param urlStr
     *            上傳的服務器的路徑
     * @throws IOException
     */
    public void uploadFromBySocket(Map<String, String> params,
            String fileFormName, File uploadFile, String newFileName,
            String urlStr) throws IOException {
        if (newFileName == null || newFileName.trim().equals("")) {
            newFileName = uploadFile.getName();
        }

        StringBuilder sb = new StringBuilder();
        /**
         * 普通的表單數據
         */

        if (params != null)
            for (String key : params.keySet()) {
                sb.append("--" + BOUNDARY + "\r\n");
                sb.append("Content-Disposition: form-data; name=\"" + key
                        + "\"" + "\r\n");
                sb.append("\r\n");
                sb.append(params.get(key) + "\r\n");
            }                                                                                                                                                  else{ab.append("\r\n");}
        /**
         * 上傳文件的頭
         */
        sb.append("--" + BOUNDARY + "\r\n");
        sb.append("Content-Disposition: form-data; name=\"" + fileFormName
                + "\"; filename=\"" + newFileName + "\"" + "\r\n");
        sb.append("Content-Type: image/jpeg" + "\r\n");// 如果服務器端有文件類型的校驗,必須明確指定ContentType
        sb.append("\r\n");

        byte[] headerInfo = sb.toString().getBytes("UTF-8");
        byte[] endInfo = ("\r\n--" + BOUNDARY + "--\r\n").getBytes("UTF-8");

        System.out.println(sb.toString());

        URL url = new URL(urlStr);
        Socket socket = new Socket(url.getHost(), url.getPort());
        OutputStream os = socket.getOutputStream();
        PrintStream ps = new PrintStream(os, true, "UTF-8");

        // 寫出請求頭
        ps.println("POST " + urlStr + " HTTP/1.1");
        ps.println("Content-Type: multipart/form-data; boundary=" + BOUNDARY);
        ps.println("Content-Length: "
                + String.valueOf(headerInfo.length + uploadFile.length()
                        + endInfo.length));
        ps.println("Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8");

        InputStream in = new FileInputStream(uploadFile);
        // 寫出數據
        os.write(headerInfo);

        byte[] buf = new byte[1024];
        int len;
        while ((len = in.read(buf)) != -1)
            os.write(buf, 0, len);

        os.write(endInfo);

        in.close();
        os.close();
    }

這里因為我們使用的是Socket,所以自然對于請求頭,我們也需要自己拼接了,沒有什么屬性設置了。參考圖二框出的部分,我們使用PrintStream完成了請求頭的拼接,接下來就是數據的拼接,這和使用HttpUrlConnection的方式一致。我們也完成了數據的上傳。

最后測試我們的代碼:

public static void main(String[] args) {
        try {

            File file = new File("D:/dtd", "dwr30.dtd");

            new Test().uploadForm(null, "uploadFile", file, "helloworld.txt",
                    "http://localhost:8080/strurts2fileupload/uploadAction");

            new Test().uploadFromBySocket(null, "uploadFile", file,
                    "hibernate-configuration-3.0.dtd",
                    "http://localhost:8080/strurts2fileupload/uploadAction");

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

效果:




如果這篇文章對你有幫助,贊一下~


源碼點擊此處下載




來自: http://blog.csdn.net//lmj623565791/article/details/23781773

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!