Merged /httpcomponents/httpclient/branches/branch_4_1:r755593-811107
git-svn-id: https://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk@811110 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
parent
9ce0d5d761
commit
d384ec4e3a
|
@ -1,3 +1,10 @@
|
|||
Changes since 4.0
|
||||
-------------------
|
||||
|
||||
* [HTTPCLIENT-834] Transparent content encoding support.
|
||||
Contributed by James Abley <james.abley at gmail.com>
|
||||
|
||||
|
||||
Release 4.0
|
||||
-------------------
|
||||
|
||||
|
|
|
@ -0,0 +1,109 @@
|
|||
/*
|
||||
* $HeadURL$
|
||||
* $Revision$
|
||||
* $Date$
|
||||
*
|
||||
* ====================================================================
|
||||
* 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.impl.client;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.apache.http.Header;
|
||||
import org.apache.http.HeaderElement;
|
||||
import org.apache.http.HttpEntity;
|
||||
import org.apache.http.HttpException;
|
||||
import org.apache.http.HttpRequest;
|
||||
import org.apache.http.HttpRequestInterceptor;
|
||||
import org.apache.http.HttpResponse;
|
||||
import org.apache.http.HttpResponseInterceptor;
|
||||
import org.apache.http.protocol.HttpContext;
|
||||
|
||||
/**
|
||||
* Class responsible for handling Content Encoding in HTTP. This takes the form of
|
||||
* an {@link HttpRequestInterceptor} that will modify the {@link HttpRequest} if the client hasn't
|
||||
* already specified an <code>Accept-Encoding</code> header. There is an accompanying
|
||||
* {@link HttpResponseInterceptor} implementation that will only examine the {@link HttpResponse}
|
||||
* if the {@link HttpRequestInterceptor} implementation did any modifications.
|
||||
* <p>
|
||||
* Instances of this class are stateless, therefore they're thread-safe and immutable.
|
||||
*
|
||||
* @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.5
|
||||
*/
|
||||
class ContentEncodingProcessor implements HttpResponseInterceptor, HttpRequestInterceptor {
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public void process(
|
||||
HttpRequest request, HttpContext context) throws HttpException, IOException {
|
||||
|
||||
/*
|
||||
* If a client of this library has already set this header, presume that they did so for
|
||||
* a reason and so this instance shouldn't handle the response at all.
|
||||
*/
|
||||
if (!request.containsHeader("Accept-Encoding")) {
|
||||
|
||||
/* Signal support for Accept-Encoding transfer encodings. */
|
||||
// TODO add compress support.
|
||||
request.addHeader("Accept-Encoding", "gzip,deflate");
|
||||
|
||||
/* Store the fact that the request was modified, so that we can potentially handle
|
||||
* the response. */
|
||||
context.setAttribute(ContentEncodingProcessor.class.getName(), Boolean.TRUE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public void process(
|
||||
HttpResponse response, HttpContext context) throws HttpException, IOException {
|
||||
|
||||
if (context.getAttribute(ContentEncodingProcessor.class.getName()) != null) {
|
||||
HttpEntity entity = response.getEntity();
|
||||
|
||||
if (entity != null) { // It wasn't a 304 Not Modified response, 204 No Content or similar
|
||||
Header ceheader = entity.getContentEncoding();
|
||||
if (ceheader != null) {
|
||||
HeaderElement[] codecs = ceheader.getElements();
|
||||
for (int i = 0, n = codecs.length; i < n; ++i) {
|
||||
if ("gzip".equalsIgnoreCase(codecs[i].getName())) {
|
||||
response.setEntity(new GzipDecompressingEntity(response.getEntity()));
|
||||
return;
|
||||
} else if ("deflate".equalsIgnoreCase(codecs[i].getName())) {
|
||||
response.setEntity(new DeflateDecompressingEntity(response.getEntity()));
|
||||
return;
|
||||
}
|
||||
// TODO add compress. identity is a no-op.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,174 @@
|
|||
/*
|
||||
* $HeadURL$
|
||||
* $Revision$
|
||||
* $Date$
|
||||
*
|
||||
* ====================================================================
|
||||
* 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.impl.client;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.PushbackInputStream;
|
||||
import java.util.zip.DataFormatException;
|
||||
import java.util.zip.Inflater;
|
||||
import java.util.zip.InflaterInputStream;
|
||||
|
||||
import org.apache.http.Header;
|
||||
import org.apache.http.HttpEntity;
|
||||
import org.apache.http.entity.HttpEntityWrapper;
|
||||
|
||||
/**
|
||||
* {@link HttpEntityWrapper} responsible for handling deflate Content Coded responses. In RFC2616
|
||||
* terms, <code>deflate</code> means a <code>zlib</code> stream as defined in RFC1950. Some server
|
||||
* implementations have misinterpreted RFC2616 to mean that a <code>deflate</code> stream as
|
||||
* defined in RFC1951 should be used (or maybe they did that since that's how IE behaves?). It's
|
||||
* confusing that <code>deflate</code> in HTTP 1.1 means <code>zlib</code> streams rather than
|
||||
* <code>deflate</code> streams. We handle both types in here, since that's what is seen on the
|
||||
* internet. Moral - prefer <code>gzip</code>!
|
||||
*/
|
||||
class DeflateDecompressingEntity extends HttpEntityWrapper {
|
||||
|
||||
/**
|
||||
* Creates a new {@link DeflateDecompressingEntity} which will wrap the specified
|
||||
* {@link HttpEntity}.
|
||||
*
|
||||
* @param entity
|
||||
* a non-null {@link HttpEntity} to be wrapped
|
||||
*/
|
||||
public DeflateDecompressingEntity(final HttpEntity entity) {
|
||||
super(entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public InputStream getContent() throws IOException {
|
||||
InputStream wrapped = this.wrappedEntity.getContent();
|
||||
|
||||
/*
|
||||
* A zlib stream will have a header.
|
||||
*
|
||||
* CMF | FLG [| DICTID ] | ...compressed data | ADLER32 |
|
||||
*
|
||||
* * CMF is one byte.
|
||||
*
|
||||
* * FLG is one byte.
|
||||
*
|
||||
* * DICTID is four bytes, and only present if FLG.FDICT is set.
|
||||
*
|
||||
* Sniff the content. Does it look like a zlib stream, with a CMF, etc? c.f. RFC1950,
|
||||
* section 2.2. http://tools.ietf.org/html/rfc1950#page-4
|
||||
*
|
||||
* We need to see if it looks like a proper zlib stream, or whether it is just a deflate
|
||||
* stream. RFC2616 calls zlib streams deflate. Confusing, isn't it? That's why some servers
|
||||
* implement deflate Content-Encoding using deflate streams, rather than zlib streams.
|
||||
*
|
||||
* We could start looking at the bytes, but to be honest, someone else has already read
|
||||
* the RFCs and implemented that for us. So we'll just use the JDK libraries and exception
|
||||
* handling to do this. If that proves slow, then we could potentially change this to check
|
||||
* the first byte - does it look like a CMF? What about the second byte - does it look like
|
||||
* a FLG, etc.
|
||||
*/
|
||||
|
||||
/* We read a small buffer to sniff the content. */
|
||||
byte[] peeked = new byte[6];
|
||||
|
||||
PushbackInputStream pushback = new PushbackInputStream(wrapped, peeked.length);
|
||||
|
||||
int headerLength = pushback.read(peeked);
|
||||
|
||||
if (headerLength == -1) {
|
||||
throw new IOException("Unable to read the response");
|
||||
}
|
||||
|
||||
/* We try to read the first uncompressed byte. */
|
||||
byte[] dummy = new byte[1];
|
||||
|
||||
Inflater inf = new Inflater();
|
||||
|
||||
try {
|
||||
int n;
|
||||
while ((n = inf.inflate(dummy)) == 0) {
|
||||
if (inf.finished()) {
|
||||
|
||||
/* Not expecting this, so fail loudly. */
|
||||
throw new IOException("Unable to read the response");
|
||||
}
|
||||
|
||||
if (inf.needsDictionary()) {
|
||||
|
||||
/* Need dictionary - then it must be zlib stream with DICTID part? */
|
||||
break;
|
||||
}
|
||||
|
||||
if (inf.needsInput()) {
|
||||
inf.setInput(peeked);
|
||||
}
|
||||
}
|
||||
|
||||
if (n == -1) {
|
||||
throw new IOException("Unable to read the response");
|
||||
}
|
||||
|
||||
/*
|
||||
* We read something without a problem, so it's a valid zlib stream. Just need to reset
|
||||
* and return an unused InputStream now.
|
||||
*/
|
||||
pushback.unread(peeked, 0, headerLength);
|
||||
return new InflaterInputStream(pushback);
|
||||
} catch (DataFormatException e) {
|
||||
|
||||
/* Presume that it's an RFC1951 deflate stream rather than RFC1950 zlib stream and try
|
||||
* again. */
|
||||
pushback.unread(peeked, 0, headerLength);
|
||||
return new InflaterInputStream(pushback, new Inflater(true));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public Header getContentEncoding() {
|
||||
|
||||
/* This HttpEntityWrapper has dealt with the Content-Encoding. */
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public long getContentLength() {
|
||||
|
||||
/* Length of inflated content is unknown. */
|
||||
return -1;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,90 @@
|
|||
/*
|
||||
* $HeadURL$
|
||||
* $Revision$
|
||||
* $Date$
|
||||
*
|
||||
* ====================================================================
|
||||
* 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.impl.client;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
|
||||
import org.apache.http.Header;
|
||||
import org.apache.http.HttpEntity;
|
||||
import org.apache.http.entity.HttpEntityWrapper;
|
||||
|
||||
/**
|
||||
* {@link HttpEntityWrapper} for handling gzip Content Coded responses.
|
||||
*/
|
||||
class GzipDecompressingEntity extends HttpEntityWrapper {
|
||||
|
||||
/**
|
||||
* Creates a new {@link GzipDecompressingEntity} which will wrap the specified
|
||||
* {@link HttpEntity}.
|
||||
*
|
||||
* @param entity
|
||||
* the non-null {@link HttpEntity} to be wrapped
|
||||
*/
|
||||
public GzipDecompressingEntity(final HttpEntity entity) {
|
||||
super(entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public InputStream getContent() throws IOException, IllegalStateException {
|
||||
|
||||
// the wrapped entity's getContent() decides about repeatability
|
||||
InputStream wrappedin = wrappedEntity.getContent();
|
||||
|
||||
return new GZIPInputStream(wrappedin);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public Header getContentEncoding() {
|
||||
|
||||
/* This HttpEntityWrapper has dealt with the Content-Encoding. */
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public long getContentLength() {
|
||||
|
||||
/* length of ungzipped content is not known */
|
||||
return -1;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,494 @@
|
|||
/*
|
||||
* $HeadURL$
|
||||
* $Revision$
|
||||
* $Date$
|
||||
*
|
||||
* ====================================================================
|
||||
* 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.impl.client;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.zip.Deflater;
|
||||
|
||||
/* Don't use Java 6 functionality, even in tests. */
|
||||
//import java.util.zip.DeflaterInputStream;
|
||||
import java.util.zip.GZIPOutputStream;
|
||||
|
||||
import org.apache.http.Header;
|
||||
import org.apache.http.HeaderElement;
|
||||
import org.apache.http.HttpEntity;
|
||||
import org.apache.http.HttpException;
|
||||
import org.apache.http.HttpRequest;
|
||||
import org.apache.http.HttpRequestInterceptor;
|
||||
import org.apache.http.HttpResponse;
|
||||
import org.apache.http.HttpResponseInterceptor;
|
||||
import org.apache.http.HttpStatus;
|
||||
import org.apache.http.HttpVersion;
|
||||
import org.apache.http.client.HttpClient;
|
||||
import org.apache.http.client.methods.HttpGet;
|
||||
import org.apache.http.conn.ClientConnectionManager;
|
||||
import org.apache.http.conn.params.ConnManagerParams;
|
||||
import org.apache.http.conn.scheme.PlainSocketFactory;
|
||||
import org.apache.http.conn.scheme.Scheme;
|
||||
import org.apache.http.conn.scheme.SchemeRegistry;
|
||||
import org.apache.http.entity.InputStreamEntity;
|
||||
import org.apache.http.entity.StringEntity;
|
||||
import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
|
||||
import org.apache.http.localserver.ServerTestBase;
|
||||
import org.apache.http.params.BasicHttpParams;
|
||||
import org.apache.http.params.HttpParams;
|
||||
import org.apache.http.params.HttpProtocolParams;
|
||||
import org.apache.http.protocol.HttpContext;
|
||||
import org.apache.http.protocol.HttpRequestHandler;
|
||||
import org.apache.http.util.EntityUtils;
|
||||
|
||||
/**
|
||||
* Test case for how Content Codings are processed. By default, we want to do the right thing and
|
||||
* require no intervention from the user of HttpClient, but we still want to let clients do their
|
||||
* own thing if they so wish.
|
||||
*/
|
||||
public class TestContentCodings extends ServerTestBase {
|
||||
|
||||
public TestContentCodings(String testName) {
|
||||
super(testName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test for when we don't get an entity back; e.g. for a 204 or 304 response; nothing blows
|
||||
* up with the new behaviour.
|
||||
*
|
||||
* @throws Exception
|
||||
* if there was a problem
|
||||
*/
|
||||
public void testResponseWithNoContent() throws Exception {
|
||||
this.localServer.register("*", new HttpRequestHandler() {
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public void handle(
|
||||
HttpRequest request,
|
||||
HttpResponse response,
|
||||
HttpContext context) throws HttpException, IOException {
|
||||
response.setStatusCode(HttpStatus.SC_NO_CONTENT);
|
||||
}
|
||||
});
|
||||
|
||||
DefaultHttpClient client = new DefaultHttpClient();
|
||||
|
||||
HttpGet request = new HttpGet("/some-resource");
|
||||
HttpResponse response = client.execute(getServerHttp(), request);
|
||||
assertEquals(HttpStatus.SC_NO_CONTENT, response.getStatusLine().getStatusCode());
|
||||
assertNull(response.getEntity());
|
||||
|
||||
client.getConnectionManager().shutdown();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test for when we are handling content from a server that has correctly interpreted RFC2616
|
||||
* to return RFC1950 streams for <code>deflate</code> content coding.
|
||||
*
|
||||
* @throws Exception
|
||||
* @see {@link DeflateDecompressingEntity}
|
||||
*/
|
||||
public void testDeflateSupportForServerReturningRfc1950Stream() throws Exception {
|
||||
final String entityText = "Hello, this is some plain text coming back.";
|
||||
|
||||
this.localServer.register("*", createDeflateEncodingRequestHandler(entityText, false));
|
||||
|
||||
DefaultHttpClient client = new DefaultHttpClient();
|
||||
|
||||
HttpGet request = new HttpGet("/some-resource");
|
||||
HttpResponse response = client.execute(getServerHttp(), request);
|
||||
assertEquals("The entity text is correctly transported", entityText,
|
||||
EntityUtils.toString(response.getEntity()));
|
||||
|
||||
client.getConnectionManager().shutdown();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test for when we are handling content from a server that has incorrectly interpreted RFC2616
|
||||
* to return RFC1951 streams for <code>deflate</code> content coding.
|
||||
*
|
||||
* @throws Exception
|
||||
* @see {@link DeflateDecompressingEntity}
|
||||
*/
|
||||
public void testDeflateSupportForServerReturningRfc1951Stream() throws Exception {
|
||||
final String entityText = "Hello, this is some plain text coming back.";
|
||||
|
||||
this.localServer.register("*", createDeflateEncodingRequestHandler(entityText, true));
|
||||
|
||||
DefaultHttpClient client = new DefaultHttpClient();
|
||||
HttpGet request = new HttpGet("/some-resource");
|
||||
HttpResponse response = client.execute(getServerHttp(), request);
|
||||
assertEquals("The entity text is correctly transported", entityText,
|
||||
EntityUtils.toString(response.getEntity()));
|
||||
|
||||
client.getConnectionManager().shutdown();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test for a server returning gzipped content.
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
public void testGzipSupport() throws Exception {
|
||||
final String entityText = "Hello, this is some plain text coming back.";
|
||||
|
||||
this.localServer.register("*", createGzipEncodingRequestHandler(entityText));
|
||||
|
||||
DefaultHttpClient client = new DefaultHttpClient();
|
||||
HttpGet request = new HttpGet("/some-resource");
|
||||
HttpResponse response = client.execute(getServerHttp(), request);
|
||||
assertEquals("The entity text is correctly transported", entityText,
|
||||
EntityUtils.toString(response.getEntity()));
|
||||
|
||||
client.getConnectionManager().shutdown();
|
||||
}
|
||||
|
||||
/**
|
||||
* Try with a bunch of client threads, to check that it's thread-safe.
|
||||
*
|
||||
* @throws Exception
|
||||
* if there was a problem
|
||||
*/
|
||||
public void testThreadSafetyOfContentCodings() throws Exception {
|
||||
final String entityText = "Hello, this is some plain text coming back.";
|
||||
|
||||
this.localServer.register("*", createGzipEncodingRequestHandler(entityText));
|
||||
|
||||
/*
|
||||
* Create a load of workers which will access the resource. Half will use the default
|
||||
* gzip behaviour; half will require identity entity.
|
||||
*/
|
||||
int clients = 100;
|
||||
|
||||
HttpParams params = new BasicHttpParams();
|
||||
ConnManagerParams.setMaxTotalConnections(params, clients);
|
||||
HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1);
|
||||
|
||||
SchemeRegistry schemeRegistry = new SchemeRegistry();
|
||||
schemeRegistry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
|
||||
|
||||
ClientConnectionManager cm = new ThreadSafeClientConnManager(params, schemeRegistry);
|
||||
final HttpClient httpClient = new DefaultHttpClient(cm, params);
|
||||
|
||||
ExecutorService executor = Executors.newFixedThreadPool(clients);
|
||||
|
||||
CountDownLatch startGate = new CountDownLatch(1);
|
||||
CountDownLatch endGate = new CountDownLatch(clients);
|
||||
|
||||
List<WorkerTask> workers = new ArrayList<WorkerTask>();
|
||||
|
||||
for (int i = 0; i < clients; ++i) {
|
||||
workers.add(new WorkerTask(httpClient, i % 2 == 0, startGate, endGate));
|
||||
}
|
||||
|
||||
for (WorkerTask workerTask : workers) {
|
||||
|
||||
/* Set them all in motion, but they will block until we call startGate.countDown(). */
|
||||
executor.execute(workerTask);
|
||||
}
|
||||
|
||||
startGate.countDown();
|
||||
|
||||
/* Wait for the workers to complete. */
|
||||
endGate.await();
|
||||
|
||||
for (WorkerTask workerTask : workers) {
|
||||
if (workerTask.isFailed()) {
|
||||
fail("A worker failed");
|
||||
}
|
||||
assertEquals(entityText, workerTask.getText());
|
||||
}
|
||||
}
|
||||
|
||||
public void testExistingProtocolInterceptorsAreNotAffected() throws Exception {
|
||||
final String entityText = "Hello, this is some plain text coming back.";
|
||||
|
||||
this.localServer.register("*", createGzipEncodingRequestHandler(entityText));
|
||||
|
||||
DefaultHttpClient client = new DefaultHttpClient();
|
||||
HttpGet request = new HttpGet("/some-resource");
|
||||
|
||||
client.addRequestInterceptor(new HttpRequestInterceptor() {
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public void process(
|
||||
HttpRequest request, HttpContext context) throws HttpException, IOException {
|
||||
request.addHeader("Accept-Encoding", "gzip");
|
||||
}
|
||||
});
|
||||
|
||||
/* Get around Java The Language's lack of mutable closures */
|
||||
final boolean clientSawGzip[] = new boolean[1];
|
||||
|
||||
client.addResponseInterceptor(new HttpResponseInterceptor() {
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public void process(
|
||||
HttpResponse response, HttpContext context) throws HttpException, IOException {
|
||||
HttpEntity entity = response.getEntity();
|
||||
if (entity != null) {
|
||||
Header ce = entity.getContentEncoding();
|
||||
|
||||
if (ce != null) {
|
||||
HeaderElement[] codecs = ce.getElements();
|
||||
for (int i = 0, n = codecs.length; i < n; ++i) {
|
||||
if ("gzip".equalsIgnoreCase(codecs[i].getName())) {
|
||||
clientSawGzip[0] = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
client.execute(getServerHttp(), request);
|
||||
|
||||
assertTrue("Client which added the new custom protocol interceptor to handle gzip responses " +
|
||||
"was unaffected.",
|
||||
clientSawGzip[0]);
|
||||
|
||||
client.getConnectionManager().shutdown();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link HttpRequestHandler} that will attempt to provide a deflate stream
|
||||
* Content-Coding.
|
||||
*
|
||||
* @param entityText
|
||||
* the non-null String entity text to be returned by the server
|
||||
* @param rfc1951
|
||||
* if true, then the stream returned will be a raw RFC1951 deflate stream, which
|
||||
* some servers return as a result of misinterpreting the HTTP 1.1 RFC. If false,
|
||||
* then it will return an RFC2616 compliant deflate encoded zlib stream.
|
||||
* @return a non-null {@link HttpRequestHandler}
|
||||
*/
|
||||
private HttpRequestHandler createDeflateEncodingRequestHandler(
|
||||
final String entityText, final boolean rfc1951) {
|
||||
return new HttpRequestHandler() {
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public void handle(
|
||||
HttpRequest request,
|
||||
HttpResponse response,
|
||||
HttpContext context) throws HttpException, IOException {
|
||||
response.setEntity(new StringEntity(entityText));
|
||||
response.addHeader("Content-Type", "text/plain");
|
||||
Header[] acceptEncodings = request.getHeaders("Accept-Encoding");
|
||||
|
||||
for (Header header : acceptEncodings) {
|
||||
for (HeaderElement element : header.getElements()) {
|
||||
if ("deflate".equalsIgnoreCase(element.getName())) {
|
||||
response.addHeader("Content-Encoding", "deflate");
|
||||
|
||||
/* Gack. DeflaterInputStream is Java 6. */
|
||||
// response.setEntity(new InputStreamEntity(new DeflaterInputStream(new
|
||||
// ByteArrayInputStream(
|
||||
// entityText.getBytes("utf-8"))), -1));
|
||||
byte[] uncompressed = entityText.getBytes("utf-8");
|
||||
Deflater compressor = new Deflater(Deflater.DEFAULT_COMPRESSION, rfc1951);
|
||||
compressor.setInput(uncompressed);
|
||||
compressor.finish();
|
||||
byte[] output = new byte[100];
|
||||
int compressedLength = compressor.deflate(output);
|
||||
byte[] compressed = new byte[compressedLength];
|
||||
System.arraycopy(output, 0, compressed, 0, compressedLength);
|
||||
response.setEntity(new InputStreamEntity(
|
||||
new ByteArrayInputStream(compressed), compressedLength));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an {@link HttpRequestHandler} implementation that will attempt to provide a gzip
|
||||
* Content-Encoding.
|
||||
*
|
||||
* @param entityText
|
||||
* the non-null String entity to be returned by the server
|
||||
* @return a non-null {@link HttpRequestHandler}
|
||||
*/
|
||||
private HttpRequestHandler createGzipEncodingRequestHandler(final String entityText) {
|
||||
return new HttpRequestHandler() {
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public void handle(
|
||||
HttpRequest request,
|
||||
HttpResponse response,
|
||||
HttpContext context) throws HttpException, IOException {
|
||||
response.setEntity(new StringEntity(entityText));
|
||||
response.addHeader("Content-Type", "text/plain");
|
||||
Header[] acceptEncodings = request.getHeaders("Accept-Encoding");
|
||||
|
||||
for (Header header : acceptEncodings) {
|
||||
for (HeaderElement element : header.getElements()) {
|
||||
if ("gzip".equalsIgnoreCase(element.getName())) {
|
||||
response.addHeader("Content-Encoding", "gzip");
|
||||
|
||||
/*
|
||||
* We have to do a bit more work with gzip versus deflate, since
|
||||
* Gzip doesn't appear to have an equivalent to DeflaterInputStream in
|
||||
* the JDK.
|
||||
*
|
||||
* UPDATE: DeflaterInputStream is Java 6 anyway, so we have to do a bit
|
||||
* of work there too!
|
||||
*/
|
||||
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
|
||||
OutputStream out = new GZIPOutputStream(bytes);
|
||||
|
||||
ByteArrayInputStream uncompressed = new ByteArrayInputStream(
|
||||
entityText.getBytes("utf-8"));
|
||||
|
||||
byte[] buf = new byte[60];
|
||||
|
||||
int n;
|
||||
while ((n = uncompressed.read(buf)) != -1) {
|
||||
out.write(buf, 0, n);
|
||||
}
|
||||
|
||||
out.close();
|
||||
|
||||
byte[] arr = bytes.toByteArray();
|
||||
response.setEntity(new InputStreamEntity(new ByteArrayInputStream(arr),
|
||||
arr.length));
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Sub-ordinate task passed off to a different thread to be executed.
|
||||
*
|
||||
* @author jabley
|
||||
*
|
||||
*/
|
||||
class WorkerTask implements Runnable {
|
||||
|
||||
/**
|
||||
* The {@link HttpClient} used to make requests.
|
||||
*/
|
||||
private final HttpClient client;
|
||||
|
||||
/**
|
||||
* The {@link HttpRequest} to be executed.
|
||||
*/
|
||||
private final HttpGet request;
|
||||
|
||||
/**
|
||||
* Flag indicating if there were failures.
|
||||
*/
|
||||
private boolean failed = false;
|
||||
|
||||
/**
|
||||
* The latch that this runnable instance should wait on.
|
||||
*/
|
||||
private final CountDownLatch startGate;
|
||||
|
||||
/**
|
||||
* The latch that this runnable instance should countdown on when the runnable is finished.
|
||||
*/
|
||||
private final CountDownLatch endGate;
|
||||
|
||||
/**
|
||||
* The text returned from the HTTP server.
|
||||
*/
|
||||
private String text;
|
||||
|
||||
WorkerTask(HttpClient client, boolean identity, CountDownLatch startGate, CountDownLatch endGate) {
|
||||
this.client = client;
|
||||
this.request = new HttpGet("/some-resource");
|
||||
if (identity) {
|
||||
request.addHeader("Accept-Encoding", "identity");
|
||||
}
|
||||
this.startGate = startGate;
|
||||
this.endGate = endGate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the text of the HTTP entity.
|
||||
*
|
||||
* @return a String - may be null.
|
||||
*/
|
||||
public String getText() {
|
||||
return this.text;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public void run() {
|
||||
try {
|
||||
startGate.await();
|
||||
try {
|
||||
HttpResponse response = client.execute(TestContentCodings.this.getServerHttp(), request);
|
||||
text = EntityUtils.toString(response.getEntity());
|
||||
} catch (Exception e) {
|
||||
failed = true;
|
||||
} finally {
|
||||
endGate.countDown();
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this task failed, otherwise false.
|
||||
*
|
||||
* @return a flag
|
||||
*/
|
||||
boolean isFailed() {
|
||||
return this.failed;
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue