Polish HttpHiddenMethodFilter
This commit is contained in:
		
							parent
							
								
									37592ea07c
								
							
						
					
					
						commit
						a99fe3eda4
					
				| 
						 | 
					@ -37,29 +37,33 @@ import org.springframework.web.server.WebFilterChain;
 | 
				
			||||||
 * return value using {@link ServerWebExchange#mutate()}.
 | 
					 * return value using {@link ServerWebExchange#mutate()}.
 | 
				
			||||||
 *
 | 
					 *
 | 
				
			||||||
 * <p>The name of the request parameter defaults to {@code _method}, but can be
 | 
					 * <p>The name of the request parameter defaults to {@code _method}, but can be
 | 
				
			||||||
 * adapted via the {@link #setMethodParam(String) methodParam} property.
 | 
					 * adapted via the {@link #setMethodParamName(String) methodParamName} property.
 | 
				
			||||||
 *
 | 
					 *
 | 
				
			||||||
 * @author Greg Turnquist
 | 
					 * @author Greg Turnquist
 | 
				
			||||||
 | 
					 * @author Rossen Stoyanchev
 | 
				
			||||||
 * @since 5.0
 | 
					 * @since 5.0
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
public class HiddenHttpMethodFilter implements WebFilter {
 | 
					public class HiddenHttpMethodFilter implements WebFilter {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/** Default method parameter: {@code _method} */
 | 
						/** Default name of the form parameter with the HTTP method to use */
 | 
				
			||||||
	public static final String DEFAULT_METHOD_PARAM = "_method";
 | 
						public static final String DEFAULT_METHOD_PARAMETER_NAME = "_method";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private String methodParamName = DEFAULT_METHOD_PARAMETER_NAME;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	private String methodParam = DEFAULT_METHOD_PARAM;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * Set the parameter name to look for HTTP methods.
 | 
						 * Set the name of the form parameter with the HTTP method to use.
 | 
				
			||||||
	 * @see #DEFAULT_METHOD_PARAM
 | 
						 * <p>By default this is set to {@code "_method"}.
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public void setMethodParam(String methodParam) {
 | 
						public void setMethodParamName(String methodParamName) {
 | 
				
			||||||
		Assert.hasText(methodParam, "'methodParam' must not be empty");
 | 
							Assert.hasText(methodParamName, "'methodParamName' must not be empty");
 | 
				
			||||||
		this.methodParam = methodParam;
 | 
							this.methodParamName = methodParamName;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * Transform an HTTP POST into another method based on {@code methodParam}
 | 
						 * Transform an HTTP POST into another method based on {@code methodParamName}
 | 
				
			||||||
	 *
 | 
						 *
 | 
				
			||||||
	 * @param exchange the current server exchange
 | 
						 * @param exchange the current server exchange
 | 
				
			||||||
	 * @param chain provides a way to delegate to the next filter
 | 
						 * @param chain provides a way to delegate to the next filter
 | 
				
			||||||
| 
						 | 
					@ -68,36 +72,22 @@ public class HiddenHttpMethodFilter implements WebFilter {
 | 
				
			||||||
	@Override
 | 
						@Override
 | 
				
			||||||
	public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
 | 
						public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if (exchange.getRequest().getMethod() == HttpMethod.POST) {
 | 
							if (exchange.getRequest().getMethod() != HttpMethod.POST) {
 | 
				
			||||||
			return exchange.getFormData()
 | 
					 | 
				
			||||||
					.map(formData -> {
 | 
					 | 
				
			||||||
						String method = formData.getFirst(methodParam);
 | 
					 | 
				
			||||||
						if (StringUtils.hasLength(method)) {
 | 
					 | 
				
			||||||
							return convertedRequest(exchange, method);
 | 
					 | 
				
			||||||
						}
 | 
					 | 
				
			||||||
						else {
 | 
					 | 
				
			||||||
							return exchange;
 | 
					 | 
				
			||||||
						}
 | 
					 | 
				
			||||||
					})
 | 
					 | 
				
			||||||
					.then(convertedExchange -> chain.filter(convertedExchange));
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		else {
 | 
					 | 
				
			||||||
			return chain.filter(exchange);
 | 
								return chain.filter(exchange);
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							return exchange.getFormData()
 | 
				
			||||||
 | 
									.map(formData -> {
 | 
				
			||||||
 | 
										String method = formData.getFirst(this.methodParamName);
 | 
				
			||||||
 | 
										return StringUtils.hasLength(method) ? mapExchange(exchange, method) : exchange;
 | 
				
			||||||
 | 
									})
 | 
				
			||||||
 | 
									.then((exchange1) -> chain.filter(exchange1));
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						private ServerWebExchange mapExchange(ServerWebExchange exchange, String methodParamValue) {
 | 
				
			||||||
	 * Mutate exchange into a new HTTP request method.
 | 
							HttpMethod httpMethod = HttpMethod.resolve(methodParamValue.toUpperCase(Locale.ENGLISH));
 | 
				
			||||||
	 *
 | 
							Assert.notNull(httpMethod, () -> "HttpMethod '" + methodParamValue + "' not supported");
 | 
				
			||||||
	 * @param exchange original {@link ServerWebExchange}
 | 
							return exchange.mutate().request(builder -> builder.method(httpMethod)).build();
 | 
				
			||||||
	 * @param method request HTTP method based on form data
 | 
					 | 
				
			||||||
	 * @return a mutated {@link ServerWebExchange}
 | 
					 | 
				
			||||||
	 */
 | 
					 | 
				
			||||||
	private ServerWebExchange convertedRequest(ServerWebExchange exchange, String method) {
 | 
					 | 
				
			||||||
		HttpMethod resolved = HttpMethod.resolve(method.toUpperCase(Locale.ENGLISH));
 | 
					 | 
				
			||||||
		Assert.notNull(resolved, () -> "HttpMethod '" + method + "' is not supported");
 | 
					 | 
				
			||||||
		return exchange.mutate()
 | 
					 | 
				
			||||||
				.request(builder -> builder.method(resolved))
 | 
					 | 
				
			||||||
				.build();
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,5 @@
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * {@link org.springframework.web.server.WebFilter} implementations for use in
 | 
				
			||||||
 | 
					 * reactive web applications.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					package org.springframework.web.filter.reactive;
 | 
				
			||||||
| 
						 | 
					@ -104,8 +104,10 @@ public class DefaultServerWebExchange implements ServerWebExchange {
 | 
				
			||||||
		try {
 | 
							try {
 | 
				
			||||||
			contentType = request.getHeaders().getContentType();
 | 
								contentType = request.getHeaders().getContentType();
 | 
				
			||||||
			if (MediaType.APPLICATION_FORM_URLENCODED.isCompatibleWith(contentType)) {
 | 
								if (MediaType.APPLICATION_FORM_URLENCODED.isCompatibleWith(contentType)) {
 | 
				
			||||||
				Map<String, Object> hints = Collections.emptyMap();
 | 
									return FORM_READER
 | 
				
			||||||
				return FORM_READER.readMono(FORM_DATA_VALUE_TYPE, request, hints).cache();
 | 
											.readMono(FORM_DATA_VALUE_TYPE, request, Collections.emptyMap())
 | 
				
			||||||
 | 
											.otherwiseIfEmpty(EMPTY_FORM_DATA)
 | 
				
			||||||
 | 
											.cache();
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		catch (InvalidMediaTypeException ex) {
 | 
							catch (InvalidMediaTypeException ex) {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -16,7 +16,7 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
package org.springframework.web.filter.reactive;
 | 
					package org.springframework.web.filter.reactive;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import java.util.Optional;
 | 
					import java.time.Duration;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import org.hamcrest.Matchers;
 | 
					import org.hamcrest.Matchers;
 | 
				
			||||||
import org.junit.Test;
 | 
					import org.junit.Test;
 | 
				
			||||||
| 
						 | 
					@ -27,6 +27,7 @@ import org.springframework.http.HttpHeaders;
 | 
				
			||||||
import org.springframework.http.HttpMethod;
 | 
					import org.springframework.http.HttpMethod;
 | 
				
			||||||
import org.springframework.http.MediaType;
 | 
					import org.springframework.http.MediaType;
 | 
				
			||||||
import org.springframework.mock.http.server.reactive.test.MockServerHttpRequest;
 | 
					import org.springframework.mock.http.server.reactive.test.MockServerHttpRequest;
 | 
				
			||||||
 | 
					import org.springframework.mock.http.server.reactive.test.MockServerWebExchange;
 | 
				
			||||||
import org.springframework.web.server.ServerWebExchange;
 | 
					import org.springframework.web.server.ServerWebExchange;
 | 
				
			||||||
import org.springframework.web.server.WebFilterChain;
 | 
					import org.springframework.web.server.WebFilterChain;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -34,116 +35,90 @@ import static org.junit.Assert.assertEquals;
 | 
				
			||||||
import static org.junit.Assert.assertThat;
 | 
					import static org.junit.Assert.assertThat;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Tests for {@link HiddenHttpMethodFilter}
 | 
					 * Tests for {@link HiddenHttpMethodFilter}.
 | 
				
			||||||
 *
 | 
					 | 
				
			||||||
 * @author Greg Turnquist
 | 
					 * @author Greg Turnquist
 | 
				
			||||||
 | 
					 * @author Rossen Stoyanchev
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
public class HiddenHttpMethodFilterTests {
 | 
					public class HiddenHttpMethodFilterTests {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	private final HiddenHttpMethodFilter filter = new HiddenHttpMethodFilter();
 | 
						private final HiddenHttpMethodFilter filter = new HiddenHttpMethodFilter();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private final TestWebFilterChain filterChain = new TestWebFilterChain();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@Test
 | 
						@Test
 | 
				
			||||||
	public void filterWithParameter() {
 | 
						public void filterWithParameter() {
 | 
				
			||||||
		ServerWebExchange mockExchange = createExchange(Optional.of("DELETE"));
 | 
							postForm("_method=DELETE").block(Duration.ZERO);
 | 
				
			||||||
 | 
							assertEquals(HttpMethod.DELETE, this.filterChain.getHttpMethod());
 | 
				
			||||||
		WebFilterChain filterChain = exchange -> {
 | 
					 | 
				
			||||||
			assertEquals("Invalid method", HttpMethod.DELETE, exchange.getRequest().getMethod());
 | 
					 | 
				
			||||||
			return Mono.empty();
 | 
					 | 
				
			||||||
		};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		StepVerifier.create(filter.filter(mockExchange, filterChain))
 | 
					 | 
				
			||||||
				.expectComplete()
 | 
					 | 
				
			||||||
				.verify();
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@Test
 | 
						@Test
 | 
				
			||||||
	public void filterWithInvalidParameter() {
 | 
						public void filterWithNoParameter() {
 | 
				
			||||||
		ServerWebExchange mockExchange = createExchange(Optional.of("INVALID"));
 | 
							postForm("").block(Duration.ZERO);
 | 
				
			||||||
 | 
							assertEquals(HttpMethod.POST, this.filterChain.getHttpMethod());
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		WebFilterChain filterChain = exchange -> Mono.empty();
 | 
						@Test
 | 
				
			||||||
 | 
						public void filterWithEmptyStringParameter() {
 | 
				
			||||||
 | 
							postForm("_method=").block(Duration.ZERO);
 | 
				
			||||||
 | 
							assertEquals(HttpMethod.POST, this.filterChain.getHttpMethod());
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		StepVerifier.create(filter.filter(mockExchange, filterChain))
 | 
						@Test
 | 
				
			||||||
 | 
						public void filterWithDifferentMethodParam() {
 | 
				
			||||||
 | 
							this.filter.setMethodParamName("_foo");
 | 
				
			||||||
 | 
							postForm("_foo=DELETE").block(Duration.ZERO);
 | 
				
			||||||
 | 
							assertEquals(HttpMethod.DELETE, this.filterChain.getHttpMethod());
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@Test
 | 
				
			||||||
 | 
						public void filterWithInvalidMethodValue() {
 | 
				
			||||||
 | 
							StepVerifier.create(postForm("_method=INVALID"))
 | 
				
			||||||
				.consumeErrorWith(error -> {
 | 
									.consumeErrorWith(error -> {
 | 
				
			||||||
					assertThat(error, Matchers.instanceOf(IllegalArgumentException.class));
 | 
										assertThat(error, Matchers.instanceOf(IllegalArgumentException.class));
 | 
				
			||||||
					assertEquals(error.getMessage(), "HttpMethod 'INVALID' is not supported");
 | 
										assertEquals(error.getMessage(), "HttpMethod 'INVALID' not supported");
 | 
				
			||||||
				})
 | 
									})
 | 
				
			||||||
				.verify();
 | 
									.verify();
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@Test
 | 
						@Test
 | 
				
			||||||
	public void filterWithNoParameter() {
 | 
						public void filterWithHttpPut() {
 | 
				
			||||||
		ServerWebExchange mockExchange = createExchange(Optional.empty());
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
		WebFilterChain filterChain = exchange -> {
 | 
							ServerWebExchange exchange = MockServerHttpRequest.put("/")
 | 
				
			||||||
			assertEquals("Invalid method", HttpMethod.POST, exchange.getRequest().getMethod());
 | 
									.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE)
 | 
				
			||||||
 | 
									.body("_method=DELETE")
 | 
				
			||||||
 | 
									.toExchange();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							this.filter.filter(exchange, this.filterChain).block(Duration.ZERO);
 | 
				
			||||||
 | 
							assertEquals(HttpMethod.PUT, this.filterChain.getHttpMethod());
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private Mono<Void> postForm(String body) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							MockServerWebExchange exchange = MockServerHttpRequest.post("/")
 | 
				
			||||||
 | 
									.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE)
 | 
				
			||||||
 | 
									.body(body)
 | 
				
			||||||
 | 
									.toExchange();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							return this.filter.filter(exchange, this.filterChain);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private static class TestWebFilterChain implements WebFilterChain {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							private HttpMethod httpMethod;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							public HttpMethod getHttpMethod() {
 | 
				
			||||||
 | 
								return this.httpMethod;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							@Override
 | 
				
			||||||
 | 
							public Mono<Void> filter(ServerWebExchange exchange) {
 | 
				
			||||||
 | 
								this.httpMethod = exchange.getRequest().getMethod();
 | 
				
			||||||
			return Mono.empty();
 | 
								return Mono.empty();
 | 
				
			||||||
		};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		StepVerifier.create(filter.filter(mockExchange, filterChain))
 | 
					 | 
				
			||||||
				.expectComplete()
 | 
					 | 
				
			||||||
				.verify();
 | 
					 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					 | 
				
			||||||
	@Test
 | 
					 | 
				
			||||||
	public void filterWithEmptyStringParameter() {
 | 
					 | 
				
			||||||
		ServerWebExchange mockExchange = createExchange(Optional.of(""));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		WebFilterChain filterChain = exchange -> {
 | 
					 | 
				
			||||||
			assertEquals("Invalid method", HttpMethod.POST, exchange.getRequest().getMethod());
 | 
					 | 
				
			||||||
			return Mono.empty();
 | 
					 | 
				
			||||||
		};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		StepVerifier.create(filter.filter(mockExchange, filterChain))
 | 
					 | 
				
			||||||
				.expectComplete()
 | 
					 | 
				
			||||||
				.verify();
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	@Test
 | 
					 | 
				
			||||||
	public void filterWithDifferentMethodParam() {
 | 
					 | 
				
			||||||
		ServerWebExchange mockExchange = createExchange("_foo", Optional.of("DELETE"));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		WebFilterChain filterChain = exchange -> {
 | 
					 | 
				
			||||||
			assertEquals("Invalid method", HttpMethod.DELETE, exchange.getRequest().getMethod());
 | 
					 | 
				
			||||||
			return Mono.empty();
 | 
					 | 
				
			||||||
		};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		filter.setMethodParam("_foo");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		StepVerifier.create(filter.filter(mockExchange, filterChain))
 | 
					 | 
				
			||||||
				.expectComplete()
 | 
					 | 
				
			||||||
				.verify();
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	@Test
 | 
					 | 
				
			||||||
	public void filterWithoutPost() {
 | 
					 | 
				
			||||||
		ServerWebExchange mockExchange = createExchange(Optional.of("DELETE")).mutate()
 | 
					 | 
				
			||||||
				.request(builder -> builder.method(HttpMethod.PUT))
 | 
					 | 
				
			||||||
				.build();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		WebFilterChain filterChain = exchange -> {
 | 
					 | 
				
			||||||
			assertEquals("Invalid method", HttpMethod.PUT, exchange.getRequest().getMethod());
 | 
					 | 
				
			||||||
			return Mono.empty();
 | 
					 | 
				
			||||||
		};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		StepVerifier.create(filter.filter(mockExchange, filterChain))
 | 
					 | 
				
			||||||
				.expectComplete()
 | 
					 | 
				
			||||||
				.verify();
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	private ServerWebExchange createExchange(Optional<String> optionalMethod) {
 | 
					 | 
				
			||||||
		return createExchange("_method", optionalMethod);
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	private ServerWebExchange createExchange(String methodName, Optional<String> optionalBody) {
 | 
					 | 
				
			||||||
		MockServerHttpRequest.BodyBuilder builder = MockServerHttpRequest
 | 
					 | 
				
			||||||
				.post("/hotels")
 | 
					 | 
				
			||||||
				.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		MockServerHttpRequest request = optionalBody
 | 
					 | 
				
			||||||
				.map(method -> builder.body(methodName + "=" + method))
 | 
					 | 
				
			||||||
				.orElse(builder.build());
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		return request.toExchange();
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue