diff --git a/spring-web/src/main/java/org/springframework/web/bind/support/WebExchangeDataBinder.java b/spring-web/src/main/java/org/springframework/web/bind/support/WebExchangeDataBinder.java index 21433865f5..ebe13fe832 100644 --- a/spring-web/src/main/java/org/springframework/web/bind/support/WebExchangeDataBinder.java +++ b/spring-web/src/main/java/org/springframework/web/bind/support/WebExchangeDataBinder.java @@ -16,7 +16,6 @@ package org.springframework.web.bind.support; -import java.util.Collections; import java.util.List; import java.util.Map; import java.util.TreeMap; @@ -24,10 +23,10 @@ import java.util.TreeMap; import reactor.core.publisher.Mono; import org.springframework.beans.MutablePropertyValues; +import org.springframework.http.codec.multipart.Part; import org.springframework.util.CollectionUtils; import org.springframework.util.MultiValueMap; import org.springframework.web.bind.WebDataBinder; -import org.springframework.web.multipart.MultipartFile; import org.springframework.web.server.ServerWebExchange; /** @@ -39,6 +38,7 @@ import org.springframework.web.server.ServerWebExchange; */ public class WebExchangeDataBinder extends WebDataBinder { + /** * Create a new instance, with default object name. * @param target the target object to bind onto (or {@code null} if the @@ -61,62 +61,52 @@ public class WebExchangeDataBinder extends WebDataBinder { /** - * Bind the URL query parameters or form data of the body of the given request - * to this binder's target. The request body is parsed if the Content-Type - * is {@code "application/x-www-form-urlencoded"}. + * Bind query params, form data, and or multipart form data to the binder target. * @param exchange the current exchange. * @return a {@code Mono} when binding is complete */ public Mono bind(ServerWebExchange exchange) { - return exchange.getRequestParams() - .map(this::getParamsToBind) - .doOnNext(values -> values.putAll(getMultipartFiles(exchange))) - .doOnNext(values -> values.putAll(getExtraValuesToBind(exchange))) - .flatMap(values -> { - doBind(new MutablePropertyValues(values)); - return Mono.empty(); + return getValuesToBind(exchange) + .doOnNext(values -> doBind(new MutablePropertyValues(values))) + .then(); + } + + /** + * Protected method to obtain the values for data binding. By default this + * method delegates to {@link #extractValuesToBind(ServerWebExchange)}. + */ + protected Mono> getValuesToBind(ServerWebExchange exchange) { + return extractValuesToBind(exchange); + } + + /** + * Combine query params and form data for multipart form data from the body + * of the request into a {@code Map} of values to use for + * data binding purposes. + * + * @param exchange the current exchange + * @return a {@code Mono} with the values to bind + */ + public static Mono> extractValuesToBind(ServerWebExchange exchange) { + + MultiValueMap queryParams = exchange.getRequest().getQueryParams(); + Mono> formData = exchange.getFormData(); + Mono> multipartData = exchange.getMultipartData(); + + return Mono.when(Mono.just(queryParams), formData, multipartData) + .map(tuple -> { + Map result = new TreeMap<>(); + tuple.getT1().forEach((key, values) -> addBindValue(result, key, values)); + tuple.getT2().forEach((key, values) -> addBindValue(result, key, values)); + tuple.getT3().forEach((key, values) -> addBindValue(result, key, values)); + return result; }); } - private Map getParamsToBind(MultiValueMap params) { - Map result = new TreeMap<>(); - for (Map.Entry> entry : params.entrySet()) { - String name = entry.getKey(); - List values = entry.getValue(); - if (CollectionUtils.isEmpty(values)) { - // Do nothing, no values found at all. - } - else if (values.size() == 1) { - result.put(name, values.get(0)); - } - else { - result.put(name, values); - } + private static void addBindValue(Map params, String key, List values) { + if (!CollectionUtils.isEmpty(values)) { + params.put(key, values.size() == 1 ? values.get(0) : values); } - return result; - } - - /** - * Bind all multipart files contained in the given request, if any (in case - * of a multipart request). - *

Multipart files will only be added to the property values if they - * are not empty or if we're configured to bind empty multipart files too. - * @param exchange the current exchange - * @return Map of field name String to MultipartFile object - */ - protected Map> getMultipartFiles(ServerWebExchange exchange) { - // TODO - return Collections.emptyMap(); - } - - /** - * Extension point that subclasses can use to add extra bind values for a - * request. Invoked before {@link #doBind(MutablePropertyValues)}. - *

The default implementation is empty. - * @param exchange the current exchange - */ - protected Map getExtraValuesToBind(ServerWebExchange exchange) { - return Collections.emptyMap(); } } diff --git a/spring-web/src/main/java/org/springframework/web/server/ServerWebExchange.java b/spring-web/src/main/java/org/springframework/web/server/ServerWebExchange.java index 9e675f5310..5f03e02858 100644 --- a/spring-web/src/main/java/org/springframework/web/server/ServerWebExchange.java +++ b/spring-web/src/main/java/org/springframework/web/server/ServerWebExchange.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2017 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. @@ -80,24 +80,23 @@ public interface ServerWebExchange { /** * Return the form data from the body of the request if the Content-Type is * {@code "application/x-www-form-urlencoded"} or an empty map otherwise. - * This method may be called multiple times. + * + *

Note: calling this method causes the request body to + * be read and parsed in full and the resulting {@code MultiValueMap} is + * cached so that this method is safe to call more than once. */ Mono> getFormData(); /** * Return the parts of a multipart request if the Content-Type is * {@code "multipart/form-data"} or an empty map otherwise. - * This method may be called multiple times. + * + *

Note: calling this method causes the request body to + * be read and parsed in full and the resulting {@code MultiValueMap} is + * cached so that this method is safe to call more than once. */ Mono> getMultipartData(); - /** - * Return a combined map that represents both - * {@link ServerHttpRequest#getQueryParams()} and {@link #getFormData()} - * or an empty map. - */ - Mono> getRequestParams(); - /** * Returns {@code true} if the one of the {@code checkNotModified} methods * in this contract were used and they returned true. diff --git a/spring-web/src/main/java/org/springframework/web/server/ServerWebExchangeDecorator.java b/spring-web/src/main/java/org/springframework/web/server/ServerWebExchangeDecorator.java index 78a774c272..f58b7eb17d 100644 --- a/spring-web/src/main/java/org/springframework/web/server/ServerWebExchangeDecorator.java +++ b/spring-web/src/main/java/org/springframework/web/server/ServerWebExchangeDecorator.java @@ -99,11 +99,6 @@ public class ServerWebExchangeDecorator implements ServerWebExchange { return getDelegate().getMultipartData(); } - @Override - public Mono> getRequestParams() { - return getDelegate().getRequestParams(); - } - @Override public boolean isNotModified() { return getDelegate().isNotModified(); diff --git a/spring-web/src/main/java/org/springframework/web/server/adapter/DefaultServerWebExchange.java b/spring-web/src/main/java/org/springframework/web/server/adapter/DefaultServerWebExchange.java index c6e92f4d8a..52967cc0e2 100644 --- a/spring-web/src/main/java/org/springframework/web/server/adapter/DefaultServerWebExchange.java +++ b/spring-web/src/main/java/org/springframework/web/server/adapter/DefaultServerWebExchange.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2017 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. @@ -88,8 +88,6 @@ public class DefaultServerWebExchange implements ServerWebExchange { private final Mono> multipartDataMono; - private final Mono> requestParamsMono; - private volatile boolean notModified; @@ -109,8 +107,6 @@ public class DefaultServerWebExchange implements ServerWebExchange { this.sessionMono = sessionManager.getSession(this).cache(); this.formDataMono = initFormData(request, codecConfigurer); this.multipartDataMono = initMultipartData(request, codecConfigurer); - this.requestParamsMono = initRequestParams(request, this.formDataMono); - } @SuppressWarnings("unchecked") @@ -165,20 +161,6 @@ public class DefaultServerWebExchange implements ServerWebExchange { return EMPTY_MULTIPART_DATA; } - private static Mono> initRequestParams( - ServerHttpRequest request, Mono> formDataMono) { - - return formDataMono - .map(formData -> { - MultiValueMap result = new LinkedMultiValueMap<>(); - result.putAll(request.getQueryParams()); - result.putAll(formData); - return CollectionUtils.unmodifiableMultiValueMap(result); - }) - .defaultIfEmpty(request.getQueryParams()) - .cache(); - } - @Override public ServerHttpRequest getRequest() { @@ -228,11 +210,6 @@ public class DefaultServerWebExchange implements ServerWebExchange { return this.multipartDataMono; } - @Override - public Mono> getRequestParams() { - return this.requestParamsMono; - } - @Override public boolean isNotModified() { return this.notModified; diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/result/condition/ParamsRequestCondition.java b/spring-webflux/src/main/java/org/springframework/web/reactive/result/condition/ParamsRequestCondition.java index ffdb0768d8..f1340b9089 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/result/condition/ParamsRequestCondition.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/result/condition/ParamsRequestCondition.java @@ -21,8 +21,6 @@ import java.util.Collections; import java.util.LinkedHashSet; import java.util.Set; -import org.springframework.util.Assert; -import org.springframework.util.MultiValueMap; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.server.ServerWebExchange; @@ -143,18 +141,12 @@ public final class ParamsRequestCondition extends AbstractRequestCondition getRequestParams(ServerWebExchange exchange) { - MultiValueMap params = exchange.getRequestParams().subscribe().peek(); - Assert.notNull(params, "Expected form data (if any) to be parsed."); - return params; + return this.value.equals(exchange.getRequest().getQueryParams().getFirst(this.name)); } } diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/AbstractHandlerMethodMapping.java b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/AbstractHandlerMethodMapping.java index 500887f979..2f0f41d89f 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/AbstractHandlerMethodMapping.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/AbstractHandlerMethodMapping.java @@ -258,32 +258,26 @@ public abstract class AbstractHandlerMethodMapping extends AbstractHandlerMap logger.debug("Looking up handler method for path " + lookupPath); } this.mappingRegistry.acquireReadLock(); - try { - // Ensure form data is parsed for "params" conditions... - return exchange.getRequestParams() - .then(exchange.getMultipartData()) - .then(Mono.defer(() -> { - HandlerMethod handlerMethod = null; - try { - handlerMethod = lookupHandlerMethod(lookupPath, exchange); - } - catch (Exception ex) { - return Mono.error(ex); - } - if (logger.isDebugEnabled()) { - if (handlerMethod != null) { - logger.debug("Returning handler method [" + handlerMethod + "]"); - } - else { - logger.debug("Did not find handler method for [" + lookupPath + "]"); - } - } - if (handlerMethod != null) { - handlerMethod = handlerMethod.createWithResolvedBean(); - } - return Mono.justOrEmpty(handlerMethod); - })); + HandlerMethod handlerMethod; + try { + handlerMethod = lookupHandlerMethod(lookupPath, exchange); + } + catch (Exception ex) { + return Mono.error(ex); + } + if (logger.isDebugEnabled()) { + if (handlerMethod != null) { + logger.debug("Returning handler method [" + handlerMethod + "]"); + } + else { + logger.debug("Did not find handler method for [" + lookupPath + "]"); + } + } + if (handlerMethod != null) { + handlerMethod = handlerMethod.createWithResolvedBean(); + } + return Mono.justOrEmpty(handlerMethod); } finally { this.mappingRegistry.releaseReadLock(); diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/ControllerMethodResolver.java b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/ControllerMethodResolver.java index 840fd3d6ef..1dd1799e76 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/ControllerMethodResolver.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/ControllerMethodResolver.java @@ -133,8 +133,8 @@ class ControllerMethodResolver { // Annotation-based... registrar.add(new RequestParamMethodArgumentResolver(beanFactory, reactiveRegistry, false)); - registrar.add(new RequestPartMethodArgumentResolver(beanFactory, reactiveRegistry, false)); registrar.add(new RequestParamMapMethodArgumentResolver(reactiveRegistry)); + registrar.add(new RequestPartMethodArgumentResolver(reactiveRegistry)); registrar.add(new PathVariableMethodArgumentResolver(beanFactory, reactiveRegistry)); registrar.add(new PathVariableMapMethodArgumentResolver(reactiveRegistry)); registrar.addIfRequestBody(readers -> new RequestBodyArgumentResolver(readers, reactiveRegistry)); diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/ModelAttributeMethodArgumentResolver.java b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/ModelAttributeMethodArgumentResolver.java index 31942bccd5..f880e27780 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/ModelAttributeMethodArgumentResolver.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/ModelAttributeMethodArgumentResolver.java @@ -216,7 +216,7 @@ public class ModelAttributeMethodArgumentResolver extends HandlerMethodArgumentR } // A single data class constructor -> resolve constructor arguments from request parameters. - return exchange.getRequestParams().map(requestParams -> { + return WebExchangeDataBinder.extractValuesToBind(exchange).map(bindValues -> { ConstructorProperties cp = ctor.getAnnotation(ConstructorProperties.class); String[] paramNames = (cp != null ? cp.value() : parameterNameDiscoverer.getParameterNames(ctor)); Assert.state(paramNames != null, () -> "Cannot resolve parameter names for constructor " + ctor); @@ -226,13 +226,9 @@ public class ModelAttributeMethodArgumentResolver extends HandlerMethodArgumentR Object[] args = new Object[paramTypes.length]; WebDataBinder binder = context.createDataBinder(exchange, null, attributeName); for (int i = 0; i < paramNames.length; i++) { - List paramValues = requestParams.get(paramNames[i]); - Object paramValue = null; - if (paramValues != null) { - paramValue = (paramValues.size() == 1 ? paramValues.get(0) : - paramValues.toArray(new String[paramValues.size()])); - } - args[i] = binder.convertIfNecessary(paramValue, paramTypes[i], new MethodParameter(ctor, i)); + Object value = bindValues.get(paramNames[i]); + value = (value != null && value instanceof List ? ((List) value).toArray() : value); + args[i] = binder.convertIfNecessary(value, paramTypes[i], new MethodParameter(ctor, i)); } return BeanUtils.instantiateClass(ctor, args); }); diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestParamMapMethodArgumentResolver.java b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestParamMapMethodArgumentResolver.java index b279473dcf..faf13bd495 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestParamMapMethodArgumentResolver.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestParamMapMethodArgumentResolver.java @@ -21,9 +21,6 @@ import java.util.Optional; import org.springframework.core.MethodParameter; import org.springframework.core.ReactiveAdapterRegistry; -import org.springframework.core.ResolvableType; -import org.springframework.http.codec.multipart.Part; -import org.springframework.util.Assert; import org.springframework.util.MultiValueMap; import org.springframework.util.StringUtils; import org.springframework.web.bind.annotation.RequestParam; @@ -51,6 +48,7 @@ import org.springframework.web.server.ServerWebExchange; public class RequestParamMapMethodArgumentResolver extends HandlerMethodArgumentResolverSupport implements SyncHandlerMethodArgumentResolver { + public RequestParamMapMethodArgumentResolver(ReactiveAdapterRegistry adapterRegistry) { super(adapterRegistry); } @@ -70,18 +68,10 @@ public class RequestParamMapMethodArgumentResolver extends HandlerMethodArgument public Optional resolveArgumentValue(MethodParameter methodParameter, BindingContext context, ServerWebExchange exchange) { - ResolvableType paramType = ResolvableType.forType(methodParameter.getGenericParameterType()); - boolean isMultiValueMap = MultiValueMap.class.isAssignableFrom(paramType.getRawClass()); - - - if (paramType.getGeneric(1).getRawClass() == Part.class) { - MultiValueMap requestParts = exchange.getMultipartData().subscribe().peek(); - Assert.notNull(requestParts, "Expected multipart data (if any) to be parsed."); - return Optional.of(isMultiValueMap ? requestParts : requestParts.toSingleValueMap()); - } - MultiValueMap requestParams = exchange.getRequestParams().subscribe().peek(); - Assert.notNull(requestParams, "Expected form data (if any) to be parsed."); - return Optional.of(isMultiValueMap ? requestParams : requestParams.toSingleValueMap()); + boolean isMultiValueMap = MultiValueMap.class.isAssignableFrom(methodParameter.getParameterType()); + MultiValueMap queryParams = exchange.getRequest().getQueryParams(); + Object value = isMultiValueMap ? queryParams : queryParams.toSingleValueMap(); + return Optional.of(value); } } diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestParamMethodArgumentResolver.java b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestParamMethodArgumentResolver.java index 816c1036b5..41fbba180b 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestParamMethodArgumentResolver.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestParamMethodArgumentResolver.java @@ -25,9 +25,6 @@ import org.springframework.beans.factory.config.ConfigurableBeanFactory; import org.springframework.core.MethodParameter; import org.springframework.core.ReactiveAdapterRegistry; import org.springframework.core.convert.converter.Converter; -import org.springframework.http.codec.multipart.Part; -import org.springframework.util.Assert; -import org.springframework.util.MultiValueMap; import org.springframework.util.StringUtils; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ValueConstants; @@ -35,7 +32,8 @@ import org.springframework.web.server.ServerWebExchange; import org.springframework.web.server.ServerWebInputException; /** - * Resolver for method arguments annotated with @{@link RequestParam}. + * Resolver for method arguments annotated with @{@link RequestParam} from URI + * query string parameters. * *

This resolver can also be created in default resolution mode in which * simple types (int, long, etc.) not annotated with @{@link RequestParam} are @@ -103,7 +101,7 @@ public class RequestParamMethodArgumentResolver extends AbstractNamedValueSyncAr protected Optional resolveNamedValue(String name, MethodParameter parameter, ServerWebExchange exchange) { - List paramValues = parameter.getParameter().getType() == Part.class ? getMultipartData(exchange).get(name) : getRequestParams(exchange).get(name); + List paramValues = exchange.getRequest().getQueryParams().get(name); Object result = null; if (paramValues != null) { result = (paramValues.size() == 1 ? paramValues.get(0) : paramValues); @@ -111,18 +109,6 @@ public class RequestParamMethodArgumentResolver extends AbstractNamedValueSyncAr return Optional.ofNullable(result); } - private MultiValueMap getRequestParams(ServerWebExchange exchange) { - MultiValueMap params = exchange.getRequestParams().subscribe().peek(); - Assert.notNull(params, "Expected form data (if any) to be parsed."); - return params; - } - - private MultiValueMap getMultipartData(ServerWebExchange exchange) { - MultiValueMap params = exchange.getMultipartData().subscribe().peek(); - Assert.notNull(params, "Expected multipart data (if any) to be parsed."); - return params; - } - @Override protected void handleMissingValue(String name, MethodParameter parameter, ServerWebExchange exchange) { String type = parameter.getNestedParameterType().getSimpleName(); diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestPartMethodArgumentResolver.java b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestPartMethodArgumentResolver.java index 7fc9acc4e8..fd78c652ce 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestPartMethodArgumentResolver.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestPartMethodArgumentResolver.java @@ -17,19 +17,13 @@ package org.springframework.web.reactive.result.method.annotation; import java.util.List; -import java.util.Map; -import java.util.Optional; -import org.springframework.beans.BeanUtils; -import org.springframework.beans.factory.config.ConfigurableBeanFactory; +import reactor.core.publisher.Mono; + import org.springframework.core.MethodParameter; import org.springframework.core.ReactiveAdapterRegistry; -import org.springframework.core.convert.converter.Converter; import org.springframework.http.codec.multipart.Part; -import org.springframework.util.Assert; -import org.springframework.util.MultiValueMap; -import org.springframework.util.StringUtils; -import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.util.CollectionUtils; import org.springframework.web.bind.annotation.RequestPart; import org.springframework.web.bind.annotation.ValueConstants; import org.springframework.web.server.ServerWebExchange; @@ -39,48 +33,25 @@ import org.springframework.web.server.ServerWebInputException; * Resolver for method arguments annotated with @{@link RequestPart}. * * @author Sebastien Deleuze + * @author Rossen Stoyanchev * @since 5.0 - * @see RequestParamMapMethodArgumentResolver */ -public class RequestPartMethodArgumentResolver extends AbstractNamedValueSyncArgumentResolver { - - private final boolean useDefaultResolution; - +public class RequestPartMethodArgumentResolver extends AbstractNamedValueArgumentResolver { /** * Class constructor with a default resolution mode flag. - * @param factory a bean factory used for resolving ${...} placeholder - * and #{...} SpEL expressions in default values, or {@code null} if default - * values are not expected to contain expressions * @param registry for checking reactive type wrappers - * @param useDefaultResolution in default resolution mode a method argument - * that is a simple type, as defined in {@link BeanUtils#isSimpleProperty}, - * is treated as a request parameter even if it isn't annotated, the - * request parameter name is derived from the method parameter name. */ - public RequestPartMethodArgumentResolver( - ConfigurableBeanFactory factory, ReactiveAdapterRegistry registry, boolean useDefaultResolution) { - - super(factory, registry); - this.useDefaultResolution = useDefaultResolution; + public RequestPartMethodArgumentResolver(ReactiveAdapterRegistry registry) { + super(null, registry); } @Override - public boolean supportsParameter(MethodParameter param) { - if (checkAnnotatedParamNoReactiveWrapper(param, RequestPart.class, this::singleParam)) { - return true; - } - else if (this.useDefaultResolution) { - return checkParameterTypeNoReactiveWrapper(param, BeanUtils::isSimpleProperty) || - BeanUtils.isSimpleProperty(param.nestedIfOptional().getNestedParameterType()); - } - return false; + public boolean supportsParameter(MethodParameter parameter) { + return parameter.hasParameterAnnotation(RequestPart.class); } - private boolean singleParam(RequestPart requestParam, Class type) { - return !Map.class.isAssignableFrom(type) || StringUtils.hasText(requestParam.name()); - } @Override protected NamedValueInfo createNamedValueInfo(MethodParameter parameter) { @@ -89,28 +60,21 @@ public class RequestPartMethodArgumentResolver extends AbstractNamedValueSyncArg } @Override - protected Optional resolveNamedValue(String name, MethodParameter parameter, - ServerWebExchange exchange) { - - List paramValues = getMultipartData(exchange).get(name); - Object result = null; - if (paramValues != null) { - result = (paramValues.size() == 1 ? paramValues.get(0) : paramValues); - } - return Optional.ofNullable(result); - } - - private MultiValueMap getMultipartData(ServerWebExchange exchange) { - MultiValueMap params = exchange.getMultipartData().subscribe().peek(); - Assert.notNull(params, "Expected multipart data (if any) to be parsed."); - return params; + protected Mono resolveName(String name, MethodParameter param, ServerWebExchange exchange) { + return exchange.getMultipartData().flatMap(allParts -> { + List parts = allParts.get(name); + if (CollectionUtils.isEmpty(parts)) { + return Mono.empty(); + } + return Mono.just(parts.size() == 1 ? parts.get(0) : parts); + }); } @Override - protected void handleMissingValue(String name, MethodParameter parameter, ServerWebExchange exchange) { - String type = parameter.getNestedParameterType().getSimpleName(); + protected void handleMissingValue(String name, MethodParameter param, ServerWebExchange exchange) { + String type = param.getNestedParameterType().getSimpleName(); String reason = "Required " + type + " parameter '" + name + "' is not present"; - throw new ServerWebInputException(reason, parameter); + throw new ServerWebInputException(reason, param); } diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/result/condition/ParamsRequestConditionTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/result/condition/ParamsRequestConditionTests.java index f7c436f8ff..79fcb2cf85 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/result/condition/ParamsRequestConditionTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/result/condition/ParamsRequestConditionTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2017 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. @@ -20,8 +20,6 @@ import java.util.Collection; import org.junit.Test; -import org.springframework.http.MediaType; -import org.springframework.mock.http.server.reactive.test.MockServerHttpRequest; import org.springframework.mock.http.server.reactive.test.MockServerWebExchange; import org.springframework.web.server.ServerWebExchange; @@ -50,17 +48,13 @@ public class ParamsRequestConditionTests { @Test public void paramPresent() throws Exception { ParamsRequestCondition condition = new ParamsRequestCondition("foo"); - assertNotNull(condition.getMatchingCondition(get("/path?foo=").toExchange())); - assertNotNull(condition.getMatchingCondition(postForm("foo="))); } @Test public void paramPresentNoMatch() throws Exception { ParamsRequestCondition condition = new ParamsRequestCondition("foo"); - assertNull(condition.getMatchingCondition(get("/path?bar=").toExchange())); - assertNull(condition.getMatchingCondition(postForm("bar="))); } @Test @@ -72,17 +66,13 @@ public class ParamsRequestConditionTests { @Test public void paramValueMatch() throws Exception { ParamsRequestCondition condition = new ParamsRequestCondition("foo=bar"); - assertNotNull(condition.getMatchingCondition(get("/path?foo=bar").toExchange())); - assertNotNull(condition.getMatchingCondition(postForm("foo=bar"))); } @Test public void paramValueNoMatch() throws Exception { ParamsRequestCondition condition = new ParamsRequestCondition("foo=bar"); - assertNull(condition.getMatchingCondition(get("/path?foo=bazz").toExchange())); - assertNull(condition.getMatchingCondition(postForm("foo=bazz"))); } @Test @@ -109,12 +99,4 @@ public class ParamsRequestConditionTests { assertEquals(2, conditions.size()); } - - private static MockServerWebExchange postForm(String formData) { - return MockServerHttpRequest.post("/") - .contentType(MediaType.APPLICATION_FORM_URLENCODED) - .body(formData) - .toExchange(); - } - } diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/ControllerMethodResolverTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/ControllerMethodResolverTests.java index 801dcce5fb..88129af0b8 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/ControllerMethodResolverTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/ControllerMethodResolverTests.java @@ -91,8 +91,8 @@ public class ControllerMethodResolverTests { AtomicInteger index = new AtomicInteger(-1); assertEquals(RequestParamMethodArgumentResolver.class, next(resolvers, index).getClass()); - assertEquals(RequestPartMethodArgumentResolver.class, next(resolvers, index).getClass()); assertEquals(RequestParamMapMethodArgumentResolver.class, next(resolvers, index).getClass()); + assertEquals(RequestPartMethodArgumentResolver.class, next(resolvers, index).getClass()); assertEquals(PathVariableMethodArgumentResolver.class, next(resolvers, index).getClass()); assertEquals(PathVariableMapMethodArgumentResolver.class, next(resolvers, index).getClass()); assertEquals(RequestBodyArgumentResolver.class, next(resolvers, index).getClass()); @@ -130,8 +130,8 @@ public class ControllerMethodResolverTests { AtomicInteger index = new AtomicInteger(-1); assertEquals(RequestParamMethodArgumentResolver.class, next(resolvers, index).getClass()); - assertEquals(RequestPartMethodArgumentResolver.class, next(resolvers, index).getClass()); assertEquals(RequestParamMapMethodArgumentResolver.class, next(resolvers, index).getClass()); + assertEquals(RequestPartMethodArgumentResolver.class, next(resolvers, index).getClass()); assertEquals(PathVariableMethodArgumentResolver.class, next(resolvers, index).getClass()); assertEquals(PathVariableMapMethodArgumentResolver.class, next(resolvers, index).getClass()); assertEquals(ModelAttributeMethodArgumentResolver.class, next(resolvers, index).getClass()); @@ -167,7 +167,6 @@ public class ControllerMethodResolverTests { AtomicInteger index = new AtomicInteger(-1); assertEquals(RequestParamMethodArgumentResolver.class, next(resolvers, index).getClass()); - assertEquals(RequestPartMethodArgumentResolver.class, next(resolvers, index).getClass()); assertEquals(RequestParamMapMethodArgumentResolver.class, next(resolvers, index).getClass()); assertEquals(PathVariableMethodArgumentResolver.class, next(resolvers, index).getClass()); assertEquals(PathVariableMapMethodArgumentResolver.class, next(resolvers, index).getClass()); @@ -198,8 +197,8 @@ public class ControllerMethodResolverTests { AtomicInteger index = new AtomicInteger(-1); assertEquals(RequestParamMethodArgumentResolver.class, next(resolvers, index).getClass()); - assertEquals(RequestPartMethodArgumentResolver.class, next(resolvers, index).getClass()); assertEquals(RequestParamMapMethodArgumentResolver.class, next(resolvers, index).getClass()); + assertEquals(RequestPartMethodArgumentResolver.class, next(resolvers, index).getClass()); assertEquals(PathVariableMethodArgumentResolver.class, next(resolvers, index).getClass()); assertEquals(PathVariableMapMethodArgumentResolver.class, next(resolvers, index).getClass()); assertEquals(RequestHeaderMethodArgumentResolver.class, next(resolvers, index).getClass()); diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/MultipartIntegrationTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/MultipartIntegrationTests.java index 33a0847344..67951746ef 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/MultipartIntegrationTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/MultipartIntegrationTests.java @@ -16,8 +16,6 @@ package org.springframework.web.reactive.result.method.annotation; -import java.util.Map; - import org.junit.Before; import org.junit.Test; import reactor.core.publisher.Mono; @@ -37,7 +35,6 @@ import org.springframework.http.server.reactive.HttpHandler; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RequestPart; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.reactive.DispatcherHandler; @@ -51,10 +48,9 @@ import static org.junit.Assert.assertEquals; public class MultipartIntegrationTests extends AbstractHttpHandlerIntegrationTests { - private AnnotationConfigApplicationContext wac; - private WebClient webClient; + @Override @Before public void setup() throws Exception { @@ -65,26 +61,10 @@ public class MultipartIntegrationTests extends AbstractHttpHandlerIntegrationTes @Override protected HttpHandler createHttpHandler() { - this.wac = new AnnotationConfigApplicationContext(); - this.wac.register(TestConfiguration.class); - this.wac.refresh(); - - return WebHttpHandlerBuilder.webHandler(new DispatcherHandler(this.wac)).build(); - } - - @Test - public void map() { - test("/map"); - } - - @Test - public void multiValueMap() { - test("/multivaluemap"); - } - - @Test - public void partParam() { - test("/partparam"); + AnnotationConfigApplicationContext wac = new AnnotationConfigApplicationContext(); + wac.register(TestConfiguration.class); + wac.refresh(); + return WebHttpHandlerBuilder.webHandler(new DispatcherHandler(wac)).build(); } @Test @@ -122,26 +102,6 @@ public class MultipartIntegrationTests extends AbstractHttpHandlerIntegrationTes @SuppressWarnings("unused") static class MultipartController { - @PostMapping("/map") - void map(@RequestParam Map parts) { - assertEquals(2, parts.size()); - assertEquals("foo.txt", parts.get("fooPart").getFilename().get()); - assertEquals("bar", parts.get("barPart").getContentAsString().block()); - } - - @PostMapping("/multivaluemap") - void multiValueMap(@RequestParam MultiValueMap parts) { - Map map = parts.toSingleValueMap(); - assertEquals(2, map.size()); - assertEquals("foo.txt", map.get("fooPart").getFilename().get()); - assertEquals("bar", map.get("barPart").getContentAsString().block()); - } - - @PostMapping("/partparam") - void partParam(@RequestParam Part fooPart) { - assertEquals("foo.txt", fooPart.getFilename().get()); - } - @PostMapping("/part") void part(@RequestPart Part fooPart) { assertEquals("foo.txt", fooPart.getFilename().get()); diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/RequestParamMapMethodArgumentResolverTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/RequestParamMapMethodArgumentResolverTests.java index 67af4ab530..c3551ca2af 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/RequestParamMapMethodArgumentResolverTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/RequestParamMapMethodArgumentResolverTests.java @@ -36,8 +36,6 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; -import static org.springframework.http.MediaType.APPLICATION_FORM_URLENCODED; -import static org.springframework.mock.http.server.reactive.test.MockServerHttpRequest.post; import static org.springframework.web.method.MvcAnnotationPredicates.requestParam; /** @@ -46,7 +44,8 @@ import static org.springframework.web.method.MvcAnnotationPredicates.requestPara */ public class RequestParamMapMethodArgumentResolverTests { - private RequestParamMapMethodArgumentResolver resolver = new RequestParamMapMethodArgumentResolver(new ReactiveAdapterRegistry()); + private final RequestParamMapMethodArgumentResolver resolver = + new RequestParamMapMethodArgumentResolver(new ReactiveAdapterRegistry()); private ResolvableMethod testMethod = ResolvableMethod.on(getClass()).named("handle").build(); @@ -85,15 +84,6 @@ public class RequestParamMapMethodArgumentResolverTests { assertEquals(Collections.singletonMap("foo", "bar"), result); } - @Test - public void resolveMapArgumentWithFormData() throws Exception { - MethodParameter param = this.testMethod.annot(requestParam().name("")).arg(Map.class); - ServerWebExchange exchange = post("/").contentType(APPLICATION_FORM_URLENCODED).body("foo=bar").toExchange(); - Object result= resolve(param, exchange); - assertTrue(result instanceof Map); - assertEquals(Collections.singletonMap("foo", "bar"), result); - } - @Test public void resolveMultiValueMapArgument() throws Exception { MethodParameter param = this.testMethod.annotPresent(RequestParam.class).arg(MultiValueMap.class); diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/RequestParamMethodArgumentResolverTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/RequestParamMethodArgumentResolverTests.java index 1a38525232..d9df158ec4 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/RequestParamMethodArgumentResolverTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/RequestParamMethodArgumentResolverTests.java @@ -28,7 +28,6 @@ import reactor.test.StepVerifier; import org.springframework.core.MethodParameter; import org.springframework.core.ReactiveAdapterRegistry; import org.springframework.format.support.DefaultFormattingConversionService; -import org.springframework.http.MediaType; import org.springframework.mock.http.server.reactive.test.MockServerHttpRequest; import org.springframework.mock.http.server.reactive.test.MockServerWebExchange; import org.springframework.web.bind.annotation.RequestParam; @@ -139,18 +138,6 @@ public class RequestParamMethodArgumentResolverTests { assertEquals("foo", resolve(param, MockServerHttpRequest.get("/path?name=foo").toExchange())); } - @Test - public void resolveWithFormData() throws Exception { - - ServerWebExchange exchange = MockServerHttpRequest.post("/path") - .contentType(MediaType.APPLICATION_FORM_URLENCODED) - .body("name=foo") - .toExchange(); - - MethodParameter param = this.testMethod.annot(requestParam().notRequired("bar")).arg(String.class); - assertEquals("foo", resolve(param, exchange)); - } - @Test public void resolveStringArray() throws Exception { MethodParameter param = this.testMethod.annotPresent(RequestParam.class).arg(String[].class);