INT-1245, INT-1256 removed old HttpInboundEndpoint

This commit is contained in:
Mark Fisher
2010-07-27 04:31:07 +00:00
parent 56f6453a8e
commit bc56173725
2 changed files with 0 additions and 707 deletions

View File

@@ -1,301 +0,0 @@
/*
* Copyright 2002-2010 the original author or authors.
*
* Licensed 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.springframework.integration.http;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.integration.core.Message;
import org.springframework.integration.gateway.SimpleMessagingGateway;
import org.springframework.integration.message.MessageTimeoutException;
import org.springframework.util.Assert;
import org.springframework.web.HttpRequestHandler;
import org.springframework.web.multipart.MultipartResolver;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.View;
/**
* An inbound endpoint for handling an HTTP request and generating a response.
* <p/>
* By default GET and POST requests are accepted, but the 'supportedMethods'
* property may be set to include others or limit the options (e.g. POST only).
* By default the request will be converted to a Message payload according to
* the rules of the {@link DefaultInboundRequestMapper}.
* <p/>
* To customize the mapping of the request to the Message payload, provide
* a reference to an {@link InboundRequestMapper} implementation to the
* {@link #setRequestMapper(InboundRequestMapper)} method.
* <p/>
* The value for {@link #expectReply} is <code>false</code> by default.
* This means that as soon as the Message is created and passed to the
* {@link #setRequestChannel(org.springframework.integration.core.MessageChannel) request channel},
* a response will be generated. If a {@link #setView(View) view} has been
* provided, it will be invoked to render the response, and it will have
* access to the request message in the model map. The corresponding key
* in that map is determined by the {@link #requestKey} property (with a
* default of "requestMessage"). If no view is provided, and the 'expectReply'
* value is <code>false</code> then a simple OK status response will be issued.
* <p/>
* To handle request-reply scenarios, set the 'expectReply' flag to
* <code>true</code>. By default, the reply Message's payload will be
* extracted prior to generating a response. The payload must be either
* a String, a byte array, or a Serializable object. To have the entire
* serialized Message written as the response body, switch the
* {@link #extractReplyPayload} value to <code>false</code>.
* <p/>
* In the request-reply case, if a 'view' is provided, the response will
* not be generated directly from the reply Message or its extracted payload.
* Instead, the model map will be passed to that view, and it will contain
* either the reply Message or payload depending on the value of
* {@link #extractReplyPayload}. The corresponding key in the map will be
* determined by the {@link #replyKey} property (with a default of "reply").
* The map will also contain the original request Message as described above.
*
* @author Mark Fisher
* @since 1.0.2
*/
public class HttpInboundEndpoint extends SimpleMessagingGateway implements HttpRequestHandler {
private static final String DEFAULT_REQUEST_KEY = "requestMessage";
private static final String DEFAULT_REPLY_KEY = "reply";
private volatile List<String> supportedMethods = Arrays.asList("GET", "POST");
private volatile boolean expectReply;
private volatile InboundRequestMapper requestMapper;
private volatile boolean extractReplyPayload = true;
private volatile View view;
private volatile String requestKey = DEFAULT_REQUEST_KEY;
private volatile String replyKey = DEFAULT_REPLY_KEY;
/**
* Specify the supported request methods for this endpoint.
* By default, only GET and POST are supported.
*/
public void setSupportedMethods(String... supportedMethods) {
Assert.notEmpty(supportedMethods, "at least one supported method is required");
for (int i = 0; i < supportedMethods.length; i++) {
supportedMethods[i] = supportedMethods[i].trim().toUpperCase();
}
this.supportedMethods = Arrays.asList(supportedMethods);
}
/**
* Specify whether this endpoint should perform a request/reply
* operation. Otherwise, it will only send the message and
* immediately generate a response. The default is 'false'.
*/
public void setExpectReply(boolean expectReply) {
this.expectReply = expectReply;
}
/**
* Specify an {@link InboundRequestMapper} implementation to map from the
* inbound {@link HttpServletRequest} instances to Messages at runtime.
* The default implementation is {@link DefaultInboundRequestMapper}.
*/
public void setRequestMapper(InboundRequestMapper requestMapper) {
Assert.notNull(requestMapper, "requestMapper must not be null");
this.requestMapper = requestMapper;
}
/**
* Specify whether the reply Message's payload should be passed in
* the response. If this is set to 'false', the entire Message will
* be sent as bytes. Otherwise, the reply Message payload must be
* a String or byte array. If a 'view' has been provided,
* the reply value will be sent in the model Map to that View.
* If the 'view' is <code>null</code>, the String or byte array
* will be written directly to the HTTP response.
* <p>The default value is 'true'.
* @see #setView(View)
*/
public void setExtractReplyPayload(boolean extractReplyPayload) {
this.extractReplyPayload = extractReplyPayload;
}
/**
* Specify a {@link View} to be used for rendering the
* response. If no View is provided, the reply Message or its
* payload will be written directly to the response.
* @see #setExtractReplyPayload(boolean)
*/
public void setView(View view) {
this.view = view;
}
/**
* Specify the key to be used when storing the request Message in the model
* map. This is only necessary when a {@link #setView(View) view} has been
* provided for rendering the response. The default key is "requestMessage".
*/
public void setRequestKey(String requestKey) {
this.requestKey = (requestKey != null) ? requestKey : DEFAULT_REQUEST_KEY;
}
/**
* Specify the key to be used when storing the reply Message or payload in
* the model map. This is only necessary when a {@link #setView(View) view}
* has been provided for rendering the response. The default key is "reply".
*/
public void setReplyKey(String replyKey) {
this.replyKey = (replyKey != null) ? replyKey : DEFAULT_REPLY_KEY;
}
@Override
protected void onInit() throws Exception {
if (this.requestMapper == null) {
this.configureDefaultRequestMapper();
}
super.onInit();
}
private void configureDefaultRequestMapper() {
DefaultInboundRequestMapper defaultMapper = new DefaultInboundRequestMapper();
if (this.getBeanFactory() != null) {
try {
MultipartResolver multipartResolver =
this.getBeanFactory().getBean(DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME, MultipartResolver.class);
if (logger.isDebugEnabled()) {
logger.debug("Using MultipartResolver [" + multipartResolver + "]");
}
defaultMapper.setMultipartResolver(multipartResolver);
}
catch (NoSuchBeanDefinitionException e) {
if (logger.isDebugEnabled()) {
logger.debug("Unable to locate MultipartResolver with name '" + DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME +
"': no multipart request handling will be supported.");
}
}
}
this.requestMapper = defaultMapper;
}
public void handleRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
Assert.notNull(this.requestMapper, "HttpInboundEndpoint has not been initialized.");
if (!this.supportedMethods.contains(request.getMethod())) {
response.setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
return;
}
try {
Message<?> requestMessage = this.requestMapper.toMessage(request);
Object reply = this.handleRequestMessage(requestMessage);
this.generateResponse(requestMessage, reply, request, response);
}
catch (ResponseStatusCodeException e) {
response.setStatus(e.getStatusCode());
}
catch (ServletException e) {
throw e;
}
catch (IOException e) {
throw e;
}
catch (Exception e) {
throw new ServletException(e);
}
}
private Object handleRequestMessage(Message<?> requestMessage) {
Object reply = null;
if (this.expectReply) {
if (this.extractReplyPayload) {
reply = this.sendAndReceive(requestMessage);
}
else {
reply = this.sendAndReceiveMessage(requestMessage);
}
if (reply == null) {
throw new MessageTimeoutException(requestMessage,
"failed to handle Message within specified timeout value");
}
}
else {
this.send(requestMessage);
}
return reply;
}
private void generateResponse(Message<?> requestMessage, Object reply,
HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws ServletException, IOException {
if (this.view != null) {
Map<String, Object> model = new HashMap<String, Object>();
model.put(this.requestKey, requestMessage);
if (reply != null) {
model.put(this.replyKey, reply);
}
try {
this.view.render(model, httpRequest, httpResponse);
}
catch (Exception e) {
throw new ServletException("failed to render view", e);
}
}
else if (reply == null) {
httpResponse.setStatus(HttpServletResponse.SC_OK);
}
else if (reply instanceof String) {
httpResponse.setContentType("text/plain");
httpResponse.setContentLength(((String) reply).length());
httpResponse.getWriter().print((String) reply);
httpResponse.flushBuffer();
}
else if (reply instanceof byte[]) {
byte[] bytes = (byte[]) reply;
httpResponse.setContentType("application/octet-stream");
httpResponse.setContentLength(bytes.length);
httpResponse.getOutputStream().write(bytes);
httpResponse.flushBuffer();
}
else if (reply instanceof Serializable) {
// either a Serializable payload or the Message itself
ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
ObjectOutputStream objectStream = new ObjectOutputStream(byteStream);
objectStream.writeObject(reply);
objectStream.flush();
objectStream.close();
byte[] bytes = byteStream.toByteArray();
httpResponse.getOutputStream().write(bytes);
httpResponse.setContentType("application/x-java-serialized-object");
httpResponse.setContentLength(bytes.length);
httpResponse.flushBuffer();
}
else {
throw new ServletException("failed to generate HTTP response from reply Message");
}
}
}

View File

@@ -1,406 +0,0 @@
/*
* Copyright 2002-2010 the original author or authors.
*
* Licensed 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.springframework.integration.http;
import static org.easymock.EasyMock.anyObject;
import static org.easymock.EasyMock.eq;
import static org.easymock.EasyMock.expect;
import static org.easymock.EasyMock.expectLastCall;
import static org.easymock.EasyMock.getCurrentArguments;
import static org.easymock.EasyMock.isA;
import static org.easymock.classextension.EasyMock.createMock;
import static org.easymock.classextension.EasyMock.replay;
import static org.easymock.classextension.EasyMock.reset;
import static org.easymock.classextension.EasyMock.verify;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.notNullValue;
import static org.junit.Assert.assertThat;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.UnsupportedEncodingException;
import java.security.Principal;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.easymock.IAnswer;
import org.easymock.classextension.ConstructorArgs;
import org.junit.Before;
import org.junit.Test;
import org.springframework.integration.core.Message;
import org.springframework.integration.core.MessageChannel;
import org.springframework.integration.core.MessageHeaders;
import org.springframework.integration.message.StringMessage;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.util.MultiValueMap;
import org.springframework.web.servlet.View;
/**
* @author Alex Peters
*/
public class HttpInboundEndpointTests {
private static final String ANY_ENCODING = "UTF-8";
private static final String ANY_STRING_PAYLOAD = "any text content..blabla...bla.äöüßß߀€€€";
private static final byte[] ANY_BINARY_PAYLOAD;
private final MessageChannel requestChannel = createMock(MessageChannel.class);
private final MessageChannel replyChannel = createMock(MessageChannel.class);
private final Object[] allMocks = new Object[] { requestChannel, replyChannel };
private HttpInboundEndpoint endpoint;
private MockHttpServletRequest request;
private MockHttpServletResponse response;
static {
try {
ANY_BINARY_PAYLOAD = "any binary content..blabla...bla.äöüßß߀€€€".getBytes(ANY_ENCODING);
}
catch (UnsupportedEncodingException e) {
throw new IllegalStateException(e);
}
}
@Before
public void initializeSample() {
endpoint = new HttpInboundEndpoint();
endpoint.setRequestChannel(requestChannel);
endpoint.setReplyChannel(replyChannel);
endpoint.afterPropertiesSet();
reset(allMocks);
request = new MockHttpServletRequest("GET", "/anyurl");
response = new MockHttpServletResponse();
response.setCharacterEncoding(ANY_ENCODING);
}
@Test
public void handleRequest_withDefaultSettingsAndUnsupportedHTTPMethods_returns405()
throws ServletException, IOException {
String[] httpMethods = { "OPTIONS", "HEAD", "PUT", "DELETE", "TRACE", "CONNECT", "ANY_INVALID" };
for (String deniedHttpMethod : httpMethods) {
request = new MockHttpServletRequest(deniedHttpMethod, "/anyurl");
endpoint.handleRequest(request, response);
assertThat("Unexpected result for http method: " + deniedHttpMethod,
response.getStatus(),
is(HttpServletResponse.SC_METHOD_NOT_ALLOWED));
}
}
@Test
public void handleRequest_withGETRequest_allReqParametersInMessagePayload()
throws ServletException, IOException {
final Map<String, String> sourceParams = addAnyParametersToRequest();
expect(requestChannel.send(isA(Message.class))).andAnswer(
new IAnswer<Boolean>() {
@SuppressWarnings("unchecked")
public Boolean answer() throws Throwable {
final Map<?, ?> msgParams = (Map<?, ?>) ((Message) getCurrentArguments()[0]).getPayload();
assertThat(msgParams.size(), is(sourceParams.size()));
for (String key : sourceParams.keySet()) {
assertThat((List<String>) msgParams.get(key),
is(Collections.singletonList(sourceParams.get(key))));
}
return true;
}
});
replay(allMocks);
endpoint.handleRequest(request, response);
verify(allMocks);
}
@Test
public void handleRequest_withEmptyGETRequest_emptyMapIsInMessagePayload()
throws ServletException, IOException {
expect(requestChannel.send(isA(Message.class))).andAnswer(
new IAnswer<Boolean>() {
@SuppressWarnings("unchecked")
public Boolean answer() throws Throwable {
final Map<?, ?> msgParams = (Map<?, ?>) ((Message) getCurrentArguments()[0]).getPayload();
assertThat(msgParams.size(), is(0));
return true;
}
});
replay(allMocks);
endpoint.handleRequest(request, response);
verify(allMocks);
}
@Test
public void handleRequest_withCustomRequestMapper_requestObjectIsInPayload()
throws ServletException, IOException {
endpoint.setRequestMapper(new InboundRequestMapper() {
public Message<?> toMessage(HttpServletRequest request) throws Exception {
return new StringMessage(request.getRequestURI());
}
});
expect(requestChannel.send(isA(Message.class))).andAnswer(
new IAnswer<Boolean>() {
@SuppressWarnings("unchecked")
public Boolean answer() throws Throwable {
assertThat(((Message) getCurrentArguments()[0]).getPayload(), is((Object) "/anyurl"));
return true;
}
});
replay(allMocks);
endpoint.handleRequest(request, response);
verify(allMocks);
}
@Test
public void handleRequest_RequestHeadersInMsgHeaders()
throws ServletException, IOException {
final Principal anyPrincipal = createMock(Principal.class);
request.setUserPrincipal(anyPrincipal);
expect(requestChannel.send(isA(Message.class))).andAnswer(
new IAnswer<Boolean>() {
@SuppressWarnings("unchecked")
public Boolean answer() throws Throwable {
MessageHeaders headers = ((Message) getCurrentArguments()[0]).getHeaders();
assertThat(headers.get(HttpHeaders.REQUEST_METHOD),
is((Object) "GET"));
assertThat(headers.get(HttpHeaders.REQUEST_URL),
is((Object) "http://localhost:80/anyurl"));
assertThat(headers.get(HttpHeaders.USER_PRINCIPAL),
is((Object) anyPrincipal));
return true;
}
});
replay(allMocks);
endpoint.handleRequest(request, response);
verify(allMocks);
}
@Test
public void handleRequest_withPOSTRequestAndTextContent_sameInMessagePayload()
throws ServletException, IOException {
final String characterEncoding = ANY_ENCODING;
addRequestContent("POST", "text/plain", characterEncoding,
ANY_STRING_PAYLOAD.getBytes(characterEncoding));
expect(requestChannel.send(isA(Message.class))).andAnswer(
new IAnswer<Boolean>() {
@SuppressWarnings("unchecked")
public Boolean answer() throws Throwable {
assertThat((String) ((Message) getCurrentArguments()[0]).getPayload(),
is(ANY_STRING_PAYLOAD));
return true;
}
});
replay(allMocks);
endpoint.handleRequest(request, response);
verify(allMocks);
}
@Test
public void handleRequest_withPOSTRequestAndFormContent_sameInMessagePayload()
throws ServletException, IOException {
addRequestContent("POST", "application/x-www-form-urlencoded", ANY_ENCODING, new byte[0]);
final Map<String, String> sourceParams = addAnyParametersToRequest();
request.setParameters(sourceParams);
expect(requestChannel.send(isA(Message.class))).andAnswer(
new IAnswer<Boolean>() {
@SuppressWarnings("unchecked")
public Boolean answer() throws Throwable {
MultiValueMap<String, String> payloadMap = (MultiValueMap<String, String>)
((Message) getCurrentArguments()[0]).getPayload();
for (String key : sourceParams.keySet()) {
assertThat(payloadMap.get(key),
is(Collections.singletonList(sourceParams.get(key))));
}
return true;
}
});
replay(allMocks);
endpoint.handleRequest(request, response);
verify(allMocks);
}
@Test
public void handleRequest_withEmptyPOSTRequest_emptyStringAsPayload()
throws ServletException, IOException {
addRequestContent("POST", "text/plain", null, new byte[0]);
expect(requestChannel.send(isA(Message.class))).andAnswer(
new IAnswer<Boolean>() {
@SuppressWarnings("unchecked")
public Boolean answer() throws Throwable {
assertThat((String) ((Message) getCurrentArguments()[0]).getPayload(), is(""));
return true;
}
});
replay(allMocks);
endpoint.handleRequest(request, response);
verify(allMocks);
}
@Test
public void handleRequest_withPOSTRequestAndBinaryContent_sameInMessagePayload()
throws ServletException, IOException {
addRequestContent("POST", "", ANY_ENCODING, ANY_BINARY_PAYLOAD);
expect(requestChannel.send(isA(Message.class))).andAnswer(
new IAnswer<Boolean>() {
@SuppressWarnings("unchecked")
public Boolean answer() throws Throwable {
assertThat((byte[]) ((Message) getCurrentArguments()[0]).getPayload(),
is(ANY_BINARY_PAYLOAD));
return true;
}
});
replay(allMocks);
endpoint.handleRequest(request, response);
verify(allMocks);
}
@Test
public void handleRequest_withPOSTRequestEmptyContentLenght_return411()
throws ServletException, IOException {
addRequestContent("POST", "", ANY_ENCODING, null);
endpoint.handleRequest(request, response);
assertThat(response.getStatus(), is(HttpServletResponse.SC_LENGTH_REQUIRED));
}
@Test
public void handleRequest_withoutReplyMessage_return200()
throws ServletException, IOException {
expect(requestChannel.send(isA(Message.class))).andReturn(true);
replay(allMocks);
endpoint.handleRequest(request, response);
assertThat(response.getStatus(), is(HttpServletResponse.SC_OK));
}
@Test
public void handleRequest_replyWithTextPayload_textAsRespContent()
throws ServletException, IOException {
setupEndpointAsMock(ANY_STRING_PAYLOAD);
replay(allMocks);
endpoint.handleRequest(request, response);
assertThat(response.getContentAsString(), is(ANY_STRING_PAYLOAD));
verify(allMocks);
}
@Test
public void handleRequest_replyWithBytePayload_bytesAsRespContent()
throws ServletException, IOException {
setupEndpointAsMock(ANY_BINARY_PAYLOAD);
replay(allMocks);
endpoint.handleRequest(request, response);
assertThat(response.getContentAsByteArray(), is(ANY_BINARY_PAYLOAD));
verify(allMocks);
}
@Test
public void handleRequest_replyWithSerializablePayload_serializableAsRespContent()
throws ServletException, IOException, ClassNotFoundException {
Date obj = new Date();
setupEndpointAsMock(obj);
replay(allMocks);
endpoint.handleRequest(request, response);
byte[] content = response.getContentAsByteArray();
Object deserializedObj = new ObjectInputStream(
new ByteArrayInputStream(content)).readObject();
assertThat(deserializedObj, is(Date.class));
assertThat((Date) deserializedObj, is(obj));
verify(allMocks);
}
@Test(expected = ServletException.class)
public void handleRequest_replyWithNonSerializablePayload_exceptionThrown()
throws ServletException, IOException, ClassNotFoundException {
Object obj = new Object();
setupEndpointAsMock(obj);
replay(allMocks);
endpoint.handleRequest(request, response);
verify(allMocks);
}
@Test
@SuppressWarnings("unchecked")
public void handleRequest_expectReplyWithView_responseDirectedToView() throws Exception {
setupEndpointAsMock(ANY_STRING_PAYLOAD);
View view = createMock(View.class);
view.render(isA(Map.class), eq(request), eq(response));
expectLastCall().andAnswer(new IAnswer<Object>() {
public Object answer() throws Throwable {
Map<?, ?> model = (Map<?, ?>) getCurrentArguments()[0];
assertThat(model.isEmpty(), is(false));
assertThat(model.get("reply"), is((Object) ANY_STRING_PAYLOAD));
assertThat(model.get("requestMessage"), is(notNullValue()));
return null;
}
});
endpoint.setView(view);
replay(view);
replay(allMocks);
endpoint.handleRequest(request, response);
verify(allMocks);
verify(view);
}
/** add some dummy parameters to the request */
private Map<String, String> addAnyParametersToRequest() {
final Map<String, String> sourceParams = new HashMap<String, String>();
for (int i = 0; i < 20; i++) {
sourceParams.put("anyParameter" + i, "anyValue" + i);
}
request.setParameters(sourceParams);
return sourceParams;
}
/** set given params on request instance */
private void addRequestContent(String httpMethod, String contentType, String encoding, byte[] content) {
request.setMethod(httpMethod);
request.setContentType(contentType);
request.setContent(content);
request.setCharacterEncoding(encoding);
}
/** create a new mocked endpoint instance and setup basic data */
private void setupEndpointAsMock(final Object anyPayload) {
try {
endpoint = createMock(HttpInboundEndpoint.class,
new ConstructorArgs(
HttpInboundEndpoint.class.getConstructor(new Class[0]),
new Object[0]),
HttpInboundEndpoint.class.getMethod("sendAndReceive", Object.class));
endpoint.afterPropertiesSet();
}
catch (Exception e) {
throw new IllegalStateException(e);
}
expect(endpoint.sendAndReceive(anyObject())).andReturn(anyPayload);
endpoint.setExpectReply(true);
replay(endpoint);
}
}