轻松把玩HttpClient之封装HttpClient工具类,添加多文件上传功能
Posted 龙轩
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了轻松把玩HttpClient之封装HttpClient工具类,添加多文件上传功能相关的知识,希望对你有一定的参考价值。
在Git上有人给我提Issue,说怎么上传文件,其实我一开始就想上这个功能,不过这半年比较忙,所以一直耽搁了。这次正好没什么任务了,赶紧完成这个功能。毕竟作为一款工具类,有基本的请求和下载功能,就差上传了,有点说不过去。好了,天不早了,咱干点正事吧。
如果你只想了解怎么用HttpClient来上传文件,可以参考这篇文章:http://blog.csdn.net/fengyuzhengfan/article/details/39941851,里面写的很清楚了。这里我主要介绍工具类中的修改。
首先,上传功能需要用到HttpMime这个,所以首先在pom中添加maven依赖(不会的自行百度)。
其次,修改Utils中的map2List方法。阅读过源码的都知道,我所有的请求参数,都是通过map来封装的,原来都是返回list,上次升级以后,返回的都是HttpEntity对象(看来下次得修改一下方法名了map2HttpEntity),把所有的参数转化为HttpEntity对象。
//装填参数
HttpEntity entity = Utils.map2List(nvps, config.map(), config.inenc());
//设置参数到请求对象中
((HttpEntityEnclosingRequestBase)request).setEntity(entity);
现在上传用到的是MultipartEntityBuilder,所以添加一个特定类型,进行特殊处理。
public static final String ENTITY_MULTIPART="$ENTITY_MULTIPART$";
private static final List<String> SPECIAL_ENTITIY = Arrays.asList(ENTITY_STRING, ENTITY_BYTES, ENTITY_FILE, ENTITY_INPUTSTREAM, ENTITY_SERIALIZABLE, ENTITY_MULTIPART);
/**
* 参数转换,将map中的参数,转到参数列表中
*
* @param nvps 参数列表
* @param map 参数列表(map)
* @throws UnsupportedEncodingException
*/
public static HttpEntity map2List(List<NameValuePair> nvps, Map<String, Object> map, String encoding) throws UnsupportedEncodingException
HttpEntity entity = null;
if(map!=null && map.size()>0)
boolean isSpecial = false;
// 拼接参数
for (Entry<String, Object> entry : map.entrySet())
if(SPECIAL_ENTITIY.contains(entry.getKey()))//判断是否在之中
isSpecial = true;
if(ENTITY_STRING.equals(entry.getKey()))//string
entity = new StringEntity(String.valueOf(entry.getValue()), encoding);
break;
else if(ENTITY_BYTES.equals(entry.getKey()))//file
entity = new ByteArrayEntity((byte[])entry.getValue());
break;
else if(ENTITY_FILE.equals(entry.getKey()))//file
//....
break;
else if(ENTITY_INPUTSTREAM.equals(entry.getKey()))//inputstream
// entity = new InputStreamEntity();
break;
else if(ENTITY_SERIALIZABLE.equals(entry.getKey()))//serializeable
// entity = new SerializableEntity()
break;
else if(ENTITY_MULTIPART.equals(entry.getKey()))//MultipartEntityBuilder
File[] files = null;
if(File.class.isAssignableFrom(entry.getValue().getClass().getComponentType()))
files=(File[])entry.getValue();
else if(entry.getValue().getClass().getComponentType()==String.class)
String[] names = (String[]) entry.getValue();
files = new File[names.length];
for (int i = 0; i < names.length; i++)
files[i] = new File(names[i]);
MultipartEntityBuilder builder = MultipartEntityBuilder.create();
builder.setCharset(Charset.forName(encoding));// 设置请求的编码格式
builder.setMode(HttpMultipartMode.BROWSER_COMPATIBLE);// 设置浏览器兼容模式
int count = 0;
for (File file : files)
builder.addBinaryBody(String.valueOf(map.get(ENTITY_MULTIPART+".name")) + count++,file);
boolean forceRemoveContentTypeCharset = (Boolean)map.get(ENTITY_MULTIPART+".rmCharset");
Map<String, Object> m = new HashMap<String, Object>();
m.putAll(map);
m.remove(ENTITY_MULTIPART);
m.remove(ENTITY_MULTIPART+".name");
m.remove(ENTITY_MULTIPART+".rmCharset");
Iterator<Entry<String, Object>> iterator = m.entrySet().iterator();
// 发送的数据
while (iterator.hasNext())
Entry<String, Object> e = iterator.next();
builder.addTextBody(e.getKey(), String.valueOf(e.getValue()), ContentType.create("text/plain", encoding));
entity = builder.build();// 生成 HTTP POST 实体
//强制去除contentType中的编码设置,否则,在某些情况下会导致上传失败
if(forceRemoveContentTypeCharset)
removeContentTypeChraset(encoding, entity);
break;
else
nvps.add(new BasicNameValuePair(entry.getKey(), String.valueOf(entry.getValue())));
else
nvps.add(new BasicNameValuePair(entry.getKey(), String.valueOf(entry.getValue())));
if(!isSpecial)
entity = new UrlEncodedFormEntity(nvps, encoding);
return entity;
这里面有一个方法removeContentTypeChraset,主要是为了解决,如果调用了setCharset,中文文件名不会乱码,但是在ContentType文件头中会多一个charset=xxx,而导致上传失败,解决办法就是强制去掉这个信息。而这个HttpEntity实际对象是MultipartFormEntity对象。这个类未声明public,所以只能包内访问。而且该类的contentType属性是private final类型。就算可以通过对象拿到这个属性,也无法修改。所以我只能通过反射来修改。
private static void removeContentTypeChraset(String encoding, HttpEntity entity)
try
Class<?> clazz = entity.getClass();
Field field = clazz.getDeclaredField("contentType");
field.setAccessible(true); //将字段的访问权限设为true:即去除private修饰符的影响
if(Modifier.isFinal(field.getModifiers()))
Field modifiersField = Field.class.getDeclaredField("modifiers"); //去除final修饰符的影响,将字段设为可修改的
modifiersField.setAccessible(true);
modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
BasicHeader o = (BasicHeader) field.get(entity);
field.set(entity, new BasicHeader(HTTP.CONTENT_TYPE, o.getValue().replace("; charset="+encoding,"")));
catch (NoSuchFieldException e)
Utils.exception(e);
catch (SecurityException e)
Utils.exception(e);
catch (IllegalArgumentException e)
Utils.exception(e);
catch (IllegalAccessException e)
Utils.exception(e);
剩下的就是HttpConfig的修改了,所有参数都是通过这个对象封装的。对于上传,我没想再建立一个file数组对象来传递参数,因为在上传时,可能还会有其他参数,都得需要通过map来传递,所以上传也直接借助map来完成。
/**
* 上传文件时用到
*/
public HttpConfig files(String[] filePaths)
return files(filePaths, "file");
/**
* 上传文件时用到
* @param filePaths 待上传文件所在路径
*/
public HttpConfig files(String[] filePaths, String inputName)
return files(filePaths, inputName, false);
/**
* 上传文件时用到
* @param filePaths 待上传文件所在路径
* @param inputName 即file input 标签的name值,默认为file
* @param forceRemoveContentTypeChraset
* @return
*/
public HttpConfig files(String[] filePaths, String inputName, boolean forceRemoveContentTypeChraset)
synchronized (getClass())
if(this.map==null)
this.map= new HashMap<String, Object>();
map.put(Utils.ENTITY_MULTIPART, filePaths);
map.put(Utils.ENTITY_MULTIPART+".name", inputName);
map.put(Utils.ENTITY_MULTIPART+".rmCharset", forceRemoveContentTypeChraset);
return this;
map方法也做了相应的修改:
/**
* 传递参数
*/
public HttpConfig map(Map<String, Object> map)
synchronized (getClass())
if(this.map==null || map==null)
this.map = map;
else
this.map.putAll(map);;
return this;
最后,来一个测试类,
import java.util.HashMap;
import java.util.Map;
import com.tgb.ccl.http.common.HttpConfig;
import com.tgb.ccl.http.common.HttpCookies;
import com.tgb.ccl.http.common.Utils;
import com.tgb.ccl.http.exception.HttpProcessException;
import com.tgb.ccl.http.httpclient.HttpClientUtil;
/**
* 上传功能测试
*
* @author arron
* @date 2016年11月2日 下午1:17:17
* @version 1.0
*/
public class TestUpload
public static void main(String[] args) throws HttpProcessException
//登录后,为上传做准备
HttpConfig config = prepareUpload();
String url= "http://test.free.800m.net:8080/up.php?action=upsave";//上传地址
String[] filePaths = "D:\\\\中国.txt","D:\\\\111.txt","C:\\\\Users\\\\160049\\\\Desktop\\\\中国.png";//待上传的文件路径
Map<String, Object> map = new HashMap<String, Object>();
map.put("path", "./tomcat/vhost/test/ROOT/");//指定其他参数
config.url(url) //设定上传地址
.encoding("GB2312") //设定编码,否则可能会引起中文乱码或导致上传失败
.files(filePaths,"myfile",true)//.files(filePaths),如果服务器端有验证input 的name值,则请传递第二个参数,如果上传失败,则尝试第三个参数设置为true
.map(map);//其他需要提交的参数
Utils.debug();//开启打印日志,调用 Utils.debug(false);关闭打印日志
String r = HttpClientUtil.upload(config);//上传
System.out.println(r);
/**
* 登录,并上传文件
*
* @return
* @throws HttpProcessException
*/
private static HttpConfig prepareUpload() throws HttpProcessException
String url ="http://test.free.800m.net:8080/";
String loginUrl = url+"login.php";
String indexUrl = url+"index.php";
HttpCookies cookies = HttpCookies.custom();
//启用cookie,用于登录后的操作
HttpConfig config = HttpConfig.custom().context(cookies.getContext());
Map<String, Object> map = new HashMap<String, Object>();
map.put("user_name", "test");
map.put("user_pass", "800m.net");
map.put("action", "login");
String loginResult = HttpClientUtil.post(config.url(loginUrl).map(map));
System.out.println("是否登录成功:"+loginResult.contains("成功"));
//打开主页
HttpClientUtil.get(config.map(null).url(indexUrl));
return config;
最新的完整代码请到GitHub上进行下载:https://github.com/Arronlong/httpclientUtil 。
httpclientUtil (QQ交流群:548452686 )
以上是关于轻松把玩HttpClient之封装HttpClient工具类,添加多文件上传功能的主要内容,如果未能解决你的问题,请参考以下文章
轻松把玩HttpClient之封装HttpClient工具类,封装输入参数,简化工具类
轻松把玩HttpClient之封装HttpClient工具类,插件式配置Header
轻松把玩HttpClient之封装HttpClient工具类,携带Cookie的请求
轻松把玩HttpClient之封装HttpClient工具类,单线程调用及多线程批量调用测试