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:
committed by
Artem Bilan
parent
f26bfd5b33
commit
4f648a7e4f
@@ -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>
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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()));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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`.
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user