需要注意的是,HttpClient并不是基本的java库(android下就有).
http://hc.apache.org/downloads.cgi
我下载的是
http://mirrors.tuna.tsinghua.edu.cn/apache/httpcomponents/httpclient/binary/httpcomponents-client-4.5.3-bin.zip
下载后,会有很多jar,大家可以酌情添加到工程,我就只添加了commons-codec-1.9.jar,commons-logging-1.2.jar,httpmime-4.5.3.jar,httpcore-4.4.6.jar,httpclient-4.5.3.jar
HttpClient网上例子比较多,我直接附上调试好了的代码:
/***************************************************************
* http,https访问的站点
***************************************************************/
package wxlib.wxsdk.lib.utils;
import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.util.Map;
import org.apache.http.HttpEntity;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.entity.mime.HttpMultipartMode;
import org.apache.http.entity.mime.MultipartEntityBuilder;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.json.JSONObject;
public class HttpRequest {
/**
* @tutorial 支持http,https
* @param string $url 要访问的站点
* @param array,object $data get只能是key/value的参数集合
* @param int $timeout 超时时间,单位秒
* @return 站点数据或失败信息
*/
static public Object get(String url, Object data/* = null*/, int timeout/* = 30*/) {
//将data转化为url参数:par1=val1&par2=va2&par3=val3
String param = "";
if(data != null && !data.equals("") && data instanceof Map) {
@SuppressWarnings("unchecked")
Map map = (Map)data;
for(Map.Entry entry : map.entrySet()) {
String key = entry.getKey() != null ? entry.getKey().toString() : "";
String val = entry.getValue() != null ? entry.getValue().toString() : "";
if(key.equals(""))
continue;
param += key + "=" + val + "&";
}
}
if(data != null && !data.equals("") && data instanceof JSONObject) {
JSONObject map = (JSONObject)data;
for(String key : map.keySet()) {
if(key == null || key.equals(""))
continue;
String val = map.optString(key);
if(val == null)
continue;
param += key + "=" + val + "&";
}
}
if(param.endsWith("&"))
param = param.substring(0, param.length() - 1);
if(!param.equals(""))
url += ((url.indexOf("?") != -1) ? "&" : "?") + param;
String result = "";
//创建http请求(post方式)
HttpGet httpGet = new HttpGet(url);
httpGet.addHeader("Content-Type", "text/html; charset=UTF-8");
//得指明使用UTF-8编码,否则到API服务器XML的中文不能被成功识别
//StringEntity stringEntity = new StringEntity(data.toString(), "UTF-8");
//httpGet.setEntity(stringEntity);
//设置请求器配置:如超时限制等
RequestConfig config = RequestConfig.custom().setSocketTimeout(timeout * 1000).setConnectTimeout(timeout * 1000).build();
httpGet.setConfig(config);
//提交post访问请求并获得返回值
//System.out.println("request: " + httpPost.getRequestLine());
try {
CloseableHttpClient httpClient = HttpClients.createDefault();
CloseableHttpResponse response = httpClient.execute(httpGet); //throw
HttpEntity httpEntity = response.getEntity();
//System.out.println("response status: " + response.getStatusLine());
result = EntityUtils.toString(httpEntity, "UTF-8"); //throw
//System.out.println("result: " + result);
try {
response.close(); //throw
}
catch(Exception e) {
}
httpClient.close(); //throw
}
catch(Exception e) {
}
//httpGet.abort();
return result;
}
/**
* @tutorial 支持http,https
* @param string $url 要访问的站点
* @param mixed $data post可以是任意类型的数据,例如string,array,object,json等
* @param int $timeout 超时时间,单位秒
* @return 站点数据或失败信息
*/
static public Object post(String url, Object data, int timeout/* = 30*/) {
String result = "";
//创建http请求(post方式)
HttpPost httpPost = new HttpPost(url);
httpPost.addHeader("Content-Type", "text/html; charset=UTF-8");
//得指明使用UTF-8编码,否则到API服务器XML的中文不能被成功识别
if(data != null && data instanceof String) { //支持上传字符串,如text,xml,json
StringEntity stringEntity = new StringEntity(data.toString(), "UTF-8");
httpPost.setEntity(stringEntity);
}
else if(data != null && data instanceof byte[]) { //支持上传原始数据,如文件
ByteArrayEntity byteArrayEntity = new ByteArrayEntity((byte[])data);
httpPost.setEntity(byteArrayEntity);
}
//设置请求器配置:如超时限制等
RequestConfig config = RequestConfig.custom().setSocketTimeout(timeout * 1000).setConnectTimeout(timeout * 1000).build();
httpPost.setConfig(config);
//提交post访问请求并获得返回值
//System.out.println("request: " + httpPost.getRequestLine());
try {
CloseableHttpClient httpClient = HttpClients.createDefault();
CloseableHttpResponse response = httpClient.execute(httpPost); //throw
HttpEntity httpEntity = response.getEntity();
//System.out.println("response status: " + response.getStatusLine());
result = EntityUtils.toString(httpEntity, "UTF-8"); //throw
//System.out.println("result: " + result);
try {
response.close(); //throw
}
catch(Exception e) {
}
httpClient.close(); //throw
}
catch(Exception e) {
}
//httpPost.abort();
return result;
}
/**
* @tutorial 支持http,https
* @param string $url 要访问的站点
* @param string $field 文件域.微信公众号一般为"media"
* @param string $file 要上传的文件名.可以是相对路径或绝对路径
* @param array $extra 额外要上传的字段.类型为key/value数组.数组元素中key为string类型,value可以是mixed类型.
* 例如:在上传视频素材时需要POST另一个表单,id为description,包含素材的描述信息,内容格式为JSON字符串.
* array('merchant' => '微微花语商城', 'description' =>'{"title": "猜灯谜抢红包活动", "introduction": "感谢您参加猜灯谜活动,祝您节日快乐"}'
* 调用方式为:
* ****$ curl -F [email protected] -F merchant='微微花语商城' -F description='{"title": "猜灯谜抢红包活动", "introduction": "感谢您参加猜灯谜活动,祝您节日快乐"}' "http://localhost/weixin/server.php"
* 对应表单为:
*
* @param int $timeout 超时时间,单位秒
* @return 站点数据或失败信息
* @see
* ****以下2者的调用方式,效果是一样的:
* ****curl_http_upload("http://localhost/weixin/server.php", "media", "a.jpg", null, 30);
* ****$ curl -F "[email protected]" "http://localhost/weixin/server.php"
* ****后台得到的$_FILES应该为:
* ****array("media" => array("name" => "a.jpg", "type" => "image/jpeg", "tmp_name" => "C:\wamp\tmp\php9CC4.tmp", "error" => 0, "size" => 46192));
* @category
* ****注:和php版比较,这里稍微美中不足的是,后台得到的mimetype不是真正的类型:
* ****array("media" => array("name" => "a.jpg", "type" => "application/octet-stream", "tmp_name" => "C:\wamp\tmp\php9CC4.tmp", "error" => 0, "size" => 46192));
*/
static public Object upload(String url, String field, String file, Object extra/* = null*/, int timeout/* = 30*/) {
String result = "";
//创建http请求(post方式)
HttpPost httpPost = new HttpPost(url);
/***********************************************************************
//httpPost.setHeader("Content-Type", "multipart/form-data; charset=UTF-8");
//builder.setCharset(Charset.forName(HTTP.UTF_8)); //设置请求的编码格式
//builder.setMode(HttpMultipartMode.BROWSER_COMPATIBLE);//设置浏览器兼容模式
上面高亮的两个地方就是在微信素材上传时的两个大坑.
1.当遇到乱码时,第一错觉就是设置请求的编码格式,恰恰在微信上传时,如果设置这个编码,就会导致多媒体文件丢失,上传不成功.(具体原因不明)
2.设置浏览器兼容模式,这个好多文章说,设置成BOWSER_COMPATIBLE就不会乱码了,问题就出现在这里,设置模式时,应该设置成HttpMultipartMode.RFC6532,这样才是真正的不会出现乱码.
3.不要设置编码,模式设置要注意.
详见<>: http://blog.csdn.net/kookob/article/details/46914777
***********************************************************************/
//httpPost.addHeader("Content-Type", "multipart/form-data; charset=UTF-8"); //"text/html; charset=UTF-8" //"multipart/form-data; charset=UTF-8" //ContentType.MULTIPART_FORM_DATA.toString()
//得指明使用UTF-8编码,否则到API服务器XML的中文不能被成功识别
//StringEntity stringEntity = new StringEntity(data.toString(), "UTF-8");
//httpPost.setEntity(stringEntity);
//添加表单项
MultipartEntityBuilder builder = MultipartEntityBuilder.create().setMode(HttpMultipartMode.RFC6532); //BROWSER_COMPATIBLE自定义charset,RFC6532=utf-8,STRICT=iso-8859-1
//$filename = realpath($file); //>>??这两个功能后期要加上
//$mimetype = mime_content_type($filename); //如果不手动设置$mimetype,curl会默认为"application/octet-stream"
builder.addBinaryBody(field, new File(file));
//builder.addPart(field, new FileBody(new File(file)));
if(extra != null && !extra.equals("") && extra instanceof Map) {
@SuppressWarnings("unchecked")
Map map = (Map)extra;
for(Map.Entry entry : map.entrySet()) {
String key = entry.getKey() != null ? entry.getKey().toString() : "";
String val = entry.getValue() != null ? entry.getValue().toString() : "";
if(key.equals(""))
continue;
//param += key + "=" + val + "&";
builder.addTextBody(key, val, ContentType.create("text/plain", "UTF-8"));
}
}
if(extra != null && !extra.equals("") && extra instanceof JSONObject) {
JSONObject map = (JSONObject)extra;
for(String key : map.keySet()) {
if(key == null || key.equals(""))
continue;
String val = map.optString(key);
if(val == null)
continue;
//param += key + "=" + val + "&";
builder.addTextBody(key, val, ContentType.create("text/plain", "UTF-8"));
}
}
httpPost.setEntity(builder.build());
//设置请求器配置:如超时限制等
RequestConfig config = RequestConfig.custom().setSocketTimeout(timeout * 1000).setConnectTimeout(timeout * 1000).build();
httpPost.setConfig(config);
//提交post访问请求并获得返回值
//System.out.println("request: " + httpPost.getRequestLine());
try {
CloseableHttpClient httpClient = HttpClients.createDefault();
CloseableHttpResponse response = httpClient.execute(httpPost); //throw
HttpEntity httpEntity = response.getEntity();
//System.out.println("response status: " + response.getStatusLine());
result = EntityUtils.toString(httpEntity, "UTF-8"); //throw
//System.out.println("result: " + result);
try {
response.close(); //throw
}
catch(Exception e) {
}
httpClient.close(); //throw
}
catch(Exception e) {
e.printStackTrace();
}
//httpPost.abort();
return result;
}
/**
* @tutorial 支持http,https
* @param string $url 要访问的站点
* @param string $file 保存到本地的文件名,如果需要的话
* @param int $timeout 超时时间,单位秒
* @return 站点数据或失败信息.对于下载来说,成功时的站点数据即为文件的二进制内容.
* 对于java版来说,是原始的数据byte[]类型.
* 可以直接输出到文件:fs.write(result);
* 如果解析成字符串的话,需指定服务器传送过来的编码:new String(result, "ISO-8859-1");new String(result, "UTF-8");
* @see
* ****成功返回时:
* ****1).如果提供了$file参数,则文件会直接保存到本地;
* ****2).同时,将文件的二进制内容作为返回值,可灵活用于缩放,旋转,渲染,保存等.
*/
static public Object download(String url, String file, int timeout/* = 30*/) {
byte[] result = null;
//创建http请求(post方式)
HttpGet httpGet = new HttpGet(url);
httpGet.addHeader("Content-Type", "text/html; charset=UTF-8");
//得指明使用UTF-8编码,否则到API服务器XML的中文不能被成功识别
//StringEntity stringEntity = new StringEntity(data.toString(), "UTF-8");
//httpGet.setEntity(stringEntity);
//设置请求器配置:如超时限制等
RequestConfig config = RequestConfig.custom().setSocketTimeout(timeout * 1000).setConnectTimeout(timeout * 1000).build();
httpGet.setConfig(config);
//提交post访问请求并获得返回值
//System.out.println("request: " + httpPost.getRequestLine());
try {
CloseableHttpClient httpClient = HttpClients.createDefault();
CloseableHttpResponse response = httpClient.execute(httpGet); //throw
HttpEntity httpEntity = response.getEntity();
//System.out.println("response status: " + response.getStatusLine());
//result = EntityUtils.toString(httpEntity, "UTF-8"); //throw
result = EntityUtils.toByteArray(httpEntity); //throw
//System.out.println("result: " + result);
try {
response.close(); //throw
}
catch(Exception e) {
}
httpClient.close(); //throw
}
catch(Exception e) {
}
//httpGet.abort();
if(result != null && result.length > 0 && file != null && !file.equals("")) {
try {
OutputStream fs = new FileOutputStream(file);
fs.write(result);
fs.close();
}
catch(Exception e) {
}
}
return result;
}
}
着重说明的是,微信公众号上传素材时,不需要设置Content-Type头,这一点很重要,我就调试了很久,才在一个大神文章里找到问题关键.详见<