本文目的是提供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编程模拟多参数多文件表单信息的请求与处理的更多相关文章

  1. SpringMVC 完美解决PUT请求参数绑定问题(普通表单和文件表单)

    一 解决方案 修改web.xml配置文件 将下面配置拷贝进去(在原有的web-app节点里面配置 其它配置不变) <!-- 处理PUT提交参数(只对基础表单生效) --> <filt ...

  2. java:JavaScript2:(setTimeout定时器,history.go()前进/后退,navigator.userAgent判断浏览器,location.href,五种方法获取标签属性,setAttribute,innerHTML,三种方法获取form表单信息,JS表单验证,DOM对象,form表单操作)

    1.open,setTimeout,setInterval,clearInterval,clearTimeout <!DOCTYPE> <html> <head> ...

  3. [js开源组件开发]query组件,获取url参数和form表单json格式

    query组件,获取url参数和form表单json格式 距离上次的组件[js开源组件开发]ajax分页组件一转眼过去了近二十天,或许我一周一组件的承诺有了质疑声,但其实我一直在做,只是没人看到……, ...

  4. 我教女朋友学编程Html系列(6)—Html常用表单控件

    做过网页的人都知道,html表单控件十分重要.基本上我们注册会员.登录用户,都需要填写用户名.密码,那些框框都是表单控件. 本来今天就想写一些常用的html表单控件,于是开始搜资料,找到了一个网页,作 ...

  5. Python爬虫笔记【一】模拟用户访问之提交表单登入—第二次(7)

    在第一次登入时遇到这个问题,页面验证码与下载下来需要识别的验证码不同的问题,从网上查寻说是叫验证码同步问题.发现是用cookie解决的,那次cookie介绍到通过cookie就可以实现时间戳同步问题, ...

  6. Java Web开发总结(三) —— request接收表单提交中文参数乱码问题

    1.以POST方式提交表单中文参数的乱码问题 <%@ page language="java" import="java.util.*" pageEnco ...

  7. Javascript和Java获取各种form表单信息的简单实例

    大家都知道我们在提交form的时候用了多种input表单.可是不是每一种input表单都是很简单的用Document.getElementById的方式就可以获取到的.有一些组合的form类似于che ...

  8. Java连接Jira,创建、修改、删除工单信息

    还不了解Jira是什么的同学可以看一下这篇文章:https://www.cnblogs.com/wgblog-code/p/11750767.html 本篇文章主要介绍如何使用Java操作Jira,包 ...

  9. Java——Java连接Jira,创建、修改、删除工单信息

    还不了解Jira是什么的同学可以看一下这篇文章:https://www.cnblogs.com/wgblog-code/p/11750767.html 本篇文章主要介绍如何使用Java操作Jira,包 ...

随机推荐

  1. webpack 项目实践

    1,必要的环境 node 环境 下载地址(http://nodejs.cn/) npm 貌似 下载地址(https://www.npmjs.com/)不过,貌似 Node 安装可自带 npm. 2, ...

  2. MVC 之var与dynamic

    如果你用MVC写过程序,那么你应该知道ViewBag这个用于前后台的数据传递工具,那么你是否对ViewBag的用法感到过疑惑呢? ViewBag.Mode1l=new object(); ViewBa ...

  3. slf4j和log4j源代码解析以及详解

    备注:下面所有代码以log4j为例 包结构 slf4j-api.jar对外提供api slf4j.log4j12.jar提供适配器 log4j.jar是log4j的jar slf4j初始化 获取ILo ...

  4. vue setTimeout--延迟操作

    有时候我们在查询后要做某些事情,例如我查询的时候要根据某个值再去查询某些东西并和这些值一起显示的时候,我们可以对渲染数据的操作进行延迟,因为代码执行的速度是很快的而访问数据的操作相对于渲染的速度慢得多 ...

  5. 简单说说SpringMVC

    距离上一次开发SpringMVC项目已经过去了大半年,有些细节已经开始遗忘,今天复习一下 先从标签说起: 和struts有各种配置文件不同,spring用标签开发. 1.@Controller在Spr ...

  6. Docker 更改镜像存储位置

    在使用 Docker 的过程中,如果我们一切都以默认的设置进行操作的话,在使用一段时间之后你应该会发现系统的根目录空间越来越少,直到有一天发现空间都被占满了,最后发现原来是被 Docker 占用了.所 ...

  7. SQL Server删除表及删除表中数据的方法

    删除表的T-SQL语句为: drop table <表名> drop是丢弃的意思,drop table表示将一个表彻底删除掉. 删除表数据有两种方法:delete和truncate. de ...

  8. axios上传图片(及vue上传图片到七牛))

    浏览器上传图片到服务端,我用过两种方法: 1.本地图片转换成base64,然后通过普通的post请求发送到服务端. 操作简单,适合小图,以及如果想兼容低版本的ie没办法用此方法 2.通过form表单提 ...

  9. COGS2608 [河南省队2016]无根树

    传送门 这题大概就是传说中的动态树形DP了吧,学习了一波…… 首先,对于没有修改的情况,不难想到树形DP,定义$f_i$表示强制必须选$i$且只能再选$i$的子树中的点的最优解,易得转移方程$f_i= ...

  10. 超级简单的jquery轮播图demo

    <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title> ...