HTTP文件上传请求格式详解,利用HttpURLConnection上传文件

来源:互联网 发布:怎么写销售数据分析表? 编辑:程序博客网 时间:2024/06/06 19:18

关于文件上传功能,现在的移动开发中有很多好的网络框架提供这个功能。早期开发Android时网络框架少,只能靠自己手写。虽然现在不用自己手写,但是弄明白原理还是很有必要的,出了问题也好排查。

要弄明白原理就得看看上传的请求都传了什么参数,下面是请求的抓包:

请求头内容:

POST /day1701/upload2 HTTP/1.1Host: localhost:8080Connection: keep-aliveContent-Length: 394Cache-Control: max-age=0Origin: http://localhost:8080Upgrade-Insecure-Requests: 1User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.96 Safari/537.36Content-Type: multipart/form-data; boundary=----WebKitFormBoundary6TAB8KxvuJTZYfUnAccept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8Referer: http://localhost:8080/day1701/form2.jspAccept-Encoding: gzip, deflate, brAccept-Language: zh-CN,zh;q=0.8Cookie: JSESSIONID=B0F1C54AB51D4453AA1E7A9D48B607A5

请求体内容:

------WebKitFormBoundary6TAB8KxvuJTZYfUnContent-Disposition: form-data; name="username"sdafdsa------WebKitFormBoundary6TAB8KxvuJTZYfUnContent-Disposition: form-data; name="f"; filename="default_launcher.conf"Content-Type: application/octet-streampackageName=com.cy.chineseonlineclassName=com.cy.chineseonline.activity.MainActivity------WebKitFormBoundary6TAB8KxvuJTZYfUn--

下面分析一下请求的内容,

首先是请求头:

最重要的内容是Content-Type,他的内容中是:

Content-Type: multipart/form-data; boundary=----WebKitFormBoundary6TAB8KxvuJTZYfUn

用分号隔开了不同的参数:
第一个参数multipart/form-data是必须的,是指提交的表单中有附件
第二个参数boundary表示分割线,请求体中会用来分割不同的请求参数
至于其他参数,在自己写请求的时候直接模仿传过去即可。

接下来是请求体

为了方便讲解,再把请求体复制过来:
以//开头的是我添加的注释,原来是没有的

------WebKitFormBoundary6TAB8KxvuJTZYfUn   // 第一行,是“--”字符串(不包括引号)和请求头中的boundary拼成的字符串Content-Disposition: form-data; name="username" // 第二行是固定的Content-Disposition: form-data;和name="请求的参数名称"  二者拼接的字符串// 这里是空行sdafdsa //这是请求的参数,就是username的具体值------WebKitFormBoundary6TAB8KxvuJTZYfUn  // 和第一行一样,是“--”字符串(不包括引号)和请求头中的boundary拼成的字符串Content-Disposition: form-data; name="f"; filename="default_launcher.conf" // 和第二行类似,但是因为是文件,所以多了一个filename参数,是指你上传的文件名Content-Type: application/octet-stream // 文件的minetype// 这里有个空行,下面是要上传的文件内容packageName=com.cy.chineseonlineclassName=com.cy.chineseonline.activity.MainActivity// 这里有个空行,上面是要上传的文件内容------WebKitFormBoundary6TAB8KxvuJTZYfUn-- // 最后一行 --和boundary和--拼接的字符串

以上是请求的内容格式分析,我们自己写上传的时候按照这个格式拼接就可以了,下面结合代码来分析,可以对照上面的内容看代码

    /**     * @param url 请求地址     * @param map 请求的参数     * @param filePath 文件路径     * @param body_data 上传的文件二进制内容     * @param charset 字符集     * @return     */    public static String doPostSubmitBody(String url, Map<String, String> map,            String filePath, byte[] body_data, String charset) {        // 设置三个常用字符串常量:换行、前缀、分界线(NEWLINE、PREFIX、BOUNDARY);        final String NEWLINE = "\r\n"; // 换行,或者说是回车        final String PREFIX = "--"; // 固定的前缀        final String BOUNDARY = "#"; // 分界线,就是上面提到的boundary,可以是任意字符串,建议写长一点,这里简单的写了一个#        HttpURLConnection httpConn = null;        BufferedInputStream bis = null;        DataOutputStream dos = null;        ByteArrayOutputStream baos = new ByteArrayOutputStream();        try {            // 实例化URL对象。调用URL有参构造方法,参数是一个url地址;            URL urlObj = new URL(url);            // 调用URL对象的openConnection()方法,创建HttpURLConnection对象;            httpConn = (HttpURLConnection) urlObj.openConnection();            // 调用HttpURLConnection对象setDoOutput(true)、setDoInput(true)、setRequestMethod("POST");            httpConn.setDoInput(true);            httpConn.setDoOutput(true);            httpConn.setRequestMethod("POST");            // 设置Http请求头信息;(Accept、Connection、Accept-Encoding、Cache-Control、Content-Type、User-Agent),不重要的就不解释了,直接参考抓包的结果设置即可            httpConn.setUseCaches(false);            httpConn.setRequestProperty("Connection", "Keep-Alive");            httpConn.setRequestProperty("Accept", "*/*");            httpConn.setRequestProperty("Accept-Encoding", "gzip, deflate");            httpConn.setRequestProperty("Cache-Control", "no-cache");            // 这个比较重要,按照上面分析的拼装出Content-Type头的内容            httpConn.setRequestProperty("Content-Type",                    "multipart/form-data; boundary=" + BOUNDARY);            // 这个参数可以参考浏览器中抓出来的内容写,用chrome或者Fiddler抓吧看看就行            httpConn.setRequestProperty(                    "User-Agent",                    "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 2.0.50727; .NET CLR 3.0.04506.30)");            // 调用HttpURLConnection对象的connect()方法,建立与服务器的真实连接;            httpConn.connect();            // 调用HttpURLConnection对象的getOutputStream()方法构建输出流对象;            dos = new DataOutputStream(httpConn.getOutputStream());            // 获取表单中上传控件之外的控件数据,写入到输出流对象(根据上面分析的抓包的内容格式拼凑字符串);            if (map != null && !map.isEmpty()) { // 这时请求中的普通参数,键值对类型的,相当于上面分析的请求中的username,可能有多个                for (Map.Entry<String, String> entry : map.entrySet()) {                    String key = entry.getKey(); // 键,相当于上面分析的请求中的username                    String value = map.get(key); // 值,相当于上面分析的请求中的sdafdsa                    dos.writeBytes(PREFIX + BOUNDARY + NEWLINE); // 像请求体中写分割线,就是前缀+分界线+换行                    dos.writeBytes("Content-Disposition: form-data; "                            + "name=\"" + key + "\"" + NEWLINE); // 拼接参数名,格式就是Content-Disposition: form-data; name="key" 其中key就是当前循环的键值对的键,别忘了最后的换行                     dos.writeBytes(NEWLINE); // 空行,一定不能少,键和值之间有一个固定的空行                    dos.writeBytes(URLEncoder.encode(value.toString(), charset)); // 将值写入                    // 或者写成:dos.write(value.toString().getBytes(charset));                    dos.writeBytes(NEWLINE); // 换行                } // 所有循环完毕,就把所有的键值对都写入了            }            // 获取表单中上传附件的数据,写入到输出流对象(根据上面分析的抓包的内容格式拼凑字符串);            if (body_data != null && body_data.length > 0) {                dos.writeBytes(PREFIX + BOUNDARY + NEWLINE);// 像请求体中写分割线,就是前缀+分界线+换行                String fileName = filePath.substring(filePath                        .lastIndexOf(File.separatorChar) + 1); // 通过文件路径截取出来文件的名称,也可以作文参数直接传过来                // 格式是:Content-Disposition: form-data; name="请求参数名"; filename="文件名"                // 我这里吧请求的参数名写成了uploadFile,是死的,实际应用要根据自己的情况修改                // 不要忘了换行                dos.writeBytes("Content-Disposition: form-data; " + "name=\""                        + "uploadFile" + "\"" + "; filename=\"" + fileName                        + "\"" + NEWLINE);                // 换行,重要!!不要忘了                dos.writeBytes(NEWLINE);                dos.write(body_data); // 上传文件的内容                dos.writeBytes(NEWLINE); // 最后换行            }            dos.writeBytes(PREFIX + BOUNDARY + PREFIX + NEWLINE); // 最后的分割线,与前面的有点不一样是前缀+分界线+前缀+换行,最后多了一个前缀            dos.flush();            // 调用HttpURLConnection对象的getInputStream()方法构建输入流对象;            byte[] buffer = new byte[8 * 1024];            int c = 0;            // 调用HttpURLConnection对象的getResponseCode()获取客户端与服务器端的连接状态码。如果是200,则执行以下操作,否则返回null;            if (httpConn.getResponseCode() == 200) {                bis = new BufferedInputStream(httpConn.getInputStream());                while ((c = bis.read(buffer)) != -1) {                    baos.write(buffer, 0, c);                    baos.flush();                }            }            // 将输入流转成字节数组,返回给客户端。            return new String(baos.toByteArray(), charset);        } catch (Exception e) {            e.printStackTrace();        } finally {            try {                if (dos != null)                    dos.close();                if (bis != null)                    bis.close();                if (baos != null)                    baos.close();                httpConn.disconnect();            } catch (Exception e) {                e.printStackTrace();            }        }        return null;    }

这个上传方法可以直接用,亲测可用。用的时候要注意把上传附件的参数名改一改,我直接写死了叫uploadFile,需要成你自己的
还有一个需要注意的是中文乱码问题,要和服务器沟通,我的事例代码中键值对参数用了编码,服务器拿到要解码,上传文件名那里没有对文件名处理,如果是中文要注意。

好了,就分析到这,重点就是要把上传文件时的参数格式弄明白,按照格式拼出来内容即可。

阅读全文
1 0
原创粉丝点击