diff --git a/httpclient-cache/src/main/java/org/apache/http/client/cache/InputLimit.java b/httpclient-cache/src/main/java/org/apache/http/client/cache/InputLimit.java new file mode 100644 index 000000000..f85faaf88 --- /dev/null +++ b/httpclient-cache/src/main/java/org/apache/http/client/cache/InputLimit.java @@ -0,0 +1,55 @@ +/* + * ==================================================================== + * 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 + * . + * + */ +package org.apache.http.client.cache; + +/** + * @since 4.1 + */ +public class InputLimit { + + private final long value; + private boolean reached; + + public InputLimit(long value) { + super(); + this.value = value; + this.reached = false; + } + + public long getValue() { + return this.value; + } + + public void reached() { + this.reached = true; + } + + public boolean isReached() { + return this.reached; + } + +} diff --git a/httpclient-cache/src/main/java/org/apache/http/client/cache/ResourceFactory.java b/httpclient-cache/src/main/java/org/apache/http/client/cache/ResourceFactory.java index 8e61a8bb1..063b842e1 100644 --- a/httpclient-cache/src/main/java/org/apache/http/client/cache/ResourceFactory.java +++ b/httpclient-cache/src/main/java/org/apache/http/client/cache/ResourceFactory.java @@ -27,6 +27,7 @@ package org.apache.http.client.cache; import java.io.IOException; +import java.io.InputStream; /** * Generates {@link Resource} instances. @@ -35,7 +36,7 @@ import java.io.IOException; */ public interface ResourceFactory { - Resource generate(String requestId, byte[] body) throws IOException; + Resource generate(String requestId, InputStream instream, InputLimit limit) throws IOException; Resource copy(String requestId, Resource resource) throws IOException; diff --git a/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/CachingHttpClient.java b/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/CachingHttpClient.java index 277fe7c46..1161e0d22 100644 --- a/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/CachingHttpClient.java +++ b/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/CachingHttpClient.java @@ -602,8 +602,7 @@ public class CachingHttpClient implements HttpClient { variants); } - HttpResponse correctIncompleteResponse(HttpResponse resp, - byte[] bodyBytes) { + HttpResponse correctIncompleteResponse(HttpResponse resp, Resource resource) { int status = resp.getStatusLine().getStatusCode(); if (status != HttpStatus.SC_OK && status != HttpStatus.SC_PARTIAL_CONTENT) { @@ -617,13 +616,14 @@ public class CachingHttpClient implements HttpClient { } catch (NumberFormatException nfe) { return resp; } - if (bodyBytes.length >= contentLength) return resp; + if (resource.length() >= contentLength) return resp; HttpResponse error = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_BAD_GATEWAY, "Bad Gateway"); error.setHeader("Content-Type","text/plain;charset=UTF-8"); - String msg = String.format("Received incomplete response with Content-Length %d but actual body length %d", contentLength, bodyBytes.length); + String msg = String.format("Received incomplete response " + + "with Content-Length %d but actual body length %d", contentLength, resource.length()); byte[] msgBytes = msg.getBytes(); - error.setHeader("Content-Length", String.format("%d",msgBytes.length)); + error.setHeader("Content-Length", Integer.toString(msgBytes.length)); error.setEntity(new ByteArrayEntity(msgBytes)); return error; } @@ -643,19 +643,17 @@ public class CachingHttpClient implements HttpClient { HttpResponse corrected = backendResponse; if (cacheable) { - SizeLimitedResponseReader responseReader = getResponseReader(backendResponse); + SizeLimitedResponseReader responseReader = getResponseReader(request, backendResponse); + responseReader.readResponse(); - if (responseReader.isResponseTooLarge()) { + if (responseReader.isLimitReached()) { return responseReader.getReconstructedResponse(); } - byte[] responseBytes = responseReader.getResponseBytes(); - corrected = correctIncompleteResponse(backendResponse, - responseBytes); + Resource resource = responseReader.getResource(); + corrected = correctIncompleteResponse(backendResponse, resource); int correctedStatus = corrected.getStatusLine().getStatusCode(); if (HttpStatus.SC_BAD_GATEWAY != correctedStatus) { - Resource resource = resourceFactory.generate( - request.getRequestLine().getUri(), responseBytes); HttpCacheEntry entry = new HttpCacheEntry( requestDate, responseDate, @@ -673,8 +671,9 @@ public class CachingHttpClient implements HttpClient { return corrected; } - SizeLimitedResponseReader getResponseReader(HttpResponse backEndResponse) { - return new SizeLimitedResponseReader(maxObjectSizeBytes, backEndResponse); + SizeLimitedResponseReader getResponseReader(HttpRequest request, HttpResponse backEndResponse) { + return new SizeLimitedResponseReader( + resourceFactory, maxObjectSizeBytes, request, backEndResponse); } } diff --git a/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/CombinedEntity.java b/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/CombinedEntity.java new file mode 100644 index 000000000..a0ef1892a --- /dev/null +++ b/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/CombinedEntity.java @@ -0,0 +1,105 @@ +/* + * ==================================================================== + * 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 + * . + * + */ +package org.apache.http.impl.client.cache; + +import java.io.FilterInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.SequenceInputStream; + +import org.apache.http.annotation.NotThreadSafe; +import org.apache.http.client.cache.Resource; +import org.apache.http.entity.AbstractHttpEntity; + +@NotThreadSafe +class CombinedEntity extends AbstractHttpEntity { + + private final Resource resource; + private final InputStream combinedStream; + + CombinedEntity(final Resource resource, final InputStream instream) throws IOException { + super(); + this.resource = resource; + this.combinedStream = new SequenceInputStream( + new ResourceStream(resource.getInputStream()), instream); + } + + public long getContentLength() { + return -1; + } + + public boolean isRepeatable() { + return false; + } + + public boolean isStreaming() { + return true; + } + + public InputStream getContent() throws IOException, IllegalStateException { + return this.combinedStream; + } + + public void writeTo(final OutputStream outstream) throws IOException { + if (outstream == null) { + throw new IllegalArgumentException("Output stream may not be null"); + } + InputStream instream = getContent(); + try { + int l; + byte[] tmp = new byte[2048]; + while ((l = instream.read(tmp)) != -1) { + outstream.write(tmp, 0, l); + } + } finally { + instream.close(); + } + } + + private void dispose() { + this.resource.dispose(); + } + + class ResourceStream extends FilterInputStream { + + protected ResourceStream(final InputStream in) { + super(in); + } + + @Override + public void close() throws IOException { + try { + super.close(); + } finally { + dispose(); + } + } + + } + +} diff --git a/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/CombinedInputStream.java b/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/CombinedInputStream.java deleted file mode 100644 index 9c681ba4e..000000000 --- a/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/CombinedInputStream.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * ==================================================================== - * 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 - * . - * - */ -package org.apache.http.impl.client.cache; - -import java.io.IOException; -import java.io.InputStream; - -/** - * A class that presents two inputstreams as a single stream - * - * @since 4.1 - */ -class CombinedInputStream extends InputStream { - - private final InputStream inputStream1; - private final InputStream inputStream2; - - /** - * Take two inputstreams and produce an object that makes them appear as if they - * are actually a 'single' input stream. - * - * @param inputStream1 - * First stream to read - * @param inputStream2 - * Second stream to read - */ - public CombinedInputStream(InputStream inputStream1, InputStream inputStream2) { - if (inputStream1 == null) - throw new IllegalArgumentException("inputStream1 cannot be null"); - if (inputStream2 == null) - throw new IllegalArgumentException("inputStream2 cannot be null"); - - this.inputStream1 = inputStream1; - this.inputStream2 = inputStream2; - } - - @Override - public int available() throws IOException { - return inputStream1.available() + inputStream2.available(); - } - - @Override - public int read() throws IOException { - int result = inputStream1.read(); - - if (result == -1) - result = inputStream2.read(); - - return result; - } -} diff --git a/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/FileResourceFactory.java b/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/FileResourceFactory.java index abacc6c9e..71746f71f 100644 --- a/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/FileResourceFactory.java +++ b/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/FileResourceFactory.java @@ -29,8 +29,10 @@ package org.apache.http.impl.client.cache; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; +import java.io.InputStream; import org.apache.http.annotation.Immutable; +import org.apache.http.client.cache.InputLimit; import org.apache.http.client.cache.Resource; import org.apache.http.client.cache.ResourceFactory; @@ -67,18 +69,33 @@ public class FileResourceFactory implements ResourceFactory { return new File(this.cacheDir, buffer.toString()); } - public Resource generate(final String requestId, final byte[] body) throws IOException { + public Resource generate( + final String requestId, + final InputStream instream, + final InputLimit limit) throws IOException { File file = generateUniqueCacheFile(requestId); FileOutputStream outstream = new FileOutputStream(file); try { - outstream.write(body); + byte[] buf = new byte[2048]; + long total = 0; + int l; + while ((l = instream.read(buf)) != -1) { + outstream.write(buf, 0, l); + total += l; + if (limit != null && total > limit.getValue()) { + limit.reached(); + break; + } + } } finally { outstream.close(); } return new FileResource(file); } - public Resource copy(final String requestId, final Resource resource) throws IOException { + public Resource copy( + final String requestId, + final Resource resource) throws IOException { File file = generateUniqueCacheFile(requestId); if (resource instanceof FileResource) { diff --git a/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/HeapResourceFactory.java b/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/HeapResourceFactory.java index a950d8037..d4e7bd992 100644 --- a/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/HeapResourceFactory.java +++ b/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/HeapResourceFactory.java @@ -28,8 +28,10 @@ package org.apache.http.impl.client.cache; import java.io.ByteArrayOutputStream; import java.io.IOException; +import java.io.InputStream; import org.apache.http.annotation.Immutable; +import org.apache.http.client.cache.InputLimit; import org.apache.http.client.cache.Resource; import org.apache.http.client.cache.ResourceFactory; @@ -41,11 +43,28 @@ import org.apache.http.client.cache.ResourceFactory; @Immutable public class HeapResourceFactory implements ResourceFactory { - public Resource generate(final String requestId, final byte[] body) throws IOException { - return new HeapResource(body); + public Resource generate( + final String requestId, + final InputStream instream, + final InputLimit limit) throws IOException { + ByteArrayOutputStream outstream = new ByteArrayOutputStream(); + byte[] buf = new byte[2048]; + long total = 0; + int l; + while ((l = instream.read(buf)) != -1) { + outstream.write(buf, 0, l); + total += l; + if (limit != null && total > limit.getValue()) { + limit.reached(); + break; + } + } + return new HeapResource(outstream.toByteArray()); } - public Resource copy(final String requestId, final Resource resource) throws IOException { + public Resource copy( + final String requestId, + final Resource resource) throws IOException { byte[] body; if (resource instanceof HeapResource) { body = ((HeapResource) resource).getByteArray(); diff --git a/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/SizeLimitedResponseReader.java b/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/SizeLimitedResponseReader.java index 73b15156d..b142bb6c0 100644 --- a/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/SizeLimitedResponseReader.java +++ b/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/SizeLimitedResponseReader.java @@ -26,132 +26,101 @@ */ package org.apache.http.impl.client.cache; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import org.apache.http.HttpEntity; +import org.apache.http.HttpRequest; import org.apache.http.HttpResponse; import org.apache.http.HttpStatus; -import org.apache.http.entity.InputStreamEntity; +import org.apache.http.annotation.NotThreadSafe; +import org.apache.http.client.cache.InputLimit; +import org.apache.http.client.cache.Resource; +import org.apache.http.client.cache.ResourceFactory; import org.apache.http.message.BasicHttpResponse; /** * @since 4.1 */ +@NotThreadSafe class SizeLimitedResponseReader { - private final int maxResponseSizeBytes; + private final ResourceFactory resourceFactory; + private final long maxResponseSizeBytes; + private final HttpRequest request; private final HttpResponse response; - private ByteArrayOutputStream outputStream; - private InputStream contentInputStream; - private boolean isTooLarge; - private boolean responseIsConsumed; - private byte[] sizeLimitedContent; - private boolean outputStreamConsumed; + private InputStream instream; + private InputLimit limit; + private Resource resource; + private boolean consumed; /** * Create an {@link HttpResponse} that is limited in size, this allows for checking * the size of objects that will be stored in the cache. - * - * @param maxResponseSizeBytes - * Maximum size that a response can be to be eligible for cache inclusion - * - * @param response - * The {@link HttpResponse} */ - public SizeLimitedResponseReader(int maxResponseSizeBytes, HttpResponse response) { + public SizeLimitedResponseReader( + ResourceFactory resourceFactory, + long maxResponseSizeBytes, + HttpRequest request, + HttpResponse response) { + super(); + this.resourceFactory = resourceFactory; this.maxResponseSizeBytes = maxResponseSizeBytes; + this.request = request; this.response = response; } - protected boolean isResponseTooLarge() throws IOException { - if (!responseIsConsumed) - isTooLarge = consumeResponse(); - - return isTooLarge; + protected void readResponse() throws IOException { + if (!consumed) { + doConsume(); + } } - private boolean consumeResponse() throws IOException { + private void ensureNotConsumed() { + if (consumed) { + throw new IllegalStateException("Response has already been consumed"); + } + } - if (responseIsConsumed) - throw new IllegalStateException( - "You cannot call this method more than once, because it consumes an underlying stream"); + private void ensureConsumed() { + if (!consumed) { + throw new IllegalStateException("Response has not been consumed"); + } + } - responseIsConsumed = true; + private void doConsume() throws IOException { + ensureNotConsumed(); + consumed = true; + + limit = new InputLimit(maxResponseSizeBytes); HttpEntity entity = response.getEntity(); - if (entity == null) - return false; - - contentInputStream = entity.getContent(); - int bytes = 0; - - outputStream = new ByteArrayOutputStream(); - - int current; - - while (bytes < maxResponseSizeBytes && (current = contentInputStream.read()) != -1) { - outputStream.write(current); - bytes++; + if (entity == null) { + return; } - - if ((current = contentInputStream.read()) != -1) { - outputStream.write(current); - return true; - } - - return false; + String uri = request.getRequestLine().getUri(); + instream = entity.getContent(); + resource = resourceFactory.generate(uri, instream, limit); } - private void consumeOutputStream() { - if (outputStreamConsumed) - throw new IllegalStateException( - "underlying output stream has already been written to byte[]"); - - if (!responseIsConsumed) - throw new IllegalStateException("Must call consumeResponse first."); - - sizeLimitedContent = outputStream.toByteArray(); - outputStreamConsumed = true; + boolean isLimitReached() { + ensureConsumed(); + return limit.isReached(); } - protected byte[] getResponseBytes() { - if (!outputStreamConsumed) - consumeOutputStream(); - - return sizeLimitedContent; + Resource getResource() { + ensureConsumed(); + return resource; } - protected HttpResponse getReconstructedResponse() { - - InputStream combinedStream = getCombinedInputStream(); - - return constructResponse(response, combinedStream); - } - - protected InputStream getCombinedInputStream() { - InputStream input1 = new ByteArrayInputStream(getResponseBytes()); - InputStream input2 = getContentInputStream(); - return new CombinedInputStream(input1, input2); - } - - protected InputStream getContentInputStream() { - return contentInputStream; - } - - protected HttpResponse constructResponse(HttpResponse originalResponse, - InputStream combinedStream) { - HttpResponse response = new BasicHttpResponse(originalResponse.getProtocolVersion(), + HttpResponse getReconstructedResponse() throws IOException { + ensureConsumed(); + HttpResponse reconstructed = new BasicHttpResponse(response.getProtocolVersion(), HttpStatus.SC_OK, "Success"); - - HttpEntity entity = new InputStreamEntity(combinedStream, -1); - response.setEntity(entity); - response.setHeaders(originalResponse.getAllHeaders()); - - return response; + reconstructed.setHeaders(response.getAllHeaders()); + reconstructed.setEntity(new CombinedEntity(resource, instream)); + return reconstructed; } } diff --git a/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestCachingHttpClient.java b/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestCachingHttpClient.java index afa03f2db..ce343a208 100644 --- a/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestCachingHttpClient.java +++ b/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestCachingHttpClient.java @@ -239,10 +239,9 @@ public class TestCachingHttpClient { responseProtocolValidationIsCalled(); getMockResponseReader(); - responseIsTooLarge(false); - byte[] buf = responseReaderReturnsBufferOfSize(100); - - generateResource(buf); + responseRead(); + responseLimitReached(false); + responseGetResource(); storeInCacheWasCalled(); responseIsGeneratedFromCache(); responseStatusLineIsInspectable(); @@ -553,8 +552,9 @@ public class TestCachingHttpClient { getCurrentDateReturns(responseDate); responsePolicyAllowsCaching(true); getMockResponseReader(); - responseIsTooLarge(true); - readerReturnsReconstructedResponse(); + responseRead(); + responseLimitReached(true); + responseGetReconstructed(); replayMocks(); @@ -575,9 +575,9 @@ public class TestCachingHttpClient { getCurrentDateReturns(responseDate); responsePolicyAllowsCaching(true); getMockResponseReader(); - responseIsTooLarge(false); - byte[] buf = responseReaderReturnsBufferOfSize(100); - generateResource(buf); + responseRead(); + responseLimitReached(false); + responseGetResource(); storeInCacheWasCalled(); responseIsGeneratedFromCache(); responseStatusLineIsInspectable(); @@ -961,7 +961,7 @@ public class TestCachingHttpClient { resp.setEntity(new ByteArrayEntity(bytes)); resp.setHeader("Content-Length","128"); - HttpResponse result = impl.correctIncompleteResponse(resp, bytes); + HttpResponse result = impl.correctIncompleteResponse(resp, new HeapResource(bytes)); Assert.assertTrue(HttpTestUtils.semanticallyTransparent(resp, result)); } @@ -974,7 +974,7 @@ public class TestCachingHttpClient { resp.setHeader("Content-Length","128"); resp.setHeader("Content-Range","bytes 0-127/255"); - HttpResponse result = impl.correctIncompleteResponse(resp, bytes); + HttpResponse result = impl.correctIncompleteResponse(resp, new HeapResource(bytes)); Assert.assertTrue(HttpTestUtils.semanticallyTransparent(resp, result)); } @@ -986,7 +986,7 @@ public class TestCachingHttpClient { resp.setEntity(new ByteArrayEntity(bytes)); resp.setHeader("Content-Length","256"); - HttpResponse result = impl.correctIncompleteResponse(resp, bytes); + HttpResponse result = impl.correctIncompleteResponse(resp, new HeapResource(bytes)); Assert.assertTrue(HttpStatus.SC_BAD_GATEWAY == result.getStatusLine().getStatusCode()); } @@ -998,7 +998,7 @@ public class TestCachingHttpClient { resp.setEntity(new ByteArrayEntity(bytes)); resp.setHeader("Content-Length","256"); - HttpResponse result = impl.correctIncompleteResponse(resp, bytes); + HttpResponse result = impl.correctIncompleteResponse(resp, new HeapResource(bytes)); Assert.assertTrue(HttpTestUtils.semanticallyTransparent(resp, result)); } @@ -1009,7 +1009,7 @@ public class TestCachingHttpClient { byte[] bytes = HttpTestUtils.getRandomBytes(128); resp.setEntity(new ByteArrayEntity(bytes)); - HttpResponse result = impl.correctIncompleteResponse(resp, bytes); + HttpResponse result = impl.correctIncompleteResponse(resp, new HeapResource(bytes)); Assert.assertTrue(HttpTestUtils.semanticallyTransparent(resp, result)); } @@ -1021,7 +1021,7 @@ public class TestCachingHttpClient { resp.setHeader("Content-Length","foo"); resp.setEntity(new ByteArrayEntity(bytes)); - HttpResponse result = impl.correctIncompleteResponse(resp, bytes); + HttpResponse result = impl.correctIncompleteResponse(resp, new HeapResource(bytes)); Assert.assertTrue(HttpTestUtils.semanticallyTransparent(resp, result)); } @@ -1033,7 +1033,7 @@ public class TestCachingHttpClient { resp.setEntity(new ByteArrayEntity(bytes)); resp.setHeader("Content-Length","256"); - HttpResponse result = impl.correctIncompleteResponse(resp, bytes); + HttpResponse result = impl.correctIncompleteResponse(resp, new HeapResource(bytes)); Header ctype = result.getFirstHeader("Content-Type"); Assert.assertEquals("text/plain;charset=UTF-8", ctype.getValue()); } @@ -1046,7 +1046,7 @@ public class TestCachingHttpClient { resp.setEntity(new ByteArrayEntity(bytes)); resp.setHeader("Content-Length","256"); - HttpResponse result = impl.correctIncompleteResponse(resp, bytes); + HttpResponse result = impl.correctIncompleteResponse(resp, new HeapResource(bytes)); int clen = Integer.parseInt(result.getFirstHeader("Content-Length").getValue()); Assert.assertTrue(clen > 0); HttpEntity body = result.getEntity(); @@ -1130,6 +1130,7 @@ public class TestCachingHttpClient { private void getMockResponseReader() { EasyMock.expect(impl.getResponseReader( + EasyMock.anyObject(), EasyMock.anyObject())).andReturn(mockResponseReader); } @@ -1147,19 +1148,20 @@ public class TestCachingHttpClient { .andReturn(null).anyTimes(); } - private byte[] responseReaderReturnsBufferOfSize(int bufferSize) { - byte[] buffer = new byte[bufferSize]; - EasyMock.expect(mockResponseReader.getResponseBytes()).andReturn(buffer); - return buffer; + private void responseRead() throws Exception { + mockResponseReader.readResponse(); } - private void readerReturnsReconstructedResponse() { - EasyMock.expect(mockResponseReader.getReconstructedResponse()).andReturn( - mockReconstructedResponse); + private void responseLimitReached(boolean limitReached) throws Exception { + EasyMock.expect(mockResponseReader.isLimitReached()).andReturn(limitReached); } - private void responseIsTooLarge(boolean tooLarge) throws Exception { - EasyMock.expect(mockResponseReader.isResponseTooLarge()).andReturn(tooLarge); + private void responseGetResource() throws Exception { + EasyMock.expect(mockResponseReader.getResource()).andReturn(new HeapResource(new byte[] {} )); + } + + private void responseGetReconstructed() throws Exception { + EasyMock.expect(mockResponseReader.getReconstructedResponse()).andReturn(mockReconstructedResponse); } private void backendCallWasMadeWithRequest(HttpRequest request) throws IOException { @@ -1245,13 +1247,6 @@ public class TestCachingHttpClient { mockCache.putEntry(theURI, entry); } - private void generateResource(byte [] b) throws IOException { - EasyMock.expect( - mockResourceFactory.generate( - EasyMock.anyObject(), - EasyMock.same(b))).andReturn(new HeapResource(b)); - } - private void copyResource() throws IOException { EasyMock.expect( mockResourceFactory.copy( diff --git a/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestCombinedEntity.java b/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestCombinedEntity.java new file mode 100644 index 000000000..e7bf759a0 --- /dev/null +++ b/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestCombinedEntity.java @@ -0,0 +1,59 @@ +/* + * ==================================================================== + * 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 + * . + * + */ +package org.apache.http.impl.client.cache; + +import java.io.ByteArrayInputStream; + +import org.apache.http.client.cache.Resource; +import org.apache.http.util.EntityUtils; +import org.easymock.classextension.EasyMock; +import org.junit.Assert; +import org.junit.Test; + +public class TestCombinedEntity { + + @Test + public void testCombinedEntityBasics() throws Exception { + Resource resource = EasyMock.createMock(Resource.class); + EasyMock.expect(resource.getInputStream()).andReturn( + new ByteArrayInputStream(new byte[] { 1, 2, 3, 4, 5 })); + resource.dispose(); + EasyMock.replay(resource); + + ByteArrayInputStream instream = new ByteArrayInputStream(new byte[] { 6, 7, 8, 9, 10 }); + CombinedEntity entity = new CombinedEntity(resource, instream); + Assert.assertEquals(-1, entity.getContentLength()); + Assert.assertFalse(entity.isRepeatable()); + Assert.assertTrue(entity.isStreaming()); + + byte[] result = EntityUtils.toByteArray(entity); + Assert.assertArrayEquals(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }, result); + + EasyMock.verify(resource); + } + +} diff --git a/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestCombinedInputStream.java b/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestCombinedInputStream.java deleted file mode 100644 index a2bd2fb68..000000000 --- a/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestCombinedInputStream.java +++ /dev/null @@ -1,118 +0,0 @@ -/* - * ==================================================================== - * 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 - * . - * - */ -package org.apache.http.impl.client.cache; - -import java.io.ByteArrayInputStream; -import java.io.InputStream; - -import org.easymock.classextension.EasyMock; -import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; - -/** - */ -public class TestCombinedInputStream { - - private InputStream mockInputStream1; - private InputStream mockInputStream2; - private CombinedInputStream impl; - - @Before - public void setUp() { - mockInputStream1 = EasyMock.createMock(InputStream.class); - mockInputStream2 = EasyMock.createMock(InputStream.class); - - impl = new CombinedInputStream(mockInputStream1, mockInputStream2); - } - - @Test - public void testCreatingInputStreamWithNullInputFails() { - - boolean gotex1 = false; - boolean gotex2 = false; - - try { - impl = new CombinedInputStream(null, mockInputStream2); - } catch (Exception ex) { - gotex1 = true; - } - - try { - impl = new CombinedInputStream(mockInputStream1, null); - } catch (Exception ex) { - gotex2 = true; - } - - Assert.assertTrue(gotex1); - Assert.assertTrue(gotex2); - - } - - @Test - public void testAvailableReturnsCorrectSize() throws Exception { - ByteArrayInputStream s1 = new ByteArrayInputStream(new byte[] { 1, 1, 1, 1, 1 }); - ByteArrayInputStream s2 = new ByteArrayInputStream(new byte[] { 1, 1, 1, 1, 1 }); - - impl = new CombinedInputStream(s1, s2); - int avail = impl.available(); - - Assert.assertEquals(10, avail); - } - - @Test - public void testFirstEmptyStreamReadsFromOtherStream() throws Exception { - org.easymock.EasyMock.expect(mockInputStream1.read()).andReturn(-1); - org.easymock.EasyMock.expect(mockInputStream2.read()).andReturn(500); - - replayMocks(); - int result = impl.read(); - verifyMocks(); - - Assert.assertEquals(500, result); - } - - @Test - public void testThatWeReadTheFirstInputStream() throws Exception { - org.easymock.EasyMock.expect(mockInputStream1.read()).andReturn(500); - - replayMocks(); - int result = impl.read(); - verifyMocks(); - - Assert.assertEquals(500, result); - } - - private void verifyMocks() { - EasyMock.verify(mockInputStream1, mockInputStream2); - } - - private void replayMocks() { - EasyMock.replay(mockInputStream1, mockInputStream2); - } - -} diff --git a/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestSizeLimitedResponseReader.java b/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestSizeLimitedResponseReader.java index fed725abb..02141bb2b 100644 --- a/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestSizeLimitedResponseReader.java +++ b/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestSizeLimitedResponseReader.java @@ -32,8 +32,11 @@ import java.io.InputStream; import org.apache.http.Header; import org.apache.http.HttpEntity; +import org.apache.http.HttpRequest; import org.apache.http.HttpResponse; -import org.apache.http.ProtocolVersion; +import org.apache.http.HttpVersion; +import org.apache.http.message.BasicRequestLine; +import org.apache.http.util.EntityUtils; import org.easymock.classextension.EasyMock; import org.junit.Assert; import org.junit.Before; @@ -41,211 +44,134 @@ import org.junit.Test; public class TestSizeLimitedResponseReader { - private static final int MAX_SIZE = 4; + private static final long MAX_SIZE = 4; private SizeLimitedResponseReader impl; + private HttpRequest mockRequest; private HttpResponse mockResponse; private HttpEntity mockEntity; - private InputStream mockInputStream; - private ProtocolVersion mockVersion; private boolean mockedImpl; @Before public void setUp() { + mockRequest = EasyMock.createMock(HttpRequest.class); mockResponse = EasyMock.createMock(HttpResponse.class); mockEntity = EasyMock.createMock(HttpEntity.class); - mockInputStream = EasyMock.createMock(InputStream.class); - mockVersion = EasyMock.createMock(ProtocolVersion.class); - } @Test public void testLargeResponseIsTooLarge() throws Exception { - - responseHasValidEntity(); - entityHasValidContentStream(); - inputStreamReturnsValidBytes(5); - - getReader(); - + byte[] buf = new byte[] { 1, 2, 3, 4, 5}; + requestReturnsRequestLine(); + responseReturnsProtocolVersion(); + responseReturnsHeaders(); + responseReturnsContent(new ByteArrayInputStream(buf)); + initReader(); replayMocks(); - boolean tooLarge = impl.isResponseTooLarge(); - byte[] result = impl.getResponseBytes(); + + impl.readResponse(); + boolean tooLarge = impl.isLimitReached(); + HttpResponse response = impl.getReconstructedResponse(); + byte[] result = EntityUtils.toByteArray(response.getEntity()); + verifyMocks(); - Assert.assertTrue(tooLarge); - - Assert.assertArrayEquals(new byte[] { 1, 1, 1, 1, 1 }, result); + Assert.assertArrayEquals(buf, result); } @Test public void testExactSizeResponseIsNotTooLarge() throws Exception { - responseHasValidEntity(); - entityHasValidContentStream(); - inputStreamReturnsValidBytes(4); - inputStreamReturnsEndOfStream(); - - getReader(); + byte[] buf = new byte[] { 1, 2, 3, 4 }; + requestReturnsRequestLine(); + responseReturnsProtocolVersion(); + responseReturnsHeaders(); + responseReturnsContent(new ByteArrayInputStream(buf)); + initReader(); replayMocks(); - boolean tooLarge = impl.isResponseTooLarge(); - byte[] result = impl.getResponseBytes(); + + impl.readResponse(); + boolean tooLarge = impl.isLimitReached(); + HttpResponse response = impl.getReconstructedResponse(); + byte[] result = EntityUtils.toByteArray(response.getEntity()); + verifyMocks(); - Assert.assertFalse(tooLarge); - - Assert.assertArrayEquals(new byte[] { 1, 1, 1, 1 }, result); + Assert.assertArrayEquals(buf, result); } @Test public void testSmallResponseIsNotTooLarge() throws Exception { - responseHasValidEntity(); - entityHasValidContentStream(); - - org.easymock.EasyMock.expect(mockInputStream.read()).andReturn(1).times(3); - - org.easymock.EasyMock.expect(mockInputStream.read()).andReturn(-1).times(2); - - getReader(); + byte[] buf = new byte[] { 1, 2, 3 }; + requestReturnsRequestLine(); + responseReturnsProtocolVersion(); + responseReturnsHeaders(); + responseReturnsContent(new ByteArrayInputStream(buf)); + initReader(); replayMocks(); - boolean tooLarge = impl.isResponseTooLarge(); - byte[] result = impl.getResponseBytes(); + + impl.readResponse(); + boolean tooLarge = impl.isLimitReached(); + HttpResponse response = impl.getReconstructedResponse(); + byte[] result = EntityUtils.toByteArray(response.getEntity()); verifyMocks(); Assert.assertFalse(tooLarge); - - Assert.assertArrayEquals(new byte[] { 1, 1, 1 }, result); + Assert.assertArrayEquals(buf, result); } @Test public void testResponseWithNoEntityIsNotTooLarge() throws Exception { responseHasNullEntity(); - getReader(); + initReader(); replayMocks(); - boolean tooLarge = impl.isResponseTooLarge(); + impl.readResponse(); + boolean tooLarge = impl.isLimitReached(); verifyMocks(); Assert.assertFalse(tooLarge); } - @Test - public void testReconstructedSmallResponseHasCorrectLength() throws Exception { - - byte[] expectedArray = new byte[] { 1, 1, 1, 1 }; - - InputStream stream = new ByteArrayInputStream(new byte[] {}); - - responseReturnsHeaders(); - responseReturnsProtocolVersion(); - - getReader(); - mockImplMethods("getResponseBytes", "getContentInputStream"); - getContentInputStreamReturns(stream); - getResponseBytesReturns(expectedArray); - replayMocks(); - - HttpResponse response = impl.getReconstructedResponse(); - - verifyMocks(); - - Assert.assertNotNull("Response should not be null", response); - InputStream resultStream = response.getEntity().getContent(); - - byte[] buffer = new byte[expectedArray.length]; - resultStream.read(buffer); - - Assert.assertArrayEquals(expectedArray, buffer); + private void responseReturnsContent(InputStream buffer) throws IOException { + EasyMock.expect(mockResponse.getEntity()).andReturn(mockEntity); + EasyMock.expect(mockEntity.getContent()).andReturn(buffer); } - private void getContentInputStreamReturns(InputStream inputStream) { - org.easymock.EasyMock.expect(impl.getContentInputStream()).andReturn(inputStream); - } - - @Test - public void testReconstructedLargeResponseHasCorrectLength() throws Exception { - - byte[] expectedArray = new byte[] { 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1 }; - byte[] arrayAfterConsumedBytes = new byte[] { 1, 1, 1, 1, 1, 1, 1 }; - byte[] smallArray = new byte[] { 2, 2, 2, 2, }; - InputStream is = new ByteArrayInputStream(arrayAfterConsumedBytes); - - responseReturnsHeaders(); - responseReturnsProtocolVersion(); - - getReader(); - mockImplMethods("getResponseBytes", "getContentInputStream"); - getResponseBytesReturns(smallArray); - getContentInputStreamReturns(is); - - replayMocks(); - - HttpResponse response = impl.getReconstructedResponse(); - - verifyMocks(); - - InputStream resultStream = response.getEntity().getContent(); - - byte[] buffer = new byte[expectedArray.length]; - resultStream.read(buffer); - - Assert.assertArrayEquals(expectedArray, buffer); - } - - private void getResponseBytesReturns(byte[] expectedArray) { - org.easymock.EasyMock.expect(impl.getResponseBytes()).andReturn(expectedArray); - } - - private void responseReturnsHeaders() { - org.easymock.EasyMock.expect(mockResponse.getAllHeaders()).andReturn(new Header[] {}); - } - - private void entityHasValidContentStream() throws IOException { - org.easymock.EasyMock.expect(mockEntity.getContent()).andReturn(mockInputStream); - } - - private void inputStreamReturnsEndOfStream() throws IOException { - org.easymock.EasyMock.expect(mockInputStream.read()).andReturn(-1); - } - - private void responseHasValidEntity() { - org.easymock.EasyMock.expect(mockResponse.getEntity()).andReturn(mockEntity); + private void requestReturnsRequestLine() { + EasyMock.expect(mockRequest.getRequestLine()).andReturn( + new BasicRequestLine("GET", "/", HttpVersion.HTTP_1_1)); } private void responseReturnsProtocolVersion() { - org.easymock.EasyMock.expect(mockResponse.getProtocolVersion()).andReturn(mockVersion); + EasyMock.expect(mockResponse.getProtocolVersion()).andReturn(HttpVersion.HTTP_1_1); } - private void inputStreamReturnsValidBytes(int times) throws IOException { - org.easymock.EasyMock.expect(mockInputStream.read()).andReturn(1).times(times); + private void responseReturnsHeaders() { + EasyMock.expect(mockResponse.getAllHeaders()).andReturn(new Header[] {}); } private void responseHasNullEntity() { - org.easymock.EasyMock.expect(mockResponse.getEntity()).andReturn(null); + EasyMock.expect(mockResponse.getEntity()).andReturn(null); } private void verifyMocks() { - EasyMock.verify(mockResponse, mockEntity, mockInputStream, mockVersion); + EasyMock.verify(mockRequest, mockResponse, mockEntity); if (mockedImpl) { EasyMock.verify(impl); } } private void replayMocks() { - EasyMock.replay(mockResponse, mockEntity, mockInputStream, mockVersion); + EasyMock.replay(mockRequest, mockResponse, mockEntity); if (mockedImpl) { EasyMock.replay(impl); } } - private void getReader() { - impl = new SizeLimitedResponseReader(MAX_SIZE, mockResponse); - } - - private void mockImplMethods(String... methods) { - mockedImpl = true; - impl = EasyMock.createMockBuilder(SizeLimitedResponseReader.class).withConstructor( - MAX_SIZE, mockResponse).addMockedMethods(methods).createMock(); + private void initReader() { + impl = new SizeLimitedResponseReader( + new HeapResourceFactory(), MAX_SIZE, mockRequest, mockResponse); } }