您的位置:首页 > 理论基础 > 计算机网络

安卓网络框架,上传图片花图,上传状态411被服务器驳回

2015-10-25 15:48 956 查看
先看下一开始使用的网络框架核心代码:

private Message doPost(final String url, final Map<String, String> params, final Map<String, InputStream> inputStreams,final InterCallback callback) {
long startTime = System.currentTimeMillis();
Message message=new Message();
String BOUNDARY = java.util.UUID.randomUUID().toString();
String PREFIX = "--", LINEND = "\r\n";
String CHARSET = XHConf.in().net_encode;
HttpURLConnection conn=null;
try {
//			if(url.indexOf("Upload/imgs")>-1) Thread.sleep(130*1000);
URL uri = new URL(url);
conn = (HttpURLConnection) uri.openConnection();
conn.setDoInput(true);// 允许输入
conn.setDoOutput(true);// 允许输出
conn.setUseCaches(false);
conn.setRequestMethod("POST"); // Post方式
conn.setConnectTimeout(XHConf.in().net_timeout*2);
conn.setReadTimeout(XHConf.in().net_timeout * 10);
//设置header
Map<String,String> header = callback.getReqHeader(new HashMap<String, String>(),url,params);
UtilLog.print(XHConf.in().log_tag_net,"d","------------------REQ_POST------------------\n"+url+"\n"+params+";"+inputStreams+"\nheader:"+header.toString());
for (Map.Entry<String, String> map : header.entrySet()) {
conn.setRequestProperty(map.getKey(), map.getValue());
}

// 首先组拼文本类型的参数
StringBuilder sb = new StringBuilder();
if(inputStreams.isEmpty()){
for (Map.Entry<String, String> entry : params.entrySet()) {
sb.append(entry.getKey() + "=" +  URLEncoder.encode(entry.getValue(),XHConf.in().net_encode) + "&");
}
if(sb.length() > 1)
sb.deleteCharAt(sb.length()-1);
conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
}else{
for (Map.Entry<String, String> entry : params.entrySet()) {
sb.append(PREFIX);
sb.append(BOUNDARY);
sb.append(LINEND);
sb.append("Content-Disposition: form-data; name=\"" + entry.getKey() + "\"" + LINEND);
sb.append("Content-Type: text/plain; charset=" + CHARSET + LINEND);
sb.append("Content-Transfer-Encoding: 8bit" + LINEND);
sb.append(LINEND);
sb.append(entry.getValue());
sb.append(LINEND);
}
conn.setRequestProperty("Content-Type", "multipart/form-data" + ";boundary=" + BOUNDARY);
}
DataOutputStream outStream = new DataOutputStream(conn.getOutputStream());
outStream.write(sb.toString().getBytes());

// 发送文件数据
if (!inputStreams.isEmpty()){
for (Map.Entry<String, InputStream> iss : inputStreams.entrySet()) {

StringBuilder sb1 = new StringBuilder();
sb1.append(PREFIX);
sb1.append(BOUNDARY);
sb1.append(LINEND);
//组装文件流数据
String[] fileNameSplit = iss.getKey().split("_");
String contentType = fileNameSplit[fileNameSplit.length - 1];
StringBuilder fileName = new StringBuilder();
if(fileNameSplit.length<3) throw new Exception("文件流的key用_无法切割:"+iss.getKey());
for(int i = 1; i < fileNameSplit.length - 1; i++){
fileName.append(fileNameSplit[i]);
}
sb1.append("Content-Disposition: form-data; name=\"" + fileNameSplit[0] + "[]\"; filename=\""
+ fileName.toString() + "\"" + LINEND);
sb1.append("Content-Type: "+contentType+"; charset=" + CHARSET + LINEND);
sb1.append(LINEND);

outStream.write(sb1.toString().getBytes());
InputStream is = iss.getValue();
byte[] buffer = new byte[1024];
int len = 0;
while (is != null && (len = is.read(buffer)) != -1) {
outStream.write(buffer, 0, len);
}
outStream.write(LINEND.getBytes());
}
// 请求结束标志
byte[] end_data = (PREFIX + BOUNDARY + PREFIX + LINEND).getBytes();
outStream.write(end_data);
}
outStream.flush();
outStream.close();

// 得到响应
int resState = conn.getResponseCode();
if (resState == 200) {
Map<String, List<String>> ml = conn.getHeaderFields();
if(ml.containsKey("Set-Cookie")){
Map<String,String> map = getPostCookieMap(ml.get("Set-Cookie"));
callback.saveCookie(map,url,"doPost");
}
message.what=REQ_OK_STRING;
message.obj=UtilString.inputStream2String(conn.getInputStream(), XHConf.in().net_encode);
} else {
message.what=REQ_STATE_ERROR;
message.obj=resState;
}
} catch (Exception e) {
message.what=REQ_EXP;
message.obj=e;
}
callback.requestTime=System.currentTimeMillis()-startTime;
if(conn!=null) conn.disconnect();
return message;
}


很简单能看出,这是用的自己拼接http header和body,然后通过安卓原生最基础的网络类HttpURLConnection或DefaultHttpClient这之类的来实现。后来发现上传图片的时候总会出现图片颜色混乱花图、图片出现半张图等现象。于是做了如下改动:

private Message doPost(final String url, final Map<String, String> params, final Map<String, InputStream> inputStreams,final InterCallback callback) {
long startTime = System.currentTimeMillis();
Message message=new Message();
//准备链接
HttpParams httpParameters = new BasicHttpParams();
HttpConnectionParams.setConnectionTimeout(httpParameters, XHConf.in().net_timeout*2);
HttpConnectionParams.setSoTimeout(httpParameters, XHConf.in().net_timeout*10);
DefaultHttpClient client = new DefaultHttpClient(httpParameters);
HttpPost post = new HttpPost(url);
Map<String,String> header = callback.getReqHeader(new HashMap<String, String>(),url,params);
for (Map.Entry<String, String> map : header.entrySet()) {
post.setHeader(map.getKey(), map.getValue());
}
UtilLog.print(XHConf.in().log_tag_net,"d","------------------REQ_POST------------------\n"+url+"\n"+params+";"+inputStreams+"\nheader:"+header.toString());
try {
//设置参数
if(inputStreams.isEmpty()){
ArrayList<NameValuePair> postEntry=new ArrayList<NameValuePair>();
for (Map.Entry<String, String> entry : params.entrySet()) {
postEntry.add(new BasicNameValuePair(entry.getKey(),entry.getValue()));
}
post.setEntity(new UrlEncodedFormEntity(postEntry,XHConf.in().net_encode));
}else{
MultipartEntity multipartEntity = new MultipartEntity();
for (Map.Entry<String, String> entry : params.entrySet()) {
StringBody sb = new StringBody(entry.getValue(),Charset.forName(XHConf.in().net_encode));
multipartEntity.addPart(entry.getKey(), sb);
}
for (Map.Entry<String, InputStream> entry : inputStreams.entrySet()) {
//组装文件流数据
String[] fileNameSplit = entry.getKey().split("_");
//					String contentType = fileNameSplit[fileNameSplit.length - 1];
StringBuilder fileName = new StringBuilder();
if(fileNameSplit.length<3) throw new Exception("文件流的key用_无法切割:"+entry.getKey());
for(int i = 1; i < fileNameSplit.length - 1; i++){
fileName.append(fileNameSplit[i]);
}
InputStreamBody isb = new InputStreamBody(entry.getValue(), fileName.toString());
multipartEntity.addPart(fileNameSplit[0] + "[]", isb);
}
post.setEntity(multipartEntity);
}

HttpResponse response = client.execute(post);
int resState=response.getStatusLine().getStatusCode();
if (resState == HttpURLConnection.HTTP_OK) {
message.what=REQ_OK_STRING;
message.obj=UtilString.inputStream2String(response.getEntity().getContent(), XHConf.in().net_encode);
callback.saveCookie(getGetCookieMap(client.getCookieStore().getCookies()),url,"doPost");
} else {
message.what=REQ_STATE_ERROR;
message.obj=resState;
}
} catch (Exception e) {
message.what=REQ_EXP;
message.obj=e;
}
callback.requestTime=System.currentTimeMillis()-startTime;
client.getConnectionManager().shutdown();
return message;
}


至此,使用的是httpmime-4.1.3.jar包中的MultipartEntity然后通过HttpPost来进行上传,用了框架之后的好处是基本上遇不到上传图片变花的问题了,但半张图的问题仍然存在,最关键的是引入了上传图片文件服务器返回诡异的411错误,仅少部分手机会出现,而且是必然。这个在stackoverflow也有提出,不过一直没啥好的解法 http://stackoverflow.com/questions/15552276/answer/submit 

进过测试发现,普通web服务器是完全没问题的,但我服务器是通过负载均衡来进行了一次转发,大概也就是在这个过程丢失了contentLength,系统也没按照content chunk读取不定长请求。第一反应是手动设置length参数,显然是错的了。

进过分析,很可能是inputStream流长度无法读取,所以contentLength消失。另外直接用流上传可能存在流中数据中断或异常,导致图片变画或只有一半。后来进一步研究才发现了MultipartEntity已经不提倡使用了,在新版的httpmime中要使用MultipartEntityBuilder。详见:http://www.2cto.com/kf/201402/276505.html

修改如下:

private Message doPost(String url, Map<String, String> params, Map<String, byte[]> byteMap, InterCallback callback) {
long startTime = System.currentTimeMillis();
Message message=new Message();
//准备连接
HttpParams httpParameters = new BasicHttpParams();
HttpConnectionParams.setConnectionTimeout(httpParameters, XHConf.in().net_timeout*2);
HttpConnectionParams.setSoTimeout(httpParameters, XHConf.in().net_timeout*10);
DefaultHttpClient client = new DefaultHttpClient(httpParameters);
Map<String,String> header = callback.getReqHeader(new HashMap<String, String>(),url,params);
header=changeHeader(url,header);
UtilLog.print(XHConf.in().log_tag_net,"d","------------------REQ_POST------------------\n"+url+"\n"+params+";"+byteMap+"\nheader:"+header.toString());
HttpPost post = new HttpPost(changeUrlFromHeader(url, header));
for (Map.Entry<String, String> map : header.entrySet()) {
post.addHeader(map.getKey(), map.getValue());
}
try {
//设置参数
if(byteMap.isEmpty()){
ArrayList<NameValuePair> postEntry=new ArrayList<NameValuePair>();
for (Map.Entry<String, String> entry : params.entrySet()) {
postEntry.add(new BasicNameValuePair(entry.getKey(),entry.getValue()));
}
post.setEntity(new UrlEncodedFormEntity(postEntry,XHConf.in().net_encode));
}else{
Charset charset=Charset.forName(XHConf.in().net_encode);
MultipartEntityBuilder multipartEntityBuilder = MultipartEntityBuilder.create();
multipartEntityBuilder.setMode(HttpMultipartMode.BROWSER_COMPATIBLE);
multipartEntityBuilder.setCharset(charset);
for (Map.Entry<String, String> entry : params.entrySet()) {
multipartEntityBuilder.addTextBody(entry.getKey(),entry.getValue(),ContentType.create("text/plain",charset));
}
for (Map.Entry<String, byte[]> entry : byteMap.entrySet()) {
//组装文件流数据
String[] fileNameSplit = entry.getKey().split("_");
String contentType = fileNameSplit[fileNameSplit.length - 1];
StringBuilder fileName = new StringBuilder();
if(fileNameSplit.length<3) throw new Exception("文件流的key用_无法切割:"+entry.getKey());
for(int i = 1; i < fileNameSplit.length - 1; i++){
fileName.append(fileNameSplit[i]);
}
multipartEntityBuilder.addBinaryBody(fileNameSplit[0] + "[]", entry.getValue(), ContentType.create(contentType), fileName.toString());
}
post.setEntity(multipartEntityBuilder.build());
}

HttpResponse response = client.execute(post);
int resState=response.getStatusLine().getStatusCode();
if (resState == HttpURLConnection.HTTP_OK) {
message.what=REQ_OK_STRING;
message.obj=UtilString.inputStream2String(response.getEntity().getContent(), XHConf.in().net_encode);
callback.saveCookie(getGetCookieMap(client.getCookieStore().getCookies()),url,"doPost");
} else {
message.what=REQ_STATE_ERROR;
message.obj=resState;
}
} catch (Exception e) {
message.what=REQ_EXP;
message.obj=e;
}
callback.requestTime=System.currentTimeMillis()-startTime;
client.getConnectionManager().shutdown();
return message;
}


注意这里用的是addBinaryBody的byte[]方法,而不是直接传inputStream,果然content-length属性已经明显出来了,但此种方法用的自然不是content chunk读取不定长的方式。不过至此网络底层的问题也基本解决啦。留下来给自己做个历史。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  ANDROID 411 网络框架