有一天,客户告诉我他们的HTTPS服务,采用了gzip方式进行压缩,因此我理所当然认为我这边需要对gzip的数据进行解压。
方案如下:
1,判断response header中是否有Content-Encoding 。如果为 gzip,则对其进行解压处理。
/**
* Get 请求
*/
public static void sendGet(String url) throws IOException {
SSLClient httpClient = null;
HttpGet httpGet = null;
CloseableHttpResponse response = null;
String result = null;
try {
//这里因为是HTTPS,所以这里HttpClient有特殊处理
httpClient = new SSLClient();
httpGet = new HttpGet(url);
Map<String, String> params = new HashMap<>(4);
params.put("Accept-Encoding", "gzip");
//设置header,告知服务端,本客户端支持gzip压缩格式
setHeader(httpGet, params);
response = httpClient.execute(httpGet);
HttpEntity resEntity = response.getEntity();
Header contentEncoding = response.getFirstHeader("Content-Encoding");
if (contentEncoding != null && contentEncoding.getValue().equalsIgnoreCase("gzip")) {
//如果返回的response header中有Content-Encoding项,且值为gzip,则进行解压处理
InputStream is = new GZIPInputStream(new BufferedInputStream(resEntity.getContent()));
StringBuffer out = new StringBuffer();
byte[] b = new byte[4096];
for (int n; (n = is.read(b)) != -1; ) {
out.append(new String(b, 0, n));
}
String strResult = out.toString();
System.out.println(strResult);
} else {
if (resEntity != null) {
result = EntityUtils.toString(resEntity, "utf-8");
System.out.println(result);
}
EntityUtils.consume(resEntity);
}
} catch (Exception ex) {
ex.printStackTrace();
} finally {
try {
response.close();
} catch (IOException e) {
e.printStackTrace();
}
httpClient.close();
}
}
/**
* set HttpGet method header
*
* @param httpGet
* @param params
*/
private static void setHeader(HttpGet httpGet, Map<String, String> params) {
if (null == httpGet) {
throw new IllegalArgumentException("httpGet is null.");
}
if (!MapUtils.isEmpty(params)) {
for (Map.Entry<String, String> entry : params.entrySet()) {
httpGet.addHeader(entry.getKey(), entry.getValue());
}
}
}
SSLClient:
package com.hello.https;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import org.apache.http.conn.ClientConnectionManager;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.scheme.SchemeRegistry;
import org.apache.http.conn.ssl.SSLSocketFactory;
import org.apache.http.impl.client.DefaultHttpClient;
public class SSLClient extends DefaultHttpClient {
public SSLClient() throws Exception{
super();
SSLContext ctx = SSLContext.getInstance("TLS");
X509TrustManager tm = new X509TrustManager() {
@Override
public void checkClientTrusted(X509Certificate[] chain,
String authType) throws CertificateException {
}
@Override
public void checkServerTrusted(X509Certificate[] chain,
String authType) throws CertificateException {
}
@Override
public X509Certificate[] getAcceptedIssuers() {
return null;
}
};
ctx.init(null, new TrustManager[]{tm}, null);
SSLSocketFactory ssf = new SSLSocketFactory(ctx,SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
ClientConnectionManager ccm = this.getConnectionManager();
SchemeRegistry sr = ccm.getSchemeRegistry();
sr.register(new Scheme("https", 443, ssf));
}
}
2,可以利用HttpClient中的ResponseContentEncoding进行自动解压
/*
* ====================================================================
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Apache Software Foundation. For more
* information on the Apache Software Foundation, please see
* <http://www.apache.org/>.
*
*/
package org.apache.http.client.protocol;
import java.io.IOException;
import java.io.InputStream;
import java.util.Locale;
import java.util.zip.GZIPInputStream;
import org.apache.http.Header;
import org.apache.http.HeaderElement;
import org.apache.http.HttpEntity;
import org.apache.http.HttpException;
import org.apache.http.HttpResponse;
import org.apache.http.HttpResponseInterceptor;
import org.apache.http.annotation.Immutable;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.DecompressingEntity;
import org.apache.http.client.entity.DeflateInputStream;
import org.apache.http.client.entity.InputStreamFactory;
import org.apache.http.config.Lookup;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.protocol.HttpContext;
/**
* {@link HttpResponseInterceptor} responsible for processing Content-Encoding
* responses.
* <p>
* Instances of this class are stateless and immutable, therefore threadsafe.
*
* @since 4.1
*
*/
@Immutable
public class ResponseContentEncoding implements HttpResponseInterceptor {
public static final String UNCOMPRESSED = "http.client.response.uncompressed";
private final static InputStreamFactory GZIP = new InputStreamFactory() {
@Override
public InputStream create(final InputStream instream) throws IOException {
return new GZIPInputStream(instream);
}
};
private final static InputStreamFactory DEFLATE = new InputStreamFactory() {
@Override
public InputStream create(final InputStream instream) throws IOException {
return new DeflateInputStream(instream);
}
};
private final Lookup<InputStreamFactory> decoderRegistry;
private final boolean ignoreUnknown;
/**
* @since 4.5
*/
public ResponseContentEncoding(final Lookup<InputStreamFactory> decoderRegistry, final boolean ignoreUnknown) {
this.decoderRegistry = decoderRegistry != null ? decoderRegistry :
RegistryBuilder.<InputStreamFactory>create()
.register("gzip", GZIP)
.register("x-gzip", GZIP)
.register("deflate", DEFLATE)
.build();
this.ignoreUnknown = ignoreUnknown;
}
/**
* @since 4.5
*/
public ResponseContentEncoding(final boolean ignoreUnknown) {
this(null, ignoreUnknown);
}
/**
* @since 4.4
*/
public ResponseContentEncoding(final Lookup<InputStreamFactory> decoderRegistry) {
this(decoderRegistry, true);
}
/**
* Handles {@code gzip} and {@code deflate} compressed entities by using the following
* decoders:
* <ul>
* <li>gzip - see {@link GZIPInputStream}</li>
* <li>deflate - see {@link DeflateInputStream}</li>
* </ul>
*/
public ResponseContentEncoding() {
this(null);
}
@Override
public void process(
final HttpResponse response,
final HttpContext context) throws HttpException, IOException {
final HttpEntity entity = response.getEntity();
final HttpClientContext clientContext = HttpClientContext.adapt(context);
final RequestConfig requestConfig = clientContext.getRequestConfig();
// entity can be null in case of 304 Not Modified, 204 No Content or similar
// check for zero length entity.
if (requestConfig.isContentCompressionEnabled() && entity != null && entity.getContentLength() != 0) {
final Header ceheader = entity.getContentEncoding();
if (ceheader != null) {
final HeaderElement[] codecs = ceheader.getElements();
for (final HeaderElement codec : codecs) {
final String codecname = codec.getName().toLowerCase(Locale.ROOT);
final InputStreamFactory decoderFactory = decoderRegistry.lookup(codecname);
if (decoderFactory != null) {
response.setEntity(new DecompressingEntity(response.getEntity(), decoderFactory));
response.removeHeaders("Content-Length");
response.removeHeaders("Content-Encoding");
response.removeHeaders("Content-MD5");
} else {
if (!"identity".equals(codecname) && !ignoreUnknown) {
throw new HttpException("Unsupported Content-Encoding: " + codec.getName());
}
}
}
}
}
}
}
我们在获取HttpClient对象时,通过如下方式即可创建能自动处理服务器端进行压缩过的数据的HttpClient
CloseableHttpClient httpclient = HttpClients.custom()
.setSSLSocketFactory(sslsf) //这里因为要请求的是https服务,所以这样处理,后面会写文介绍此处
.build();
为什么要这样操作呢?那是因为HttpClients.custom()方法会返回一个HttpClientBuilder对象,这个HttpClientBuilder对象用于构建httpclient 对象。我们不妨看看HttpClientBuilder类的build方法:
public CloseableHttpClient build() {
// Create main request executor
// We copy the instance fields to avoid changing them, and rename to avoid accidental use of the wrong version
PublicSuffixMatcher publicSuffixMatcherCopy = this.publicSuffixMatcher;
if (publicSuffixMatcherCopy == null) {
publicSuffixMatcherCopy = PublicSuffixMatcherLoader.getDefault();
}
HttpRequestExecutor requestExecCopy = this.requestExec;
if (requestExecCopy == null) {
requestExecCopy = new HttpRequestExecutor();
}
HttpClientConnectionManager connManagerCopy = this.connManager;
if (connManagerCopy == null) {
LayeredConnectionSocketFactory sslSocketFactoryCopy = this.sslSocketFactory;
if (sslSocketFactoryCopy == null) {
final String[] supportedProtocols = systemProperties ? split(
System.getProperty("https.protocols")) : null;
final String[] supportedCipherSuites = systemProperties ? split(
System.getProperty("https.cipherSuites")) : null;
HostnameVerifier hostnameVerifierCopy = this.hostnameVerifier;
if (hostnameVerifierCopy == null) {
hostnameVerifierCopy = new DefaultHostnameVerifier(publicSuffixMatcherCopy);
}
if (sslContext != null) {
sslSocketFactoryCopy = new SSLConnectionSocketFactory(
sslContext, supportedProtocols, supportedCipherSuites, hostnameVerifierCopy);
} else {
if (systemProperties) {
sslSocketFactoryCopy = new SSLConnectionSocketFactory(
(SSLSocketFactory) SSLSocketFactory.getDefault(),
supportedProtocols, supportedCipherSuites, hostnameVerifierCopy);
} else {
sslSocketFactoryCopy = new SSLConnectionSocketFactory(
SSLContexts.createDefault(),
hostnameVerifierCopy);
}
}
}
@SuppressWarnings("resource")
final PoolingHttpClientConnectionManager poolingmgr = new PoolingHttpClientConnectionManager(
RegistryBuilder.<ConnectionSocketFactory>create()
.register("http", PlainConnectionSocketFactory.getSocketFactory())
.register("https", sslSocketFactoryCopy)
.build(),
null,
null,
null,
connTimeToLive,
connTimeToLiveTimeUnit != null ? connTimeToLiveTimeUnit : TimeUnit.MILLISECONDS);
if (defaultSocketConfig != null) {
poolingmgr.setDefaultSocketConfig(defaultSocketConfig);
}
if (defaultConnectionConfig != null) {
poolingmgr.setDefaultConnectionConfig(defaultConnectionConfig);
}
if (systemProperties) {
String s = System.getProperty("http.keepAlive", "true");
if ("true".equalsIgnoreCase(s)) {
s = System.getProperty("http.maxConnections", "5");
final int max = Integer.parseInt(s);
poolingmgr.setDefaultMaxPerRoute(max);
poolingmgr.setMaxTotal(2 * max);
}
}
if (maxConnTotal > 0) {
poolingmgr.setMaxTotal(maxConnTotal);
}
if (maxConnPerRoute > 0) {
poolingmgr.setDefaultMaxPerRoute(maxConnPerRoute);
}
connManagerCopy = poolingmgr;
}
ConnectionReuseStrategy reuseStrategyCopy = this.reuseStrategy;
if (reuseStrategyCopy == null) {
if (systemProperties) {
final String s = System.getProperty("http.keepAlive", "true");
if ("true".equalsIgnoreCase(s)) {
reuseStrategyCopy = DefaultConnectionReuseStrategy.INSTANCE;
} else {
reuseStrategyCopy = NoConnectionReuseStrategy.INSTANCE;
}
} else {
reuseStrategyCopy = DefaultConnectionReuseStrategy.INSTANCE;
}
}
ConnectionKeepAliveStrategy keepAliveStrategyCopy = this.keepAliveStrategy;
if (keepAliveStrategyCopy == null) {
keepAliveStrategyCopy = DefaultConnectionKeepAliveStrategy.INSTANCE;
}
AuthenticationStrategy targetAuthStrategyCopy = this.targetAuthStrategy;
if (targetAuthStrategyCopy == null) {
targetAuthStrategyCopy = TargetAuthenticationStrategy.INSTANCE;
}
AuthenticationStrategy proxyAuthStrategyCopy = this.proxyAuthStrategy;
if (proxyAuthStrategyCopy == null) {
proxyAuthStrategyCopy = ProxyAuthenticationStrategy.INSTANCE;
}
UserTokenHandler userTokenHandlerCopy = this.userTokenHandler;
if (userTokenHandlerCopy == null) {
if (!connectionStateDisabled) {
userTokenHandlerCopy = DefaultUserTokenHandler.INSTANCE;
} else {
userTokenHandlerCopy = NoopUserTokenHandler.INSTANCE;
}
}
String userAgentCopy = this.userAgent;
if (userAgentCopy == null) {
if (systemProperties) {
userAgentCopy = System.getProperty("http.agent");
}
if (userAgentCopy == null) {
userAgentCopy = VersionInfo.getUserAgent("Apache-HttpClient",
"org.apache.http.client", getClass());
}
}
ClientExecChain execChain = createMainExec(
requestExecCopy,
connManagerCopy,
reuseStrategyCopy,
keepAliveStrategyCopy,
new ImmutableHttpProcessor(new RequestTargetHost(), new RequestUserAgent(userAgentCopy)),
targetAuthStrategyCopy,
proxyAuthStrategyCopy,
userTokenHandlerCopy);
execChain = decorateMainExec(execChain);
HttpProcessor httpprocessorCopy = this.httpprocessor;
if (httpprocessorCopy == null) {
final HttpProcessorBuilder b = HttpProcessorBuilder.create();
if (requestFirst != null) {
for (final HttpRequestInterceptor i: requestFirst) {
b.addFirst(i);
}
}
if (responseFirst != null) {
for (final HttpResponseInterceptor i: responseFirst) {
b.addFirst(i);
}
}
b.addAll(
new RequestDefaultHeaders(defaultHeaders),
new RequestContent(),
new RequestTargetHost(),
new RequestClientConnControl(),
new RequestUserAgent(userAgentCopy),
new RequestExpectContinue());
if (!cookieManagementDisabled) {
b.add(new RequestAddCookies());
}
if (!contentCompressionDisabled) {
if (contentDecoderMap != null) {
final List<String> encodings = new ArrayList<String>(contentDecoderMap.keySet());
Collections.sort(encodings);
b.add(new RequestAcceptEncoding(encodings));
} else {
b.add(new RequestAcceptEncoding());
}
}
if (!authCachingDisabled) {
b.add(new RequestAuthCache());
}
if (!cookieManagementDisabled) {
b.add(new ResponseProcessCookies());
}
if (!contentCompressionDisabled) {
if (contentDecoderMap != null) {
final RegistryBuilder<InputStreamFactory> b2 = RegistryBuilder.create();
for (Map.Entry<String, InputStreamFactory> entry: contentDecoderMap.entrySet()) {
b2.register(entry.getKey(), entry.getValue());
}
b.add(new ResponseContentEncoding(b2.build()));
} else {
b.add(new ResponseContentEncoding());
}
}
if (requestLast != null) {
for (final HttpRequestInterceptor i: requestLast) {
b.addLast(i);
}
}
if (responseLast != null) {
for (final HttpResponseInterceptor i: responseLast) {
b.addLast(i);
}
}
httpprocessorCopy = b.build();
}
execChain = new ProtocolExec(execChain, httpprocessorCopy);
execChain = decorateProtocolExec(execChain);
// Add request retry executor, if not disabled
if (!automaticRetriesDisabled) {
HttpRequestRetryHandler retryHandlerCopy = this.retryHandler;
if (retryHandlerCopy == null) {
retryHandlerCopy = DefaultHttpRequestRetryHandler.INSTANCE;
}
execChain = new RetryExec(execChain, retryHandlerCopy);
}
HttpRoutePlanner routePlannerCopy = this.routePlanner;
if (routePlannerCopy == null) {
SchemePortResolver schemePortResolverCopy = this.schemePortResolver;
if (schemePortResolverCopy == null) {
schemePortResolverCopy = DefaultSchemePortResolver.INSTANCE;
}
if (proxy != null) {
routePlannerCopy = new DefaultProxyRoutePlanner(proxy, schemePortResolverCopy);
} else if (systemProperties) {
routePlannerCopy = new SystemDefaultRoutePlanner(
schemePortResolverCopy, ProxySelector.getDefault());
} else {
routePlannerCopy = new DefaultRoutePlanner(schemePortResolverCopy);
}
}
// Add redirect executor, if not disabled
if (!redirectHandlingDisabled) {
RedirectStrategy redirectStrategyCopy = this.redirectStrategy;
if (redirectStrategyCopy == null) {
redirectStrategyCopy = DefaultRedirectStrategy.INSTANCE;
}
execChain = new RedirectExec(execChain, routePlannerCopy, redirectStrategyCopy);
}
// Optionally, add service unavailable retry executor
final ServiceUnavailableRetryStrategy serviceUnavailStrategyCopy = this.serviceUnavailStrategy;
if (serviceUnavailStrategyCopy != null) {
execChain = new ServiceUnavailableRetryExec(execChain, serviceUnavailStrategyCopy);
}
// Optionally, add connection back-off executor
if (this.backoffManager != null && this.connectionBackoffStrategy != null) {
execChain = new BackoffStrategyExec(execChain, this.connectionBackoffStrategy, this.backoffManager);
}
Lookup<AuthSchemeProvider> authSchemeRegistryCopy = this.authSchemeRegistry;
if (authSchemeRegistryCopy == null) {
authSchemeRegistryCopy = RegistryBuilder.<AuthSchemeProvider>create()
.register(AuthSchemes.BASIC, new BasicSchemeFactory())
.register(AuthSchemes.DIGEST, new DigestSchemeFactory())
.register(AuthSchemes.NTLM, new NTLMSchemeFactory())
.register(AuthSchemes.SPNEGO, new SPNegoSchemeFactory())
.register(AuthSchemes.KERBEROS, new KerberosSchemeFactory())
.build();
}
Lookup<CookieSpecProvider> cookieSpecRegistryCopy = this.cookieSpecRegistry;
if (cookieSpecRegistryCopy == null) {
cookieSpecRegistryCopy = CookieSpecRegistries.createDefault(publicSuffixMatcherCopy);
}
CookieStore defaultCookieStore = this.cookieStore;
if (defaultCookieStore == null) {
defaultCookieStore = new BasicCookieStore();
}
CredentialsProvider defaultCredentialsProvider = this.credentialsProvider;
if (defaultCredentialsProvider == null) {
if (systemProperties) {
defaultCredentialsProvider = new SystemDefaultCredentialsProvider();
} else {
defaultCredentialsProvider = new BasicCredentialsProvider();
}
}
List<Closeable> closeablesCopy = closeables != null ? new ArrayList<Closeable>(closeables) : null;
if (!this.connManagerShared) {
if (closeablesCopy == null) {
closeablesCopy = new ArrayList<Closeable>(1);
}
final HttpClientConnectionManager cm = connManagerCopy;
if (evictExpiredConnections || evictIdleConnections) {
final IdleConnectionEvictor connectionEvictor = new IdleConnectionEvictor(cm,
maxIdleTime > 0 ? maxIdleTime : 10, maxIdleTimeUnit != null ? maxIdleTimeUnit : TimeUnit.SECONDS);
closeablesCopy.add(new Closeable() {
@Override
public void close() throws IOException {
connectionEvictor.shutdown();
}
});
connectionEvictor.start();
}
closeablesCopy.add(new Closeable() {
@Override
public void close() throws IOException {
cm.shutdown();
}
});
}
return new InternalHttpClient(
execChain,
connManagerCopy,
routePlannerCopy,
cookieSpecRegistryCopy,
authSchemeRegistryCopy,
defaultCookieStore,
defaultCredentialsProvider,
defaultRequestConfig != null ? defaultRequestConfig : RequestConfig.DEFAULT,
closeablesCopy);
}
其中关键点是
image.png
我们在看看new ResponseContentEncoding():
这里有对gzip压缩格式进行处理。
HttpClientBuilder中 image.png
HttpProcessorBuilder image.png
我们可以看到其有两种类型的拦截器链,一个是用于请求的request,一个是用于响应的response,而ResponseContentEncoding就是response中的一个拦截器,用于处理压缩数据的。
综上,我自己推测,HttpClient通过这种方法自己设置了用于解压服务器端压缩过的gzip数据。
网友评论