INT-3818: Multipart Pass Through Converter

JIRA: https://jira.spring.io/browse/INT-3818

INT-3818: Fix Pre JDK8 Syntax

Polishing - PR Comments

Add author name
This commit is contained in:
Gary Russell
2015-09-02 17:30:35 -04:00
committed by Artem Bilan
parent f26bfd5b33
commit 4f648a7e4f
5 changed files with 217 additions and 8 deletions

View File

@@ -1,11 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/integration/http"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:int="http://www.springframework.org/schema/integration"
xsi:schemaLocation="http://www.springframework.org/schema/integration/http http://www.springframework.org/schema/integration/http/spring-integration-http.xsd
<beans:beans
xmlns="http://www.springframework.org/schema/integration/http"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:int="http://www.springframework.org/schema/integration"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/integration/http http://www.springframework.org/schema/integration/http/spring-integration-http.xsd
http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd">
<inbound-gateway path="/test" request-channel="testChannel"
payload-expression="T(org.springframework.web.context.request.RequestContextHolder).requestAttributes.request.queryString"/>
@@ -21,4 +24,21 @@
<int:queue/>
</int:channel>
<inbound-gateway path="/testmp" request-channel="testChannelmp"
merge-with-default-converters="false"
message-converters="converters"
request-payload-type="byte[]" />
<util:list id="converters">
<beans:bean class="org.springframework.http.converter.ByteArrayHttpMessageConverter" />
<beans:bean class="org.springframework.http.converter.StringHttpMessageConverter" />
<beans:bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter" />
</util:list>
<int:channel id="testChannelmp"/>
<outbound-gateway id="proxyGatewaymp" request-channel="testChannelmp"
http-method="POST"
url="http://testServer/testmp"/>
</beans:beans>

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013 the original author or authors.
* Copyright 2013-2015 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.
@@ -16,9 +16,11 @@
package org.springframework.integration.http;
import static org.hamcrest.Matchers.instanceOf;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThat;
import java.net.URI;
import java.text.DateFormat;
@@ -41,7 +43,6 @@ import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.integration.http.outbound.HttpRequestExecutingMessageHandler;
import org.springframework.messaging.Message;
@@ -49,6 +50,7 @@ import org.springframework.messaging.MessageHeaders;
import org.springframework.messaging.PollableChannel;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.util.LinkedMultiValueMap;
@@ -63,10 +65,12 @@ import org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter;
/**
* @author Artem Bilan
* @author Gary Russell
* @since 3.0
*/
@ContextConfiguration
@RunWith(SpringJUnit4ClassRunner.class)
@DirtiesContext
public class HttpProxyScenarioTests {
private final HandlerAdapter handlerAdapter = new HttpRequestHandlerAdapter();
@@ -78,6 +82,10 @@ public class HttpProxyScenarioTests {
@Qualifier("proxyGateway.handler")
private HttpRequestExecutingMessageHandler handler;
@Autowired
@Qualifier("proxyGatewaymp.handler")
private HttpRequestExecutingMessageHandler handlermp;
@Autowired
private PollableChannel checkHeadersChannel;
@@ -114,6 +122,7 @@ public class HttpProxyScenarioTests {
final String contentDispositionValue = "attachment; filename=\"test.txt\"";
Mockito.doAnswer(new Answer<ResponseEntity<?>>() {
@Override
public ResponseEntity<?> answer(InvocationOnMock invocation) throws Throwable {
URI uri = (URI) invocation.getArguments()[0];
@@ -129,6 +138,7 @@ public class HttpProxyScenarioTests {
responseHeaders.set("Content-Disposition", contentDispositionValue);
return new ResponseEntity<Object>(responseHeaders, HttpStatus.OK);
}
}).when(template).exchange(Mockito.any(URI.class), Mockito.any(HttpMethod.class),
Mockito.any(HttpEntity.class), (Class<?>) Mockito.any(Class.class));
@@ -155,4 +165,57 @@ public class HttpProxyScenarioTests {
RequestContextHolder.resetRequestAttributes();
}
@Test
public void testHttpMultipartProxyScenario() throws Exception {
MockHttpServletRequest request = new MockHttpServletRequest("POST", "/testmp");
request.addHeader("Connection", "Keep-Alive");
request.setContentType("multipart/form-data;boundary=----WebKitFormBoundarywABD2xqC1FLBijlQ");
request.setContent("foo".getBytes());
Object handler = this.handlerMapping.getHandler(request).getHandler();
assertNotNull(handler);
MockHttpServletResponse response = new MockHttpServletResponse();
RestTemplate template = Mockito.spy(new RestTemplate());
Mockito.doAnswer(new Answer<ResponseEntity<?>>() {
@Override
public ResponseEntity<?> answer(InvocationOnMock invocation) throws Throwable {
URI uri = (URI) invocation.getArguments()[0];
assertEquals(new URI("http://testServer/testmp"), uri);
HttpEntity<?> httpEntity = (HttpEntity<?>) invocation.getArguments()[2];
HttpHeaders httpHeaders = httpEntity.getHeaders();
assertEquals("Keep-Alive", httpHeaders.getFirst("Connection"));
assertEquals("multipart/form-data;boundary=----WebKitFormBoundarywABD2xqC1FLBijlQ",
httpHeaders.getContentType().toString());
HttpEntity<?> entity = (HttpEntity<?>) invocation.getArguments()[2];
assertThat(entity.getBody(), instanceOf(byte[].class));
assertEquals("foo", new String((byte[])entity.getBody()));
MultiValueMap<String, String> responseHeaders = new LinkedMultiValueMap<String, String>(httpHeaders);
responseHeaders.set("Connection", "close");
responseHeaders.set("Content-Type", "text/plain");
return new ResponseEntity<Object>(responseHeaders, HttpStatus.OK);
}
}).when(template).exchange(Mockito.any(URI.class), Mockito.any(HttpMethod.class),
Mockito.any(HttpEntity.class), (Class<?>) Mockito.any(Class.class));
PropertyAccessor dfa = new DirectFieldAccessor(this.handlermp);
dfa.setPropertyValue("restTemplate", template);
RequestAttributes attributes = new ServletRequestAttributes(request);
RequestContextHolder.setRequestAttributes(attributes);
this.handlerAdapter.handle(request, response, handler);
assertEquals("close", response.getHeaderValue("Connection"));
assertEquals("text/plain", response.getContentType());
RequestContextHolder.resetRequestAttributes();
}
}

View File

@@ -0,0 +1,95 @@
/*
* Copyright 2015 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.inbound;
import static org.hamcrest.Matchers.instanceOf;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThat;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import java.util.Collections;
import java.util.Enumeration;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.junit.Test;
import org.mockito.Matchers;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.http.converter.ByteArrayHttpMessageConverter;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.integration.channel.QueueChannel;
import org.springframework.messaging.Message;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
/**
* @author Gary Russell
* @since 4.2
*
*/
public class MultipartAsRawByteArrayTests {
@SuppressWarnings("unchecked")
@Test
public void testMultiPass() throws Exception {
HttpRequestHandlingMessagingGateway gw = new HttpRequestHandlingMessagingGateway(false);
gw.setMessageConverters(
Collections.<HttpMessageConverter<?>> singletonList(new ByteArrayHttpMessageConverter()));
gw.setMergeWithDefaultConverters(false);
QueueChannel requestChannel = new QueueChannel();
gw.setRequestChannel(requestChannel);
gw.setBeanFactory(mock(BeanFactory.class));
gw.setRequestPayloadType(byte[].class);
gw.afterPropertiesSet();
HttpServletRequest request = mock(HttpServletRequest.class);
ServletInputStream sis = mock(ServletInputStream.class);
doAnswer(new Answer<Integer>() {
int done;
@Override
public Integer answer(InvocationOnMock invocation) throws Throwable {
byte[] buff = (byte[]) invocation.getArguments()[0];
buff[0] = 'f';
buff[1] = 'o';
buff[2] = 'o';
return done++ > 0 ? -1 : 3;
}
}).when(sis).read(Matchers.any(byte[].class));
when(request.getInputStream()).thenReturn(sis);
when(request.getMethod()).thenReturn("POST");
when(request.getHeaderNames()).thenReturn(mock(Enumeration.class));
when(request.getContentType()).thenReturn("multipart/form-data");
RequestContextHolder.setRequestAttributes(mock(RequestAttributes.class));
gw.handleRequest(request, mock(HttpServletResponse.class));
Message<?> received = requestChannel.receive(10000);
assertNotNull(received);
assertThat(received.getPayload(), instanceOf(byte[].class));
assertEquals("foo", new String((byte[])received.getPayload()));
}
}

View File

@@ -58,6 +58,32 @@ If it does in fact locate that bean, then the support for MultipartFiles will be
Otherwise, it will fail when trying to map a multipart-file request to a Spring Integration Message.
For more on Spring's support for MultipartResolvers, refer to the http://static.springsource.org/spring/docs/current/spring-framework-reference/html/mvc.html#mvc-multipart[Spring Reference Manual].
[NOTE]
====
If you wish to proxy a `multipart/form-data` to another server, it may be better to keep it in raw form.
To handle this situation, do not add the `multipartResolver` bean to the context; configure the endpoint to expect
a `byte[]` request; customize the message converters to include a `ByteArrayHttpMessageConverter`, and
disable the default multipart converter.
You may need some other converter(s) for the replies:
[source, xml]
----
<int-http:inbound-gateway
channel="receiveChannel"
path="/inboundAdapter.htm"
request-payload-type="byte[]"
message-converters="converters"
merge-with-default-converters="false"
supported-methods="POST" />
<util:list id="converters">
<beans:bean class="org.springframework.http.converter.ByteArrayHttpMessageConverter" />
<beans:bean class="org.springframework.http.converter.StringHttpMessageConverter" />
<beans:bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter" />
</util:list>
----
====
In sending a response to the client there are a number of ways to customize the behavior of the gateway.
By default the gateway will simply acknowledge that the request was received by sending a 200 status code back.
It is possible to customize this response by providing a 'viewName' to be resolved by the Spring MVC `ViewResolver`.

View File

@@ -292,6 +292,11 @@ The default is now `500 Internal Server Error` instead of `200 OK`.
See <<http-response-statuscode>> for more information.
===== Form Data
Documentation is provided for when proxying `multipart/form-data` requests.
See <<http>> for more information.
[[x4.2-gw]]
==== Gateway Changes