欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

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

程序员文章站 2024-02-01 12:53:58
...

通过构造基于 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 查看抓取到的包数据格式:

在 Android 上通过模拟 HTTP multipart/form-data 请求协议信息实现图片上传
            
    
    博客分类: 业务需求-架构-设计模式 androidhttpimageform-data 

 

下面将分别通过按照 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());
    }

}
 

 

参考资料: