從原理角度解析Android (Java) http 文件上傳
轉載請標明出處: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