Android 上传PHP xUtils Bug修复分析过程

起因

作为全职PHPer偶尔需要客串下Androider,最近公司的一个项目需要Android的客户端(主要图片特效处理及其上传),自己就客串下Androider.

之前有过Android开发经验所以做这个挺顺手的,几乎所有东西直接github中拿过来改改就用,不过在处理图片上传的时候选择了xUtils这个

开源工具类,用起来确实比较好用,挺方便的,例如如下代码就可以实现上传:

RequestParams params = newRequestParams();
params.addBodyParameter("file", file);
HttpUtils httpUtils = newHttpUtils();
httpUtils.send(HttpRequest.HttpMethod.POST, UPLOAD_URL, params, newRequestCallBack<String>() {
@Override
//上传失败处理方法
publicvoidonFailure(HttpException arg0, String msg) {
 alert(msg);
 }
@Override
//上传进度处理
publicvoidonLoading(longtotal,longcurrent,
booleanisUploading) {
if(isUploading) {
 Log.i(LOG_NAME, "upload:"+ current +"/"+ total);
 }
 }
@Override
//上传成功处理
publicvoidonSuccess(ResponseInfo<String> responseInfo) {
 alert(responseInfo.result);
 Log.i(LOG_NAME, responseInfo.result);
 }
});
可以看到用起来比较方便,如果自己写还是比较麻烦的。不过最让人头疼的不是使用方法,而是作为接收端为PHP的话是接收不到上传的文件,最后经证实

不仅仅是PHP C# 也有问题, 网上搜素了下不少人都遇到问题不过没有解决方案,看来只能自己动手解决了

问题分析

既然要解决问题,那么就需要分析bug可能出现的地方,既然是HTTP上传那么我们得知道在PHP 在HTTP协议中文件是怎么处理上传的,直接在官方文档

就可以找到

PHP 能够接受任何来自符合 RFC-1867 标准的浏览器(包括 Netscape Navigator 3 及更高版本,打了补丁的 Microsoft Internet Explorer 3 或者更高版本)上传的文件。

这个是PHP官方文档中给出的解释, PHP在处理上传的时候遵循的是RFC-1867标准,那么我们接下来看看什么是RFC-1867。

这里我给出一个RFC-1867的说明文档地址 RFC-1867 说明 ,太长了就不放在这里了只拿核心重点内容过来看看:

# The client might send back the following data use POST method:

Content-type: multipart/form-data, boundary=AaB03x

--AaB03x
content-disposition: form-data; name="field1"

Joe Blow
--AaB03x
content-disposition: form-data; name="pics"; filename="file1.txt"
Content-Type: text/plain

 ... contents of file1.txt ...
--AaB03x--
这里通俗点讲,RFC-1867通过HTTP协议传输特定的格式来实现上传文件,比如我们上传一个文件名字叫做hello.txt的文本到服务端,那么发起请求的HTTP格式应该就如下:

POST /up.phpHTTP/1.1
Host:creturn.com
Content-Length:294
Content-type:multipart/form-data, boundary=AaB03x

--AaB03x
content-disposition: form-data; name="file"; filename="hello.txt"
Content-Type: text/plain

hello
--AaB03x--
这里HTTP头部协议没有写完整只是为了说明问题写的格式。在这些描述信息中

第一个Content-type(http头部描述信息)值multipart/form-data 是告诉服务器此次发送的数据是上传文件数据, boundary是告诉服务器文件数据之间分割标示符是 AaB03x
在HTTP body数据中以—AaB03x 分割多个文件,每个分隔符下面的描述信息是作为上传文件的描述信息
发送结束后需要以分隔符加“—”符号进行标示
如果这三点没有问题那么就能正确上传,当然次实例中肯定不成功因为HTTP头部协议描述信息简短切不争取(比如长度)

解决过程

既然上面已经对问题进行分析了,同样也知道了只要发送过程是按照RFC-1867的标准进行发送那么至少PHP是能够接收到上传的文件,那么接下来我们要解决的就是如果判断

或者查看xUtils发送文件过程中是否遵循了RFC-1867标准

那么如何查看xUtils是否发送了正确的数据格式? 有两种方案,一个是利用代理工具抓包,另外一个方案就是直接抓包

这里就说说直接抓包,代理抓包可以google一大堆。

pc 上面建立无线热点分享给手机,这样所有的数据都通过电脑走,不会用pc分享热点的google一大堆,或者为了偷懒买个传说中的mini WIFI都行

抓包工具window推荐smartSniff, linux或者os x直接就tcpdump也行

先写个简单的app装到手机上这里给出上传代码:

String UPLOAD_URL = "http://www.creturn.com/up.php";
File file = newFile(Environment.getExternalStorageDirectory() ,"hello.txt");
RequestParams params = newRequestParams();
params.addBodyParameter("file", file);
HttpUtils httpUtils = newHttpUtils();
httpUtils.send(HttpRequest.HttpMethod.POST, UPLOAD_URL, params, newRequestCallBack<String>() {
@Override
//上传失败处理方法
publicvoidonFailure(HttpException arg0, String msg) {
 alert(msg);
 }
@Override
//上传进度处理
publicvoidonLoading(longtotal,longcurrent,
booleanisUploading) {
if(isUploading) {
 Log.i(LOG_NAME, "upload:"+ current +"/"+ total);
 }
 }
@Override
//上传成功处理
publicvoidonSuccess(ResponseInfo<String> responseInfo) {
 alert(responseInfo.result);
 Log.i(LOG_NAME, responseInfo.result);
 }
});
上面代码作用是把sdcard根目录的hello.txt文件上传到UPLOAD_URL, 所以在sdcard根目录放一个hello.txt文件里面内容随便写点

php服务端这边就直接打印上传的文件信息就行,代码很简单:

<?php
print_r($_FILES);
?>
如果上传成功就会反馈上传文件的信息,写好app装到手机连接好wifi然后在pc上面抓包,我这里用的是smart sniffer

抓包

打开smart sniffer
选择菜单 Options -> Capture Options
选择你分享wifi的网卡
确定点击开始抓包
在手机app上操作上传文件时候就可以看到抓包工具中已经有相应的http数据,抓包工具会对所有流量抓取所以如果有其他包干扰还可以

设置相应的过滤规则,这里就不阐述google就能找到

看看我们抓到的包内容:

图种可以看到我们上传的HTTP包信息,不过很明显反馈的信息提示是没有上传成功的。

那么接下来我们怎么去分析这个?怎么去排查问题?很多人应该能够想到,要是有个正确参照物不就很容易分析出问题出处?

那么我们在建立一个html文件用浏览器同样上传sdcard里面的hello.txt文件,html内容如下:

<html>
<body>

<formaction="http://www.creturn.com/up.php"method="post"enctype="multipart/form-data">
<labelfor="file">Filename:</label>
<inputtype="file"name="file"id="file"/>
<br/>
<inputtype="submit"name="submit"value="Submit"/>
</form>

</body>
</html>
用同样的方法抓包看看正确的包内容是什么样的:

POST /up.phpHTTP/1.1
Host:www.creturn.com
Connection:keep-alive
Content-Length:294
Cache-Control:max-age=0
Accept:text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Origin:http://222.73.234.196
User-Agent:Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.63 Safari/537.36
Content-Type:multipart/form-data; boundary=----WebKitFormBoundaryIexdOW8e2EZyciDK
Referer:http://222.73.234.196/up.html
DontTrackMeHere:gzip,deflate,sdch
Accept-Language:zh-CN,zh;q=0.8

------WebKitFormBoundaryIexdOW8e2EZyciDK
Content-Disposition: form-data; name="file"; filename="hello.txt"
Content-Type: text/plain

hello upload
------WebKitFormBoundaryIexdOW8e2EZyciDK
Content-Disposition: form-data; name="submit"

Submit
------WebKitFormBoundaryIexdOW8e2EZyciDK--
可以看到处理HTTP头部描述信息和包体里面多了一个Submit,几乎一样

之前说过上传过程中的几个重点,然后对比下我们发现Content-Type描述信息多了一个charset字符编码描述信息

那么要做测试的话肯定就要把不同的地方去掉,然后对包进行回放看看是否成功

包回放指的是包数据包重新发送一次

回放数据包有两种方法,一种直接修改xUtils源码重新上传。这里说一个简单的方法window自带的telnet ,用telnet 链接服务器80端口

手动发送数据

注意: windows7默认没有安装需要在控制面板->程序和功能->打开或者关闭windows功能中开启

如何进行手动发送?按照我们之前的想法去掉charset描述信息然后手动发送,那么先去掉charset信息后的包内容放入记事本:

POST /up.phpHTTP/1.1
User-Agent:Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_1) AppleWebKit/537.73.11 (KHTML, like Gecko) Version/7.0.1 Safari/537.73.11
Content-Length:233
Content-Type:multipart/form-data; boundary=6wYgRevA02R_Uy4EJP31EcIJtsBlZtRv
Host:wwwcreturn.com
Connection:Keep-Alive
DontTrackMeHere:gzip

--6wYgRevA02R_Uy4EJP31EcIJtsBlZtRv
Content-Disposition: form-data; name="file"; filename="hello.txt"
Content-Type: application/octet-stream
Content-Transfer-Encoding: binary

hello upload

--6wYgRevA02R_Uy4EJP31EcIJtsBlZtRv--
打开cmd 然后输入telnet www.creturn.com 80 然后黏贴进去看看效果

为了印证我们的才行可以把charset加上去和去掉的进行对比看看是不是加了之后就收不到上传文件的信息。

其实根据HTTP协议来讲理论上加不加charset应该不会影响上传,但结果这个问题确实是由于charset引起的。

接下来就简单了找到根源解决就行,在源码里面进行搜索 boundary ,找到地方根据作者写的方法注释掉其中添加charset的代码:

protectedStringgenerateContentType(
finalString boundary,
finalCharset charset) {
 StringBuilder buffer = newStringBuilder();
 buffer.append("multipart/"+ multipartSubtype +"; boundary=");
 buffer.append(boundary);
//这里就是需要注释掉的代码
/*if (charset != null) {
 buffer.append("; charset=");
 buffer.append(charset.name());
 }*/
returnbuffer.toString();
}

时间: 2024-11-02 23:41:03

Android 上传PHP xUtils Bug修复分析过程的相关文章

Android上传多张图片到服务器

问题描述 Android上传多张图片到服务器 单张上传我已经成功了,不知道怎么处理多张图片上传,求求求求解!!! 解决方案 http://download.csdn.net/download/javalishilong/6312977 解决方案二: private void toUploadFile(File file String fileKey String RequestURL Map param) { String result = null; requestTime= 0; long

android 上传文件到服务器

问题描述 android 上传文件到服务器 已知文件存放路径,例如"/sdcard/abc.doc",如何用最简单的语句将其上传到一个网络申请的空间(地址已有),跪求解答,很急!!! 解决方案 Android 上传文件,图片.以及服务器端接收相关. 解决方案二: 不应该先将其变成File对象,完了作为参数传给服务器端吗?服务器端做处理啊..

android上传多图片~PHP做服务器~~~怎么做

问题描述 android上传多图片-PHP做服务器---怎么做 android上传多图片-PHP做服务器---求大神求救 解决方案 php不懂,android就用post上传啊!你可以搜搜,很多的 解决方案二: PHP服务器用wamp搭建就行了, 然后写一个处理图片上传的PHP文件,网上应该有,直接查找就行. 最后就是在android上传,post给你写好的PHP图片处理文件. 其实都可以在度娘那里问到

Android上传文件到服务端并显示进度条

最近在做上传文件的服务,简单看了网上的教程.结合实践共享出代码. 由于网上的大多数没有服务端的代码,这可不行呀,没服务端怎么调试呢. Ok,先上代码. Android 上传比较简单,主要用到的是 HttpURLConnection 类,然后加一个进度条组件. private ProgressBar mPgBar; class UploadTask extends AsyncTask<Object,Integer,Void>{ private DataOutputStream outputStr

android 上传文件到服务器代码实例

android对于上传文件,还是很简单的,和java里面的上传都是一样的,基本上都是熟悉操作输出流和输入流!还有一个特别重要的就是需要一些content-type这些参数的配置!  如果这些都弄好了,上传就很简单了!   下面是我写的一个上传的工具类:复制代码 代码如下:package com.spring.sky.image.upload.network; import java.io.DataOutputStream;import java.io.File;import java.io.Fi

unity php-js如何阻止默认的android上传功能

问题描述 js如何阻止默认的android上传功能 现在是将网页嵌入到了Unity,然后Unity外面有一层Android,用js写了一个上传图片的代码,只在浏览器里运行是正常的,但是嵌入到android以后,点击上传,会自动调用Android默认的选择本地文件的功能,并且带了裁剪等一系列功能. 我现在想要屏蔽掉默认的选择文件功能,只想用自己写的js来选择图片.怎么屏蔽掉呢.搞了我好久了.

android上传大文件亲测可用,上传200M个文件,不到3分钟

之前贴过个例子是android 入门学习笔记 上传大文件 这种的文件大小限制很严,一般30M以上就报错了.网上查了一下,还是推荐用Socket连接进行大文件上传. 今天测试了一下之前网上找的例子,通过Socket实现的android下大文件上传,服务器端用java接收.测试上传了个200M的文件,不到三分钟!还是可以接受的. 只是做了个简单的测试例子,还没有考虑到权限问题(手机上传资料到服务器端,应该需要做身份验证..) [html] view plaincopy connection.setC

Android上传文件至服务器

本实例实现每隔5秒上传一次,通过服务器端获取手机上传过来的文件信息并做相应处理:采用Android+Struts2技术.            一.Android端实现文件上传 1).新建一个Android项目命名为androidUpload,目录结构如下:           2).新建FormFile类,用来封装文件信息 package com.ljq.utils; import java.io.File; import java.io.FileInputStream; import jav

Android上传文件到服务器的方法

本文实例为大家分享了Android端实现文件上传的具体代码,供大家参考,具体内容如下 1).新建一个Android项目命名为androidUpload,目录结构如下: 2).新建FormFile类,用来封装文件信息 package com.ljq.utils; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.InputStream;