diff --git a/CHANGE.md b/CHANGE.md index e1c3cc8d..7ec34694 100644 --- a/CHANGE.md +++ b/CHANGE.md @@ -445,8 +445,16 @@ + `release`: weixin4j-[mp|qy] upgrade to 1.5.3,weixin4j-server upgrade to 1.0.5 - + **weixin4j-[mp|qy]**: 媒体接口类(MediaApi)查询素材接口调整:去掉offset,count替换为Pageable类 + + **weixin4j-[mp|qy]**: 媒体接口类(MediaApi)查询素材接口调整:去掉offset,count替换为Pageable类 * 2015-08-18 - + 比较大的改动:重构了HttpClient部分 + + 比较大的改动:重构了HttpClient部分 + +* 2015-09-08 + + + weixin4j-mp:新增批量获取用户信息接口 + +* 2015-09-10 + + + 对Netty-Http-Client的支持 \ No newline at end of file diff --git a/weixin4j-base/pom.xml b/weixin4j-base/pom.xml index beb55685..f7f01e98 100644 --- a/weixin4j-base/pom.xml +++ b/weixin4j-base/pom.xml @@ -29,5 +29,11 @@ 4.3 true + + io.netty + netty-all + 4.0.30.Final + true + \ No newline at end of file diff --git a/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/AbstractHttpClient.java b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/AbstractHttpClient.java index 48cebf0d..217e3355 100644 --- a/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/AbstractHttpClient.java +++ b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/AbstractHttpClient.java @@ -106,4 +106,25 @@ public abstract class AbstractHttpClient implements HttpClient { } return execute(new HttpRequest(method, buf.toString())); } + + protected boolean hasError(HttpStatus status) { + return (status.series() == HttpStatus.Series.CLIENT_ERROR || status + .series() == HttpStatus.Series.SERVER_ERROR); + } + + protected void handleResponse(HttpResponse response) + throws HttpClientException { + HttpStatus status = response.getStatus(); + if (hasError(status)) { + switch (status.series()) { + case CLIENT_ERROR: + case SERVER_ERROR: + throw new HttpClientException(String.format("%d %s", + status.getStatusCode(), status.getStatusText())); + default: + throw new HttpClientException("Unknown status code [" + status + + "]"); + } + } + } } diff --git a/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/AbstractHttpResponse.java b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/AbstractHttpResponse.java new file mode 100644 index 00000000..5eecfc7e --- /dev/null +++ b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/AbstractHttpResponse.java @@ -0,0 +1,31 @@ +package com.foxinmy.weixin4j.http; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; + +/** + * + * @className AbstractHttpResponse + * @author jy + * @date 2015年9月7日 + * @since JDK 1.7 + * @see + */ +public abstract class AbstractHttpResponse implements HttpResponse { + + private final byte[] content; + + public AbstractHttpResponse(byte[] content) { + this.content = content; + } + + @Override + public byte[] getContent() { + return content; + } + + @Override + public InputStream getBody() { + return content != null ? new ByteArrayInputStream(content) : null; + } +} diff --git a/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/HttpResponse.java b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/HttpResponse.java index 45e1c661..a5598b52 100644 --- a/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/HttpResponse.java +++ b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/HttpResponse.java @@ -24,14 +24,21 @@ public interface HttpResponse extends HttpMessage { * * @return */ - HttpStatus getStatus() throws HttpClientException; + HttpStatus getStatus(); /** * 响应内容 * * @return */ - InputStream getBody() throws HttpClientException; + InputStream getBody(); + + /** + * 响应内容 + * + * @return + */ + byte[] getContent(); /** * 释放资源 diff --git a/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/HttpStatus.java b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/HttpStatus.java index 72dba067..5200772b 100644 --- a/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/HttpStatus.java +++ b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/HttpStatus.java @@ -31,7 +31,6 @@ package com.foxinmy.weixin4j.http; * Constants enumerating the HTTP status codes. All status codes defined in * RFC1945 (HTTP/1.0), RFC2616 (HTTP/1.1), and RFC2518 (WebDAV) are listed. * - * @see StatusLine * * @since 4.0 */ @@ -188,6 +187,54 @@ public final class HttpStatus { return statusText; } + /** + * Returns the HTTP status series of this status code. + * + * @see HttpStatus.Series + */ + public Series series() { + return Series.valueOf(this); + } + + /** + * Java 5 enumeration of HTTP status series. + *

+ * Retrievable via {@link HttpStatus#series()}. + */ + public static enum Series { + + INFORMATIONAL(1), SUCCESSFUL(2), REDIRECTION(3), CLIENT_ERROR(4), SERVER_ERROR( + 5); + + private final int value; + + private Series(int value) { + this.value = value; + } + + /** + * Return the integer value of this status series. Ranges from 1 to 5. + */ + public int value() { + return this.value; + } + + public static Series valueOf(int status) { + int seriesCode = status / 100; + for (Series series : values()) { + if (series.value == seriesCode) { + return series; + } + } + throw new IllegalArgumentException("No matching constant for [" + + status + "]"); + } + + public static Series valueOf(HttpStatus status) { + return valueOf(status.statusCode); + } + } + @Override public String toString() { return "[" + statusCode + "," + statusText + "]"; diff --git a/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/SimpleHttpClient.java b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/SimpleHttpClient.java index 2c4c5991..b278be92 100644 --- a/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/SimpleHttpClient.java +++ b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/SimpleHttpClient.java @@ -1,29 +1,24 @@ package com.foxinmy.weixin4j.http; import java.io.IOException; +import java.io.InputStream; import java.io.OutputStream; import java.net.HttpURLConnection; import java.net.Proxy; import java.net.URI; import java.net.URLConnection; -import java.security.KeyManagementException; -import java.security.NoSuchAlgorithmException; -import java.security.cert.CertificateException; -import java.security.cert.X509Certificate; -import java.util.HashMap; import java.util.Iterator; import java.util.List; -import java.util.Map; import java.util.Map.Entry; import javax.net.ssl.HostnameVerifier; import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLSession; -import javax.net.ssl.X509TrustManager; import com.foxinmy.weixin4j.http.entity.HttpEntity; -import com.foxinmy.weixin4j.model.Consts; +import com.foxinmy.weixin4j.http.factory.HttpClientFactory; +import com.foxinmy.weixin4j.util.IOUtil; import com.foxinmy.weixin4j.util.StringUtil; /** @@ -46,27 +41,6 @@ public class SimpleHttpClient extends AbstractHttpClient implements HttpClient { }; } - protected X509TrustManager createX509TrustManager() { - return new X509TrustManager() { - @Override - public X509Certificate[] getAcceptedIssuers() { - return null; - } - - @Override - public void checkServerTrusted( - X509Certificate[] paramArrayOfX509Certificate, - String paramString) throws CertificateException { - } - - @Override - public void checkClientTrusted( - X509Certificate[] paramArrayOfX509Certificate, - String paramString) throws CertificateException { - } - }; - } - protected HttpURLConnection createHttpConnection(HttpRequest request) throws IOException { URI uri = request.getURI(); @@ -83,11 +57,7 @@ public class SimpleHttpClient extends AbstractHttpClient implements HttpClient { hostnameVerifier = params.getHostnameVerifier(); } if (sslContext == null) { - sslContext = SSLContext.getInstance("TLS"); - sslContext - .init(null, - new X509TrustManager[] { createX509TrustManager() }, - new java.security.SecureRandom()); + sslContext = HttpClientFactory.allowSSLContext(); } if (hostnameVerifier == null) { hostnameVerifier = createHostnameVerifier(); @@ -96,23 +66,14 @@ public class SimpleHttpClient extends AbstractHttpClient implements HttpClient { connection.setSSLSocketFactory(sslContext.getSocketFactory()); connection.setHostnameVerifier(hostnameVerifier); return connection; - } catch (NoSuchAlgorithmException | KeyManagementException e) { - throw new IOException(e.getMessage()); + } catch (HttpClientException e) { + throw new IOException(e); } } else { return (HttpURLConnection) urlConnection; } } - protected Map createDefualtHeader() { - Map header = new HashMap(); - header.put("User-Agent", "simple-httpclient"); - header.put("Accept", "text/xml,text/javascript"); - header.put("Accept-Charset", Consts.UTF_8.name()); - header.put("Accept-Encoding", Consts.UTF_8.name()); - return header; - } - @Override public HttpResponse execute(HttpRequest request) throws HttpClientException { HttpResponse response = null; @@ -141,25 +102,31 @@ public class SimpleHttpClient extends AbstractHttpClient implements HttpClient { connection.setDoOutput(false); } // set headers - for (Iterator> headerIterator = createDefualtHeader() - .entrySet().iterator(); headerIterator.hasNext();) { - Entry header = headerIterator.next(); - connection.setRequestProperty(header.getKey(), - header.getValue()); - } HttpHeaders headers = request.getHeaders(); - if (headers != null) { - for (Iterator>> headerIterator = headers - .entrySet().iterator(); headerIterator.hasNext();) { - Entry> header = headerIterator.next(); - if (HttpHeaders.COOKIE.equalsIgnoreCase(header.getKey())) { - connection.setRequestProperty(header.getKey(), - StringUtil.join(header.getValue(), ';')); - } else { - for (String headerValue : header.getValue()) { - connection.addRequestProperty(header.getKey(), - headerValue != null ? headerValue : ""); - } + if (headers == null) { + headers = new HttpHeaders(); + if (!headers.containsKey(HttpHeaders.HOST)) { + headers.set(HttpHeaders.HOST, request.getURI().getHost()); + } + // Add default accept headers + if (!headers.containsKey(HttpHeaders.ACCEPT)) { + headers.set(HttpHeaders.ACCEPT, "*/*"); + } + // Add default user agent + if (!headers.containsKey(HttpHeaders.USER_AGENT)) { + headers.set(HttpHeaders.USER_AGENT, "jdk/httpclient"); + } + } + for (Iterator>> headerIterator = headers + .entrySet().iterator(); headerIterator.hasNext();) { + Entry> header = headerIterator.next(); + if (HttpHeaders.COOKIE.equalsIgnoreCase(header.getKey())) { + connection.setRequestProperty(header.getKey(), + StringUtil.join(header.getValue(), ';')); + } else { + for (String headerValue : header.getValue()) { + connection.addRequestProperty(header.getKey(), + headerValue != null ? headerValue : ""); } } } @@ -170,16 +137,15 @@ public class SimpleHttpClient extends AbstractHttpClient implements HttpClient { if (httpEntity != null) { // Read Out Exception when connection.disconnect(); /* + * if (httpEntity.getContentLength() > 0l) { + * connection.setFixedLengthStreamingMode(httpEntity + * .getContentLength()); } else { connection + * .setChunkedStreamingMode(params != null ? params + * .getChunkSize() : 4096); } + */ if (httpEntity.getContentLength() > 0l) { - connection.setFixedLengthStreamingMode(httpEntity - .getContentLength()); - } else { - connection - .setChunkedStreamingMode(params != null ? params - .getChunkSize() : 4096); - }*/ - if (httpEntity.getContentLength() > 0l) { - connection.setRequestProperty(HttpHeaders.CONTENT_LENGTH, + connection.setRequestProperty( + HttpHeaders.CONTENT_LENGTH, Long.toString(httpEntity.getContentLength())); } if (httpEntity.getContentType() != null) { @@ -198,7 +164,12 @@ public class SimpleHttpClient extends AbstractHttpClient implements HttpClient { output.close(); } // building response - response = new SimpleHttpResponse(connection); + InputStream input = connection.getErrorStream() != null ? connection + .getErrorStream() : connection.getInputStream(); + byte[] content = IOUtil.toByteArray(input); + response = new SimpleHttpResponse(connection, content); + input.close(); + handleResponse(response); } catch (IOException e) { throw new HttpClientException("I/O error on " + request.getMethod().name() + " request for \"" diff --git a/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/SimpleHttpResponse.java b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/SimpleHttpResponse.java index fb70be55..193d881e 100644 --- a/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/SimpleHttpResponse.java +++ b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/SimpleHttpResponse.java @@ -1,7 +1,6 @@ package com.foxinmy.weixin4j.http; import java.io.IOException; -import java.io.InputStream; import java.net.HttpURLConnection; import java.util.Iterator; import java.util.List; @@ -17,7 +16,7 @@ import java.util.Map.Entry; * @since JDK 1.7 * @see */ -public class SimpleHttpResponse implements HttpResponse { +public class SimpleHttpResponse extends AbstractHttpResponse { private final HttpURLConnection connection; @@ -25,7 +24,8 @@ public class SimpleHttpResponse implements HttpResponse { private HttpVersion protocol; private HttpStatus status; - public SimpleHttpResponse(HttpURLConnection connection) { + public SimpleHttpResponse(HttpURLConnection connection, byte[] content) { + super(content); this.connection = connection; } @@ -65,28 +65,18 @@ public class SimpleHttpResponse implements HttpResponse { } @Override - public HttpStatus getStatus() throws HttpClientException { + public HttpStatus getStatus() { if (status == null) { try { status = new HttpStatus(connection.getResponseCode(), connection.getResponseMessage()); } catch (IOException e) { - throw new HttpClientException("I/O Error on getStatus", e); + throw new RuntimeException("I/O Error on getStatus", e); } } return status; } - @Override - public InputStream getBody() throws HttpClientException { - try { - return connection.getErrorStream() != null ? connection - .getErrorStream() : connection.getInputStream(); - } catch (IOException e) { - throw new HttpClientException("I/O Error on getBody", e); - } - } - @Override public void close() { connection.disconnect(); diff --git a/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/factory/HttpClientFactory.java b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/factory/HttpClientFactory.java index c51a65ba..2d381bb0 100644 --- a/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/factory/HttpClientFactory.java +++ b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/factory/HttpClientFactory.java @@ -1,6 +1,15 @@ package com.foxinmy.weixin4j.http.factory; +import java.security.KeyManagementException; +import java.security.NoSuchAlgorithmException; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; + +import javax.net.ssl.SSLContext; +import javax.net.ssl.X509TrustManager; + import com.foxinmy.weixin4j.http.HttpClient; +import com.foxinmy.weixin4j.http.HttpClientException; /** * HttpClient工厂生产类:参考netty的InternalLoggerFactory @@ -26,12 +35,16 @@ public abstract class HttpClientFactory { private static HttpClientFactory newDefaultFactory() { HttpClientFactory f; try { - f = new HttpComponent4Factory(); - } catch (Throwable e2) { + f = new Netty4HttpClientFactory(); + } catch (Throwable e1) { try { - f = new HttpComponent3Factory(); - } catch (Throwable e3) { - f = new SimpleHttpClientFactory(); + f = new HttpComponent4Factory(); + } catch (Throwable e2) { + try { + f = new HttpComponent3Factory(); + } catch (Throwable e3) { + f = new SimpleHttpClientFactory(); + } } } return f; @@ -73,4 +86,37 @@ public abstract class HttpClientFactory { * @return */ public abstract HttpClient newInstance(); + + public static SSLContext allowSSLContext() throws HttpClientException { + try { + SSLContext sslContext = SSLContext.getInstance("TLS"); + sslContext.init(null, + new X509TrustManager[] { createX509TrustManager() }, + new java.security.SecureRandom()); + return sslContext; + } catch (NoSuchAlgorithmException | KeyManagementException e) { + throw new HttpClientException("Create SSLContext Error:", e); + } + } + + protected static X509TrustManager createX509TrustManager() { + return new X509TrustManager() { + @Override + public X509Certificate[] getAcceptedIssuers() { + return null; + } + + @Override + public void checkServerTrusted( + X509Certificate[] paramArrayOfX509Certificate, + String paramString) throws CertificateException { + } + + @Override + public void checkClientTrusted( + X509Certificate[] paramArrayOfX509Certificate, + String paramString) throws CertificateException { + } + }; + } } diff --git a/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/factory/HttpComponent3.java b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/factory/HttpComponent3.java index f7228d0d..721b5ad7 100644 --- a/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/factory/HttpComponent3.java +++ b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/factory/HttpComponent3.java @@ -109,18 +109,30 @@ public class HttpComponent3 extends AbstractHttpClient { } com.foxinmy.weixin4j.http.HttpHeaders headers = request .getHeaders(); - if (headers != null) { - for (Iterator>> headerIterator = headers - .entrySet().iterator(); headerIterator.hasNext();) { - Entry> header = headerIterator.next(); - if (HttpHeaders.COOKIE.equalsIgnoreCase(header.getKey())) { - httpMethod.setRequestHeader(header.getKey(), - StringUtil.join(header.getValue(), ';')); - } else { - for (String headerValue : header.getValue()) { - httpMethod.addRequestHeader(header.getKey(), - headerValue != null ? headerValue : ""); - } + if (headers == null) { + headers = new HttpHeaders(); + } + if (!headers.containsKey(HttpHeaders.HOST)) { + headers.set(HttpHeaders.HOST, uri.getHost()); + } + // Add default accept headers + if (!headers.containsKey(HttpHeaders.ACCEPT)) { + headers.set(HttpHeaders.ACCEPT, "*/*"); + } + // Add default user agent + if (!headers.containsKey(HttpHeaders.USER_AGENT)) { + headers.set(HttpHeaders.USER_AGENT, "apache/httpclient3"); + } + for (Iterator>> headerIterator = headers + .entrySet().iterator(); headerIterator.hasNext();) { + Entry> header = headerIterator.next(); + if (HttpHeaders.COOKIE.equalsIgnoreCase(header.getKey())) { + httpMethod.setRequestHeader(header.getKey(), + StringUtil.join(header.getValue(), ';')); + } else { + for (String headerValue : header.getValue()) { + httpMethod.addRequestHeader(header.getKey(), + headerValue != null ? headerValue : ""); } } } @@ -153,6 +165,7 @@ public class HttpComponent3 extends AbstractHttpClient { } httpClient.executeMethod(httpMethod); response = new HttpComponent3Response(httpMethod); + handleResponse(response); } catch (IOException e) { throw new HttpClientException("I/O error on " + request.getMethod().name() + " request for \"" diff --git a/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/factory/HttpComponent3Response.java b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/factory/HttpComponent3Response.java index 499153cf..ece04ff6 100644 --- a/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/factory/HttpComponent3Response.java +++ b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/factory/HttpComponent3Response.java @@ -1,16 +1,13 @@ package com.foxinmy.weixin4j.http.factory; -import java.io.ByteArrayInputStream; import java.io.IOException; -import java.io.InputStream; import org.apache.commons.httpclient.Header; import org.apache.commons.httpclient.HttpMethod; import org.apache.commons.httpclient.protocol.Protocol; -import com.foxinmy.weixin4j.http.HttpClientException; +import com.foxinmy.weixin4j.http.AbstractHttpResponse; import com.foxinmy.weixin4j.http.HttpHeaders; -import com.foxinmy.weixin4j.http.HttpResponse; import com.foxinmy.weixin4j.http.HttpStatus; import com.foxinmy.weixin4j.http.HttpVersion; @@ -23,22 +20,17 @@ import com.foxinmy.weixin4j.http.HttpVersion; * @since JDK 1.7 * @see */ -public class HttpComponent3Response implements HttpResponse { +public class HttpComponent3Response extends AbstractHttpResponse { private final HttpMethod httpMethod; private HttpHeaders headers; private HttpVersion protocol; private HttpStatus status; - private InputStream body; - public HttpComponent3Response(HttpMethod httpMethod) { + public HttpComponent3Response(HttpMethod httpMethod) throws IOException { + super(httpMethod.getResponseBody()); this.httpMethod = httpMethod; - try { - this.body = new ByteArrayInputStream(httpMethod.getResponseBody()); - } catch (IOException e) { - ; - } } @Override @@ -71,7 +63,7 @@ public class HttpComponent3Response implements HttpResponse { } @Override - public HttpStatus getStatus() throws HttpClientException { + public HttpStatus getStatus() { if (status == null) { status = new HttpStatus(httpMethod.getStatusCode(), httpMethod.getStatusText()); @@ -79,11 +71,6 @@ public class HttpComponent3Response implements HttpResponse { return status; } - @Override - public InputStream getBody() throws HttpClientException { - return body; - } - @Override public void close() { httpMethod.releaseConnection(); diff --git a/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/factory/HttpComponent4.java b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/factory/HttpComponent4.java index 1f9fcc0a..2d772853 100644 --- a/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/factory/HttpComponent4.java +++ b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/factory/HttpComponent4.java @@ -26,6 +26,7 @@ import org.apache.http.client.methods.HttpTrace; import org.apache.http.conn.ssl.X509HostnameVerifier; import org.apache.http.entity.AbstractHttpEntity; import org.apache.http.entity.InputStreamEntity; +import org.apache.http.util.EntityUtils; import com.foxinmy.weixin4j.http.AbstractHttpClient; import com.foxinmy.weixin4j.http.HttpHeaders; @@ -57,18 +58,27 @@ public abstract class HttpComponent4 extends AbstractHttpClient { } protected void addHeaders(HttpHeaders headers, HttpRequestBase uriRequest) { - if (headers != null) { - for (Iterator>> headerIterator = headers - .entrySet().iterator(); headerIterator.hasNext();) { - Entry> header = headerIterator.next(); - if (HttpHeaders.COOKIE.equalsIgnoreCase(header.getKey())) { + if (headers == null) { + headers = new HttpHeaders(); + } + // Add default accept headers + if (!headers.containsKey(HttpHeaders.ACCEPT)) { + headers.set(HttpHeaders.ACCEPT, "*/*"); + } + // Add default user agent + if (!headers.containsKey(HttpHeaders.USER_AGENT)) { + headers.set(HttpHeaders.USER_AGENT, "apache/httpclient4"); + } + for (Iterator>> headerIterator = headers + .entrySet().iterator(); headerIterator.hasNext();) { + Entry> header = headerIterator.next(); + if (HttpHeaders.COOKIE.equalsIgnoreCase(header.getKey())) { + uriRequest.addHeader(header.getKey(), + StringUtil.join(header.getValue(), ';')); + } else { + for (String headerValue : header.getValue()) { uriRequest.addHeader(header.getKey(), - StringUtil.join(header.getValue(), ';')); - } else { - for (String headerValue : header.getValue()) { - uriRequest.addHeader(header.getKey(), - headerValue != null ? headerValue : ""); - } + headerValue != null ? headerValue : ""); } } } @@ -94,6 +104,11 @@ public abstract class HttpComponent4 extends AbstractHttpClient { } } + protected byte[] getContent(org.apache.http.HttpResponse httpResponse) + throws IOException { + return EntityUtils.toByteArray(httpResponse.getEntity()); + } + protected static class CustomHostnameVerifier implements X509HostnameVerifier { diff --git a/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/factory/HttpComponent4_1.java b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/factory/HttpComponent4_1.java index ccb29d9c..8f72907b 100644 --- a/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/factory/HttpComponent4_1.java +++ b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/factory/HttpComponent4_1.java @@ -13,7 +13,6 @@ import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.params.CoreConnectionPNames; import org.apache.http.params.CoreProtocolPNames; -import com.foxinmy.weixin4j.http.HttpClient; import com.foxinmy.weixin4j.http.HttpClientException; import com.foxinmy.weixin4j.http.HttpHeaders; import com.foxinmy.weixin4j.http.HttpParams; @@ -87,7 +86,9 @@ public class HttpComponent4_1 extends HttpComponent4 { addEntity(request.getEntity(), uriRequest); org.apache.http.HttpResponse httpResponse = httpClient .execute(uriRequest); - response = new HttpComponent4_1Response(httpClient, httpResponse); + response = new HttpComponent4_1Response(httpResponse, + getContent(httpResponse)); + handleResponse(response); } catch (IOException e) { throw new HttpClientException("I/O error on " + request.getMethod().name() + " request for \"" diff --git a/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/factory/HttpComponent4_1Response.java b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/factory/HttpComponent4_1Response.java index c1175617..f3879e1f 100644 --- a/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/factory/HttpComponent4_1Response.java +++ b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/factory/HttpComponent4_1Response.java @@ -1,16 +1,13 @@ package com.foxinmy.weixin4j.http.factory; import java.io.IOException; -import java.io.InputStream; import org.apache.http.Header; -import org.apache.http.HttpEntity; import org.apache.http.ProtocolVersion; import org.apache.http.StatusLine; -import com.foxinmy.weixin4j.http.HttpClientException; +import com.foxinmy.weixin4j.http.AbstractHttpResponse; import com.foxinmy.weixin4j.http.HttpHeaders; -import com.foxinmy.weixin4j.http.HttpResponse; import com.foxinmy.weixin4j.http.HttpStatus; import com.foxinmy.weixin4j.http.HttpVersion; @@ -23,18 +20,16 @@ import com.foxinmy.weixin4j.http.HttpVersion; * @since JDK 1.7 * @see */ -public class HttpComponent4_1Response implements HttpResponse { - private final org.apache.http.client.HttpClient httpClient; +public class HttpComponent4_1Response extends AbstractHttpResponse { private final org.apache.http.HttpResponse httpResponse; private HttpHeaders headers; private HttpVersion protocol; private HttpStatus status; - public HttpComponent4_1Response( - org.apache.http.client.HttpClient httpClient, - org.apache.http.HttpResponse httpResponse) { - this.httpClient = httpClient; + public HttpComponent4_1Response(org.apache.http.HttpResponse httpResponse, + byte[] content) throws IOException { + super(content); this.httpResponse = httpResponse; } @@ -64,7 +59,7 @@ public class HttpComponent4_1Response implements HttpResponse { } @Override - public HttpStatus getStatus() throws HttpClientException { + public HttpStatus getStatus() { if (status == null) { StatusLine statusLine = httpResponse.getStatusLine(); status = new HttpStatus(statusLine.getStatusCode(), @@ -74,17 +69,11 @@ public class HttpComponent4_1Response implements HttpResponse { } @Override - public InputStream getBody() throws HttpClientException { + public void close() { try { - HttpEntity entity = this.httpResponse.getEntity(); - return (entity != null ? entity.getContent() : null); + httpResponse.getEntity().consumeContent(); } catch (IOException e) { - throw new HttpClientException("I/O Error on getBody", e); + ; } } - - @Override - public void close() { - httpClient.getConnectionManager().closeExpiredConnections(); - } } diff --git a/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/factory/HttpComponent4_2.java b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/factory/HttpComponent4_2.java index 5a0eaf07..9a7b1977 100644 --- a/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/factory/HttpComponent4_2.java +++ b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/factory/HttpComponent4_2.java @@ -69,7 +69,9 @@ public class HttpComponent4_2 extends HttpComponent4 { addHeaders(request.getHeaders(), uriRequest); addEntity(request.getEntity(), uriRequest); CloseableHttpResponse httpResponse = httpClient.execute(uriRequest); - response = new HttpComponent4_2Response(httpResponse); + response = new HttpComponent4_2Response(httpResponse, + getContent(httpResponse)); + handleResponse(response); } catch (IOException e) { throw new HttpClientException("I/O error on " + request.getMethod().name() + " request for \"" diff --git a/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/factory/HttpComponent4_2Response.java b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/factory/HttpComponent4_2Response.java index f1b681de..ea2d0921 100644 --- a/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/factory/HttpComponent4_2Response.java +++ b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/factory/HttpComponent4_2Response.java @@ -1,17 +1,15 @@ package com.foxinmy.weixin4j.http.factory; import java.io.IOException; -import java.io.InputStream; import org.apache.http.Header; -import org.apache.http.HttpEntity; import org.apache.http.ProtocolVersion; import org.apache.http.StatusLine; import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.util.EntityUtils; -import com.foxinmy.weixin4j.http.HttpClientException; +import com.foxinmy.weixin4j.http.AbstractHttpResponse; import com.foxinmy.weixin4j.http.HttpHeaders; -import com.foxinmy.weixin4j.http.HttpResponse; import com.foxinmy.weixin4j.http.HttpStatus; import com.foxinmy.weixin4j.http.HttpVersion; @@ -24,7 +22,7 @@ import com.foxinmy.weixin4j.http.HttpVersion; * @since JDK 1.7 * @see */ -public class HttpComponent4_2Response implements HttpResponse { +public class HttpComponent4_2Response extends AbstractHttpResponse { private final CloseableHttpResponse httpResponse; @@ -32,7 +30,9 @@ public class HttpComponent4_2Response implements HttpResponse { private HttpVersion protocol; private HttpStatus status; - public HttpComponent4_2Response(CloseableHttpResponse httpResponse) { + public HttpComponent4_2Response(CloseableHttpResponse httpResponse, + byte[] content) { + super(content); this.httpResponse = httpResponse; } @@ -62,7 +62,7 @@ public class HttpComponent4_2Response implements HttpResponse { } @Override - public HttpStatus getStatus() throws HttpClientException { + public HttpStatus getStatus() { if (status == null) { StatusLine statusLine = httpResponse.getStatusLine(); status = new HttpStatus(statusLine.getStatusCode(), @@ -71,20 +71,10 @@ public class HttpComponent4_2Response implements HttpResponse { return status; } - @Override - public InputStream getBody() throws HttpClientException { - try { - HttpEntity entity = this.httpResponse.getEntity(); - return (entity != null ? entity.getContent() : null); - } catch (IOException e) { - throw new HttpClientException("I/O Error on getBody", e); - } - } - @Override public void close() { try { - // EntityUtils.consume(httpResponse.getEntity()); + EntityUtils.consume(httpResponse.getEntity()); httpResponse.close(); } catch (IOException ex) { ; diff --git a/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/factory/Netty4HttpClient.java b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/factory/Netty4HttpClient.java new file mode 100644 index 00000000..bd51d9cf --- /dev/null +++ b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/factory/Netty4HttpClient.java @@ -0,0 +1,223 @@ +package com.foxinmy.weixin4j.http.factory; + +import io.netty.bootstrap.Bootstrap; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufAllocator; +import io.netty.buffer.ByteBufOutputStream; +import io.netty.channel.Channel; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelFutureListener; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelOption; +import io.netty.channel.SimpleChannelInboundHandler; +import io.netty.handler.codec.http.DefaultFullHttpRequest; +import io.netty.handler.codec.http.DefaultHttpRequest; +import io.netty.handler.codec.http.FullHttpResponse; +import io.netty.handler.codec.http.HttpMethod; +import io.netty.handler.codec.http.HttpVersion; +import io.netty.handler.ssl.SslHandler; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.URI; +import java.util.Iterator; +import java.util.List; +import java.util.Map.Entry; +import java.util.concurrent.ExecutionException; + +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLEngine; + +import com.foxinmy.weixin4j.http.AbstractHttpClient; +import com.foxinmy.weixin4j.http.HttpClientException; +import com.foxinmy.weixin4j.http.HttpHeaders; +import com.foxinmy.weixin4j.http.HttpParams; +import com.foxinmy.weixin4j.http.HttpRequest; +import com.foxinmy.weixin4j.http.HttpResponse; +import com.foxinmy.weixin4j.http.entity.HttpEntity; +import com.foxinmy.weixin4j.util.SettableFuture; +import com.foxinmy.weixin4j.util.StringUtil; + +/** + * Netty 4.x + * + * @className Netty4HttpClient + * @author jy + * @date 2015年8月30日 + * @since JDK 1.7 + * @see + */ +public class Netty4HttpClient extends AbstractHttpClient { + + private final Bootstrap bootstrap; + + public Netty4HttpClient(Bootstrap bootstrap) { + this.bootstrap = bootstrap; + } + + @Override + public HttpResponse execute(HttpRequest request) throws HttpClientException { + HttpResponse response = null; + try { + final URI uri = request.getURI(); + final HttpParams params = request.getParams(); + if (params != null) { + bootstrap.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, + params.getConnectTimeout()); + } + final boolean useProxy = params != null + && params.getProxy() != null; + final boolean useSSL = "https".equals(uri.getScheme()) && !useProxy; + final DefaultHttpRequest uriRequest = createRequest(request); + final SettableFuture future = new SettableFuture(); + ChannelFutureListener listener = new ChannelFutureListener() { + @Override + public void operationComplete(ChannelFuture channelFuture) + throws Exception { + if (channelFuture.isSuccess()) { + Channel channel = channelFuture.channel(); + // ssl + SSLContext sslContext = null; + if (useSSL) { + if (params != null + && params.getSSLContext() != null) { + sslContext = params.getSSLContext(); + } else { + sslContext = HttpClientFactory + .allowSSLContext(); + } + SSLEngine sslEngine = sslContext.createSSLEngine(); + sslEngine.setUseClientMode(true); + channel.pipeline().addFirst( + new SslHandler(sslEngine)); + } + channel.pipeline().addLast(new RequestHandler(future)); + channel.writeAndFlush(uriRequest); + } else { + future.setException(channelFuture.cause()); + } + } + }; + InetSocketAddress address = new InetSocketAddress( + InetAddress.getByName(uri.getHost()), getPort(uri)); + if (useProxy) { + address = (InetSocketAddress) params.getProxy().address(); + } + bootstrap.connect(address).syncUninterruptibly() + .addListener(listener); + response = future.get(); + handleResponse(response); + } catch (IOException | InterruptedException | ExecutionException e) { + throw new HttpClientException("I/O error on " + + request.getMethod().name() + " request for \"" + + request.getURI().toString() + "\":" + e.getMessage(), e); + } finally { + if (response != null) { + response.close(); + } + } + return response; + } + + private DefaultHttpRequest createRequest(HttpRequest request) + throws IOException { + HttpMethod method = HttpMethod.valueOf(request.getMethod().name()); + URI uri = request.getURI(); + String url = StringUtil.isBlank(uri.getRawPath()) ? "/" : uri + .getRawPath(); + if (StringUtil.isNotBlank(uri.getRawQuery())) { + url += "?" + uri.getRawQuery(); + } + DefaultHttpRequest uriRequest = new DefaultHttpRequest( + HttpVersion.HTTP_1_1, method, url); + // entity + HttpEntity entity = request.getEntity(); + if (entity != null) { + ByteBuf byteBuf = ByteBufAllocator.DEFAULT.buffer(); + try (ByteBufOutputStream out = new ByteBufOutputStream(byteBuf)) { + entity.writeTo(out); + } + uriRequest = new DefaultFullHttpRequest( + uriRequest.getProtocolVersion(), uriRequest.getMethod(), + uriRequest.getUri(), byteBuf); + if (entity.getContentType() != null) { + uriRequest.headers().add(HttpHeaders.CONTENT_TYPE, + entity.getContentType().toString()); + } + if (entity.getContentLength() < 0) { + uriRequest.headers().add(HttpHeaders.TRANSFER_ENCODING, + io.netty.handler.codec.http.HttpHeaders.Values.CHUNKED); + } else { + uriRequest.headers().add(HttpHeaders.CONTENT_LENGTH, + entity.getContentLength()); + } + } + // header + HttpHeaders headers = request.getHeaders(); + if (headers == null) { + headers = new HttpHeaders(); + } + if (!headers.containsKey(HttpHeaders.HOST)) { + headers.set(HttpHeaders.HOST, uri.getHost()); + } + // Add default accept headers + if (!headers.containsKey(HttpHeaders.ACCEPT)) { + headers.set(HttpHeaders.ACCEPT, "*/*"); + } + // Add default user agent + if (!headers.containsKey(HttpHeaders.USER_AGENT)) { + headers.set(HttpHeaders.USER_AGENT, "netty/httpclient"); + } + for (Iterator>> headerIterator = headers + .entrySet().iterator(); headerIterator.hasNext();) { + Entry> header = headerIterator.next(); + uriRequest.headers().add(header.getKey(), header.getValue()); + } + uriRequest.headers().set(HttpHeaders.CONNECTION, + io.netty.handler.codec.http.HttpHeaders.Values.CLOSE); + return uriRequest; + } + + private int getPort(URI uri) { + int port = uri.getPort(); + if (port == -1) { + if ("http".equalsIgnoreCase(uri.getScheme())) { + port = 80; + } else if ("https".equalsIgnoreCase(uri.getScheme())) { + port = 443; + } + } + return port; + } + + private static class RequestHandler extends + SimpleChannelInboundHandler { + + private final SettableFuture future; + + public RequestHandler(SettableFuture future) { + this.future = future; + } + + @Override + protected void channelRead0(ChannelHandlerContext context, + FullHttpResponse response) throws Exception { + byte[] content = null; + ByteBuf byteBuf = response.content(); + if (byteBuf.hasArray()) { + content = byteBuf.array(); + } else { + content = new byte[byteBuf.readableBytes()]; + byteBuf.readBytes(content); + } + future.set(new Netty4HttpResponse(context, response, content)); + } + + @Override + public void exceptionCaught(ChannelHandlerContext context, + Throwable cause) throws Exception { + future.setException(cause); + } + } +} diff --git a/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/factory/Netty4HttpClientFactory.java b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/factory/Netty4HttpClientFactory.java new file mode 100644 index 00000000..cb89eef8 --- /dev/null +++ b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/factory/Netty4HttpClientFactory.java @@ -0,0 +1,64 @@ +package com.foxinmy.weixin4j.http.factory; + +import io.netty.bootstrap.Bootstrap; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.ChannelOption; +import io.netty.channel.ChannelPipeline; +import io.netty.channel.EventLoopGroup; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.channel.socket.SocketChannel; +import io.netty.channel.socket.nio.NioSocketChannel; +import io.netty.handler.codec.http.HttpClientCodec; +import io.netty.handler.codec.http.HttpContentDecompressor; +import io.netty.handler.codec.http.HttpObjectAggregator; +import io.netty.handler.codec.http.HttpResponseDecoder; +import io.netty.handler.stream.ChunkedWriteHandler; + +import com.foxinmy.weixin4j.http.HttpClient; + +/** + * 使用Netty + * + * @className Netty4HttpClientFactory + * @author jy + * @date 2015年8月30日 + * @since JDK 1.7 + * @see + */ +public class Netty4HttpClientFactory extends HttpClientFactory { + /** + * worker线程数,默认设置为cpu的核数 * 4 + */ + private final int workerThreads; + + public Netty4HttpClientFactory() { + this(Runtime.getRuntime().availableProcessors() * 4); + } + + public Netty4HttpClientFactory(int workerThreads) { + this.workerThreads = workerThreads; + } + + @Override + public HttpClient newInstance() { + Bootstrap b = new Bootstrap(); + b.option(ChannelOption.SO_KEEPALIVE, true).option( + ChannelOption.TCP_NODELAY, true); + EventLoopGroup workerGroup = new NioEventLoopGroup(workerThreads); + b.group(workerGroup).channel(NioSocketChannel.class) + .handler(new ChannelInitializer() { + @Override + protected void initChannel(SocketChannel channel) + throws Exception { + ChannelPipeline pipeline = channel.pipeline(); + pipeline.addLast(new HttpClientCodec()); + pipeline.addLast(new HttpContentDecompressor()); + pipeline.addLast(new ChunkedWriteHandler()); + pipeline.addLast(new HttpResponseDecoder()); + pipeline.addLast(new HttpObjectAggregator( + Integer.MAX_VALUE)); + } + }); + return new Netty4HttpClient(b); + } +} diff --git a/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/factory/Netty4HttpResponse.java b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/factory/Netty4HttpResponse.java new file mode 100644 index 00000000..78092bfb --- /dev/null +++ b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/factory/Netty4HttpResponse.java @@ -0,0 +1,78 @@ +package com.foxinmy.weixin4j.http.factory; + +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.http.FullHttpResponse; +import io.netty.handler.codec.http.HttpResponseStatus; + +import java.util.Map; + +import com.foxinmy.weixin4j.http.AbstractHttpResponse; +import com.foxinmy.weixin4j.http.HttpHeaders; +import com.foxinmy.weixin4j.http.HttpStatus; +import com.foxinmy.weixin4j.http.HttpVersion; + +/** + * Netty Respone::Requires Netty 4.x or higher + * + * @className Netty4HttpResponse + * @author jy + * @date 2015年8月30日 + * @since JDK 1.7 + * @see + */ +public class Netty4HttpResponse extends AbstractHttpResponse { + + private final ChannelHandlerContext context; + private final FullHttpResponse response; + + private HttpVersion protocol; + private HttpStatus status; + private volatile HttpHeaders headers; + + public Netty4HttpResponse(ChannelHandlerContext context, + FullHttpResponse response, byte[] content) { + super(content); + this.context = context; + this.response = response; + this.response.retain(); + } + + @Override + public HttpHeaders getHeaders() { + if (this.headers == null) { + HttpHeaders headers = new HttpHeaders(); + for (Map.Entry entry : this.response.headers()) { + headers.add(entry.getKey(), entry.getValue()); + } + this.headers = headers; + } + return this.headers; + } + + @Override + public HttpVersion getProtocol() { + if (protocol == null) { + io.netty.handler.codec.http.HttpVersion version = response + .getProtocolVersion(); + this.protocol = new HttpVersion(version.protocolName(), + version.majorVersion(), version.majorVersion(), + version.isKeepAliveDefault()); + } + return protocol; + } + + @Override + public HttpStatus getStatus() { + if (status == null) { + HttpResponseStatus status = response.getStatus(); + this.status = new HttpStatus(status.code(), status.reasonPhrase()); + } + return status; + } + + @Override + public void close() { + this.response.release(); + this.context.close(); + } +} diff --git a/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/weixin/WeixinRequestExecutor.java b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/weixin/WeixinRequestExecutor.java index 9af976e5..e61ecedf 100644 --- a/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/weixin/WeixinRequestExecutor.java +++ b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/weixin/WeixinRequestExecutor.java @@ -20,6 +20,7 @@ import com.foxinmy.weixin4j.http.entity.FormUrlEntity; import com.foxinmy.weixin4j.http.entity.HttpEntity; import com.foxinmy.weixin4j.http.entity.StringEntity; import com.foxinmy.weixin4j.http.factory.HttpClientFactory; +import com.foxinmy.weixin4j.http.factory.Netty4HttpClientFactory; import com.foxinmy.weixin4j.model.Consts; import com.foxinmy.weixin4j.util.StringUtil; import com.foxinmy.weixin4j.util.WeixinErrorUtil; @@ -44,6 +45,7 @@ public class WeixinRequestExecutor { } public WeixinRequestExecutor(HttpParams params) { + HttpClientFactory.setDefaultFactory(new Netty4HttpClientFactory()); this.httpClient = HttpClientFactory.getInstance(); this.params = params; } @@ -97,10 +99,6 @@ public class WeixinRequestExecutor { HttpResponse httpResponse = httpClient.execute(request); HttpStatus status = httpResponse.getStatus(); HttpHeaders headers = httpResponse.getHeaders(); - if (status.getStatusCode() >= HttpStatus.SC_MULTIPLE_CHOICES) { - throw new WeixinException(String.format("request fail : %d-%s", - status.getStatusCode(), status.getStatusText())); - } WeixinResponse response = new WeixinResponse(headers, status, httpResponse.getBody()); String contentType = headers.getFirst(HttpHeaders.CONTENT_TYPE); diff --git a/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/weixin/WeixinResponse.java b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/weixin/WeixinResponse.java index 1e17d5b6..55b06cc0 100644 --- a/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/weixin/WeixinResponse.java +++ b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/weixin/WeixinResponse.java @@ -42,6 +42,7 @@ public class WeixinResponse { try { text = StringUtil.newStringUtf8(IOUtil.toByteArray(body)); } catch (IOException e) { + e.printStackTrace(); ; } } diff --git a/weixin4j-base/src/main/java/com/foxinmy/weixin4j/util/SettableFuture.java b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/util/SettableFuture.java new file mode 100644 index 00000000..f4d3b2f3 --- /dev/null +++ b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/util/SettableFuture.java @@ -0,0 +1,191 @@ +/* + * Copyright 2002-2014 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.foxinmy.weixin4j.util; + +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.concurrent.FutureTask; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicReference; + +/** + * A {@link org.springframework.util.concurrent.ListenableFuture + * ListenableFuture} whose value can be set via {@link #set(Object)} or + * {@link #setException(Throwable)}. It may also be cancelled. + * + *

+ * Inspired by {@code com.google.common.util.concurrent.SettableFuture}. + * + * @author Mattias Severson + * @author Rossen Stoyanchev + * @since 4.1 + */ +public class SettableFuture implements Future { + + private final SettableTask settableTask; + + private final FutureTask futureTask; + + public SettableFuture() { + this.settableTask = new SettableTask(); + this.futureTask = new FutureTask(this.settableTask); + } + + /** + * Set the value of this future. This method will return {@code true} if the + * value was set successfully, or {@code false} if the future has already + * been set or cancelled. + * + * @param value + * the value that will be set. + * @return {@code true} if the value was successfully set, else + * {@code false}. + */ + public boolean set(T value) { + boolean success = this.settableTask.setValue(value); + if (success) { + this.futureTask.run(); + } + return success; + } + + /** + * Set the exception of this future. This method will return {@code true} if + * the exception was set successfully, or {@code false} if the future has + * already been set or cancelled. + * + * @param exception + * the value that will be set. + * @return {@code true} if the exception was successfully set, else + * {@code false}. + */ + public boolean setException(Throwable exception) { + boolean success = this.settableTask.setException(exception); + if (success) { + this.futureTask.run(); + } + return success; + } + + @Override + public boolean cancel(boolean mayInterruptIfRunning) { + this.settableTask.setCancelled(); + boolean cancelled = this.futureTask.cancel(mayInterruptIfRunning); + if (cancelled && mayInterruptIfRunning) { + interruptTask(); + } + return cancelled; + } + + @Override + public boolean isCancelled() { + return this.futureTask.isCancelled(); + } + + @Override + public boolean isDone() { + return this.futureTask.isDone(); + } + + /** + * Retrieve the value. + *

+ * Will return the value if it has been set via {@link #set(Object)}, throw + * an {@link java.util.concurrent.ExecutionException} if it has been set via + * {@link #setException(Throwable)} or throw a + * {@link java.util.concurrent.CancellationException} if it has been + * cancelled. + * + * @return The value associated with this future. + */ + @Override + public T get() throws InterruptedException, ExecutionException { + return this.futureTask.get(); + } + + /** + * Retrieve the value. + *

+ * Will return the value if it has been set via {@link #set(Object)}, throw + * an {@link java.util.concurrent.ExecutionException} if it has been set via + * {@link #setException(Throwable)} or throw a + * {@link java.util.concurrent.CancellationException} if it has been + * cancelled. + * + * @param timeout + * the maximum time to wait. + * @param unit + * the time unit of the timeout argument. + * @return The value associated with this future. + */ + @Override + public T get(long timeout, TimeUnit unit) throws InterruptedException, + ExecutionException, TimeoutException { + return this.futureTask.get(timeout, unit); + } + + /** + * Subclasses can override this method to implement interruption of the + * future's computation. The method is invoked automatically by a successful + * call to {@link #cancel(boolean) cancel(true)}. + *

+ * The default implementation does nothing. + */ + protected void interruptTask() { + } + + private static class SettableTask implements Callable { + + private static final String NO_VALUE = SettableFuture.class + .getName() + ".NO_VALUE"; + + private final AtomicReference value = new AtomicReference( + NO_VALUE); + + private volatile boolean cancelled = false; + + public boolean setValue(T value) { + if (this.cancelled) { + return false; + } + return this.value.compareAndSet(NO_VALUE, value); + } + + public boolean setException(Throwable exception) { + if (this.cancelled) { + return false; + } + return this.value.compareAndSet(NO_VALUE, exception); + } + + public void setCancelled() { + this.cancelled = true; + } + + @SuppressWarnings("unchecked") + @Override + public T call() throws Exception { + if (value.get() instanceof Exception) { + throw (Exception) value.get(); + } + return (T) value.get(); + } + } + +} diff --git a/weixin4j-base/src/test/java/com/foxinmy/weixin4j/base/test/HttpClientTest.java b/weixin4j-base/src/test/java/com/foxinmy/weixin4j/base/test/HttpClientTest.java index 17df70c2..bfe3e182 100644 --- a/weixin4j-base/src/test/java/com/foxinmy/weixin4j/base/test/HttpClientTest.java +++ b/weixin4j-base/src/test/java/com/foxinmy/weixin4j/base/test/HttpClientTest.java @@ -1,6 +1,9 @@ package com.foxinmy.weixin4j.base.test; -import org.apache.commons.httpclient.methods.GetMethod; +import java.io.IOException; +import java.net.InetSocketAddress; +import java.net.Proxy; +import java.net.Proxy.Type; import com.foxinmy.weixin4j.http.HttpClient; import com.foxinmy.weixin4j.http.HttpClientException; @@ -11,11 +14,7 @@ import com.foxinmy.weixin4j.http.HttpResponse; import com.foxinmy.weixin4j.http.factory.HttpClientFactory; import com.foxinmy.weixin4j.http.factory.HttpComponent3Factory; import com.foxinmy.weixin4j.http.factory.HttpComponent4Factory; -import com.foxinmy.weixin4j.http.factory.SimpleHttpClientFactory; -import com.foxinmy.weixin4j.model.WeixinAccount; -import com.foxinmy.weixin4j.token.FileTokenStorager; -import com.foxinmy.weixin4j.token.TokenHolder; -import com.foxinmy.weixin4j.util.Weixin4jConfigUtil; +import com.foxinmy.weixin4j.util.IOUtil; public class HttpClientTest { @@ -23,13 +22,12 @@ public class HttpClientTest { "https://www.baidu.com"); static { HttpParams params = new HttpParams(); - //params.setProxy(new Proxy(Type.HTTP, new InetSocketAddress( - // "218.92.227.170", 11095))); + params.setProxy(new Proxy(Type.HTTP, new InetSocketAddress( + "117.136.234.9", 80))); request.setParams(params); } public static void test1() throws HttpClientException { - HttpClientFactory.setDefaultFactory(new SimpleHttpClientFactory()); HttpClient httpClient = HttpClientFactory.getInstance(); HttpResponse response = httpClient.execute(request); print(response); @@ -51,28 +49,23 @@ public class HttpClientTest { public static void print(HttpResponse response) throws HttpClientException { System.err.println(response.getStatus()); - System.err.println(response.getBody()); + try { + System.err.println(new String( + IOUtil.toByteArray(response.getBody()))); + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } System.err.println(response.getHeaders()); System.err.println(response.getProtocol()); } - public static void test() { - org.apache.commons.httpclient.HttpClient hc = new org.apache.commons.httpclient.HttpClient(); - hc.getHostConfiguration().setProxy("127.0.0.1", 1080); - org.apache.commons.httpclient.HttpMethod hm = new GetMethod( - "http://www.baidu.com"); - try { - hc.executeMethod(hm); - } catch (Exception e) { - e.printStackTrace(); - } - } - public static void main(String[] args) throws Exception { - //test1(); + test1(); System.out.println("---------------------"); - //test2(); + // test2(); System.out.println("---------------------"); - //test3(); + // test3(); + // test4(); } } diff --git a/weixin4j-mp/CHANGE.md b/weixin4j-mp/CHANGE.md index 362d7ed8..f4327780 100644 --- a/weixin4j-mp/CHANGE.md +++ b/weixin4j-mp/CHANGE.md @@ -148,4 +148,8 @@ + version upgrade to 1.5.3 - + 媒体接口类(MediaApi)查询素材接口调整:去掉offset,count替换为Pageable类 \ No newline at end of file + + 媒体接口类(MediaApi)查询素材接口调整:去掉offset,count替换为Pageable类 + +* 2015-09-08 + + + 新增批量获取用户信息接口 \ No newline at end of file diff --git a/weixin4j-mp/src/main/java/com/foxinmy/weixin4j/mp/type/Lang.java b/weixin4j-mp/src/main/java/com/foxinmy/weixin4j/mp/type/Lang.java index 0b9790ce..9d12f8bb 100644 --- a/weixin4j-mp/src/main/java/com/foxinmy/weixin4j/mp/type/Lang.java +++ b/weixin4j-mp/src/main/java/com/foxinmy/weixin4j/mp/type/Lang.java @@ -1,5 +1,6 @@ package com.foxinmy.weixin4j.mp.type; + /** * 国家地区语言版本 * @className Lang diff --git a/weixin4j-server/README.md b/weixin4j-server/README.md index 61450065..6d60596a 100644 --- a/weixin4j-server/README.md +++ b/weixin4j-server/README.md @@ -54,7 +54,7 @@ base on netty. } }; // 当消息类型为文本(text)时回复「HelloWorld」, 否则回复调试消息 - new WeixinServerBootstrap(token,appid, aesKey).addHandler( + new WeixinServerBootstrap(token, appid, aesKey).addHandler( messageHandler, DebugMessageHandler.global).startup(); } } diff --git a/weixin4j-server/src/main/java/com/foxinmy/weixin4j/mp/event/ScanEventMessage.java b/weixin4j-server/src/main/java/com/foxinmy/weixin4j/mp/event/ScanEventMessage.java index 27ed5e21..5a1ca3af 100644 --- a/weixin4j-server/src/main/java/com/foxinmy/weixin4j/mp/event/ScanEventMessage.java +++ b/weixin4j-server/src/main/java/com/foxinmy/weixin4j/mp/event/ScanEventMessage.java @@ -47,7 +47,7 @@ public class ScanEventMessage extends EventMessage { } public String getParameter() { - return eventKey.replace("qrscene_", ""); + return eventKey.replaceFirst("qrscene_", ""); } @Override