GH-8806: Ignore HTTP body for DELETE & TRACE

Fixes: #8806

According to RFC 9110, the `TRACE` request must not contain the body and DELETE (like GET and HEAD)
should not.

* Fix `BaseHttpInboundEndpoint` adding `TRACE` & `DELETE` to the `NON_READABLE_BODY_HTTP_METHODS` list
* Clean up typos and links in the `http/inbound.adoc`
This commit is contained in:
Artem Bilan
2023-12-12 17:25:01 -05:00
parent 9bd8001a61
commit 6207bca3bd
3 changed files with 43 additions and 20 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2017-2022 the original author or authors.
* Copyright 2017-2023 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.
@@ -62,7 +62,7 @@ public class BaseHttpInboundEndpoint extends MessagingGatewaySupport implements
protected static final boolean ROME_TOOLS_PRESENT = ClassUtils.isPresent("com.rometools.rome.feed.atom.Feed", null);
protected static final List<HttpMethod> NON_READABLE_BODY_HTTP_METHODS =
Arrays.asList(HttpMethod.GET, HttpMethod.HEAD, HttpMethod.OPTIONS);
Arrays.asList(HttpMethod.GET, HttpMethod.HEAD, HttpMethod.DELETE, HttpMethod.OPTIONS, HttpMethod.TRACE);
protected final AtomicInteger activeCount = new AtomicInteger(); // NOSONAR

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2023 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.
@@ -92,7 +92,7 @@ public class HttpRequestHandlingMessagingGatewayTests extends AbstractHttpInboun
assertThat(message).isNotNull();
assertThat(message.getPayload().getClass()).isEqualTo(LinkedMultiValueMap.class);
LinkedMultiValueMap<String, String> map = (LinkedMultiValueMap<String, String>) message.getPayload();
assertThat(map.get("foo").size()).isEqualTo(1);
assertThat(map.get("foo")).hasSize(1);
assertThat(map.getFirst("foo")).isEqualTo("bar");
}
@@ -193,6 +193,7 @@ public class HttpRequestHandlingMessagingGatewayTests extends AbstractHttpInboun
protected boolean doSend(Message<?> message, long timeout) {
throw new RuntimeException("Planned");
}
};
HttpRequestHandlingMessagingGateway gateway = new HttpRequestHandlingMessagingGateway(true);
gateway.setBeanFactory(mock(BeanFactory.class));
@@ -234,11 +235,8 @@ public class HttpRequestHandlingMessagingGatewayTests extends AbstractHttpInboun
LinkedMultiValueMap<String, String> map = (LinkedMultiValueMap<String, String>) message.getPayload();
List<String> fooValues = map.get("foo");
List<String> barValues = map.get("bar");
assertThat(fooValues.size()).isEqualTo(1);
assertThat(fooValues.get(0)).isEqualTo("123");
assertThat(barValues.size()).isEqualTo(2);
assertThat(barValues.get(0)).isEqualTo("456");
assertThat(barValues.get(1)).isEqualTo("789");
assertThat(fooValues).containsExactly("123");
assertThat(barValues).containsExactly("456", "789");
}
@Test
@@ -281,8 +279,7 @@ public class HttpRequestHandlingMessagingGatewayTests extends AbstractHttpInboun
HttpRequestHandlingMessagingGateway gateway = new HttpRequestHandlingMessagingGateway(false);
gateway.setBeanFactory(mock(BeanFactory.class));
ParameterizedTypeReference<List<TestBean>> parameterizedTypeReference =
new ParameterizedTypeReference<List<TestBean>>() {
new ParameterizedTypeReference<>() {
};
gateway.setRequestPayloadType(ResolvableType.forType(parameterizedTypeReference));
gateway.setRequestChannel(channel);
@@ -314,14 +311,12 @@ public class HttpRequestHandlingMessagingGatewayTests extends AbstractHttpInboun
assertThat(bean).extracting(TestBean::getName).isEqualTo("T. Bean");
assertThat(bean).extracting(TestBean::getAge).isEqualTo(42);
});
}
@Test
public void INT2680DuplicateContentTypeHeader() throws Exception {
final DirectChannel requestChannel = new DirectChannel();
DirectChannel requestChannel = new DirectChannel();
requestChannel.subscribe(new AbstractReplyProducingMessageHandler() {
@Override
@@ -478,7 +473,35 @@ public class HttpRequestHandlingMessagingGatewayTests extends AbstractHttpInboun
verify(multipartResolver).isMultipart(any(HttpServletRequest.class));
}
private class ContentTypeCheckingMockHttpServletResponse extends MockHttpServletResponse {
@Test
public void deleteRequestBodyIgnored() throws Exception {
QueueChannel channel = new QueueChannel();
HttpRequestHandlingMessagingGateway gateway = new HttpRequestHandlingMessagingGateway(false);
gateway.setBeanFactory(mock(BeanFactory.class));
gateway.setRequestChannel(channel);
gateway.afterPropertiesSet();
gateway.start();
MockHttpServletRequest request = new MockHttpServletRequest("DELETE", "/delete");
request.setContent("This content is ignored for DELETE".getBytes());
request.setParameter("one", "1");
request.addParameter("two", "2");
MockHttpServletResponse response = new MockHttpServletResponse();
gateway.handleRequest(request, response);
Message<?> message = channel.receive(0);
assertThat(message).isNotNull();
assertThat(message.getPayload()).isNotNull();
assertThat(message.getPayload().getClass()).isEqualTo(LinkedMultiValueMap.class);
@SuppressWarnings("unchecked")
LinkedMultiValueMap<String, String> map = (LinkedMultiValueMap<String, String>) message.getPayload();
List<String> oneValues = map.get("one");
List<String> twoValues = map.get("two");
assertThat(oneValues).containsExactly("1");
assertThat(twoValues).containsExactly("2");
}
private static class ContentTypeCheckingMockHttpServletResponse extends MockHttpServletResponse {
private final List<String> contentTypeList = new ArrayList<>();

View File

@@ -14,12 +14,12 @@ The easiest way to do this is to use Spring's https://docs.spring.io/spring/docs
----
Notice that the servlet name matches the bean name.
For more information on using the `HttpRequestHandlerServlet`, see https://docs.spring.io/spring/docs/current/spring-framework-reference/html/remoting.html[Remoting and web services using Spring], which is part of the Spring Framework Reference documentation.
For more information see the `HttpRequestHandlerServlet` Javadocs.
If you are running within a Spring MVC application, then the aforementioned explicit servlet definition is not necessary.
In that case, the bean name for your gateway can be matched against the URL path as you would for a Spring MVC Controller bean.
For more information, see
https://docs.spring.io/spring/docs/current/spring-framework-reference/web.html#mvc[Web MVC framework], which is part of the Spring Framework Reference documentation.
https://docs.spring.io/spring-framework/reference/web/webmvc.html[Web MVC framework], which is part of the Spring Framework Reference documentation.
TIP: For a sample application and the corresponding configuration, see the https://github.com/spring-projects/spring-integration-samples[Spring Integration Samples] repository.
It contains the https://github.com/spring-projects/spring-integration-samples/tree/main/basic/http[HTTP sample] application, which demonstrates Spring Integration's HTTP support.
@@ -52,7 +52,7 @@ If the request has been wrapped as a `MultipartHttpServletRequest`, when you use
NOTE: The HTTP inbound endpoint locates a `MultipartResolver` in the context if one has a bean name of `multipartResolver` (the same name expected by Spring's `DispatcherServlet`).
If it does locate that bean, the support for multipart files is enabled on the inbound request mapper.
Otherwise, it fails when it tries to map a multipart file request to a Spring Integration `Message`.
For more on Spring's support for `MultipartResolver`, see the https://docs.spring.io/spring/docs/current/spring-framework-reference/web.html#mvc-multipart[Spring Reference Manual].
For more on Spring's support for `MultipartResolver`, see the https://docs.spring.io/spring-framework/reference/web/webmvc/mvc-servlet/multipart.html[Spring Reference Manual].
[NOTE]
====
@@ -113,6 +113,6 @@ By default, the key for that map entry is 'reply', but you can override this def
== Payload Validation
Starting with version 5.2, the HTTP inbound endpoints can be supplied with a `Validator` to check a payload before sending into the channel.
This payload is already a result of conversion and extraction after `payloadExpression` to narrow a validation scope in regards to the valuable data.
The validation failure handling is fully the same what we have in Spring MVC https://docs.spring.io/spring/docs/current/spring-framework-reference/web.html#mvc-exceptionhandlers[Error Handling].
This payload is already a result of conversion and extraction after `payloadExpression` to narrow a validation scope in regard to the valuable data.
The validation failure handling is fully the same what we have in Spring MVC https://docs.spring.io/spring-framework/reference/web/webmvc/mvc-servlet/exceptionhandlers.html[Error Handling].