You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@hc.apache.org by ol...@apache.org on 2009/03/18 17:30:49 UTC
svn commit: r755629 - in /httpcomponents/httpclient/branches/branch_4_1: ./
httpclient/src/main/java/org/apache/http/impl/client/
httpclient/src/test/java/org/apache/http/impl/client/
Author: olegk
Date: Wed Mar 18 16:30:49 2009
New Revision: 755629
URL: http://svn.apache.org/viewvc?rev=755629&view=rev
Log:
HTTPCLIENT-834: Transparent content encoding support (initial patch).
* Support for gzip and deflate content codings
Contributed by James Abley <james.abley at gmail.com>
Added:
httpcomponents/httpclient/branches/branch_4_1/httpclient/src/main/java/org/apache/http/impl/client/ContentEncodingProcessor.java
httpcomponents/httpclient/branches/branch_4_1/httpclient/src/main/java/org/apache/http/impl/client/DeflateDecompressingEntity.java
httpcomponents/httpclient/branches/branch_4_1/httpclient/src/main/java/org/apache/http/impl/client/GzipDecompressingEntity.java
httpcomponents/httpclient/branches/branch_4_1/httpclient/src/test/java/org/apache/http/impl/client/TestContentCodings.java
Modified:
httpcomponents/httpclient/branches/branch_4_1/RELEASE_NOTES.txt
httpcomponents/httpclient/branches/branch_4_1/httpclient/src/main/java/org/apache/http/impl/client/AbstractHttpClient.java
Modified: httpcomponents/httpclient/branches/branch_4_1/RELEASE_NOTES.txt
URL: http://svn.apache.org/viewvc/httpcomponents/httpclient/branches/branch_4_1/RELEASE_NOTES.txt?rev=755629&r1=755628&r2=755629&view=diff
==============================================================================
--- httpcomponents/httpclient/branches/branch_4_1/RELEASE_NOTES.txt (original)
+++ httpcomponents/httpclient/branches/branch_4_1/RELEASE_NOTES.txt Wed Mar 18 16:30:49 2009
@@ -1,3 +1,9 @@
+Changes in 4.1 branch
+-------------------
+
+* [HTTPCLIENT-834] Transparent content encoding support.
+ Contributed by James Abley <james.abley at gmail.com>
+
Changes since 4.0 beta 2
-------------------
Modified: httpcomponents/httpclient/branches/branch_4_1/httpclient/src/main/java/org/apache/http/impl/client/AbstractHttpClient.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpclient/branches/branch_4_1/httpclient/src/main/java/org/apache/http/impl/client/AbstractHttpClient.java?rev=755629&r1=755628&r2=755629&view=diff
==============================================================================
--- httpcomponents/httpclient/branches/branch_4_1/httpclient/src/main/java/org/apache/http/impl/client/AbstractHttpClient.java (original)
+++ httpcomponents/httpclient/branches/branch_4_1/httpclient/src/main/java/org/apache/http/impl/client/AbstractHttpClient.java Wed Mar 18 16:30:49 2009
@@ -533,6 +533,16 @@
} else {
execContext = new DefaultedHttpContext(context, defaultContext);
}
+
+ // Copy the HttpProcessor (along with interceptors) and add the default
+ // handling for Content Codings
+ // This is the last interceptor added, so that client code is unaffected
+ // by this addition.
+ BasicHttpProcessor httpProcessorCopy = getHttpProcessor().copy();
+ ContentEncodingProcessor ceProcessor = new ContentEncodingProcessor();
+ httpProcessorCopy.addRequestInterceptor(ceProcessor);
+ httpProcessorCopy.addResponseInterceptor(ceProcessor);
+
// Create a director for this request
director = createClientRequestDirector(
getRequestExecutor(),
@@ -540,7 +550,7 @@
getConnectionReuseStrategy(),
getConnectionKeepAliveStrategy(),
getRoutePlanner(),
- getHttpProcessor().copy(),
+ httpProcessorCopy,
getHttpRequestRetryHandler(),
getRedirectHandler(),
getTargetAuthenticationHandler(),
@@ -691,5 +701,4 @@
return result;
}
-
} // class AbstractHttpClient
Added: httpcomponents/httpclient/branches/branch_4_1/httpclient/src/main/java/org/apache/http/impl/client/ContentEncodingProcessor.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpclient/branches/branch_4_1/httpclient/src/main/java/org/apache/http/impl/client/ContentEncodingProcessor.java?rev=755629&view=auto
==============================================================================
--- httpcomponents/httpclient/branches/branch_4_1/httpclient/src/main/java/org/apache/http/impl/client/ContentEncodingProcessor.java (added)
+++ httpcomponents/httpclient/branches/branch_4_1/httpclient/src/main/java/org/apache/http/impl/client/ContentEncodingProcessor.java Wed Mar 18 16:30:49 2009
@@ -0,0 +1,78 @@
+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.
+ }
+ }
+ }
+ }
+ }
+
+}
Added: httpcomponents/httpclient/branches/branch_4_1/httpclient/src/main/java/org/apache/http/impl/client/DeflateDecompressingEntity.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpclient/branches/branch_4_1/httpclient/src/main/java/org/apache/http/impl/client/DeflateDecompressingEntity.java?rev=755629&view=auto
==============================================================================
--- httpcomponents/httpclient/branches/branch_4_1/httpclient/src/main/java/org/apache/http/impl/client/DeflateDecompressingEntity.java (added)
+++ httpcomponents/httpclient/branches/branch_4_1/httpclient/src/main/java/org/apache/http/impl/client/DeflateDecompressingEntity.java Wed Mar 18 16:30:49 2009
@@ -0,0 +1,143 @@
+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;
+ }
+
+}
Added: httpcomponents/httpclient/branches/branch_4_1/httpclient/src/main/java/org/apache/http/impl/client/GzipDecompressingEntity.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpclient/branches/branch_4_1/httpclient/src/main/java/org/apache/http/impl/client/GzipDecompressingEntity.java?rev=755629&view=auto
==============================================================================
--- httpcomponents/httpclient/branches/branch_4_1/httpclient/src/main/java/org/apache/http/impl/client/GzipDecompressingEntity.java (added)
+++ httpcomponents/httpclient/branches/branch_4_1/httpclient/src/main/java/org/apache/http/impl/client/GzipDecompressingEntity.java Wed Mar 18 16:30:49 2009
@@ -0,0 +1,59 @@
+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;
+ }
+
+}
Added: httpcomponents/httpclient/branches/branch_4_1/httpclient/src/test/java/org/apache/http/impl/client/TestContentCodings.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpclient/branches/branch_4_1/httpclient/src/test/java/org/apache/http/impl/client/TestContentCodings.java?rev=755629&view=auto
==============================================================================
--- httpcomponents/httpclient/branches/branch_4_1/httpclient/src/test/java/org/apache/http/impl/client/TestContentCodings.java (added)
+++ httpcomponents/httpclient/branches/branch_4_1/httpclient/src/test/java/org/apache/http/impl/client/TestContentCodings.java Wed Mar 18 16:30:49 2009
@@ -0,0 +1,463 @@
+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;
+ }
+ }
+}
Re: svn commit: r755629
Posted by Oleg Kalnichevski <ol...@apache.org>.
sebb wrote:
> On 18/03/2009, olegk@apache.org <ol...@apache.org> wrote:
>> Author: olegk
>> Date: Wed Mar 18 16:30:49 2009
>> New Revision: 755629
>>
>> URL: http://svn.apache.org/viewvc?rev=755629&view=rev
>> Log:
>> HTTPCLIENT-834: Transparent content encoding support (initial patch).
>>
>> * Support for gzip and deflate content codings
>>
>> Contributed by James Abley <james.abley at gmail.com>
>>
>> Added:
>> httpcomponents/httpclient/branches/branch_4_1/httpclient/src/main/java/org/apache/http/impl/client/ContentEncodingProcessor.java
>> httpcomponents/httpclient/branches/branch_4_1/httpclient/src/main/java/org/apache/http/impl/client/DeflateDecompressingEntity.java
>> httpcomponents/httpclient/branches/branch_4_1/httpclient/src/main/java/org/apache/http/impl/client/GzipDecompressingEntity.java
>> httpcomponents/httpclient/branches/branch_4_1/httpclient/src/test/java/org/apache/http/impl/client/TestContentCodings.java
>
> AL headers are needed for the new files ...
>
> Also please svn:eol-style native (if initial committer does that
> hopefully fewer differences will be logged)
>
My bad. Fixed.
Oleg
> ---------------------------------------------------------------------
> To unsubscribe, e-mail: dev-unsubscribe@hc.apache.org
> For additional commands, e-mail: dev-help@hc.apache.org
>
---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@hc.apache.org
For additional commands, e-mail: dev-help@hc.apache.org
Re: svn commit: r755629 - in /httpcomponents/httpclient/branches/branch_4_1:
./ httpclient/src/main/java/org/apache/http/impl/client/ httpclient/src/test/java/org/apache/http/impl/client/
Posted by sebb <se...@gmail.com>.
On 18/03/2009, olegk@apache.org <ol...@apache.org> wrote:
> Author: olegk
> Date: Wed Mar 18 16:30:49 2009
> New Revision: 755629
>
> URL: http://svn.apache.org/viewvc?rev=755629&view=rev
> Log:
> HTTPCLIENT-834: Transparent content encoding support (initial patch).
>
> * Support for gzip and deflate content codings
>
> Contributed by James Abley <james.abley at gmail.com>
>
> Added:
> httpcomponents/httpclient/branches/branch_4_1/httpclient/src/main/java/org/apache/http/impl/client/ContentEncodingProcessor.java
> httpcomponents/httpclient/branches/branch_4_1/httpclient/src/main/java/org/apache/http/impl/client/DeflateDecompressingEntity.java
> httpcomponents/httpclient/branches/branch_4_1/httpclient/src/main/java/org/apache/http/impl/client/GzipDecompressingEntity.java
> httpcomponents/httpclient/branches/branch_4_1/httpclient/src/test/java/org/apache/http/impl/client/TestContentCodings.java
AL headers are needed for the new files ...
Also please svn:eol-style native (if initial committer does that
hopefully fewer differences will be logged)
---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@hc.apache.org
For additional commands, e-mail: dev-help@hc.apache.org