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)