diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/jodd/ApacheHttpClientBuilder.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/jodd/ApacheHttpClientBuilder.java
new file mode 100644
index 00000000..ce021a04
--- /dev/null
+++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/jodd/ApacheHttpClientBuilder.java
@@ -0,0 +1,53 @@
+package me.chanjar.weixin.common.util.http.jodd;
+
+import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
+import org.apache.http.impl.client.CloseableHttpClient;
+
+/**
+ * httpclient build interface
+ * @author kakotor
+ */
+public interface ApacheHttpClientBuilder {
+
+ /**
+ * 构建httpclient实例
+ *
+ * @return new instance of CloseableHttpClient
+ */
+ CloseableHttpClient build();
+
+ /**
+ * 代理服务器地址
+ *
+ * @param httpProxyHost
+ */
+ ApacheHttpClientBuilder httpProxyHost(String httpProxyHost);
+
+ /**
+ * 代理服务器端口
+ *
+ * @param httpProxyPort
+ */
+ ApacheHttpClientBuilder httpProxyPort(int httpProxyPort);
+
+ /**
+ * 代理服务器用户名
+ *
+ * @param httpProxyUsername
+ */
+ ApacheHttpClientBuilder httpProxyUsername(String httpProxyUsername);
+
+ /**
+ * 代理服务器密码
+ *
+ * @param httpProxyPassword
+ */
+ ApacheHttpClientBuilder httpProxyPassword(String httpProxyPassword);
+
+ /**
+ * ssl连接socket工厂
+ *
+ * @param sslConnectionSocketFactory
+ */
+ ApacheHttpClientBuilder sslConnectionSocketFactory(SSLConnectionSocketFactory sslConnectionSocketFactory);
+}
diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/jodd/DefaultApacheHttpClientBuilder.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/jodd/DefaultApacheHttpClientBuilder.java
new file mode 100644
index 00000000..3f667abe
--- /dev/null
+++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/jodd/DefaultApacheHttpClientBuilder.java
@@ -0,0 +1,292 @@
+package me.chanjar.weixin.common.util.http.jodd;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.http.annotation.NotThreadSafe;
+import org.apache.http.auth.AuthScope;
+import org.apache.http.auth.UsernamePasswordCredentials;
+import org.apache.http.client.CredentialsProvider;
+import org.apache.http.client.HttpRequestRetryHandler;
+import org.apache.http.client.config.RequestConfig;
+import org.apache.http.config.Registry;
+import org.apache.http.config.RegistryBuilder;
+import org.apache.http.config.SocketConfig;
+import org.apache.http.conn.HttpClientConnectionManager;
+import org.apache.http.conn.socket.ConnectionSocketFactory;
+import org.apache.http.conn.socket.PlainConnectionSocketFactory;
+import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
+import org.apache.http.impl.client.BasicCredentialsProvider;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClientBuilder;
+import org.apache.http.impl.client.HttpClients;
+import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
+import org.apache.http.protocol.HttpContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * httpclient 连接管理器
+ *
+ * @author kakotor
+ */
+@NotThreadSafe
+public class DefaultApacheHttpClientBuilder implements ApacheHttpClientBuilder {
+ protected final Logger log = LoggerFactory.getLogger(DefaultApacheHttpClientBuilder.class);
+ private final AtomicBoolean prepared = new AtomicBoolean(false);
+ private int connectionRequestTimeout = 3000;
+ private int connectionTimeout = 5000;
+ private int soTimeout = 5000;
+ private int idleConnTimeout = 60000;
+ private int checkWaitTime = 60000;
+ private int maxConnPerHost = 10;
+ private int maxTotalConn = 50;
+ private String userAgent;
+ private HttpRequestRetryHandler httpRequestRetryHandler = new HttpRequestRetryHandler() {
+ @Override
+ public boolean retryRequest(IOException exception, int executionCount, HttpContext context) {
+ return false;
+ }
+ };
+ private SSLConnectionSocketFactory sslConnectionSocketFactory = SSLConnectionSocketFactory.getSocketFactory();
+ private PlainConnectionSocketFactory plainConnectionSocketFactory = PlainConnectionSocketFactory.getSocketFactory();
+ private String httpProxyHost;
+ private int httpProxyPort;
+ private String httpProxyUsername;
+ private String httpProxyPassword;
+ /**
+ * 闲置连接监控线程
+ */
+ private IdleConnectionMonitorThread idleConnectionMonitorThread;
+ private HttpClientBuilder httpClientBuilder;
+
+ private DefaultApacheHttpClientBuilder() {
+ }
+
+ public static DefaultApacheHttpClientBuilder get() {
+ return new DefaultApacheHttpClientBuilder();
+ }
+
+ @Override
+ public ApacheHttpClientBuilder httpProxyHost(String httpProxyHost) {
+ this.httpProxyHost = httpProxyHost;
+ return this;
+ }
+
+ @Override
+ public ApacheHttpClientBuilder httpProxyPort(int httpProxyPort) {
+ this.httpProxyPort = httpProxyPort;
+ return this;
+ }
+
+ @Override
+ public ApacheHttpClientBuilder httpProxyUsername(String httpProxyUsername) {
+ this.httpProxyUsername = httpProxyUsername;
+ return this;
+ }
+
+ @Override
+ public ApacheHttpClientBuilder httpProxyPassword(String httpProxyPassword) {
+ this.httpProxyPassword = httpProxyPassword;
+ return this;
+ }
+
+ @Override
+ public ApacheHttpClientBuilder sslConnectionSocketFactory(SSLConnectionSocketFactory sslConnectionSocketFactory) {
+ this.sslConnectionSocketFactory = sslConnectionSocketFactory;
+ return this;
+ }
+
+ /**
+ * 获取链接的超时时间设置,默认3000ms
+ *
+ * 设置为零时不超时,一直等待.
+ * 设置为负数是使用系统默认设置(非上述的3000ms的默认值,而是httpclient的默认设置).
+ *
+ *
+ * @param connectionRequestTimeout 获取链接的超时时间设置(单位毫秒),默认3000ms
+ */
+ public void setConnectionRequestTimeout(int connectionRequestTimeout) {
+ this.connectionRequestTimeout = connectionRequestTimeout;
+ }
+
+ /**
+ * 建立链接的超时时间,默认为5000ms.由于是在链接池获取链接,此设置应该并不起什么作用
+ *
+ * 设置为零时不超时,一直等待.
+ * 设置为负数是使用系统默认设置(非上述的5000ms的默认值,而是httpclient的默认设置).
+ *
+ *
+ * @param connectionTimeout 建立链接的超时时间设置(单位毫秒),默认5000ms
+ */
+ public void setConnectionTimeout(int connectionTimeout) {
+ this.connectionTimeout = connectionTimeout;
+ }
+
+ /**
+ * 默认NIO的socket超时设置,默认5000ms.
+ *
+ * @param soTimeout 默认NIO的socket超时设置,默认5000ms.
+ * @see java.net.SocketOptions#SO_TIMEOUT
+ */
+ public void setSoTimeout(int soTimeout) {
+ this.soTimeout = soTimeout;
+ }
+
+ /**
+ * 空闲链接的超时时间,默认60000ms.
+ *
+ * 超时的链接将在下一次空闲链接检查是被销毁
+ *
+ *
+ * @param idleConnTimeout 空闲链接的超时时间,默认60000ms.
+ */
+ public void setIdleConnTimeout(int idleConnTimeout) {
+ this.idleConnTimeout = idleConnTimeout;
+ }
+
+ /**
+ * 检查空间链接的间隔周期,默认60000ms.
+ *
+ * @param checkWaitTime 检查空间链接的间隔周期,默认60000ms.
+ */
+ public void setCheckWaitTime(int checkWaitTime) {
+ this.checkWaitTime = checkWaitTime;
+ }
+
+ /**
+ * 每路的最大链接数,默认10
+ *
+ * @param maxConnPerHost 每路的最大链接数,默认10
+ */
+ public void setMaxConnPerHost(int maxConnPerHost) {
+ this.maxConnPerHost = maxConnPerHost;
+ }
+
+ /**
+ * 最大总连接数,默认50
+ *
+ * @param maxTotalConn 最大总连接数,默认50
+ */
+ public void setMaxTotalConn(int maxTotalConn) {
+ this.maxTotalConn = maxTotalConn;
+ }
+
+ /**
+ * 自定义httpclient的User Agent
+ *
+ * @param userAgent User Agent
+ */
+ public void setUserAgent(String userAgent) {
+ this.userAgent = userAgent;
+ }
+
+ public IdleConnectionMonitorThread getIdleConnectionMonitorThread() {
+ return this.idleConnectionMonitorThread;
+ }
+
+ private synchronized void prepare() {
+ if(prepared.get()){
+ return;
+ }
+ Registry registry = RegistryBuilder.create()
+ .register("http", this.plainConnectionSocketFactory)
+ .register("https", this.sslConnectionSocketFactory)
+ .build();
+
+ @SuppressWarnings("resource")
+ PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(registry);
+ connectionManager.setMaxTotal(this.maxTotalConn);
+ connectionManager.setDefaultMaxPerRoute(this.maxConnPerHost);
+ connectionManager.setDefaultSocketConfig(
+ SocketConfig.copy(SocketConfig.DEFAULT)
+ .setSoTimeout(this.soTimeout)
+ .build()
+ );
+
+ this.idleConnectionMonitorThread = new IdleConnectionMonitorThread(
+ connectionManager, this.idleConnTimeout, this.checkWaitTime);
+ this.idleConnectionMonitorThread.setDaemon(true);
+ this.idleConnectionMonitorThread.start();
+
+ this.httpClientBuilder = HttpClients.custom()
+ .setConnectionManager(connectionManager)
+ .setConnectionManagerShared(true)
+ .setDefaultRequestConfig(
+ RequestConfig.custom()
+ .setSocketTimeout(this.soTimeout)
+ .setConnectTimeout(this.connectionTimeout)
+ .setConnectionRequestTimeout(this.connectionRequestTimeout)
+ .build()
+ )
+ .setRetryHandler(this.httpRequestRetryHandler);
+
+ if (StringUtils.isNotBlank(this.httpProxyHost)
+ && StringUtils.isNotBlank(this.httpProxyUsername)) {
+ // 使用代理服务器 需要用户认证的代理服务器
+ CredentialsProvider provider = new BasicCredentialsProvider();
+ provider.setCredentials(
+ new AuthScope(this.httpProxyHost, this.httpProxyPort),
+ new UsernamePasswordCredentials(this.httpProxyUsername,
+ this.httpProxyPassword));
+ this.httpClientBuilder.setDefaultCredentialsProvider(provider);
+ }
+
+ if (StringUtils.isNotBlank(this.userAgent)) {
+ this.httpClientBuilder.setUserAgent(this.userAgent);
+ }
+ prepared.set(true);
+ }
+
+ @Override
+ public CloseableHttpClient build() {
+ if(!prepared.get()){
+ prepare();
+ }
+ return this.httpClientBuilder.build();
+ }
+
+ public static class IdleConnectionMonitorThread extends Thread {
+ private final HttpClientConnectionManager connMgr;
+ private final int idleConnTimeout;
+ private final int checkWaitTime;
+ private volatile boolean shutdown;
+
+ public IdleConnectionMonitorThread(HttpClientConnectionManager connMgr, int idleConnTimeout, int checkWaitTime) {
+ super("IdleConnectionMonitorThread");
+ this.connMgr = connMgr;
+ this.idleConnTimeout = idleConnTimeout;
+ this.checkWaitTime = checkWaitTime;
+ }
+
+ @Override
+ public void run() {
+ try {
+ while (!this.shutdown) {
+ synchronized (this) {
+ wait(this.checkWaitTime);
+ this.connMgr.closeExpiredConnections();
+ this.connMgr.closeIdleConnections(this.idleConnTimeout,
+ TimeUnit.MILLISECONDS);
+ }
+ }
+ } catch (InterruptedException ignore) {
+ }
+ }
+
+ public void trigger() {
+ synchronized (this) {
+ notifyAll();
+ }
+ }
+
+ public void shutdown() {
+ this.shutdown = true;
+ synchronized (this) {
+ notifyAll();
+ }
+ }
+ }
+}
diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/jodd/InputStreamResponseHandler.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/jodd/InputStreamResponseHandler.java
new file mode 100644
index 00000000..a766d4c2
--- /dev/null
+++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/jodd/InputStreamResponseHandler.java
@@ -0,0 +1,28 @@
+package me.chanjar.weixin.common.util.http.jodd;
+
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpResponse;
+import org.apache.http.StatusLine;
+import org.apache.http.client.HttpResponseException;
+import org.apache.http.client.ResponseHandler;
+import org.apache.http.util.EntityUtils;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+public class InputStreamResponseHandler implements ResponseHandler {
+
+ public static final ResponseHandler INSTANCE = new InputStreamResponseHandler();
+
+ @Override
+ public InputStream handleResponse(final HttpResponse response) throws IOException {
+ final StatusLine statusLine = response.getStatusLine();
+ final HttpEntity entity = response.getEntity();
+ if (statusLine.getStatusCode() >= 300) {
+ EntityUtils.consume(entity);
+ throw new HttpResponseException(statusLine.getStatusCode(), statusLine.getReasonPhrase());
+ }
+ return entity == null ? null : entity.getContent();
+ }
+
+}
diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/jodd/MediaDownloadRequestExecutor.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/jodd/MediaDownloadRequestExecutor.java
new file mode 100644
index 00000000..ff617e8e
--- /dev/null
+++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/jodd/MediaDownloadRequestExecutor.java
@@ -0,0 +1,76 @@
+package me.chanjar.weixin.common.util.http.jodd;
+
+import jodd.http.HttpRequest;
+import jodd.http.HttpResponse;
+import jodd.http.ProxyInfo;
+import me.chanjar.weixin.common.bean.result.WxError;
+import me.chanjar.weixin.common.exception.WxErrorException;
+import me.chanjar.weixin.common.util.fs.FileUtils;
+import org.apache.commons.lang3.StringUtils;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * 下载媒体文件请求执行器,请求的参数是String, 返回的结果是File
+ * 视频文件不支持下载
+ *
+ * @author Daniel Qian
+ */
+public class MediaDownloadRequestExecutor implements RequestExecutor {
+
+ private File tmpDirFile;
+
+ public MediaDownloadRequestExecutor() {
+ }
+
+ public MediaDownloadRequestExecutor(File tmpDirFile) {
+ this.tmpDirFile = tmpDirFile;
+ }
+
+ @Override
+ public File execute(ProxyInfo httpProxy, String uri, String queryParam) throws WxErrorException, IOException {
+ if (queryParam != null) {
+ if (uri.indexOf('?') == -1) {
+ uri += '?';
+ }
+ uri += uri.endsWith("?") ? queryParam : '&' + queryParam;
+ }
+
+ HttpRequest httpRequest = HttpRequest.post(uri);
+ HttpResponse response = httpRequest.send();
+ String contentType = response.header("Content-Type");
+ if (contentType != null && contentType.startsWith("application/json")) {
+ // application/json; encoding=utf-8 下载媒体文件出错
+ throw new WxErrorException(WxError.fromJson(response.bodyText()));
+ }
+
+ String fileName = getFileName(response);
+ if (StringUtils.isBlank(fileName)) {
+ return null;
+ }
+
+ InputStream inputStream = new ByteArrayInputStream(response.bodyBytes());
+ String[] nameAndExt = fileName.split("\\.");
+ return FileUtils.createTmpFile(inputStream, nameAndExt[0], nameAndExt[1], this.tmpDirFile);
+ }
+
+ private String getFileName(HttpResponse response) throws WxErrorException {
+ String content = response.header("Content-disposition");
+ if (content == null || content.length() == 0) {
+ throw new WxErrorException(WxError.newBuilder().setErrorMsg("无法获取到文件名").build());
+ }
+
+ Pattern p = Pattern.compile(".*filename=\"(.*)\"");
+ Matcher m = p.matcher(content);
+ if (m.matches()) {
+ return m.group(1);
+ }
+ throw new WxErrorException(WxError.newBuilder().setErrorMsg("无法获取到文件名").build());
+ }
+
+}
diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/jodd/MediaUploadRequestExecutor.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/jodd/MediaUploadRequestExecutor.java
new file mode 100644
index 00000000..43d7bbae
--- /dev/null
+++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/jodd/MediaUploadRequestExecutor.java
@@ -0,0 +1,39 @@
+package me.chanjar.weixin.common.util.http.jodd;
+
+import jodd.http.HttpRequest;
+import jodd.http.HttpResponse;
+import jodd.http.ProxyInfo;
+import jodd.http.net.SocketHttpConnectionProvider;
+import me.chanjar.weixin.common.bean.result.WxError;
+import me.chanjar.weixin.common.bean.result.WxMediaUploadResult;
+import me.chanjar.weixin.common.exception.WxErrorException;
+
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * 上传媒体文件请求执行器,请求的参数是File, 返回的结果是String
+ *
+ * @author Daniel Qian
+ */
+public class MediaUploadRequestExecutor implements RequestExecutor {
+
+ @Override
+ public WxMediaUploadResult execute(ProxyInfo httpProxy, String uri, File file) throws WxErrorException, IOException {
+ HttpRequest request = HttpRequest.post(uri);
+ if (httpProxy != null) {
+ SocketHttpConnectionProvider provider = new SocketHttpConnectionProvider();
+ provider.useProxy(httpProxy);
+ request.withConnectionProvider(provider);
+ }
+ request.form("media", file);
+ HttpResponse response = request.send();
+ String responseContent =response.bodyText();
+ WxError error = WxError.fromJson(responseContent);
+ if (error.getErrorCode() != 0) {
+ throw new WxErrorException(error);
+ }
+ return WxMediaUploadResult.fromJson(responseContent);
+ }
+
+}
diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/jodd/RequestExecutor.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/jodd/RequestExecutor.java
new file mode 100644
index 00000000..ccc82702
--- /dev/null
+++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/jodd/RequestExecutor.java
@@ -0,0 +1,25 @@
+package me.chanjar.weixin.common.util.http.jodd;
+
+import jodd.http.ProxyInfo;
+import me.chanjar.weixin.common.exception.WxErrorException;
+
+import java.io.IOException;
+
+/**
+ * http请求执行器
+ *
+ * @param 返回值类型
+ * @param 请求参数类型
+ */
+public interface RequestExecutor {
+
+ /**
+ * @param httpProxy http代理对象,如果没有配置代理则为空
+ * @param uri uri
+ * @param data 数据
+ * @throws WxErrorException
+ * @throws IOException
+ */
+ T execute(ProxyInfo httpProxy, String uri, E data) throws WxErrorException, IOException;
+
+}
diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/jodd/SimpleGetRequestExecutor.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/jodd/SimpleGetRequestExecutor.java
new file mode 100644
index 00000000..de84ad7b
--- /dev/null
+++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/jodd/SimpleGetRequestExecutor.java
@@ -0,0 +1,43 @@
+package me.chanjar.weixin.common.util.http.jodd;
+
+import jodd.http.HttpRequest;
+import jodd.http.HttpResponse;
+import jodd.http.ProxyInfo;
+import jodd.http.net.SocketHttpConnectionProvider;
+import me.chanjar.weixin.common.bean.result.WxError;
+import me.chanjar.weixin.common.exception.WxErrorException;
+
+import java.io.IOException;
+
+/**
+ * 简单的GET请求执行器,请求的参数是String, 返回的结果也是String
+ *
+ * @author Daniel Qian
+ */
+public class SimpleGetRequestExecutor implements RequestExecutor {
+
+ @Override
+ public String execute(ProxyInfo httpProxy, String uri, String queryParam) throws WxErrorException, IOException {
+ if (queryParam != null) {
+ if (uri.indexOf('?') == -1) {
+ uri += '?';
+ }
+ uri += uri.endsWith("?") ? queryParam : '&' + queryParam;
+ }
+
+ HttpRequest request = HttpRequest.get(uri);
+ if (httpProxy != null) {
+ SocketHttpConnectionProvider provider = new SocketHttpConnectionProvider();
+ provider.useProxy(httpProxy);
+ request.withConnectionProvider(provider);
+ }
+ HttpResponse response = request.send();
+ String responseContent = response.bodyText();
+ WxError error = WxError.fromJson(responseContent);
+ if (error.getErrorCode() != 0) {
+ throw new WxErrorException(error);
+ }
+ return responseContent;
+ }
+
+}
diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/jodd/SimplePostRequestExecutor.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/jodd/SimplePostRequestExecutor.java
new file mode 100644
index 00000000..276b1310
--- /dev/null
+++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/jodd/SimplePostRequestExecutor.java
@@ -0,0 +1,51 @@
+package me.chanjar.weixin.common.util.http.jodd;
+
+import jodd.http.HttpRequest;
+import jodd.http.HttpResponse;
+import jodd.http.ProxyInfo;
+import jodd.http.net.SocketHttpConnectionProvider;
+import me.chanjar.weixin.common.bean.result.WxError;
+import me.chanjar.weixin.common.exception.WxErrorException;
+
+import java.io.IOException;
+
+/**
+ * 简单的POST请求执行器,请求的参数是String, 返回的结果也是String
+ *
+ * @author Daniel Qian
+ */
+public class SimplePostRequestExecutor implements RequestExecutor {
+
+ @Override
+ public String execute(ProxyInfo httpProxy, String uri, String postEntity) throws WxErrorException, IOException {
+ HttpRequest request = HttpRequest.post(uri);
+ if (httpProxy != null) {
+ SocketHttpConnectionProvider provider = new SocketHttpConnectionProvider();
+ provider.useProxy(httpProxy);
+ request.withConnectionProvider(provider);
+ }
+ if (postEntity != null) {
+ request.bodyText(postEntity);
+ }
+ HttpResponse response = request.send();
+
+ String responseContent = response.bodyText();
+ if (responseContent.isEmpty()) {
+ throw new WxErrorException(
+ WxError.newBuilder().setErrorCode(9999).setErrorMsg("无响应内容")
+ .build());
+ }
+
+ if (responseContent.startsWith("")) {
+ //xml格式输出直接返回
+ return responseContent;
+ }
+
+ WxError error = WxError.fromJson(responseContent);
+ if (error.getErrorCode() != 0) {
+ throw new WxErrorException(error);
+ }
+ return responseContent;
+ }
+
+}
diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/jodd/URIUtil.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/jodd/URIUtil.java
new file mode 100644
index 00000000..ac293d79
--- /dev/null
+++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/jodd/URIUtil.java
@@ -0,0 +1,48 @@
+package me.chanjar.weixin.common.util.http.jodd;
+
+
+import org.apache.commons.lang3.StringUtils;
+
+import java.io.UnsupportedEncodingException;
+
+public class URIUtil {
+
+ private static final String ALLOWED_CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.!~*'()";
+
+ public static String encodeURIComponent(String input) {
+ if (StringUtils.isEmpty(input)) {
+ return input;
+ }
+
+ int l = input.length();
+ StringBuilder o = new StringBuilder(l * 3);
+ try {
+ for (int i = 0; i < l; i++) {
+ String e = input.substring(i, i + 1);
+ if (ALLOWED_CHARS.indexOf(e) == -1) {
+ byte[] b = e.getBytes("utf-8");
+ o.append(getHex(b));
+ continue;
+ }
+ o.append(e);
+ }
+ return o.toString();
+ } catch (UnsupportedEncodingException e) {
+ e.printStackTrace();
+ }
+ return input;
+ }
+
+ private static String getHex(byte buf[]) {
+ StringBuilder o = new StringBuilder(buf.length * 3);
+ for (int i = 0; i < buf.length; i++) {
+ int n = buf[i] & 0xff;
+ o.append("%");
+ if (n < 0x10) {
+ o.append("0");
+ }
+ o.append(Long.toString(n, 16).toUpperCase());
+ }
+ return o.toString();
+ }
+}
diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/jodd/Utf8ResponseHandler.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/jodd/Utf8ResponseHandler.java
new file mode 100644
index 00000000..e26d3e33
--- /dev/null
+++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/jodd/Utf8ResponseHandler.java
@@ -0,0 +1,33 @@
+package me.chanjar.weixin.common.util.http.jodd;
+
+import org.apache.http.Consts;
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpResponse;
+import org.apache.http.StatusLine;
+import org.apache.http.client.HttpResponseException;
+import org.apache.http.client.ResponseHandler;
+import org.apache.http.util.EntityUtils;
+
+import java.io.IOException;
+
+/**
+ * copy from {@link org.apache.http.impl.client.BasicResponseHandler}
+ *
+ * @author Daniel Qian
+ */
+public class Utf8ResponseHandler implements ResponseHandler {
+
+ public static final ResponseHandler INSTANCE = new Utf8ResponseHandler();
+
+ @Override
+ public String handleResponse(final HttpResponse response) throws IOException {
+ final StatusLine statusLine = response.getStatusLine();
+ final HttpEntity entity = response.getEntity();
+ if (statusLine.getStatusCode() >= 300) {
+ EntityUtils.consume(entity);
+ throw new HttpResponseException(statusLine.getStatusCode(), statusLine.getReasonPhrase());
+ }
+ return entity == null ? null : EntityUtils.toString(entity, Consts.UTF_8);
+ }
+
+}