首先感谢一下几篇文章,读了这几篇文章,才对如何构造http协议包传输混合数据有了个大体的了解。
使用Javascript XMLHttpRequest模拟表单(Form)提交上传文件
上传文件multipart form-data boundary 说明
如何使用multipart/form-data格式上传文件php
原本几乎没有和协议大过什么交道,习惯了用库了。此次项目中须要上传文件以及一些文本信息,使用 java.net.HttpURLConnection,原本可使用 org.apache.http.client.methods.HttpPost,很是简单,网上也有不少示例代码,但由于以前的代码一直用的前者这个 API,没有直接的上传文件的方法,为了改动较少,因此打算本身构造上传文件的表单。java
在网上看了几篇文章,找了几段示例代码,但都没能成功实现功能,后来经过查协议,抓包的方式,终于搞通了。其实缘由也很简单,就是本身构造的http数据的格式不对,协议对格式的要求很是严格,不少地方的换行都是很必要的,否则就会产生错误。node
上代码,已运行测试。服务器端用php接收,正常。apache
public static final String HTTP_METHOD_GET = "GET"; public static final String HTTP_METHOD_POST = "POST"; public static final String BOUNDARYSTR = "aifudao7816510d1hq"; public static final String BOUNDARY = "--" + BOUNDARYSTR + "\r\n"; HttpURLConnection conn = (HttpURLConnection) url.openConnection(); String cookie = bp_rfn_bp_session_id + "=" + Aifudao.globalBpSid; Log.d("cookie:" + cookie); conn.addRequestProperty("cookie", cookie); conn.setConnectTimeout(5 * 1000); conn.setRequestMethod(mMethod); if (mMethod.equals(HTTP_METHOD_POST)) { conn.setDoOutput(true); conn.setUseCaches(false); if (mFiles.size() > 0) { conn.setRequestProperty("Content-type", "multipart/form-data;boundary=" + BOUNDARYSTR); } conn.connect(); BufferedOutputStream out = new BufferedOutputStream(conn.getOutputStream()); StringBuilder sb = new StringBuilder(); if (mFiles.size() > 0) { Iterator<String> it = mPostMap.keySet().iterator(); while (it.hasNext()) { String str = it.next(); sb.append(BOUNDARY); sb.append("Content-Disposition:form-data;name=\""); sb.append(str); sb.append("\"\r\n\r\n"); sb.append(mPostMap.get(str)); sb.append("\r\n"); } } else { Iterator<String> it = mPostMap.keySet().iterator(); while (it.hasNext()) { String str = it.next(); sb.append(";"); sb.append(str); sb.append("="); sb.append(mPostMap.get(str)); } } // post the string data. out.write(sb.toString().getBytes()); // post file data if (mFiles != null && mFiles.size() > 0) { for (int i = 0; i < mFiles.size(); i++) { File file = mFiles.get(i); if (!file.exists()) { Log.e("cant find post file."); continue; } Log.d("start upload file."); out.write(BOUNDARY.getBytes()); StringBuilder filenamesb = new StringBuilder(); filenamesb .append("Content-Disposition:form-data;Content-Type:application/octet-stream;name=\"uploadfile"); filenamesb.append(i); filenamesb.append("\";filename=\""); filenamesb.append(file.getName() + "\"\r\n\r\n"); out.write(filenamesb.toString().getBytes()); FileInputStream fis = new FileInputStream(file); byte[] buffer = new byte[8192]; // 8k int count = 0; // 读取文件 while ((count = fis.read(buffer)) != -1) { out.write(buffer, 0, count); } out.write("\r\n\r\n".getBytes()); fis.close(); } } out.write(("--" + BOUNDARYSTR + "--\r\n").getBytes()); out.flush(); out.close(); } 而后后面的接收相应的代码就和普通的get方法没有什么区别了。 注意的几个重要并且容易出错的地方: 1.设置头信息 conn.setRequestProperty(“Content-type”, “multipart/form-data;boundary=” + BOUNDARYSTR); 这里须要在头信息里把类型设置为multipart/form-data,boundary设为一个通常不会和数据冲突的随机字符串,这是用来区分你的多种数据的。 2.构造POST数组 在服务器端通常都有一个post数组来接收post数据,这段代码就是用来构造post数据的。 while (it.hasNext()) { String str = it.next(); sb.append(BOUNDARY); sb.append("Content-Disposition:form-data;name=\""); sb.append(str); sb.append("\"\r\n\r\n"); sb.append(mPostMap.get(str)); sb.append("\r\n"); }
注意其中的那几个换行,貌似都是颇有必要的。以前换行格式没有正确,就总是拿不到post数据。数组
3.在合适的地方插入分界字符串和换行服务器
out.write(BOUNDARY.getBytes());cookie
协议默认使用 –BOUNDARYSTR 的格式来做为分界字符串,而在结尾的时候使用 –BOUNDARYSTR– 的格式做为结束标志session
4.在某些数据后面加入必要换行app
- StringBuilder filenamesb = new StringBuilder();
- filenamesb
- .append("Content-Disposition:form-data;Content-Type:application/octet-stream;name=\"uploadfile");
- filenamesb.append(i);
- filenamesb.append("\";filename=\"");
- filenamesb.append(file.getName() + "\"\r\n\r\n");
- out.write(filenamesb.toString().getBytes());
- FileInputStream fis = new FileInputStream(file);
- byte[] buffer = new byte[8192]; // 8k
- int count = 0;
- // 读取文件
- while ((count = fis.read(buffer)) != -1) {
- out.write(buffer, 0, count);
- }
- out.write("\r\n\r\n".getBytes());
fis.close();post
这里注意的是,在文件名后的两个换行很必要,不然可能出现文件名包含一部分文件数据的状况。文件二进制数据写入完成后,也有两个换行(这个地方我记不清是不是必须的了,但这段代码是能正常运行的)。
若是还不行,那确定是构造格式的问题,最好本身把本身发送的数据打印出来仔细对比和抓到的http包数据之间的区别,注意,不少换行和数据分界字符串都很严格。