diff --git a/httpclient/src/main/java/org/apache/http/conn/EofSensorInputStream.java b/httpclient/src/main/java/org/apache/http/conn/EofSensorInputStream.java index 8957f8c87..47287a63b 100644 --- a/httpclient/src/main/java/org/apache/http/conn/EofSensorInputStream.java +++ b/httpclient/src/main/java/org/apache/http/conn/EofSensorInputStream.java @@ -33,14 +33,8 @@ import org.apache.http.annotation.NotThreadSafe; /** * A stream wrapper that triggers actions on {@link #close close()} and EOF. - * Primarily used to auto-release an underlying - * {@link ManagedClientConnection connection} - * when the response body is consumed or no longer needed. - *

- * This class is based on AutoCloseInputStream in HttpClient 3.1, - * but has notable differences. It does not allow mark/reset, distinguishes - * different kinds of event, and does not always close the underlying stream - * on EOF. That decision is left to the {@link EofSensorWatcher watcher}. + * Primarily used to auto-release an underlying managed connection when the response + * body is consumed or no longer needed. * * @see EofSensorWatcher * @@ -87,15 +81,21 @@ public class EofSensorInputStream extends InputStream implements ConnectionRelea public EofSensorInputStream(final InputStream in, final EofSensorWatcher watcher) { if (in == null) { - throw new IllegalArgumentException - ("Wrapped stream may not be null."); + throw new IllegalArgumentException("Wrapped stream may not be null"); } - wrappedStream = in; selfClosed = false; eofWatcher = watcher; } + boolean isSelfClosed() { + return selfClosed; + } + + InputStream getWrappedStream() { + return wrappedStream; + } + /** * Checks whether the underlying stream can be read from. * @@ -148,18 +148,7 @@ public class EofSensorInputStream extends InputStream implements ConnectionRelea @Override public int read(byte[] b) throws IOException { - int l = -1; - - if (isReadAllowed()) { - try { - l = wrappedStream.read(b); - checkEOF(l); - } catch (IOException ex) { - checkAbort(); - throw ex; - } - } - return l; + return read(b, 0, b.length); } @Override diff --git a/httpclient/src/main/java/org/apache/http/impl/client/builder/ConnectionReleaseTriggerImpl.java b/httpclient/src/main/java/org/apache/http/impl/client/builder/ConnectionReleaseTriggerImpl.java index 2a2ce4593..cdb92be57 100644 --- a/httpclient/src/main/java/org/apache/http/impl/client/builder/ConnectionReleaseTriggerImpl.java +++ b/httpclient/src/main/java/org/apache/http/impl/client/builder/ConnectionReleaseTriggerImpl.java @@ -95,8 +95,22 @@ class ConnectionReleaseTriggerImpl implements ConnectionReleaseTrigger, Cancella return; } this.released = true; - this.manager.releaseConnection(this.managedConn, - this.state, this.validDuration, this.tunit); + if (this.reusable) { + this.manager.releaseConnection(this.managedConn, + this.state, this.validDuration, this.tunit); + } else { + try { + this.managedConn.close(); + log.debug("Connection discarded"); + } catch (IOException ex) { + if (this.log.isDebugEnabled()) { + this.log.debug(ex.getMessage(), ex); + } + } finally { + this.manager.releaseConnection( + this.managedConn, null, 0, TimeUnit.MILLISECONDS); + } + } } } diff --git a/httpclient/src/main/java/org/apache/http/impl/client/builder/HttpRequestWrapper.java b/httpclient/src/main/java/org/apache/http/impl/client/builder/HttpRequestWrapper.java index 340f1d96b..3458b019d 100644 --- a/httpclient/src/main/java/org/apache/http/impl/client/builder/HttpRequestWrapper.java +++ b/httpclient/src/main/java/org/apache/http/impl/client/builder/HttpRequestWrapper.java @@ -27,11 +27,7 @@ package org.apache.http.impl.client.builder; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; import java.net.URI; -import java.net.URISyntaxException; import org.apache.http.annotation.NotThreadSafe; @@ -44,10 +40,8 @@ import org.apache.http.ProtocolException; import org.apache.http.ProtocolVersion; import org.apache.http.RequestLine; import org.apache.http.client.methods.HttpUriRequest; -import org.apache.http.entity.HttpEntityWrapper; import org.apache.http.message.AbstractHttpMessage; import org.apache.http.message.BasicRequestLine; -import org.apache.http.params.HttpProtocolParams; import org.apache.http.protocol.HTTP; /** @@ -62,34 +56,22 @@ class HttpRequestWrapper extends AbstractHttpMessage implements HttpRequest { private final HttpRequest original; private URI uri; - private String method; - private ProtocolVersion version; private HttpHost virtualHost; - private HttpRequestWrapper( - final ProtocolVersion version, - final URI uri, - final String method, - final HttpRequest request) { + private HttpRequestWrapper(final HttpRequest request) { super(); this.original = request; - this.uri = uri; - this.method = method; - this.version = version; + if (request instanceof HttpUriRequest) { + this.uri = ((HttpUriRequest) request).getURI(); + } else { + this.uri = null; + } setParams(request.getParams()); setHeaders(request.getAllHeaders()); } public ProtocolVersion getProtocolVersion() { - if (this.version != null) { - return this.version; - } else { - return HttpProtocolParams.getVersion(getParams()); - } - } - - public void setProtocolVersion(final ProtocolVersion version) { - this.version = version; + return this.original.getProtocolVersion(); } public URI getURI() { @@ -101,18 +83,18 @@ class HttpRequestWrapper extends AbstractHttpMessage implements HttpRequest { } public RequestLine getRequestLine() { + ProtocolVersion version = this.original.getRequestLine().getProtocolVersion(); + String method = this.original.getRequestLine().getMethod(); String uritext = null; if (this.uri != null) { - uritext = uri.toASCIIString(); + uritext = this.uri.toASCIIString(); + } else { + uritext = this.original.getRequestLine().getUri(); } if (uritext == null || uritext.length() == 0) { uritext = "/"; } - return new BasicRequestLine(this.method, uritext, getProtocolVersion()); - } - - public String getMethod() { - return this.method; + return new BasicRequestLine(method, uritext, version); } public HttpRequest getOriginal() { @@ -133,22 +115,17 @@ class HttpRequestWrapper extends AbstractHttpMessage implements HttpRequest { @Override public String toString() { - return this.method + " " + this.uri + " " + this.headergroup; + return getRequestLine() + " " + this.headergroup; } static class HttpEntityEnclosingRequestWrapper extends HttpRequestWrapper implements HttpEntityEnclosingRequest { private HttpEntity entity; - private boolean consumed; - public HttpEntityEnclosingRequestWrapper( - final ProtocolVersion version, - final URI uri, - final String method, - final HttpEntityEnclosingRequest request) + public HttpEntityEnclosingRequestWrapper(final HttpEntityEnclosingRequest request) throws ProtocolException { - super(version, uri, method, request); + super(request); setEntity(request.getEntity()); } @@ -157,7 +134,7 @@ class HttpRequestWrapper extends AbstractHttpMessage implements HttpRequest { } public void setEntity(final HttpEntity entity) { - this.entity = entity != null ? new EntityWrapper(entity) : null; + this.entity = entity != null ? new RequestEntityWrapper(entity) : null; } public boolean expectContinue() { @@ -167,32 +144,7 @@ class HttpRequestWrapper extends AbstractHttpMessage implements HttpRequest { @Override public boolean isRepeatable() { - return this.entity == null || this.entity.isRepeatable() || !this.consumed; - } - - class EntityWrapper extends HttpEntityWrapper { - - EntityWrapper(final HttpEntity entity) { - super(entity); - } - - @Deprecated - @Override - public void consumeContent() throws IOException { - consumed = true; - super.consumeContent(); - } - - @Override - public InputStream getContent() throws IOException { - return super.getContent(); - } - - @Override - public void writeTo(final OutputStream outstream) throws IOException { - consumed = true; - super.writeTo(outstream); - } + return this.entity == null || this.entity.isRepeatable(); } } @@ -201,28 +153,10 @@ class HttpRequestWrapper extends AbstractHttpMessage implements HttpRequest { if (request == null) { return null; } - ProtocolVersion version; - URI uri; - String method; - if (request instanceof HttpUriRequest) { - version = ((HttpUriRequest) request).getProtocolVersion(); - uri = ((HttpUriRequest) request).getURI(); - method = ((HttpUriRequest) request).getMethod(); - } else { - RequestLine requestLine = request.getRequestLine(); - version = request.getProtocolVersion(); - try { - uri = new URI(requestLine.getUri()); - } catch (URISyntaxException ex) { - throw new ProtocolException("Invalid request URI: " + requestLine.getUri(), ex); - } - method = request.getRequestLine().getMethod(); - } if (request instanceof HttpEntityEnclosingRequest) { - return new HttpEntityEnclosingRequestWrapper(version, uri, method, - (HttpEntityEnclosingRequest) request); + return new HttpEntityEnclosingRequestWrapper((HttpEntityEnclosingRequest) request); } else { - return new HttpRequestWrapper(version, uri, method, request); + return new HttpRequestWrapper(request); } } diff --git a/httpclient/src/main/java/org/apache/http/impl/client/builder/ProtocolExec.java b/httpclient/src/main/java/org/apache/http/impl/client/builder/ProtocolExec.java index 2f821c52e..c359460b0 100644 --- a/httpclient/src/main/java/org/apache/http/impl/client/builder/ProtocolExec.java +++ b/httpclient/src/main/java/org/apache/http/impl/client/builder/ProtocolExec.java @@ -78,23 +78,25 @@ class ProtocolExec implements ClientExecChain { final HttpRoute route) throws ProtocolException { try { URI uri = request.getURI(); - if (route.getProxyHost() != null && !route.isTunnelled()) { - // Make sure the request URI is absolute - if (!uri.isAbsolute()) { - HttpHost target = route.getTargetHost(); - uri = URIUtils.rewriteURI(uri, target, true); + if (uri != null) { + if (route.getProxyHost() != null && !route.isTunnelled()) { + // Make sure the request URI is absolute + if (!uri.isAbsolute()) { + HttpHost target = route.getTargetHost(); + uri = URIUtils.rewriteURI(uri, target, true); + } else { + uri = URIUtils.rewriteURI(uri); + } } else { - uri = URIUtils.rewriteURI(uri); - } - } else { - // Make sure the request URI is relative - if (uri.isAbsolute()) { - uri = URIUtils.rewriteURI(uri, null); - } else { - uri = URIUtils.rewriteURI(uri); + // Make sure the request URI is relative + if (uri.isAbsolute()) { + uri = URIUtils.rewriteURI(uri, null); + } else { + uri = URIUtils.rewriteURI(uri); + } } + request.setURI(uri); } - request.setURI(uri); } catch (URISyntaxException ex) { throw new ProtocolException("Invalid URI: " + request.getRequestLine().getUri(), ex); diff --git a/httpclient/src/main/java/org/apache/http/impl/client/builder/RequestEntityWrapper.java b/httpclient/src/main/java/org/apache/http/impl/client/builder/RequestEntityWrapper.java new file mode 100644 index 000000000..b1e513385 --- /dev/null +++ b/httpclient/src/main/java/org/apache/http/impl/client/builder/RequestEntityWrapper.java @@ -0,0 +1,50 @@ +package org.apache.http.impl.client.builder; + +import java.io.IOException; +import java.io.OutputStream; + +import org.apache.http.HttpEntity; +import org.apache.http.annotation.NotThreadSafe; +import org.apache.http.entity.HttpEntityWrapper; + +/** + * A wrapper class for {@link HttpEntity} enclosed in a request message. + * + * @since 4.3 + */ +@NotThreadSafe +class RequestEntityWrapper extends HttpEntityWrapper { + + private boolean consumed = false; + + RequestEntityWrapper(final HttpEntity entity) { + super(entity); + } + + @Override + public boolean isRepeatable() { + if (!this.consumed) { + return true; + } else { + return super.isRepeatable(); + } + } + + @Deprecated + @Override + public void consumeContent() throws IOException { + consumed = true; + super.consumeContent(); + } + + @Override + public void writeTo(final OutputStream outstream) throws IOException { + consumed = true; + super.writeTo(outstream); + } + + public boolean isConsumed() { + return consumed; + } + +} \ No newline at end of file diff --git a/httpclient/src/main/java/org/apache/http/impl/client/builder/ResponseEntityWrapper.java b/httpclient/src/main/java/org/apache/http/impl/client/builder/ResponseEntityWrapper.java index fff998e07..b6dcb70c4 100644 --- a/httpclient/src/main/java/org/apache/http/impl/client/builder/ResponseEntityWrapper.java +++ b/httpclient/src/main/java/org/apache/http/impl/client/builder/ResponseEntityWrapper.java @@ -37,10 +37,9 @@ import org.apache.http.annotation.NotThreadSafe; import org.apache.http.conn.EofSensorInputStream; import org.apache.http.conn.EofSensorWatcher; import org.apache.http.entity.HttpEntityWrapper; -import org.apache.http.util.EntityUtils; /** - * A wrapper class for {@link HttpEntity} encloded in a response message. + * A wrapper class for {@link HttpEntity} enclosed in a response message. * * @since 4.3 */ @@ -66,7 +65,6 @@ class ResponseEntityWrapper extends HttpEntityWrapper implements EofSensorWatche if (this.connReleaseTrigger != null) { try { if (this.connReleaseTrigger.isReusable()) { - EntityUtils.consume(this.wrappedEntity); this.connReleaseTrigger.releaseConnection(); } } finally { @@ -93,8 +91,12 @@ class ResponseEntityWrapper extends HttpEntityWrapper implements EofSensorWatche @Override public void writeTo(final OutputStream outstream) throws IOException { - this.wrappedEntity.writeTo(outstream); - releaseConnection(); + try { + this.wrappedEntity.writeTo(outstream); + releaseConnection(); + } finally { + cleanup(); + } } public boolean eofDetected(final InputStream wrapped) throws IOException { diff --git a/httpclient/src/test/java/org/apache/http/conn/TestEofSensorInputStream.java b/httpclient/src/test/java/org/apache/http/conn/TestEofSensorInputStream.java new file mode 100644 index 000000000..59a018b3c --- /dev/null +++ b/httpclient/src/test/java/org/apache/http/conn/TestEofSensorInputStream.java @@ -0,0 +1,227 @@ +/* + * ==================================================================== + * + * 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.conn; + +import java.io.IOException; +import java.io.InputStream; + +import junit.framework.Assert; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mockito; + +public class TestEofSensorInputStream { + + private InputStream instream; + private EofSensorWatcher eofwatcher; + private EofSensorInputStream eofstream; + + @Before + public void setup() throws Exception { + instream = Mockito.mock(InputStream.class); + eofwatcher = Mockito.mock(EofSensorWatcher.class); + eofstream = new EofSensorInputStream(instream, eofwatcher); + } + + @Test + public void testClose() throws Exception { + Mockito.when(eofwatcher.streamClosed(Mockito.any())).thenReturn(true); + + eofstream.close(); + + Assert.assertTrue(eofstream.isSelfClosed()); + Assert.assertNull(eofstream.getWrappedStream()); + + Mockito.verify(instream, Mockito.times(1)).close(); + Mockito.verify(eofwatcher).streamClosed(instream); + + eofstream.close(); + } + + @Test + public void testCloseIOError() throws Exception { + Mockito.when(eofwatcher.streamClosed(Mockito.any())).thenThrow(new IOException()); + + try { + eofstream.close(); + Assert.fail("IOException expected"); + } catch (IOException ex) { + } + Assert.assertTrue(eofstream.isSelfClosed()); + Assert.assertNull(eofstream.getWrappedStream()); + + Mockito.verify(eofwatcher).streamClosed(instream); + } + + @Test + public void testReleaseConnection() throws Exception { + Mockito.when(eofwatcher.streamClosed(Mockito.any())).thenReturn(true); + + eofstream.releaseConnection(); + + Assert.assertTrue(eofstream.isSelfClosed()); + Assert.assertNull(eofstream.getWrappedStream()); + + Mockito.verify(instream, Mockito.times(1)).close(); + Mockito.verify(eofwatcher).streamClosed(instream); + + eofstream.releaseConnection(); + } + + @Test + public void testAbortConnection() throws Exception { + Mockito.when(eofwatcher.streamAbort(Mockito.any())).thenReturn(true); + + eofstream.abortConnection(); + + Assert.assertTrue(eofstream.isSelfClosed()); + Assert.assertNull(eofstream.getWrappedStream()); + + Mockito.verify(instream, Mockito.times(1)).close(); + Mockito.verify(eofwatcher).streamAbort(instream); + + eofstream.abortConnection(); + } + + @Test + public void testAbortConnectionIOError() throws Exception { + Mockito.when(eofwatcher.streamAbort(Mockito.any())).thenThrow(new IOException()); + + try { + eofstream.abortConnection(); + Assert.fail("IOException expected"); + } catch (IOException ex) { + } + Assert.assertTrue(eofstream.isSelfClosed()); + Assert.assertNull(eofstream.getWrappedStream()); + + Mockito.verify(eofwatcher).streamAbort(instream); + } + + @Test + public void testRead() throws Exception { + Mockito.when(eofwatcher.eofDetected(Mockito.any())).thenReturn(true); + Mockito.when(instream.read()).thenReturn(0, -1); + + Assert.assertEquals(0, eofstream.read()); + + Assert.assertFalse(eofstream.isSelfClosed()); + Assert.assertNotNull(eofstream.getWrappedStream()); + + Mockito.verify(eofwatcher, Mockito.never()).eofDetected(instream); + + Assert.assertEquals(-1, eofstream.read()); + + Assert.assertFalse(eofstream.isSelfClosed()); + Assert.assertNull(eofstream.getWrappedStream()); + + Mockito.verify(instream, Mockito.times(1)).close(); + Mockito.verify(eofwatcher).eofDetected(instream); + + Assert.assertEquals(-1, eofstream.read()); + } + + @Test + public void testReadIOError() throws Exception { + Mockito.when(eofwatcher.eofDetected(Mockito.any())).thenReturn(true); + Mockito.when(instream.read()).thenThrow(new IOException()); + + try { + eofstream.read(); + Assert.fail("IOException expected"); + } catch (IOException ex) { + } + Assert.assertFalse(eofstream.isSelfClosed()); + Assert.assertNull(eofstream.getWrappedStream()); + + Mockito.verify(eofwatcher).streamAbort(instream); + } + + @Test + public void testReadByteArray() throws Exception { + Mockito.when(eofwatcher.eofDetected(Mockito.any())).thenReturn(true); + Mockito.when(instream.read(Mockito.any(), Mockito.anyInt(), Mockito.anyInt())) + .thenReturn(1, -1); + + byte[] tmp = new byte[1]; + + Assert.assertEquals(1, eofstream.read(tmp)); + + Assert.assertFalse(eofstream.isSelfClosed()); + Assert.assertNotNull(eofstream.getWrappedStream()); + + Mockito.verify(eofwatcher, Mockito.never()).eofDetected(instream); + + Assert.assertEquals(-1, eofstream.read(tmp)); + + Assert.assertFalse(eofstream.isSelfClosed()); + Assert.assertNull(eofstream.getWrappedStream()); + + Mockito.verify(instream, Mockito.times(1)).close(); + Mockito.verify(eofwatcher).eofDetected(instream); + + Assert.assertEquals(-1, eofstream.read(tmp)); + } + + @Test + public void testReadByteArrayIOError() throws Exception { + Mockito.when(eofwatcher.eofDetected(Mockito.any())).thenReturn(true); + Mockito.when(instream.read(Mockito.any(), Mockito.anyInt(), Mockito.anyInt())) + .thenThrow(new IOException()); + + byte[] tmp = new byte[1]; + try { + eofstream.read(tmp); + Assert.fail("IOException expected"); + } catch (IOException ex) { + } + Assert.assertFalse(eofstream.isSelfClosed()); + Assert.assertNull(eofstream.getWrappedStream()); + + Mockito.verify(eofwatcher).streamAbort(instream); + } + + @Test + public void testReadAfterAbort() throws Exception { + Mockito.when(eofwatcher.streamAbort(Mockito.any())).thenReturn(true); + + eofstream.abortConnection(); + + try { + eofstream.read(); + Assert.fail("IOException expected"); + } catch (IOException ex) { + } + byte[] tmp = new byte[1]; + try { + eofstream.read(tmp); + Assert.fail("IOException expected"); + } catch (IOException ex) { + } + } + +} diff --git a/httpclient/src/test/java/org/apache/http/impl/client/builder/TestConnectionReleaseTriggerImpl.java b/httpclient/src/test/java/org/apache/http/impl/client/builder/TestConnectionReleaseTriggerImpl.java new file mode 100644 index 000000000..05ecc906d --- /dev/null +++ b/httpclient/src/test/java/org/apache/http/impl/client/builder/TestConnectionReleaseTriggerImpl.java @@ -0,0 +1,150 @@ +/* + * ==================================================================== + * + * 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.builder; + +import junit.framework.Assert; + +import java.io.IOException; + +import java.util.concurrent.TimeUnit; + +import org.apache.commons.logging.Log; +import org.apache.http.HttpClientConnection; +import org.apache.http.conn.HttpClientConnectionManager; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mockito; + +public class TestConnectionReleaseTriggerImpl { + + private Log log; + private HttpClientConnectionManager mgr; + private HttpClientConnection conn; + private ConnectionReleaseTriggerImpl releaseTrigger; + + @Before + public void setup() { + log = Mockito.mock(Log.class); + mgr = Mockito.mock(HttpClientConnectionManager.class); + conn = Mockito.mock(HttpClientConnection.class); + releaseTrigger = new ConnectionReleaseTriggerImpl(log, mgr, conn); + } + + @Test + public void testAbortConnection() throws Exception { + releaseTrigger.abortConnection(); + + Assert.assertTrue(releaseTrigger.isReleased()); + + Mockito.verify(conn).shutdown(); + Mockito.verify(mgr).releaseConnection(conn, null, 0, TimeUnit.MILLISECONDS); + + releaseTrigger.abortConnection(); + + Mockito.verify(conn, Mockito.times(1)).shutdown(); + Mockito.verify(mgr, Mockito.times(1)).releaseConnection( + Mockito.any(), + Mockito.anyObject(), + Mockito.anyLong(), + Mockito.any()); + } + + @Test + public void testAbortConnectionIOError() throws Exception { + Mockito.doThrow(new IOException()).when(conn).shutdown(); + + releaseTrigger.abortConnection(); + + Assert.assertTrue(releaseTrigger.isReleased()); + + Mockito.verify(conn).shutdown(); + Mockito.verify(mgr).releaseConnection(conn, null, 0, TimeUnit.MILLISECONDS); + } + + @Test + public void testCancell() throws Exception { + Assert.assertTrue(releaseTrigger.cancel()); + + Assert.assertTrue(releaseTrigger.isReleased()); + + Mockito.verify(conn).shutdown(); + Mockito.verify(mgr).releaseConnection(conn, null, 0, TimeUnit.MILLISECONDS); + + Assert.assertFalse(releaseTrigger.cancel()); + + Mockito.verify(conn, Mockito.times(1)).shutdown(); + Mockito.verify(mgr, Mockito.times(1)).releaseConnection( + Mockito.any(), + Mockito.anyObject(), + Mockito.anyLong(), + Mockito.any()); + } + + @Test + public void testReleaseConnectionReusable() throws Exception { + releaseTrigger.setState("some state"); + releaseTrigger.setValidFor(100, TimeUnit.SECONDS); + releaseTrigger.markReusable(); + + releaseTrigger.releaseConnection(); + + Assert.assertTrue(releaseTrigger.isReleased()); + + Mockito.verify(conn, Mockito.never()).close(); + Mockito.verify(mgr).releaseConnection(conn, "some state", 100, TimeUnit.SECONDS); + + releaseTrigger.releaseConnection(); + + Mockito.verify(mgr, Mockito.times(1)).releaseConnection( + Mockito.any(), + Mockito.anyObject(), + Mockito.anyLong(), + Mockito.any()); + } + + @Test + public void testReleaseConnectionNonReusable() throws Exception { + releaseTrigger.setState("some state"); + releaseTrigger.setValidFor(100, TimeUnit.SECONDS); + releaseTrigger.markNonReusable(); + + releaseTrigger.releaseConnection(); + + Assert.assertTrue(releaseTrigger.isReleased()); + + Mockito.verify(conn, Mockito.times(1)).close(); + Mockito.verify(mgr).releaseConnection(conn, null, 0, TimeUnit.MILLISECONDS); + + releaseTrigger.releaseConnection(); + + Mockito.verify(mgr, Mockito.times(1)).releaseConnection( + Mockito.any(), + Mockito.anyObject(), + Mockito.anyLong(), + Mockito.any()); + } + +} diff --git a/httpclient/src/test/java/org/apache/http/impl/client/builder/TestRequestEntityWrapper.java b/httpclient/src/test/java/org/apache/http/impl/client/builder/TestRequestEntityWrapper.java new file mode 100644 index 000000000..a551685a5 --- /dev/null +++ b/httpclient/src/test/java/org/apache/http/impl/client/builder/TestRequestEntityWrapper.java @@ -0,0 +1,63 @@ +/* + * ==================================================================== + * + * 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.builder; + +import java.io.OutputStream; + +import junit.framework.Assert; + +import org.apache.http.HttpEntity; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mockito; + +public class TestRequestEntityWrapper { + + private HttpEntity entity; + private RequestEntityWrapper wrapper; + + @Before + public void setup() throws Exception { + entity = Mockito.mock(HttpEntity.class);; + wrapper = new RequestEntityWrapper(entity); + } + + @Test + public void testNonRepeatableEntityWriteTo() throws Exception { + Mockito.when(entity.isRepeatable()).thenReturn(false); + + Assert.assertTrue(wrapper.isRepeatable()); + + OutputStream outstream = Mockito.mock(OutputStream.class); + wrapper.writeTo(outstream); + + Assert.assertTrue(wrapper.isConsumed()); + Assert.assertFalse(wrapper.isRepeatable()); + + Mockito.verify(entity).writeTo(outstream); + } + +} diff --git a/httpclient/src/test/java/org/apache/http/impl/client/builder/TestResponseEntityWrapper.java b/httpclient/src/test/java/org/apache/http/impl/client/builder/TestResponseEntityWrapper.java new file mode 100644 index 000000000..d7c81136e --- /dev/null +++ b/httpclient/src/test/java/org/apache/http/impl/client/builder/TestResponseEntityWrapper.java @@ -0,0 +1,140 @@ +/* + * ==================================================================== + * + * 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.builder; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.SocketException; + +import junit.framework.Assert; + +import org.apache.http.HttpEntity; +import org.apache.http.util.EntityUtils; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mockito; + +public class TestResponseEntityWrapper { + + private InputStream instream; + private HttpEntity entity; + private ConnectionReleaseTriggerImpl releaseTrigger; + private ResponseEntityWrapper wrapper; + + @Before + public void setup() throws Exception { + instream = Mockito.mock(InputStream.class); + entity = Mockito.mock(HttpEntity.class);; + Mockito.when(entity.getContent()).thenReturn(instream); + releaseTrigger = Mockito.mock(ConnectionReleaseTriggerImpl.class); + wrapper = new ResponseEntityWrapper(entity, releaseTrigger); + } + + @Test + public void testReusableEntityStreamClosed() throws Exception { + Mockito.when(entity.isStreaming()).thenReturn(true); + Mockito.when(releaseTrigger.isReusable()).thenReturn(true); + EntityUtils.consume(wrapper); + + Mockito.verify(instream, Mockito.times(1)).close(); + Mockito.verify(releaseTrigger).releaseConnection(); + } + + @Test + public void testReusableEntityStreamClosedIOError() throws Exception { + Mockito.when(entity.isStreaming()).thenReturn(true); + Mockito.when(releaseTrigger.isReusable()).thenReturn(true); + Mockito.doThrow(new IOException()).when(instream).close(); + try { + EntityUtils.consume(wrapper); + Assert.fail("IOException expected"); + } catch (IOException ex) { + } + Mockito.verify(releaseTrigger).abortConnection(); + } + + @Test + public void testEntityStreamClosedIOErrorAlreadyReleased() throws Exception { + Mockito.when(entity.isStreaming()).thenReturn(true); + Mockito.when(releaseTrigger.isReusable()).thenReturn(true); + Mockito.when(releaseTrigger.isReleased()).thenReturn(true); + Mockito.doThrow(new SocketException()).when(instream).close(); + EntityUtils.consume(wrapper); + Mockito.verify(releaseTrigger).abortConnection(); + } + + @Test + public void testReusableEntityWriteTo() throws Exception { + OutputStream outstream = Mockito.mock(OutputStream.class); + Mockito.when(entity.isStreaming()).thenReturn(true); + Mockito.when(releaseTrigger.isReusable()).thenReturn(true); + wrapper.writeTo(outstream); + Mockito.verify(releaseTrigger).releaseConnection(); + } + + @Test + public void testReusableEntityWriteToIOError() throws Exception { + OutputStream outstream = Mockito.mock(OutputStream.class); + Mockito.when(entity.isStreaming()).thenReturn(true); + Mockito.when(releaseTrigger.isReusable()).thenReturn(true); + Mockito.doThrow(new IOException()).when(entity).writeTo(outstream); + try { + wrapper.writeTo(outstream); + Assert.fail("IOException expected"); + } catch (IOException ex) { + } + Mockito.verify(releaseTrigger, Mockito.never()).releaseConnection(); + Mockito.verify(releaseTrigger).abortConnection(); + } + + @Test + public void testReusableEntityEndOfStream() throws Exception { + Mockito.when(instream.read()).thenReturn(-1); + Mockito.when(entity.isStreaming()).thenReturn(true); + Mockito.when(releaseTrigger.isReusable()).thenReturn(true); + InputStream content = wrapper.getContent(); + Assert.assertEquals(-1, content.read()); + Mockito.verify(instream).close(); + Mockito.verify(releaseTrigger).releaseConnection(); + } + + @Test + public void testReusableEntityEndOfStreamIOError() throws Exception { + Mockito.when(instream.read()).thenReturn(-1); + Mockito.when(entity.isStreaming()).thenReturn(true); + Mockito.when(releaseTrigger.isReusable()).thenReturn(true); + Mockito.doThrow(new IOException()).when(instream).close(); + InputStream content = wrapper.getContent(); + try { + content.read(); + Assert.fail("IOException expected"); + } catch (IOException ex) { + } + Mockito.verify(releaseTrigger).abortConnection(); + } + +} diff --git a/httpclient/src/test/java/org/apache/http/conn/TestConnectionReuse.java b/httpclient/src/test/java/org/apache/http/impl/client/integration/TestConnectionReuse.java similarity index 99% rename from httpclient/src/test/java/org/apache/http/conn/TestConnectionReuse.java rename to httpclient/src/test/java/org/apache/http/impl/client/integration/TestConnectionReuse.java index c7b1cfe6e..2bc2b46de 100644 --- a/httpclient/src/test/java/org/apache/http/conn/TestConnectionReuse.java +++ b/httpclient/src/test/java/org/apache/http/impl/client/integration/TestConnectionReuse.java @@ -25,7 +25,7 @@ * */ -package org.apache.http.conn; +package org.apache.http.impl.client.integration; import java.io.IOException; import java.net.InetSocketAddress;