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

博客讲述了在安卓开发中遇到的一个网络框架问题,当上传图片时,由于服务器负载均衡导致411 Length Required错误。文章分析了问题原因,指出旧版MultipartEntity的使用可能存在问题,并推荐使用MultipartEntityBuilder来解决流长度无法读取及图片上传异常的问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

先看下一开始使用的网络框架核心代码:

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读取不定长的方式。不过至此网络底层的问题也基本解决啦。留下来给自己做个历史。



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值