Java使用HTTP编程模拟多参数多文件表单信息的请求与处理
本文目的是提供Java环境下模拟浏览器页面提交多参数多文件表单请求以及解析请求的Demo代码。这里用Java提供的HttpURLConnection类做HTTP请求,再原始点可以直接使用socket。使用socket的话,通用性会更好点。
首先要了解一个概念,文件和参数一起上传,HTTP请求头中包含了如下格式。
- Content-Type: multipart/form-data; boundary=boundarystr
请求体的数据格式也是有一个标准的,具体如下。
- HTTP请求头
- --boundarystr
- Content-Disposition: form-data; name="param1"
- param1value
- --boundarystr
- Content-Disposition: form-data; name="param2"
- param2value
- --boundarystr
- Content-Disposition: form-data; name="param3"; filename="filename1"
- Content-Type: application/octet-stream
- 文件数据
- --boundarystr
- Content-Disposition: form-data; name="param4"; filename="filename2"
- Content-Type: application/octet-stream
- 文件数据
- --boundarystr--
boundary参数一般会取一些类似于UUID这样的值。请求头和请求体之间隔了两个回车,也就是一个"\r\n\r\n"。双横杠boundary和Content-Disposistion描述之间有一个回车("\r\n")。name和filename的参数值需要加双引号。Content描述和数据之间有两个回车("\r\n\r\n")。请求提最后的boundary后尾需要跟上双横杠。例子中,用application/octet-stream通用的描述文件数据内容格式,如果事先知道文件格式,可以根据MIME类型(http://www.w3school.com.cn/media/media_mimeref.asp)填写。好处是解析时候比较方便。
在编写代码时必须严格遵循这个消息格式,包括换行和横杠以及boundary。否则会导致数据无法解析。
下面是一个制作该HTTP消息并进行请求的代码。
- /**
- * 制作多参数多文件消息并进行请求
- * @param urlString 目标url
- * @param mutiFileList 含有文件信息的List<map>,最简单的情况是map中包含文件名称和路径,每个map表示一个文件。
- * @param params 普通参数map,key作为参数名称,value作为参数值
- * @return
- */
- public String httpUploadMutiFile(String urlString,List<Map<String, Object>> mutiFileList, Map<String, String> params){
- String repString = null;
- InputStream is = null;
- OutputStream out = null;
- //定义boundarystr
- final String BOUNDARYSTR = Long.toHexString(System.currentTimeMillis());
- //定义回车换行
- final String CRLF = "\r\n";
- final String BOUNDARY = "--"+BOUNDARYSTR+CRLF;
- StringBuilder paramsText = new StringBuilder();
- byte[] paraTextByte;
- try{
- //首先制作普通参数信息
- if(params != null && params.size() != 0){
- for(String key:params.keySet()){
- paramsText.append(BOUNDARY);
- paramsText.append("Content-Disposition: form-data;name=\""+key+"\""+CRLF);
- paramsText.append("Content-type: text/plain"+CRLF+CRLF);
- paramsText.append(params.get(key));
- paramsText.append(CRLF);
- }
- paraTextByte = paramsText.toString().getBytes("UTF-8");
- }else{
- //
- paraTextByte = null;
- }
- }catch (Exception e){
- e.printStackTrace();
- paraTextByte = null;
- }
- //写入参数部分信息
- try{
- //先制作请求头
- URL url = new URL(urlString);
- HttpURLConnection con = (HttpURLConnection) url.openConnection();
- con.setDoInput(true);
- con.setDoOutput(true);
- con.setRequestMethod("POST");
- con.setRequestProperty("connection", "Keep-Alive");
- con.setRequestProperty("Charset", "UTF-8");
- con.setRequestProperty("Content-type", "multipart/form-data;boundary=" + BOUNDARYSTR);
- // 获得输出流
- out = new DataOutputStream(con.getOutputStream());
- // 写入普通参数部分
- if(paraTextByte != null && paraTextByte.length > 0){
- out.write(paraTextByte);
- }
- //写入文件信息
- Map<String, Object> fileInfoMap;
- for(int i = 0; i < mutiFileList.size(); i++){
- fileInfoMap = mutiFileList.get(i);
- //当前文件map存有文件名称(uuid型)和文件的中文名称,以及文件的字节数据
- // 如果文件过大,不建议使用该方式,建议传递文件路径再读取写入。
- String fileName = (String)fileInfoMap.get("fileName");
- String suffix = fileName.substring(fileName.indexOf("."));
- String chFileName = fileInfoMap.get("chName")+suffix;
- StringBuilder sb = new StringBuilder();
- sb.append(BOUNDARY);
- sb.append("Content-Disposition: form-data;name=\""+chFileName+"\";filename=\""
- + chFileName + "\""+CRLF);
- //sb.append("Content-Type:application/octet-stream"+CRLF+CRLF);
- //文件均是jpg图片类型
- sb.append("Content-Type:image/jpg"+CRLF+CRLF);
- out.write(sb.toString().getBytes());
- //写入输出流
- byte[] fileData = (byte[])fileInfoMap.get("data");
- /**
- * 如果map里存储的是文件路径,那么可以使用FileInputStream读取文件信息,并写入到OutputStream中。
- * 具体就不写了。
- */
- out.write(fileData);
- out.write(CRLF.getBytes("UTF-8"));
- }
- byte[] foot = ("--" + BOUNDARYSTR + "--"+CRLF).getBytes("UTF-8");// 定义最后数据分隔线
- out.write(foot);
- out.flush();
- is = con.getInputStream();
- repString = ioTool.getStringFromInputStream(is);
- }catch (Exception e){
- e.printStackTrace();
- logger.error("往业务系统写入文件失败:"+e.getMessage());
- }
- return repString;
- }
下面是数据的解析。
以SpringMVC配置的HTTP接口为例。但具体的解析写法和Spring没什么关系。
在写这篇博客之前,我尝试使用了ServletFileUpload类和request.getParts()两种方法来解析。前一种的好处是有很多便利的Servlet API可以使用。但是,HTTP请求体内容只能解析一次。这意味着,在进行解析前我们不能使用以下方法操作request。https://stackoverflow.com/questions/13881272/servletfileuploadparserequestrequest-returns-an-empty-list
- request.getParameter();
- request.getParameterMap();
- request.getParameterNames();
- request.getParameterValues();
- request.getReader();
- request.getInputStream();
感觉不是很灵活。在servlet3.0后,request增加了getPart()和getParts()方法,利用这些方法处理文件数据表单可能要好一些。
- @RequestMapping(value = "uploadFile",method = RequestMethod.POST)
- public void handleMutiPartForm(HttpServletRequest request, HttpServletResponse response){
- //可以使用request.getParameter()获取普通参数数据
- String param1 = request.getParameter("param1");
- String param2 = request.getParameter("param2");
- //获取parts
- Collection<Part> parts;
- try{
- parts = request.getParts();
- }catch (IOException ioe){
- parts = null;
- }catch (ServletException se){
- parts = null;
- }
- if(parts != null){
- //遍历parts
- //因为所有的参数信息都会在Collection<Part>中体现
- //这里只需要获取文件部分
- String saveFilePath = "D://FileUpload/";
- byte[] bytes = new byte[512];
- for(Part part:parts){
- //Content-type: image/* 图片类型数据
- if(part.getContentType().startsWith("image")){
- String fileName = part.getSubmittedFileName();
- String paramName = part.getName();
- try{
- InputStream ins = part.getInputStream();
- String filePath = saveFilePath+fileName;
- File file = new File(filePath);
- if(!file.exists()){
- file.createNewFile();
- }
- FileOutputStream fos = new FileOutputStream(file);
- while(ins.read(bytes, 0, 512) != -1){
- fos.write(bytes);
- }
- ins.close();
- fos.close();
- }catch (IOException ioe){
- }
- }
- }
- }
- try{
- response.getWriter().write("返回的信息");
- }catch (IOException ioe){
- }
- }
RFC相关介绍:
http://www.faqs.org/rfcs/rfc2388.html
stackoverflow相关问题:
https://stackoverflow.com/questions/2793150/using-java-net-urlconnection-to-fire-and-handle-http-requests/
Java使用HTTP编程模拟多参数多文件表单信息的请求与处理的更多相关文章
- SpringMVC 完美解决PUT请求参数绑定问题(普通表单和文件表单)
一 解决方案 修改web.xml配置文件 将下面配置拷贝进去(在原有的web-app节点里面配置 其它配置不变) <!-- 处理PUT提交参数(只对基础表单生效) --> <filt ...
- java:JavaScript2:(setTimeout定时器,history.go()前进/后退,navigator.userAgent判断浏览器,location.href,五种方法获取标签属性,setAttribute,innerHTML,三种方法获取form表单信息,JS表单验证,DOM对象,form表单操作)
1.open,setTimeout,setInterval,clearInterval,clearTimeout <!DOCTYPE> <html> <head> ...
- [js开源组件开发]query组件,获取url参数和form表单json格式
query组件,获取url参数和form表单json格式 距离上次的组件[js开源组件开发]ajax分页组件一转眼过去了近二十天,或许我一周一组件的承诺有了质疑声,但其实我一直在做,只是没人看到……, ...
- 我教女朋友学编程Html系列(6)—Html常用表单控件
做过网页的人都知道,html表单控件十分重要.基本上我们注册会员.登录用户,都需要填写用户名.密码,那些框框都是表单控件. 本来今天就想写一些常用的html表单控件,于是开始搜资料,找到了一个网页,作 ...
- Python爬虫笔记【一】模拟用户访问之提交表单登入—第二次(7)
在第一次登入时遇到这个问题,页面验证码与下载下来需要识别的验证码不同的问题,从网上查寻说是叫验证码同步问题.发现是用cookie解决的,那次cookie介绍到通过cookie就可以实现时间戳同步问题, ...
- Java Web开发总结(三) —— request接收表单提交中文参数乱码问题
1.以POST方式提交表单中文参数的乱码问题 <%@ page language="java" import="java.util.*" pageEnco ...
- Javascript和Java获取各种form表单信息的简单实例
大家都知道我们在提交form的时候用了多种input表单.可是不是每一种input表单都是很简单的用Document.getElementById的方式就可以获取到的.有一些组合的form类似于che ...
- Java连接Jira,创建、修改、删除工单信息
还不了解Jira是什么的同学可以看一下这篇文章:https://www.cnblogs.com/wgblog-code/p/11750767.html 本篇文章主要介绍如何使用Java操作Jira,包 ...
- Java——Java连接Jira,创建、修改、删除工单信息
还不了解Jira是什么的同学可以看一下这篇文章:https://www.cnblogs.com/wgblog-code/p/11750767.html 本篇文章主要介绍如何使用Java操作Jira,包 ...
随机推荐
- vs2015 活动解决方案或项目由选择的源代码管理插件以外的插件管理
1.vs2015切换源代码管理插件,svn无法切换到git,点击是将关闭项目 解决方案: 找到项目中.sln 文件,使用编辑器打开,将Svn-Managed = true 设置为false
- vs中插件影响代码自动创建后台事件问题
CSS Tools 启用之后会影响代码自动创建后台事件,禁用之后解决.禁用之后鼠标悬浮不能看图片,颜色也不能展示
- Android sdk manager 下载速度慢的问题
不多说了,直接附上方法: 首先打开Ecplise 中Android sdk manager,打开后, 在此窗口的上方打开偏好设置选项,然后在里面设置HTTP Proxy server和HTTP Pro ...
- Eclipse软件使用说明
http://www.ziqiangxuetang.com/eclipse/eclipse-explore-menus.html
- Centeros7下安装Mysql 2018最新版,非常简单
下载Mysql的rpm安装包 shell> wget http://dev.mysql.com/get/ mysql-community-release-el7-5.noarch.rpm安装sh ...
- SQLite的Integer类型
SQLite 中的 INTEGER:带符号的整型,具体取决有存入数字的范围大小,根据大小可以使用1,2,3,4,6,8字节来存储. 在SQLite中,存储分类和数据类型也有一定的差别,如INTEGER ...
- 高级功能:很有用的javascript自定义事件
之前写了篇文章<原生javascript实现类似jquery on方法的行为监听>比较浅显,能够简单的使用场景. 这里的自定义事件指的是区别javascript默认的与DOM交互的事件,比 ...
- python之定义函数
1.定义函数和参数检查 通过def语句定义一个函数,自己定义的函数,当参数个数不对时,python解释器可以抛出TypeError,但是当参数类型不对时,无法抛出TypeError,为此可以通过isi ...
- OneNet平台初探成功
1.经过半个月的研究,终于成功对接OneNet平台,实现远程控制LED灯的亮灭 2.在调试的过程中也遇到了很多问题,做一下总结 3.硬件:STM32F103C8T6的最小系统板,ESP8266-WiF ...
- Android批量打包提速 - 1分钟900个市场不是梦
版权声明: 欢迎转载,但请保留文章原始出处 作者:GavinCT 出处:http://www.cnblogs.com/ct2011/p/4152323.html 黎明前的黑暗 使用Ant或者Gradl ...