cxf-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From egl...@apache.org
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 GMT
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<String, String> internalHeaders = new HashMap<String, String>();
     
@@ -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<String, List<String>> headers = 
                 new HashMap<String, List<String>>();
             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<String, List<String>> headers = new HashMap<String, List<String>>();
@@ -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)



Mime
View raw message