Support @HttpExchange for server-side handling
See gh-30980
This commit is contained in:
committed by
rstoyanchev
parent
646fd3edcc
commit
d1d5b54f12
@@ -35,6 +35,7 @@ import org.springframework.lang.Nullable;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.util.StringValueResolver;
|
||||
import org.springframework.web.accept.ContentNegotiationManager;
|
||||
import org.springframework.web.bind.annotation.CrossOrigin;
|
||||
@@ -43,6 +44,7 @@ import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.cors.CorsConfiguration;
|
||||
import org.springframework.web.method.HandlerMethod;
|
||||
import org.springframework.web.service.annotation.HttpExchange;
|
||||
import org.springframework.web.servlet.handler.MatchableHandlerMapping;
|
||||
import org.springframework.web.servlet.handler.RequestMatchResult;
|
||||
import org.springframework.web.servlet.mvc.condition.AbstractRequestCondition;
|
||||
@@ -71,6 +73,7 @@ import org.springframework.web.util.pattern.PathPatternParser;
|
||||
* @author Arjen Poutsma
|
||||
* @author Rossen Stoyanchev
|
||||
* @author Sam Brannen
|
||||
* @author Olga Maciaszek-Sharma
|
||||
* @since 3.1
|
||||
*/
|
||||
public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMapping
|
||||
@@ -331,18 +334,27 @@ public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMappi
|
||||
}
|
||||
|
||||
/**
|
||||
* Delegates to {@link #createRequestMappingInfo(RequestMapping, RequestCondition)},
|
||||
* supplying the appropriate custom {@link RequestCondition} depending on whether
|
||||
* Delegates to {@link #createRequestMappingInfo(RequestMapping, RequestCondition)}
|
||||
* or {@link #createRequestMappingInfo(HttpExchange, RequestCondition)},
|
||||
* depending on which annotation is found on the element being processed,
|
||||
* and supplying the appropriate custom {@link RequestCondition} depending on whether
|
||||
* the supplied {@code annotatedElement} is a class or method.
|
||||
* @see #getCustomTypeCondition(Class)
|
||||
* @see #getCustomMethodCondition(Method)
|
||||
*/
|
||||
@Nullable
|
||||
private RequestMappingInfo createRequestMappingInfo(AnnotatedElement element) {
|
||||
RequestMapping requestMapping = AnnotatedElementUtils.findMergedAnnotation(element, RequestMapping.class);
|
||||
RequestCondition<?> condition = (element instanceof Class<?> clazz ?
|
||||
getCustomTypeCondition(clazz) : getCustomMethodCondition((Method) element));
|
||||
return (requestMapping != null ? createRequestMappingInfo(requestMapping, condition) : null);
|
||||
RequestMapping requestMapping = AnnotatedElementUtils.findMergedAnnotation(element, RequestMapping.class);
|
||||
if(requestMapping != null){
|
||||
return createRequestMappingInfo(requestMapping, condition);
|
||||
}
|
||||
HttpExchange httpExchange = AnnotatedElementUtils.findMergedAnnotation(element, HttpExchange.class);
|
||||
if(httpExchange != null){
|
||||
return createRequestMappingInfo(httpExchange, condition);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -400,6 +412,28 @@ public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMappi
|
||||
return builder.options(this.config).build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a {@link RequestMappingInfo} from the supplied
|
||||
* {@link HttpExchange @HttpExchange} annotation, which is either
|
||||
* a directly declared annotation, a meta-annotation, or the synthesized
|
||||
* result of merging annotation attributes within an annotation hierarchy.
|
||||
*/
|
||||
protected RequestMappingInfo createRequestMappingInfo(
|
||||
HttpExchange httpExchange,
|
||||
@Nullable RequestCondition<?> customCondition) {
|
||||
|
||||
RequestMappingInfo.Builder builder = RequestMappingInfo
|
||||
.paths(resolveEmbeddedValuesInPatterns(
|
||||
toTextArray(httpExchange.value())))
|
||||
.methods(toMethodArray(httpExchange.method()))
|
||||
.consumes(toTextArray(httpExchange.contentType()))
|
||||
.produces(httpExchange.accept());
|
||||
if (customCondition != null) {
|
||||
builder.customCondition(customCondition);
|
||||
}
|
||||
return builder.options(this.config).build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve placeholder values in the given array of patterns.
|
||||
* @return a new array with updated patterns
|
||||
@@ -417,6 +451,24 @@ public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMappi
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static String[] toTextArray(String string) {
|
||||
if (StringUtils.hasText(string)) {
|
||||
return new String[] { string };
|
||||
}
|
||||
return new String[] {};
|
||||
}
|
||||
|
||||
private static RequestMethod[] toMethodArray(String method) {
|
||||
RequestMethod requestMethod = null;
|
||||
if (StringUtils.hasText(method)) {
|
||||
requestMethod = RequestMethod.resolve(method);
|
||||
}
|
||||
return requestMethod != null ? new RequestMethod[] { requestMethod }
|
||||
: new RequestMethod[] {};
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void registerMapping(RequestMappingInfo mapping, Object handler, Method method) {
|
||||
super.registerMapping(mapping, handler, method);
|
||||
|
||||
@@ -47,11 +47,14 @@ import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.context.support.StaticWebApplicationContext;
|
||||
import org.springframework.web.method.HandlerTypePredicate;
|
||||
import org.springframework.web.service.annotation.HttpExchange;
|
||||
import org.springframework.web.servlet.handler.PathPatternsParameterizedTest;
|
||||
import org.springframework.web.servlet.mvc.condition.ConsumesRequestCondition;
|
||||
import org.springframework.web.servlet.mvc.condition.MediaTypeExpression;
|
||||
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
|
||||
import org.springframework.web.testfixture.servlet.MockHttpServletRequest;
|
||||
import org.springframework.web.util.ServletRequestPathUtils;
|
||||
import org.springframework.web.util.pattern.PathPattern;
|
||||
import org.springframework.web.util.pattern.PathPatternParser;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
@@ -62,6 +65,7 @@ import static org.mockito.Mockito.mock;
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @author Sam Brannen
|
||||
* @author Olga Maciaszek-Sharma
|
||||
*/
|
||||
public class RequestMappingHandlerMappingTests {
|
||||
|
||||
@@ -278,6 +282,53 @@ public class RequestMappingHandlerMappingTests {
|
||||
assertComposedAnnotationMapping(RequestMethod.PATCH);
|
||||
}
|
||||
|
||||
@SuppressWarnings("DataFlowIssue")
|
||||
@Test
|
||||
void httpExchangeWithDefaultValues() throws NoSuchMethodException {
|
||||
RequestMappingHandlerMapping mapping = new RequestMappingHandlerMapping();
|
||||
mapping.setApplicationContext(new StaticWebApplicationContext());
|
||||
mapping.afterPropertiesSet();
|
||||
|
||||
RequestMappingInfo mappingInfo = mapping.getMappingForMethod(
|
||||
HttpExchangeController.class.getMethod("defaultValuesExchange"),
|
||||
HttpExchangeController.class);
|
||||
|
||||
assertThat(mappingInfo.getPathPatternsCondition().getPatterns())
|
||||
.extracting(PathPattern::toString)
|
||||
.containsOnly("/exchange");
|
||||
assertThat(mappingInfo.getMethodsCondition().getMethods()).isEmpty();
|
||||
assertThat(mappingInfo.getParamsCondition().getExpressions()).isEmpty();
|
||||
assertThat(mappingInfo.getHeadersCondition().getExpressions()).isEmpty();
|
||||
assertThat(mappingInfo.getConsumesCondition().getExpressions()).isEmpty();
|
||||
assertThat(mappingInfo.getProducesCondition().getExpressions()).isEmpty();
|
||||
}
|
||||
|
||||
@SuppressWarnings("DataFlowIssue")
|
||||
@Test
|
||||
void httpExchangeWithCustomValues() throws NoSuchMethodException {
|
||||
RequestMappingHandlerMapping mapping = new RequestMappingHandlerMapping();
|
||||
mapping.setApplicationContext(new StaticWebApplicationContext());
|
||||
mapping.afterPropertiesSet();
|
||||
|
||||
RequestMappingInfo mappingInfo = mapping.getMappingForMethod(
|
||||
HttpExchangeController.class.getMethod("customValuesExchange"),
|
||||
HttpExchangeController.class);
|
||||
|
||||
assertThat(mappingInfo.getPathPatternsCondition().getPatterns())
|
||||
.extracting(PathPattern::toString)
|
||||
.containsOnly("/exchange/custom");
|
||||
assertThat(mappingInfo.getMethodsCondition().getMethods())
|
||||
.containsOnly(RequestMethod.POST);
|
||||
assertThat(mappingInfo.getParamsCondition().getExpressions()).isEmpty();
|
||||
assertThat(mappingInfo.getHeadersCondition().getExpressions()).isEmpty();
|
||||
assertThat(mappingInfo.getConsumesCondition().getExpressions())
|
||||
.extracting(MediaTypeExpression::getMediaType)
|
||||
.containsOnly(MediaType.APPLICATION_JSON);
|
||||
assertThat(mappingInfo.getProducesCondition().getExpressions())
|
||||
.extracting(MediaTypeExpression::getMediaType)
|
||||
.containsOnly(MediaType.valueOf("text/plain;charset=UTF-8"));
|
||||
}
|
||||
|
||||
private RequestMappingInfo assertComposedAnnotationMapping(RequestMethod requestMethod) throws Exception {
|
||||
|
||||
RequestMappingHandlerMapping mapping = new RequestMappingHandlerMapping();
|
||||
@@ -367,6 +418,18 @@ public class RequestMappingHandlerMappingTests {
|
||||
}
|
||||
}
|
||||
|
||||
@RestController
|
||||
@HttpExchange("/exchange")
|
||||
static class HttpExchangeController {
|
||||
|
||||
@HttpExchange
|
||||
public void defaultValuesExchange(){}
|
||||
|
||||
@HttpExchange(value = "/custom", accept = "text/plain;charset=UTF-8",
|
||||
method = "POST", contentType = "application/json")
|
||||
public void customValuesExchange(){}
|
||||
}
|
||||
|
||||
|
||||
private static class Foo {
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user