MyException - 我的异常网
当前位置:我的异常网» Android » 在 Android 上经过模拟 HTTP multipart/form-data

在 Android 上经过模拟 HTTP multipart/form-data 请求协议信息实现图片上传

www.MyException.Cn  网友分享于:2015-08-26  浏览:116次
在 Android 上通过模拟 HTTP multipart/form-data 请求协议信息实现图片上传

通过构造基于 HTTP 协议的传输内容实现图片自动上传到服务器功能 。如果自己编码构造 HTTP 协议,那么编写的代码质量肯定不高,建议模仿 HttpClient .zip examples \mime\ClientMultipartFormPost.java 来实现,并通过源码来进一步理解如何优雅高效地构造 HTTP 协议传输内容。

 

自己构造 HTTP 协议传输内容的想法,从何而来呢?灵感启迪于这篇博文“Android下的应用编程——用HTTP协议实现文件上传功能 ”,以前从未想过通过抓取 HTTP 请求数据格式,根据协议自己构造数据来实现数据提交。哎,Out 了。因为 Apache HttpClient 框架就是通过此方式来实现的,以前从未注意到,看来以后要多多向前人学习啊!结果是:阅读了此框架的源码后,才知道自己编写的代码和人家相比真不是一个档次的。现在已经下定决心了,多读开源框架代码,不但可以熟悉相关业务流程,而且还可以学到设计模式在实际业务需求中的应用,更重要的是领悟其中的思想。业务流程、实践能力、框架思想,一举三得,何乐而不为呢。^_^

 

test.html 部分源码:

<form action="Your_Action_Url " method="post" enctype="multipart/form-data " name="form1" id="form1">
  <p>
    <label for="upload_file"></label>
    <input type="file" name="upload_file" id="upload_file " />
  </p>
  <p>
    <input type="submit" name="action" id="action " value="upload " />
  </p>
</form>

通过 HttpWatch 查看抓取到的包数据格式:

通过 HttpWatch 查看抓取到的包数据格式

 

下面将分别通过按照 HttpWatch 抓取下来的协议格式内容构造传输内容实现文件上传功能和基于 HttpClient 框架实现文件上传功能。

项目配置目录Your_Project/config ,相关文件 如下:

actionUrl.properties 文件内容:

Your_Action_Url

formDataParams.properties 文件内容(对应 HTML Form 属性内容):

action =upload

imageParams.properties 文件内容(这里文件路径已配置死了,不好!建议在程序中动态设置,即通过传入相关参数实现。):

upload_file =images/roewe.jpg

MIMETypes.properties 文件内容(参考自 Multimedia MIME Reference ):

jpeg:image/jpeg
jpg:image/jpeg
png:image/png
gif:image/gif

 

1. 在《Android下的应用编程——用HTTP协议实现文件上传功能 》代码的基础上,通过进一步改进得到如下代码(Java、Android 都可以 run):

/**
 * 文件名称:UploadImage.java
 *
 * 版权信息:Apache License, Version 2.0
 *
 * 功能描述:实现图片文件上传。
 *
 * 创建日期:2011-5-10
 *
 * 作者:Bert Lee
 */

/*
 * 修改历史:
 */
public class UploadImage {
    String multipart_form_data = "multipart/form-data";
    String twoHyphens = "--";
    String boundary = "****************fD4fH3gL0hK7aI6";    // 数据分隔符
    String lineEnd = System.getProperty("line.separator");    // The value is "\r\n" in Windows.
    
    /*
     * 上传图片内容,格式请参考HTTP 协议格式。
     * 人人网Photos.upload中的”程序调用“http://wiki.dev.renren.com/wiki/Photos.upload#.E7.A8.8B.E5.BA.8F.E8.B0.83.E7.94.A8
     * 对其格式解释的非常清晰。
     * 格式如下所示:
     * --****************fD4fH3hK7aI6
     * Content-Disposition: form-data; name="upload_file"; filename="apple.jpg"
     * Content-Type: image/jpeg
     *
     * 这儿是文件的内容,二进制流的形式
     */
    private void addImageContent(Image[] files, DataOutputStream output) {
        for(Image file : files) {
            StringBuilder split = new StringBuilder();
            split.append(twoHyphens + boundary + lineEnd);
            split.append("Content-Disposition: form-data; name=\"" + file.getFormName() + "\"; filename=\"" + file.getFileName() + "\"" + lineEnd);
            split.append("Content-Type: " + file.getContentType() + lineEnd);
            split.append(lineEnd);
            try {
                // 发送图片数据
                output.writeBytes(split.toString());
                output.write(file.getData(), 0, file.getData().length);
                output.writeBytes(lineEnd);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    }
    
    /*
     * 构建表单字段内容,格式请参考HTTP 协议格式(用FireBug可以抓取到相关数据)。(以便上传表单相对应的参数值)
     * 格式如下所示:
     * --****************fD4fH3hK7aI6
     * Content-Disposition: form-data; name="action"
     * // 一空行,必须有
     * upload
     */
    private void addFormField(Set<Map.Entry<Object,Object>> params, DataOutputStream output) {
        StringBuilder sb = new StringBuilder();
        for(Map.Entry<Object, Object> param : params) {
            sb.append(twoHyphens + boundary + lineEnd);
            sb.append("Content-Disposition: form-data; name=\"" + param.getKey() + "\"" + lineEnd);
            sb.append(lineEnd);
            sb.append(param.getValue() + lineEnd);
        }
        try {
            output.writeBytes(sb.toString());// 发送表单字段数据
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
    
    /**
     * 直接通过 HTTP 协议提交数据到服务器,实现表单提交功能。
     * @param actionUrl 上传路径
     * @param params 请求参数key为参数名,value为参数值
     * @param files 上传文件信息
     * @return 返回请求结果
     */
    public String post(String actionUrl, Set<Map.Entry<Object,Object>> params, Image[] files) {
        HttpURLConnection conn = null;
        DataOutputStream output = null;
        BufferedReader input = null;
        try {
            URL url = new URL(actionUrl);
            conn = (HttpURLConnection) url.openConnection();
            conn.setConnectTimeout(120000);
            conn.setDoInput(true);        // 允许输入
            conn.setDoOutput(true);        // 允许输出
            conn.setUseCaches(false);    // 不使用Cache
            conn.setRequestMethod("POST");
            conn.setRequestProperty("Connection", "keep-alive");
            conn.setRequestProperty("Content-Type", multipart_form_data + "; boundary=" + boundary);
            
            conn.connect();
            output = new DataOutputStream(conn.getOutputStream());
            
            addImageContent(files, output);    // 添加图片内容
            
            addFormField(params, output);    // 添加表单字段内容
            
            output.writeBytes(twoHyphens + boundary + twoHyphens + lineEnd);// 数据结束标志
            output.flush();
            
            int code = conn.getResponseCode();
            if(code != 200) {
                throw new RuntimeException("请求‘" + actionUrl +"’失败!");
            }
            
            input = new BufferedReader(new InputStreamReader(conn.getInputStream()));
            StringBuilder response = new StringBuilder();
            String oneLine;
            while((oneLine = input.readLine()) != null) {
                response.append(oneLine + lineEnd);
            }
            
            return response.toString();
        } catch (IOException e) {
            throw new RuntimeException(e);
        } finally {
            // 统一释放资源
            try {
                if(output != null) {
                    output.close();
                }
                if(input != null) {
                    input.close();
                }
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
            
            if(conn != null) {
                conn.disconnect();
            }
        }
    }
    
    public static void main(String[] args) {
        try {
            String response = "";
            
            BufferedReader in = new BufferedReader(new FileReader("config/actionUrl.properties"));
            String actionUrl = in.readLine();
            
            // 读取表单对应的字段名称及其值
            Properties formDataParams = new Properties();
            formDataParams.load(new FileInputStream(new File("config/formDataParams.properties")));
            Set<Map.Entry<Object,Object>> params = formDataParams.entrySet();
            
            // 读取图片所对应的表单字段名称及图片路径
            Properties imageParams = new Properties();
            imageParams.load(new FileInputStream(new File("config/imageParams.properties")));
            Set<Map.Entry<Object,Object>> images = imageParams.entrySet();
            Image[] files = new Image[images.size()];
            int i = 0;
            for(Map.Entry<Object,Object> image : images) {
                Image file = new Image(image.getValue().toString(), image.getKey().toString());
                files[i++] = file;
            }
//            Image file = new Image("images/apple.jpg", "upload_file");
//            Image[] files = new Image[0];
//            files[0] = file;
            
            response = new UploadImage().post(actionUrl, params, files);
            System.out.println("返回结果:" + response);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}


2. 基于 HttpClient 框架实现文件上传,实例代码如下:

/**
 * 文件名称:ClientMultipartFormPost.java
 *
 * 版权信息:Apache License, Version 2.0
 *
 * 功能描述:通过 HttpClient 4.1.1 实现文件上传。
 *
 * 创建日期:2011-5-15
 *
 * 作者:Bert Lee
 */

/*
 * 修改历史:
 */
public class ClientMultipartFormPost {
    /**
     * 直接通过 HttpMime's MultipartEntity 提交数据到服务器,实现表单提交功能。
     * @return Post 请求所返回的内容
     */
    public static String filePost() {
        HttpClient httpclient = new DefaultHttpClient();
        
        try {
            BufferedReader in = new BufferedReader(new FileReader("config/actionUrl.properties"));
            String actionUrl;
            actionUrl = in.readLine();
            HttpPost httppost = new HttpPost(actionUrl);
            
            // 通过阅读源码可知,要想实现图片上传功能,必须将 MultipartEntity 的模式设置为 BROWSER_COMPATIBLE 。
            MultipartEntity multiEntity = new MultipartEntity(HttpMultipartMode.BROWSER_COMPATIBLE);
//            MultipartEntity multiEntity = new MultipartEntity();
            
            // 读取图片的 MIME Type 类型集
            Properties mimeTypes = new Properties();
            mimeTypes.load(new FileInputStream(new File("config/MIMETypes.properties")));
            
            // 构造图片数据
            Properties imageParams = new Properties();
            imageParams.load(new FileInputStream(new File("config/imageParams.properties")));
            String fileType;
            for(Map.Entry<Object,Object> image : imageParams.entrySet()) {
                String path = image.getValue().toString();
                fileType = path.substring(path.lastIndexOf(".") + 1);
                FileBody binaryContent = new FileBody(new File(path), mimeTypes.get(fileType).toString());
//                FileBody binaryContent = new FileBody(new File(path));
                multiEntity.addPart(image.getKey().toString(), binaryContent);
            }
            
            // 构造表单参数数据
            Properties formDataParams = new Properties();
            formDataParams.load(new FileInputStream(new File("config/formDataParams.properties")));
            for(Entry<Object, Object> param : formDataParams.entrySet()) {
                multiEntity.addPart(param.getKey().toString(), new StringBody(param.getValue().toString()));
            }
            
            httppost.setEntity(multiEntity);
//            Out.println("executing request " + httppost.getRequestLine());
            
            HttpResponse response = httpclient.execute(httppost);
            HttpEntity resEntity = response.getEntity();
            
//            Out.println("-------------------");
//            Out.println(response.getStatusLine());
            if(resEntity != null) {
                String returnContent = EntityUtils.toString(resEntity);
                EntityUtils.consume(resEntity);
                
                return returnContent; // 返回页面内容
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 释放资源
            httpclient.getConnectionManager().shutdown();
        }
        return null;
    }

    // 测试
    public static void main(String[] args) {
        Out.println("Response content: " + ClientMultipartFormPost.filePost());
    }

}
 

 

参考资料:

  • Android下的应用编程——用HTTP协议实现文件上传功能
  • How to upload a file using Java HttpClient library


文章评论

Java程序员必看电影
Java程序员必看电影
“肮脏的”IT工作排行榜
“肮脏的”IT工作排行榜
旅行,写作,编程
旅行,写作,编程
鲜为人知的编程真相
鲜为人知的编程真相
团队中“技术大拿”并非越多越好
团队中“技术大拿”并非越多越好
聊聊HTTPS和SSL/TLS协议
聊聊HTTPS和SSL/TLS协议
Web开发者需具备的8个好习惯
Web开发者需具备的8个好习惯
Java 与 .NET 的平台发展之争
Java 与 .NET 的平台发展之争
总结2014中国互联网十大段子
总结2014中国互联网十大段子
编程语言是女人
编程语言是女人
我的丈夫是个程序员
我的丈夫是个程序员
老程序员的下场
老程序员的下场
中美印日四国程序员比较
中美印日四国程序员比较
程序员眼里IE浏览器是什么样的
程序员眼里IE浏览器是什么样的
写给自己也写给你 自己到底该何去何从
写给自己也写给你 自己到底该何去何从
程序员应该关注的一些事儿
程序员应该关注的一些事儿
 程序员的样子
程序员的样子
初级 vs 高级开发者 哪个性价比更高?
初级 vs 高级开发者 哪个性价比更高?
每天工作4小时的程序员
每天工作4小时的程序员
5款最佳正则表达式编辑调试器
5款最佳正则表达式编辑调试器
程序员必看的十大电影
程序员必看的十大电影
十大编程算法助程序员走上高手之路
十大编程算法助程序员走上高手之路
什么才是优秀的用户界面设计
什么才是优秀的用户界面设计
那些争议最大的编程观点
那些争议最大的编程观点
代码女神横空出世
代码女神横空出世
10个帮程序员减压放松的网站
10个帮程序员减压放松的网站
我是如何打败拖延症的
我是如何打败拖延症的
2013年美国开发者薪资调查报告
2013年美国开发者薪资调查报告
我跳槽是因为他们的显示器更大
我跳槽是因为他们的显示器更大
当下全球最炙手可热的八位少年创业者
当下全球最炙手可热的八位少年创业者
不懂技术不要对懂技术的人说这很容易实现
不懂技术不要对懂技术的人说这很容易实现
做程序猿的老婆应该注意的一些事情
做程序猿的老婆应该注意的一些事情
Web开发人员为什么越来越懒了?
Web开发人员为什么越来越懒了?
Google伦敦新总部 犹如星级庄园
Google伦敦新总部 犹如星级庄园
老美怎么看待阿里赴美上市
老美怎么看待阿里赴美上市
如何成为一名黑客
如何成为一名黑客
要嫁就嫁程序猿—钱多话少死的早
要嫁就嫁程序猿—钱多话少死的早
为什么程序员都是夜猫子
为什么程序员都是夜猫子
程序员和编码员之间的区别
程序员和编码员之间的区别
漫画:程序员的工作
漫画:程序员的工作
10个调试和排错的小建议
10个调试和排错的小建议
程序员最害怕的5件事 你中招了吗?
程序员最害怕的5件事 你中招了吗?
2013年中国软件开发者薪资调查报告
2013年中国软件开发者薪资调查报告
那些性感的让人尖叫的程序员
那些性感的让人尖叫的程序员
如何区分一个程序员是“老手“还是“新手“?
如何区分一个程序员是“老手“还是“新手“?
程序员都该阅读的书
程序员都该阅读的书
科技史上最臭名昭著的13大罪犯
科技史上最臭名昭著的13大罪犯
程序员周末都喜欢做什么?
程序员周末都喜欢做什么?
程序员的一天:一寸光阴一寸金
程序员的一天:一寸光阴一寸金
软件开发程序错误异常ExceptionCopyright © 2009-2015 MyException 版权所有