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

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

文件上傳是咱們項目中常常使用的功能,通常咱們的服務器可能都是web服務器,當咱們使用非瀏覽器客戶端上傳文件時,好比手機(Android)等上傳,可能就須要對傳輸的數據進行規範化的拼接,說白了,就是咱們得本身完成瀏覽器幫咱們作的事。java

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


當點擊了上傳之後,這裏我使用了firefox的firebug來觀察網絡信息,能夠看到發出了一個POST請求,下面我框出的是請求頭信息。裏面包含一些請求的配置數據。瀏覽器


接下來看這張圖:服務器

咱們能夠看到咱們發送的數據,一個是name爲username的普通表單數據,一個爲name爲uploadFile的一個文件數據,能夠看得出來,瀏覽器把文件數據轉化成了2進制而後按特定的格式發給服務器了。網絡


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

一、使用HttpUrlConnectionsocket

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設置了一系列屬性其實就是在設置圖二中看到的請求頭信息。

因而,咱們完成了請求頭的設置,以及須要上傳數據的拼接,因此咱們完成了瀏覽器的工做,天然就實現文件上傳了。測試

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

	/**
	 * 
	 * @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();
		}
	}

效果:




若是這篇文章對你有幫助,贊一下~


源碼點擊此處下載

相關文章
相關標籤/搜索