從原理角度解析Android (Java) http 檔案上傳
阿新 • • 發佈:2019-02-17
轉載請標明出處:http://blog.csdn.net/lmj623565791/article/details/23781773
檔案上傳是我們專案中經常使用的功能,一般我們的伺服器可能都是web伺服器,當我們使用非瀏覽器客戶端上傳檔案時,比如手機(Android)等上傳,可能就需要對傳輸的資料進行規範化的拼接,說白了,就是我們得自己完成瀏覽器幫我們做的事。
我首先寫了伺服器端程式碼,用來接收我們的資料,一會會貼出原始碼。然後寫了個web頁面用於上次,便於我們看其中的原理。
當點選了上傳以後,這裡我使用了firefox的firebug來觀察網路資訊,可以看到發出了一個POST請求,下面我框出的是請求頭資訊。裡面包含一些請求的配置資料。
接下來看這張圖:
我們可以看到我們傳送的資料,一個是name為username的普通表單資料,一個為name為uploadFile的一個檔案資料,可以看得出來,瀏覽器把檔案資料轉化成了2進位制然後按特定的格式發給伺服器了。
好了,下面開始實現上傳,模擬瀏覽器的操作。
1、使用HttpUrlConnection
我詳細解釋一下,首先我拼接了需要傳送的資料,其實就是咱們在圖三中看到的資料,然後使用HttpUrlConnetion設定了一系列屬性其實就是在設定圖二中看到的請求頭資訊。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("上傳成功"); } }
於是,我們完成了請求頭的設定,以及需要上傳資料的拼接,所以我們完成了瀏覽器的工作,自然就實現檔案上傳了。
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();
}
}
效果:
如果這篇文章對你有幫助,贊一下~