INT-668, INT-673
This commit is contained in:
@@ -17,6 +17,7 @@
|
||||
package org.springframework.integration.http;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.util.ArrayList;
|
||||
@@ -26,7 +27,6 @@ import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.ServletRequest;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
@@ -36,23 +36,30 @@ import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.integration.core.Message;
|
||||
import org.springframework.integration.message.MessageBuilder;
|
||||
import org.springframework.web.multipart.MultipartException;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import org.springframework.web.multipart.MultipartHttpServletRequest;
|
||||
import org.springframework.web.multipart.MultipartResolver;
|
||||
|
||||
/**
|
||||
* Default implementation of {@link InboundRequestMapper} for inbound HttpServletRequests.
|
||||
* The request will be mapped according to the following rules:
|
||||
* <ul>
|
||||
* <li>For a GET request, the parameter Map will be copied as the payload.
|
||||
* The map's keys will be Strings, and the values will be String arrays
|
||||
* <li>For a GET request or a POST request with a Content-Type of
|
||||
* "application/x-www-form-urlencoded", the parameter Map will be copied as the
|
||||
* payload. The map's keys will be Strings, and the values will be String arrays
|
||||
* as described for {@link ServletRequest#getParameterMap()}.</li>
|
||||
* <li>If a MultipartResolver has been provided, and a multipart request is
|
||||
* detected, the multipart file content will be converted to String for any
|
||||
* "text" content type, or byte arrays otherwise.</li>
|
||||
* <li>For other request types, the request body will be used as the payload
|
||||
* and the type will depend on the Content-Type header value. If it
|
||||
* begins with "text", a String will be created. If the Content-Type
|
||||
* is "application/x-java-serialized-object", the request body will be
|
||||
* expected to contain a Serializable Object, and that will be used as
|
||||
* the message payload. Otherwise, the payload will be a byte array.
|
||||
* The parameter Map values will then be added as Message headers.</li>
|
||||
* and the type will depend on the Content-Type header value. If it begins with
|
||||
* "text", a String will be created. If the Content-Type is
|
||||
* "application/x-java-serialized-object", the request body will be expected to
|
||||
* contain a Serializable Object, and that will be used as the message payload.
|
||||
* Otherwise, the payload will be a byte array.</li>
|
||||
* </ul>
|
||||
* In both cases, the original request headers will be passed in the
|
||||
* In all cases, the original request headers will be passed in the
|
||||
* MessageHeaders. Likewise, the following headers will be added:
|
||||
* <ul>
|
||||
* <li>{@link HttpHeaders#REQUEST_URL}</li>
|
||||
@@ -65,73 +72,179 @@ import org.springframework.integration.message.MessageBuilder;
|
||||
*/
|
||||
public class DefaultInboundRequestMapper implements InboundRequestMapper {
|
||||
|
||||
private Log logger = LogFactory.getLog(getClass());
|
||||
private final Log logger = LogFactory.getLog(getClass());
|
||||
|
||||
private volatile MultipartResolver multipartResolver;
|
||||
|
||||
private String multipartCharset = null;
|
||||
|
||||
|
||||
/**
|
||||
* Specify the {@link MultipartResolver} to use when checking requests.
|
||||
* If no resolver is provided, this mapper will not support multipart
|
||||
* requests.
|
||||
*/
|
||||
public void setMultipartResolver(MultipartResolver multipartResolver) {
|
||||
this.multipartResolver = multipartResolver;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify the charset name to use when converting multipart file content
|
||||
* into Strings.
|
||||
*/
|
||||
public void setMultipartCharset(String multipartCharset) {
|
||||
this.multipartCharset = multipartCharset;
|
||||
}
|
||||
|
||||
|
||||
public Message<?> toMessage(HttpServletRequest request) throws Exception {
|
||||
Message<?> message = null;
|
||||
String contentType = request.getContentType();
|
||||
if (request.getMethod().equals("GET")) {
|
||||
message = this.createMessageFromGetRequest(request);
|
||||
try {
|
||||
request = this.checkMultipart(request);
|
||||
Object payload = createPayloadFromRequest(request);
|
||||
MessageBuilder<?> builder = MessageBuilder.withPayload(payload);
|
||||
this.populateHeaders(request, builder);
|
||||
return builder.build();
|
||||
}
|
||||
else {
|
||||
Object payload = null;
|
||||
if (contentType != null && contentType.startsWith("text")) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("received " + request.getMethod()
|
||||
+ " request, creating payload with text content");
|
||||
}
|
||||
StringBuilder sb = new StringBuilder();
|
||||
BufferedReader reader = request.getReader();
|
||||
String line = reader.readLine();
|
||||
while (line != null) {
|
||||
sb.append(line);
|
||||
line = reader.readLine();
|
||||
}
|
||||
payload = sb.toString();
|
||||
}
|
||||
else if (contentType != null && contentType.equals("application/x-java-serialized-object")) {
|
||||
try {
|
||||
payload = new ObjectInputStream(request.getInputStream()).readObject();
|
||||
}
|
||||
catch (ClassNotFoundException e) {
|
||||
throw new ServletException("failed to deserialize Object in request", e);
|
||||
}
|
||||
finally {
|
||||
this.cleanupMultipart(request);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the request into a multipart request to make multiparts available.
|
||||
* If no multipart resolver is set, simply use the existing request.
|
||||
* @param request current HTTP request
|
||||
* @return the processed request (multipart wrapper if necessary)
|
||||
* @see MultipartResolver#resolveMultipart
|
||||
*/
|
||||
private HttpServletRequest checkMultipart(HttpServletRequest request) throws MultipartException {
|
||||
if (this.multipartResolver != null && this.multipartResolver.isMultipart(request)) {
|
||||
if (request instanceof MultipartHttpServletRequest) {
|
||||
logger.debug("Request is already a MultipartHttpServletRequest");
|
||||
}
|
||||
else {
|
||||
InputStream stream = request.getInputStream();
|
||||
int length = request.getContentLength();
|
||||
if (length == -1) {
|
||||
throw new ResponseStatusCodeException(HttpServletResponse.SC_LENGTH_REQUIRED);
|
||||
}
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("received " + request.getMethod() + " request, "
|
||||
+ "creating byte array payload with content lenth: " + length);
|
||||
}
|
||||
byte[] bytes = new byte[length];
|
||||
stream.read(bytes, 0, length);
|
||||
payload = bytes;
|
||||
return this.multipartResolver.resolveMultipart(request);
|
||||
}
|
||||
MessageBuilder<?> builder = MessageBuilder.withPayload(payload);
|
||||
this.populateHeaders(request, builder, true);
|
||||
message = builder.build();
|
||||
}
|
||||
return message;
|
||||
return request;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up any resources used by the given multipart request (if any).
|
||||
* @param request current HTTP request
|
||||
* @see MultipartResolver#cleanupMultipart
|
||||
*/
|
||||
private void cleanupMultipart(HttpServletRequest request) {
|
||||
if (this.multipartResolver != null && request instanceof MultipartHttpServletRequest) {
|
||||
this.multipartResolver.cleanupMultipart((MultipartHttpServletRequest) request);
|
||||
}
|
||||
}
|
||||
|
||||
private Object createPayloadFromRequest(HttpServletRequest request) throws Exception {
|
||||
Object payload = null;
|
||||
String contentType = request.getContentType() != null ? request.getContentType() : "";
|
||||
if (request instanceof MultipartHttpServletRequest) {
|
||||
payload = this.createPayloadFromMultipartRequest((MultipartHttpServletRequest) request);
|
||||
}
|
||||
else if (contentType.startsWith("multipart/form-data")) {
|
||||
throw new IllegalArgumentException("Content-Type of 'multipart/form-data' requires a MultipartResolver." +
|
||||
" Try configuring a MultipartResolver within the ApplicationContext.");
|
||||
}
|
||||
else if (request.getMethod().equals("GET")) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("received GET request, using parameter map as payload");
|
||||
}
|
||||
payload = this.createPayloadFromParameterMap(request);
|
||||
}
|
||||
else if (contentType.startsWith("application/x-www-form-urlencoded")) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("received " + request.getMethod()
|
||||
+ " request with form data, using parameter map as payload");
|
||||
}
|
||||
payload = createPayloadFromParameterMap(request);
|
||||
}
|
||||
else if (contentType.startsWith("text")) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("received " + request.getMethod()
|
||||
+ " request, creating payload with text content");
|
||||
}
|
||||
payload = createPayloadFromTextContent(request);
|
||||
}
|
||||
else if (contentType.startsWith("application/x-java-serialized-object")) {
|
||||
payload = createPayloadFromSerializedObject(request);
|
||||
}
|
||||
else {
|
||||
payload = createPayloadFromInputStream(request);
|
||||
}
|
||||
return payload;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private Message<?> createMessageFromGetRequest(HttpServletRequest request) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("received GET request, using parameter map as payload");
|
||||
private Object createPayloadFromMultipartRequest(MultipartHttpServletRequest multipartRequest) {
|
||||
Map<String, Object> payloadMap = new HashMap<String, Object>(multipartRequest.getParameterMap());
|
||||
Map<String, MultipartFile> fileMap = (Map<String, MultipartFile>) multipartRequest.getFileMap();
|
||||
for (Map.Entry<String, MultipartFile> entry : fileMap.entrySet()) {
|
||||
MultipartFile multipartFile = entry.getValue();
|
||||
try {
|
||||
if (multipartFile.getContentType() != null && multipartFile.getContentType().startsWith("text")) {
|
||||
String multipartFileAsString = this.multipartCharset != null ?
|
||||
new String(multipartFile.getBytes(), this.multipartCharset) :
|
||||
new String(multipartFile.getBytes());
|
||||
payloadMap.put(entry.getKey(), multipartFileAsString);
|
||||
}
|
||||
else {
|
||||
payloadMap.put(entry.getKey(), multipartFile.getBytes());
|
||||
}
|
||||
}
|
||||
catch (IOException e) {
|
||||
throw new IllegalArgumentException("Cannot read contents of multipart file", e);
|
||||
}
|
||||
}
|
||||
return Collections.unmodifiableMap(payloadMap);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private Object createPayloadFromParameterMap(HttpServletRequest request) {
|
||||
Map<String, String[]> parameterMap = new HashMap<String, String[]>(request.getParameterMap());
|
||||
MessageBuilder<?> builder = MessageBuilder.withPayload(Collections.unmodifiableMap(parameterMap));
|
||||
this.populateHeaders(request, builder, false);
|
||||
return builder.build();
|
||||
return Collections.unmodifiableMap(parameterMap);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private void populateHeaders(HttpServletRequest request, MessageBuilder<?> builder, boolean includeParameters) {
|
||||
private Object createPayloadFromTextContent(HttpServletRequest request) throws IOException {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
BufferedReader reader = request.getReader();
|
||||
String line = reader.readLine();
|
||||
while (line != null) {
|
||||
sb.append(line);
|
||||
line = reader.readLine();
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private Object createPayloadFromSerializedObject(HttpServletRequest request) {
|
||||
try {
|
||||
return new ObjectInputStream(request.getInputStream()).readObject();
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new IllegalArgumentException("failed to deserialize Object in request", e);
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] createPayloadFromInputStream(HttpServletRequest request) throws Exception {
|
||||
InputStream stream = request.getInputStream();
|
||||
int length = request.getContentLength();
|
||||
if (length == -1) {
|
||||
throw new ResponseStatusCodeException(HttpServletResponse.SC_LENGTH_REQUIRED);
|
||||
}
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("received " + request.getMethod() + " request, "
|
||||
+ "creating byte array payload with content lenth: " + length);
|
||||
}
|
||||
byte[] bytes = new byte[length];
|
||||
stream.read(bytes, 0, length);
|
||||
return bytes;
|
||||
}
|
||||
|
||||
private void populateHeaders(HttpServletRequest request, MessageBuilder<?> builder) {
|
||||
Enumeration<?> headerNames = request.getHeaderNames();
|
||||
if (headerNames != null) {
|
||||
while (headerNames.hasMoreElements()) {
|
||||
@@ -151,9 +264,6 @@ public class DefaultInboundRequestMapper implements InboundRequestMapper {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (includeParameters) {
|
||||
builder.copyHeaders(request.getParameterMap());
|
||||
}
|
||||
builder.setHeader(HttpHeaders.REQUEST_URL, request.getRequestURL().toString());
|
||||
builder.setHeader(HttpHeaders.REQUEST_METHOD, request.getMethod());
|
||||
builder.setHeader(HttpHeaders.USER_PRINCIPAL, request.getUserPrincipal());
|
||||
|
||||
@@ -29,11 +29,14 @@ 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;
|
||||
|
||||
/**
|
||||
@@ -87,7 +90,7 @@ public class HttpInboundEndpoint extends SimpleMessagingGateway implements HttpR
|
||||
|
||||
private volatile boolean expectReply;
|
||||
|
||||
private volatile InboundRequestMapper requestMapper = new DefaultInboundRequestMapper();
|
||||
private volatile InboundRequestMapper requestMapper;
|
||||
|
||||
private volatile boolean extractReplyPayload = true;
|
||||
|
||||
@@ -172,7 +175,37 @@ public class HttpInboundEndpoint extends SimpleMessagingGateway implements HttpR
|
||||
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 = (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;
|
||||
|
||||
@@ -95,6 +95,7 @@ public class HttpInboundEndpointTests {
|
||||
endpoint = new HttpInboundEndpoint();
|
||||
endpoint.setRequestChannel(requestChannel);
|
||||
endpoint.setReplyChannel(replyChannel);
|
||||
endpoint.afterPropertiesSet();
|
||||
reset(allmocks);
|
||||
request = new MockHttpServletRequest("GET", "/anyurl");
|
||||
response = new MockHttpServletResponse();
|
||||
@@ -220,18 +221,18 @@ public class HttpInboundEndpointTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void handleRequest_withPOSTRequestAndParamsOnly_sameInMessageHeader()
|
||||
public void handleRequest_withPOSTRequestAndFormContent_sameInMessagePayload()
|
||||
throws ServletException, IOException {
|
||||
addRequestContent("POST", "text/plain", ANY_ENCODING, new byte[0]);
|
||||
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 {
|
||||
MessageHeaders headers = ((Message) getCurrentArguments()[0]).getHeaders();
|
||||
Map<String, String> payloadMap = (Map<String, String>) ((Message) getCurrentArguments()[0]).getPayload();
|
||||
for (String key : sourceParams.keySet()) {
|
||||
assertThat(headers.get(key),
|
||||
assertThat(payloadMap.get(key),
|
||||
is((Object) new String[] { sourceParams.get(key) }));
|
||||
}
|
||||
return true;
|
||||
@@ -388,6 +389,7 @@ public class HttpInboundEndpointTests {
|
||||
HttpInboundEndpoint.class.getConstructor(new Class[0]),
|
||||
new Object[0]),
|
||||
HttpInboundEndpoint.class.getMethod("sendAndReceive", Object.class));
|
||||
endpoint.afterPropertiesSet();
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new IllegalStateException(e);
|
||||
|
||||
Reference in New Issue
Block a user