Return-Path: Delivered-To: apmail-incubator-cxf-commits-archive@locus.apache.org Received: (qmail 9206 invoked from network); 26 Jul 2007 13:14:48 -0000 Received: from hermes.apache.org (HELO mail.apache.org) (140.211.11.2) by minotaur.apache.org with SMTP; 26 Jul 2007 13:14:48 -0000 Received: (qmail 80849 invoked by uid 500); 26 Jul 2007 13:14:49 -0000 Delivered-To: apmail-incubator-cxf-commits-archive@incubator.apache.org Received: (qmail 80799 invoked by uid 500); 26 Jul 2007 13:14:48 -0000 Mailing-List: contact cxf-commits-help@incubator.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: cxf-dev@incubator.apache.org Delivered-To: mailing list cxf-commits@incubator.apache.org Received: (qmail 80790 invoked by uid 99); 26 Jul 2007 13:14:48 -0000 Received: from herse.apache.org (HELO herse.apache.org) (140.211.11.133) by apache.org (qpsmtpd/0.29) with ESMTP; Thu, 26 Jul 2007 06:14:48 -0700 X-ASF-Spam-Status: No, hits=-99.5 required=10.0 tests=ALL_TRUSTED,NO_REAL_NAME X-Spam-Check-By: apache.org Received: from [140.211.11.3] (HELO eris.apache.org) (140.211.11.3) by apache.org (qpsmtpd/0.29) with ESMTP; Thu, 26 Jul 2007 06:14:46 -0700 Received: by eris.apache.org (Postfix, from userid 65534) id A11B41A981A; Thu, 26 Jul 2007 06:14:26 -0700 (PDT) Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Subject: svn commit: r559803 - in /incubator/cxf/trunk: common/common/src/main/java/org/apache/cxf/helpers/ rt/transports/http/src/main/java/org/apache/cxf/transport/http/ rt/transports/http/src/test/java/org/apache/cxf/transport/http/ Date: Thu, 26 Jul 2007 13:14:26 -0000 To: cxf-commits@incubator.apache.org From: eglynn@apache.org X-Mailer: svnmailer-1.1.0 Message-Id: <20070726131426.A11B41A981A@eris.apache.org> X-Virus-Checked: Checked by ClamAV on apache.org Author: eglynn Date: Thu Jul 26 06:14:25 2007 New Revision: 559803 URL: http://svn.apache.org/viewvc?view=rev&rev=559803 Log: Better determination of when a partial response payload is present in the HTTP reply to a oneway or decoupled request (see CXF-845). Modified: incubator/cxf/trunk/common/common/src/main/java/org/apache/cxf/helpers/HttpHeaderHelper.java incubator/cxf/trunk/rt/transports/http/src/main/java/org/apache/cxf/transport/http/HTTPConduit.java incubator/cxf/trunk/rt/transports/http/src/test/java/org/apache/cxf/transport/http/HTTPConduitURLEasyMockTest.java Modified: incubator/cxf/trunk/common/common/src/main/java/org/apache/cxf/helpers/HttpHeaderHelper.java URL: http://svn.apache.org/viewvc/incubator/cxf/trunk/common/common/src/main/java/org/apache/cxf/helpers/HttpHeaderHelper.java?view=diff&rev=559803&r1=559802&r2=559803 ============================================================================== --- incubator/cxf/trunk/common/common/src/main/java/org/apache/cxf/helpers/HttpHeaderHelper.java (original) +++ incubator/cxf/trunk/common/common/src/main/java/org/apache/cxf/helpers/HttpHeaderHelper.java Thu Jul 26 06:14:25 2007 @@ -29,6 +29,11 @@ public static final String CONTENT_ID = "Content-ID"; public static final String CONTENT_TRANSFER_ENCODING = "Content-Transfer-Encoding"; public static final String COOKIE = "Cookie"; + public static final String TRANSFER_ENCODING = "Transfer-Encoding"; + public static final String CHUNKED = "chunked"; + public static final String CONNECTION = "Connection"; + public static final String CLOSE = "close"; + private static Map internalHeaders = new HashMap(); @@ -36,6 +41,8 @@ internalHeaders.put("Content-Type", "content-type"); internalHeaders.put("Content-ID", "content-id"); internalHeaders.put("Content-Transfer-Encoding", "content-transfer-encoding"); + internalHeaders.put("Transfer-Encoding", "transfer-encoding"); + internalHeaders.put("Connection", "connection"); } private HttpHeaderHelper() { Modified: incubator/cxf/trunk/rt/transports/http/src/main/java/org/apache/cxf/transport/http/HTTPConduit.java URL: http://svn.apache.org/viewvc/incubator/cxf/trunk/rt/transports/http/src/main/java/org/apache/cxf/transport/http/HTTPConduit.java?view=diff&rev=559803&r1=559802&r2=559803 ============================================================================== --- incubator/cxf/trunk/rt/transports/http/src/main/java/org/apache/cxf/transport/http/HTTPConduit.java (original) +++ incubator/cxf/trunk/rt/transports/http/src/main/java/org/apache/cxf/transport/http/HTTPConduit.java Thu Jul 26 06:14:25 2007 @@ -22,6 +22,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.io.PushbackInputStream; import java.net.HttpURLConnection; import java.net.InetSocketAddress; import java.net.MalformedURLException; @@ -925,18 +926,77 @@ } /** + * @return true if expecting a decoupled response + */ + private boolean isDecoupled() { + return decoupledDestination != null; + } + + /** + * Get an input stream containing the partial response if one is present. + * * @param connection the connection in question * @param responseCode the response code - * @return true if a partial response is pending on the connection + * @return an input stream if a partial response is pending on the connection */ - private boolean isPartialResponse( + protected static InputStream getPartialResponse( HttpURLConnection connection, int responseCode + ) throws IOException { + InputStream in = null; + if (responseCode == HttpURLConnection.HTTP_ACCEPTED + || responseCode == HttpURLConnection.HTTP_OK) { + if (connection.getContentLength() > 0) { + in = connection.getInputStream(); + } else if (hasChunkedResponse(connection) + || hasEofTerminatedResponse(connection)) { + // ensure chunked or EOF-terminated response is non-empty + in = getNonEmptyContent(connection); + } + } + return in; + } + + /** + * @param connection the given HttpURLConnection + * @return true iff the connection has a chunked response pending + */ + private static boolean hasChunkedResponse(HttpURLConnection connection) { + return HttpHeaderHelper.CHUNKED.equalsIgnoreCase( + connection.getHeaderField(HttpHeaderHelper.TRANSFER_ENCODING)); + } + + /** + * @param connection the given HttpURLConnection + * @return true iff the connection has a chunked response pending + */ + private static boolean hasEofTerminatedResponse( + HttpURLConnection connection + ) { + return HttpHeaderHelper.CLOSE.equalsIgnoreCase( + connection.getHeaderField(HttpHeaderHelper.CONNECTION)); + } + + /** + * @param connection the given HttpURLConnection + * @return an input stream containing the response content if non-empty + */ + private static InputStream getNonEmptyContent( + HttpURLConnection connection ) { - return (responseCode == HttpURLConnection.HTTP_ACCEPTED - && connection.getContentLength() != 0) - || (responseCode == HttpURLConnection.HTTP_OK - && connection.getContentLength() > 0); + InputStream in = null; + try { + PushbackInputStream pin = + new PushbackInputStream(connection.getInputStream()); + int c = pin.read(); + if (c != -1) { + pin.unread((byte)c); + in = pin; + } + } catch (IOException ioe) { + // ignore + } + return in; } /** @@ -1852,16 +1912,20 @@ Exchange exchange = outMessage.getExchange(); - if (isOneway(exchange) - && !isPartialResponse(connection, responseCode)) { - // oneway operation without partial response - connection.getInputStream().close(); - return; + InputStream in = null; + if (isOneway(exchange) || isDecoupled()) { + in = getPartialResponse(connection, responseCode); + if (in == null) { + // oneway operation or decoupled MEP without + // partial response + connection.getInputStream().close(); + return; + } } Message inMessage = new MessageImpl(); inMessage.setExchange(exchange); - InputStream in = null; + Map> headers = new HashMap>(); for (String key : connection.getHeaderFields().keySet()) { @@ -1886,10 +1950,12 @@ } } - in = connection.getErrorStream(); - if (null == in) { - in = connection.getInputStream(); - } + in = in == null + ? connection.getErrorStream() == null + ? connection.getInputStream() + : connection.getErrorStream() + : in; + if (in == null) { LOG.log(Level.WARNING, "Input Stream is null!"); } Modified: incubator/cxf/trunk/rt/transports/http/src/test/java/org/apache/cxf/transport/http/HTTPConduitURLEasyMockTest.java URL: http://svn.apache.org/viewvc/incubator/cxf/trunk/rt/transports/http/src/test/java/org/apache/cxf/transport/http/HTTPConduitURLEasyMockTest.java?view=diff&rev=559803&r1=559802&r2=559803 ============================================================================== --- incubator/cxf/trunk/rt/transports/http/src/test/java/org/apache/cxf/transport/http/HTTPConduitURLEasyMockTest.java (original) +++ incubator/cxf/trunk/rt/transports/http/src/test/java/org/apache/cxf/transport/http/HTTPConduitURLEasyMockTest.java Thu Jul 26 06:14:25 2007 @@ -22,6 +22,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.io.PushbackInputStream; import java.net.HttpURLConnection; import java.net.Proxy; import java.net.URL; @@ -31,13 +32,11 @@ import java.util.List; import java.util.Map; -import javax.servlet.ServletInputStream; -import javax.servlet.ServletOutputStream; - import org.apache.cxf.Bus; import org.apache.cxf.bus.CXFBusImpl; import org.apache.cxf.configuration.security.AuthorizationPolicy; import org.apache.cxf.helpers.CastUtils; +import org.apache.cxf.message.Exchange; import org.apache.cxf.message.Message; import org.apache.cxf.message.MessageImpl; import org.apache.cxf.service.model.EndpointInfo; @@ -58,6 +57,9 @@ /** */ public class HTTPConduitURLEasyMockTest extends Assert { + + private enum ResponseStyle { NONE, BACK_CHANNEL, DECOUPLED }; + private enum ResponseDelimiter { LENGTH, CHUNKED, EOF }; private static final String NOWHERE = "http://nada.nothing.nowhere.null/"; private static final String PAYLOAD = "message payload"; @@ -68,8 +70,8 @@ private Proxy proxy; private Message inMessage; private MessageObserver observer; - private ServletOutputStream os; - private ServletInputStream is; + private OutputStream os; + private InputStream is; /** * This is an extension to the HTTPConduit that replaces @@ -156,17 +158,156 @@ verifySentMessage(conduit, message); finalVerify(); } + + @Test + public void testSendOnewayExplicitLenghtPartialResponse() + throws Exception { + control = EasyMock.createNiceControl(); + HTTPConduit conduit = setUpConduit(true, false, true); + Message message = new MessageImpl(); + conduit.prepare(message); + verifySentMessage(conduit, + message, + ResponseStyle.NONE, + ResponseDelimiter.LENGTH, + false); // non-empty response + finalVerify(); + } + + @Test + public void testSendOnewayChunkedPartialResponse() + throws Exception { + control = EasyMock.createNiceControl(); + HTTPConduit conduit = setUpConduit(true, false, true); + Message message = new MessageImpl(); + conduit.prepare(message); + verifySentMessage(conduit, + message, + ResponseStyle.NONE, + ResponseDelimiter.CHUNKED, + false); // non-empty response + finalVerify(); + } @Test - public void testSendDecoupled() throws Exception { + public void testSendOnewayChunkedEmptyPartialResponse() + throws Exception { + control = EasyMock.createNiceControl(); + HTTPConduit conduit = setUpConduit(true, false, true); + Message message = new MessageImpl(); + conduit.prepare(message); + verifySentMessage(conduit, + message, + ResponseStyle.NONE, + ResponseDelimiter.CHUNKED, + true); // empty response + finalVerify(); + } + + @Test + public void testSendOnewayEOFTerminatedPartialResponse() + throws Exception { control = EasyMock.createNiceControl(); HTTPConduit conduit = setUpConduit(true, false, true); Message message = new MessageImpl(); conduit.prepare(message); - verifySentMessage(conduit, message, false, true); + verifySentMessage(conduit, + message, + ResponseStyle.NONE, + ResponseDelimiter.EOF, + false); // non-empty response finalVerify(); } + @Test + public void testSendOnewayEOFTerminatedEmptyPartialResponse() + throws Exception { + control = EasyMock.createNiceControl(); + HTTPConduit conduit = setUpConduit(true, false, true); + Message message = new MessageImpl(); + conduit.prepare(message); + verifySentMessage(conduit, + message, + ResponseStyle.NONE, + ResponseDelimiter.EOF, + true); // empty response + finalVerify(); + } + + @Test + public void testSendDecoupledExplicitLenghtPartialResponse() + throws Exception { + control = EasyMock.createNiceControl(); + HTTPConduit conduit = setUpConduit(true, false, true); + Message message = new MessageImpl(); + conduit.prepare(message); + verifySentMessage(conduit, + message, + ResponseStyle.DECOUPLED, + ResponseDelimiter.LENGTH, + false); // non-empty response + finalVerify(); + } + + @Test + public void testSendDecoupledChunkedPartialResponse() + throws Exception { + control = EasyMock.createNiceControl(); + HTTPConduit conduit = setUpConduit(true, false, true); + Message message = new MessageImpl(); + conduit.prepare(message); + verifySentMessage(conduit, + message, + ResponseStyle.DECOUPLED, + ResponseDelimiter.CHUNKED, + false); // non-empty response + finalVerify(); + } + + @Test + public void testSendDecoupledChunkedEmptyPartialResponse() + throws Exception { + control = EasyMock.createNiceControl(); + HTTPConduit conduit = setUpConduit(true, false, true); + Message message = new MessageImpl(); + conduit.prepare(message); + verifySentMessage(conduit, + message, + ResponseStyle.DECOUPLED, + ResponseDelimiter.CHUNKED, + true); // empty response + finalVerify(); + } + + @Test + public void testSendDecoupledEOFTerminatedPartialResponse() + throws Exception { + control = EasyMock.createNiceControl(); + HTTPConduit conduit = setUpConduit(true, false, true); + Message message = new MessageImpl(); + conduit.prepare(message); + verifySentMessage(conduit, + message, + ResponseStyle.DECOUPLED, + ResponseDelimiter.EOF, + false); // non-empty response + finalVerify(); + } + + @Test + public void testSendDecoupledEOFTerminatedEmptyPartialResponse() + throws Exception { + control = EasyMock.createNiceControl(); + HTTPConduit conduit = setUpConduit(true, false, true); + Message message = new MessageImpl(); + conduit.prepare(message); + verifySentMessage(conduit, + message, + ResponseStyle.DECOUPLED, + ResponseDelimiter.EOF, + true); // empty response + finalVerify(); + } private void setUpHeaders(Message message) { Map> headers = new HashMap>(); @@ -182,6 +323,12 @@ message.put(AuthorizationPolicy.class, authPolicy); } + private void setUpOneway(Message message) { + Exchange exchange = control.createMock(Exchange.class); + message.setExchange(exchange); + exchange.isOneWay(); + EasyMock.expectLastCall().andReturn(true); + } private HTTPConduit setUpConduit( boolean send, @@ -287,13 +434,45 @@ Message message, boolean expectHeaders) throws IOException { - verifySentMessage(conduit, message, expectHeaders, false); + verifySentMessage(conduit, + message, + expectHeaders, + ResponseStyle.BACK_CHANNEL); + } + + private void verifySentMessage(HTTPConduit conduit, + Message message, + boolean expectHeaders, + ResponseStyle style) + throws IOException { + verifySentMessage(conduit, + message, + expectHeaders, + style, + ResponseDelimiter.LENGTH, + false); } private void verifySentMessage(HTTPConduit conduit, Message message, + ResponseStyle style, + ResponseDelimiter delimiter, + boolean emptyResponse) + throws IOException { + verifySentMessage(conduit, + message, + false, + style, + delimiter, + emptyResponse); + } + + private void verifySentMessage(HTTPConduit conduit, + Message message, boolean expectHeaders, - boolean decoupled) + ResponseStyle style, + ResponseDelimiter delimiter, + boolean emptyResponse) throws IOException { control.verify(); control.reset(); @@ -310,7 +489,11 @@ os.close(); EasyMock.expectLastCall(); - verifyHandleResponse(decoupled); + if (style == ResponseStyle.NONE) { + setUpOneway(message); + } + + verifyHandleResponse(style, delimiter); control.replay(); @@ -321,17 +504,22 @@ assertNotNull("expected in message", inMessage); Map headerMap = (Map) inMessage.get(Message.PROTOCOL_HEADERS); assertEquals("unexpected response headers", headerMap.size(), 0); - Integer expectedResponseCode = decoupled - ? HttpURLConnection.HTTP_ACCEPTED - : HttpURLConnection.HTTP_OK; + Integer expectedResponseCode = style == ResponseStyle.BACK_CHANNEL + ? HttpURLConnection.HTTP_OK + : HttpURLConnection.HTTP_ACCEPTED; assertEquals("unexpected response code", expectedResponseCode, inMessage.get(Message.RESPONSE_CODE)); - assertTrue("unexpected content formats", - inMessage.getContentFormats().contains(InputStream.class)); - assertSame("unexpected content", is, inMessage.getContent(InputStream.class)); + if (!emptyResponse) { + assertTrue("unexpected content formats", + inMessage.getContentFormats().contains(InputStream.class)); + InputStream content = inMessage.getContent(InputStream.class); + if (!(content instanceof PushbackInputStream)) { + assertSame("unexpected content", is, content); + } + } - if (decoupled) { + if (style == ResponseStyle.DECOUPLED) { verifyDecoupledResponse(conduit); } @@ -351,7 +539,7 @@ connection.getRequestMethod(); EasyMock.expectLastCall().andReturn("POST"); - os = EasyMock.createMock(ServletOutputStream.class); + os = EasyMock.createMock(OutputStream.class); connection.getOutputStream(); EasyMock.expectLastCall().andReturn(os); @@ -381,19 +569,57 @@ return wrappedOS; } - private void verifyHandleResponse(boolean decoupled) throws IOException { + private void verifyHandleResponse(ResponseStyle style, ResponseDelimiter delimiter) + throws IOException { + verifyHandleResponse(style, delimiter, false); + } + + private void verifyHandleResponse(ResponseStyle style, + ResponseDelimiter delimiter, + boolean emptyResponse) throws IOException { connection.getHeaderFields(); EasyMock.expectLastCall().andReturn(Collections.EMPTY_MAP); - int responseCode = decoupled - ? HttpURLConnection.HTTP_ACCEPTED - : HttpURLConnection.HTTP_OK; + int responseCode = style == ResponseStyle.BACK_CHANNEL + ? HttpURLConnection.HTTP_OK + : HttpURLConnection.HTTP_ACCEPTED; connection.getResponseCode(); EasyMock.expectLastCall().andReturn(responseCode).anyTimes(); - connection.getErrorStream(); - EasyMock.expectLastCall().andReturn(null); - is = EasyMock.createMock(ServletInputStream.class); + is = EasyMock.createMock(InputStream.class); connection.getInputStream(); - EasyMock.expectLastCall().andReturn(is); + EasyMock.expectLastCall().andReturn(is).anyTimes(); + switch (style) { + case NONE: + case DECOUPLED: + connection.getContentLength(); + if (delimiter == ResponseDelimiter.CHUNKED + || delimiter == ResponseDelimiter.EOF) { + EasyMock.expectLastCall().andReturn(-1); + if (delimiter == ResponseDelimiter.CHUNKED) { + connection.getHeaderField("Transfer-Encoding"); + EasyMock.expectLastCall().andReturn("chunked"); + } else if (delimiter == ResponseDelimiter.EOF) { + connection.getHeaderField("Connection"); + EasyMock.expectLastCall().andReturn("close"); + } + is.read(); + EasyMock.expectLastCall().andReturn(emptyResponse ? -1 : (int)'<'); + } else { + EasyMock.expectLastCall().andReturn(123); + } + if (emptyResponse) { + is.close(); + EasyMock.expectLastCall(); + } + break; + + case BACK_CHANNEL: + connection.getErrorStream(); + EasyMock.expectLastCall().andReturn(null); + break; + + default: + break; + } } private void verifyDecoupledResponse(HTTPConduit conduit)