/**
* 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.
*/
package org.apache.cxf.transport.http;
import java.io.ByteArrayInputStream;
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;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import org.apache.cxf.Bus;
import org.apache.cxf.bus.extension.ExtensionManagerBus;
import org.apache.cxf.configuration.jsse.TLSClientParameters;
import org.apache.cxf.configuration.security.AuthorizationPolicy;
import org.apache.cxf.helpers.CastUtils;
import org.apache.cxf.io.AbstractThresholdOutputStream;
import org.apache.cxf.message.Exchange;
import org.apache.cxf.message.Message;
import org.apache.cxf.message.MessageContentsList;
import org.apache.cxf.message.MessageImpl;
import org.apache.cxf.service.model.EndpointInfo;
import org.apache.cxf.transport.MessageObserver;
import org.apache.cxf.transport.https.HttpsURLConnectionFactory;
import org.apache.cxf.ws.addressing.EndpointReferenceType;
import org.easymock.EasyMock;
import org.easymock.IMocksControl;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
/**
*/
public class HTTPConduitURLEasyMockTest extends Assert {
private static String oldHttpProxyHost;
private enum ResponseStyle { NONE, BACK_CHANNEL, BACK_CHANNEL_ERROR, DECOUPLED, ONEWAY_NONE };
private enum ResponseDelimiter { LENGTH, CHUNKED, EOF };
private static final String NOWHERE = "http://nada.nothing.nowhere.null/";
private static final String PAYLOAD = "message payload";
private IMocksControl control;
private EndpointInfo endpointInfo;
private HttpsURLConnectionFactory connectionFactory;
private HttpURLConnection connection;
private Proxy proxy;
private Message inMessage;
private MessageObserver observer;
private OutputStream os;
private InputStream is;
@BeforeClass
public static void disableHttpProxy() throws Exception {
oldHttpProxyHost = System.getProperty("http.proxyHost");
if (oldHttpProxyHost != null) {
// disable http proxy so that the connection mocking works (see setUpConduit)
System.clearProperty("http.proxyHost");
}
}
@AfterClass
public static void revertHttpProxy() throws Exception {
if (oldHttpProxyHost != null) {
System.setProperty("http.proxyHost", oldHttpProxyHost);
}
}
/**
* This is an extension to the HTTPConduit that replaces
* the dynamic assignment of the HttpURLConnectionFactory,
* and we just use the EasyMocked version for this test.
*/
private class HTTPTestConduit extends URLConnectionHTTPConduit {
HTTPTestConduit(
Bus associatedBus,
EndpointInfo endpoint,
EndpointReferenceType epr,
HttpsURLConnectionFactory testFactory
) throws IOException {
super(associatedBus, endpoint, epr);
connectionFactory = testFactory;
}
}
/**
* @throws java.lang.Exception
*/
@Before
public void setUp() throws Exception {
}
/**
* @throws java.lang.Exception
*/
@After
public void tearDown() throws Exception {
// avoid intermittent spurious failures on EasyMock detecting finalize
// calls by mocking up only class data members (no local variables)
// and explicitly making available for GC post-verify
connectionFactory = null;
connection = null;
proxy = null;
inMessage = null;
observer = null;
os = null;
is = null;
}
@Test
public void testSend() throws Exception {
control = EasyMock.createNiceControl();
HTTPConduit conduit = setUpConduit(true, false);
Message message = createMessage();
conduit.prepare(message);
verifySentMessage(conduit, message, "POST");
finalVerify();
}
@Test
public void testSendWithHeaders() throws Exception {
control = EasyMock.createNiceControl();
HTTPConduit conduit = setUpConduit(true, false);
Message message = createMessage();
setUpHeaders(message);
conduit.prepare(message);
verifySentMessage(conduit, message, true, "POST", false);
finalVerify();
}
private Message createMessage() {
Message message = new MessageImpl();
message.put("Content-Type", "text/xml;charset=utf8");
message.setContent(List.class, new MessageContentsList("<body/>"));
return message;
}
public void testSendWithHeadersCheckErrorStream() throws Exception {
control = EasyMock.createNiceControl();
HTTPConduit conduit = setUpConduit(true, false);
Message message = new MessageImpl();
message.put("Content-Type", "text/xml;charset=utf8");
setUpHeaders(message);
conduit.prepare(message);
verifySentMessage(conduit, message, true, "POST", true);
finalVerify();
}
@Test
public void testSendHttpConnection() throws Exception {
control = EasyMock.createNiceControl();
HTTPConduit conduit = setUpConduit(true, false);
Message message = createMessage();
conduit.prepare(message);
verifySentMessage(conduit, message, "POST");
finalVerify();
}
@Test
public void testSendHttpConnectionAutoRedirect() throws Exception {
control = EasyMock.createNiceControl();
HTTPConduit conduit = setUpConduit(true, true);
Message message = createMessage();
conduit.prepare(message);
verifySentMessage(conduit, message, "POST");
finalVerify();
}
@Test
public void testSendHttpGetConnectionAutoRedirect() throws Exception {
control = EasyMock.createNiceControl();
HTTPConduit conduit = setUpConduit(true, true, "GET");
Message message = new MessageImpl();
message.put(Message.HTTP_REQUEST_METHOD, "GET");
conduit.prepare(message);
verifySentMessage(conduit, message, "GET");
conduit.close(message);
finalVerify();
}
@Test
public void testSendHttpGetConnection() throws Exception {
control = EasyMock.createNiceControl();
HTTPConduit conduit = setUpConduit(true, false, "GET");
Message message = new MessageImpl();
message.put(Message.HTTP_REQUEST_METHOD, "GET");
conduit.prepare(message);
verifySentMessage(conduit, message, "GET");
conduit.close(message);
finalVerify();
}
@Test
public void testSendOnewayChunkedEmptyPartialResponseProcessResponse()
throws Exception {
control = EasyMock.createNiceControl();
HTTPConduit conduit = setUpConduit(true, false);
Message message = createMessage();
conduit.prepare(message);
message.put(Message.PROCESS_ONEWAY_RESPONSE, Boolean.TRUE);
verifySentMessage(conduit,
message,
ResponseStyle.NONE,
ResponseDelimiter.CHUNKED,
true, // empty response
"POST");
finalVerify();
}
@Test
public void testSendOnewayDoNotProcessResponse()
throws Exception {
control = EasyMock.createNiceControl();
HTTPConduit conduit = setUpConduit(true, false);
Message message = createMessage();
conduit.prepare(message);
verifySentMessage(conduit,
message,
ResponseStyle.ONEWAY_NONE,
ResponseDelimiter.CHUNKED,
true, // empty response
"POST");
finalVerify();
}
@Test
public void testSendTwowayDecoupledEmptyPartialResponse()
throws Exception {
control = EasyMock.createNiceControl();
HTTPConduit conduit = setUpConduit(true, false);
Message message = createMessage();
conduit.prepare(message);
verifySentMessage(conduit,
message,
ResponseStyle.DECOUPLED,
ResponseDelimiter.EOF,
true, // empty response
"POST");
finalVerify();
}
private void setUpHeaders(Message message) {
Map<String, List<String>> headers = new TreeMap<String, List<String>>(String.CASE_INSENSITIVE_ORDER);
List<String> contentTypes = new ArrayList<>();
contentTypes.add("text/xml;charset=utf8");
headers.put("content-type", contentTypes);
List<String> acceptTypes = new ArrayList<>();
acceptTypes.add("text/xml;charset=utf8");
acceptTypes.add("text/plain");
headers.put("Accept", acceptTypes);
message.put(Message.PROTOCOL_HEADERS, headers);
AuthorizationPolicy authPolicy = new AuthorizationPolicy();
authPolicy.setUserName("BJ");
authPolicy.setPassword("value");
message.put(AuthorizationPolicy.class, authPolicy);
}
private void setUpExchange(Message message, boolean oneway) {
Exchange exchange = control.createMock(Exchange.class);
message.setExchange(exchange);
exchange.isOneWay();
EasyMock.expectLastCall().andReturn(oneway).anyTimes();
exchange.isSynchronous();
EasyMock.expectLastCall().andReturn(true).anyTimes();
exchange.isEmpty();
EasyMock.expectLastCall().andReturn(true).anyTimes();
}
private HTTPConduit setUpConduit(boolean send, boolean autoRedirect) throws Exception {
return setUpConduit(send, autoRedirect, "POST");
}
private HTTPConduit setUpConduit(
boolean send,
boolean autoRedirect,
String method
) throws Exception {
endpointInfo = new EndpointInfo();
endpointInfo.setAddress(NOWHERE + "bar/foo");
connectionFactory =
control.createMock(HttpsURLConnectionFactory.class);
if (send) {
//proxy = control.createMock(Proxy.class);
proxy = Proxy.NO_PROXY;
connection =
control.createMock(HttpURLConnection.class);
connection.getURL();
EasyMock.expectLastCall().andReturn(new URL(NOWHERE + "bar/foo")).anyTimes();
connectionFactory.createConnection((TLSClientParameters)EasyMock.isNull(),
EasyMock.eq(proxy),
EasyMock.eq(new URL(NOWHERE + "bar/foo")));
EasyMock.expectLastCall().andReturn(connection);
connection.setDoOutput(true);
EasyMock.expectLastCall();
connection.setRequestMethod(method);
EasyMock.expectLastCall();
if (!autoRedirect && "POST".equals(method)) {
connection.setChunkedStreamingMode(-1);
EasyMock.expectLastCall();
}
connection.getRequestMethod();
EasyMock.expectLastCall().andReturn(method).anyTimes();
connection.setInstanceFollowRedirects(false);
EasyMock.expectLastCall().times(1);
connection.setConnectTimeout(303030);
EasyMock.expectLastCall();
connection.setReadTimeout(404040);
EasyMock.expectLastCall();
connection.setUseCaches(false);
EasyMock.expectLastCall();
}
ExtensionManagerBus bus = new ExtensionManagerBus();
control.replay();
HTTPConduit conduit = new HTTPTestConduit(bus,
endpointInfo,
null,
connectionFactory);
conduit.finalizeConfig();
if (send) {
conduit.getClient().setConnectionTimeout(303030);
conduit.getClient().setReceiveTimeout(404040);
conduit.getClient().setAutoRedirect(autoRedirect);
if (!autoRedirect) {
conduit.getClient().setAllowChunking(true);
conduit.getClient().setChunkingThreshold(0);
}
}
observer = new MessageObserver() {
public void onMessage(Message m) {
inMessage = m;
}
};
conduit.setMessageObserver(observer);
return conduit;
}
private void verifySentMessage(HTTPConduit conduit, Message message, String method)
throws IOException {
verifySentMessage(conduit, message, false, method, false);
}
private void verifySentMessage(HTTPConduit conduit,
Message message,
boolean expectHeaders,
String method,
boolean errorExpected)
throws IOException {
verifySentMessage(conduit,
message,
expectHeaders,
errorExpected ? ResponseStyle.BACK_CHANNEL_ERROR : ResponseStyle.BACK_CHANNEL,
method);
}
private void verifySentMessage(HTTPConduit conduit,
Message message,
boolean expectHeaders,
ResponseStyle style,
String method)
throws IOException {
verifySentMessage(conduit,
message,
expectHeaders,
style,
ResponseDelimiter.LENGTH,
false,
method);
}
private void verifySentMessage(HTTPConduit conduit,
Message message,
ResponseStyle style,
ResponseDelimiter delimiter,
boolean emptyResponse,
String method)
throws IOException {
verifySentMessage(conduit,
message,
false,
style,
delimiter,
emptyResponse,
method);
}
private void verifySentMessage(HTTPConduit conduit,
Message message,
boolean expectHeaders,
ResponseStyle style,
ResponseDelimiter delimiter,
boolean emptyResponse,
String method)
throws IOException {
control.verify();
control.reset();
OutputStream wrappedOS = verifyRequestHeaders(message, expectHeaders, method);
if (!"GET".equals(method)) {
os.write(PAYLOAD.getBytes(), 0, PAYLOAD.length());
EasyMock.expectLastCall();
os.flush();
EasyMock.expectLastCall();
os.flush();
EasyMock.expectLastCall();
os.close();
EasyMock.expectLastCall();
}
setUpExchange(message, style == ResponseStyle.NONE || style == ResponseStyle.ONEWAY_NONE);
connection.getRequestMethod();
EasyMock.expectLastCall().andReturn(method).anyTimes();
verifyHandleResponse(style, delimiter, emptyResponse, conduit);
control.replay();
wrappedOS.flush();
wrappedOS.flush();
wrappedOS.close();
if ((style == ResponseStyle.NONE && !emptyResponse)
|| style == ResponseStyle.BACK_CHANNEL
|| style == ResponseStyle.BACK_CHANNEL_ERROR) {
assertNotNull("expected in message", inMessage);
Map<?, ?> headerMap = (Map<?, ?>) inMessage.get(Message.PROTOCOL_HEADERS);
assertEquals("unexpected response headers", headerMap.size(), 0);
Integer expectedResponseCode = getResponseCode(style);
assertEquals("unexpected response code",
expectedResponseCode,
inMessage.get(Message.RESPONSE_CODE));
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);
}
}
}
finalVerify();
}
private OutputStream verifyRequestHeaders(Message message, boolean expectHeaders, String method)
throws IOException {
Map<String, List<String>> headers =
CastUtils.cast((Map<?, ?>)message.get(Message.PROTOCOL_HEADERS));
assertNotNull("expected request headers set", headers);
assertTrue("expected output stream format",
message.getContentFormats().contains(OutputStream.class));
connection.getRequestMethod();
EasyMock.expectLastCall().andReturn(method).anyTimes();
if (!"GET".equals(method)) {
os = EasyMock.createMock(OutputStream.class);
connection.getOutputStream();
EasyMock.expectLastCall().andReturn(os);
}
message.put(HTTPConduit.KEY_HTTP_CONNECTION, connection);
if (expectHeaders) {
connection.setRequestProperty(EasyMock.eq("Authorization"),
EasyMock.eq("Basic Qko6dmFsdWU="));
EasyMock.expectLastCall();
connection.setRequestProperty(EasyMock.eq("Content-Type"),
EasyMock.eq("text/xml;charset=utf8"));
EasyMock.expectLastCall();
connection.setRequestProperty(EasyMock.eq("Accept"),
EasyMock.eq("text/xml;charset=utf8,text/plain"));
EasyMock.expectLastCall();
}
connection.getRequestProperties();
EasyMock.expectLastCall().andReturn(new HashMap<String, List<String>>()).anyTimes();
control.replay();
AbstractThresholdOutputStream wrappedOS
= (AbstractThresholdOutputStream) message.getContent(OutputStream.class);
assertNotNull("expected output stream", wrappedOS);
wrappedOS.write(PAYLOAD.getBytes());
wrappedOS.unBuffer();
control.verify();
control.reset();
return wrappedOS;
}
private void verifyHandleResponse(ResponseStyle style,
ResponseDelimiter delimiter,
boolean emptyResponse,
HTTPConduit conduit) throws IOException {
connection.getHeaderFields();
EasyMock.expectLastCall().andReturn(Collections.EMPTY_MAP).anyTimes();
int responseCode = getResponseCode(style);
if (conduit.getClient().isAutoRedirect()) {
connection.getResponseCode();
EasyMock.expectLastCall().andReturn(301).once().andReturn(responseCode).anyTimes();
connection.getURL();
EasyMock.expectLastCall().andReturn(new URL(NOWHERE + "bar/foo/redirect")).anyTimes();
} else {
connection.getResponseCode();
EasyMock.expectLastCall().andReturn(responseCode).anyTimes();
}
switch (style) {
case NONE:
case DECOUPLED:
is = control.createMock(InputStream.class);
connection.getInputStream();
EasyMock.expectLastCall().andReturn(is).anyTimes();
connection.getContentLength();
if (delimiter == ResponseDelimiter.CHUNKED
|| delimiter == ResponseDelimiter.EOF) {
EasyMock.expectLastCall().andReturn(-1).anyTimes();
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();
if (emptyResponse) {
EasyMock.expectLastCall().andReturn(-1).anyTimes();
} else {
EasyMock.expectLastCall().andReturn((int)'<');
}
} else {
EasyMock.expectLastCall().andReturn(123).anyTimes();
}
if (emptyResponse) {
is.close();
EasyMock.expectLastCall();
}
break;
case BACK_CHANNEL:
is = EasyMock.createMock(InputStream.class);
connection.getInputStream();
EasyMock.expectLastCall().andReturn(is).anyTimes();
break;
case BACK_CHANNEL_ERROR:
is = EasyMock.createMock(InputStream.class);
connection.getInputStream();
EasyMock.expectLastCall().andReturn(is).anyTimes();
connection.getErrorStream();
EasyMock.expectLastCall().andReturn(null);
break;
case ONEWAY_NONE:
connection.getInputStream();
EasyMock.expectLastCall().andReturn(new ByteArrayInputStream(new byte[0])).anyTimes();
break;
default:
break;
}
}
private void finalVerify() {
if (control != null) {
control.verify();
control = null;
}
}
private int getResponseCode(ResponseStyle style) {
int code;
if (style == ResponseStyle.BACK_CHANNEL) {
code = HttpURLConnection.HTTP_OK;
} else if (style == ResponseStyle.BACK_CHANNEL_ERROR) {
code = HttpURLConnection.HTTP_BAD_REQUEST;
} else {
code = HttpURLConnection.HTTP_ACCEPTED;
}
return code;
}
}