001/* 002 * Copyright 2002-2018 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * https://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.web.filter.reactive; 018 019import java.util.Arrays; 020import java.util.Collections; 021import java.util.List; 022import java.util.Locale; 023 024import reactor.core.publisher.Mono; 025 026import org.springframework.http.HttpMethod; 027import org.springframework.http.server.reactive.ServerHttpRequest; 028import org.springframework.util.Assert; 029import org.springframework.util.StringUtils; 030import org.springframework.web.server.ServerWebExchange; 031import org.springframework.web.server.WebFilter; 032import org.springframework.web.server.WebFilterChain; 033 034/** 035 * Reactive {@link WebFilter} that converts posted method parameters into HTTP methods, 036 * retrievable via {@link ServerHttpRequest#getMethod()}. Since browsers currently only 037 * support GET and POST, a common technique is to use a normal POST with an additional 038 * hidden form field ({@code _method}) to pass the "real" HTTP method along. 039 * This filter reads that parameter and changes the {@link ServerHttpRequest#getMethod()} 040 * return value using {@link ServerWebExchange#mutate()}. 041 * 042 * <p>The name of the request parameter defaults to {@code _method}, but can be 043 * adapted via the {@link #setMethodParamName(String) methodParamName} property. 044 * 045 * @author Greg Turnquist 046 * @author Rossen Stoyanchev 047 * @since 5.0 048 */ 049public class HiddenHttpMethodFilter implements WebFilter { 050 051 private static final List<HttpMethod> ALLOWED_METHODS = 052 Collections.unmodifiableList(Arrays.asList(HttpMethod.PUT, 053 HttpMethod.DELETE, HttpMethod.PATCH)); 054 055 /** Default name of the form parameter with the HTTP method to use. */ 056 public static final String DEFAULT_METHOD_PARAMETER_NAME = "_method"; 057 058 059 private String methodParamName = DEFAULT_METHOD_PARAMETER_NAME; 060 061 062 /** 063 * Set the name of the form parameter with the HTTP method to use. 064 * <p>By default this is set to {@code "_method"}. 065 */ 066 public void setMethodParamName(String methodParamName) { 067 Assert.hasText(methodParamName, "'methodParamName' must not be empty"); 068 this.methodParamName = methodParamName; 069 } 070 071 072 /** 073 * Transform an HTTP POST into another method based on {@code methodParamName}. 074 * @param exchange the current server exchange 075 * @param chain provides a way to delegate to the next filter 076 * @return {@code Mono<Void>} to indicate when request processing is complete 077 */ 078 @Override 079 public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) { 080 081 if (exchange.getRequest().getMethod() != HttpMethod.POST) { 082 return chain.filter(exchange); 083 } 084 085 return exchange.getFormData() 086 .map(formData -> { 087 String method = formData.getFirst(this.methodParamName); 088 return StringUtils.hasLength(method) ? mapExchange(exchange, method) : exchange; 089 }) 090 .flatMap(chain::filter); 091 } 092 093 private ServerWebExchange mapExchange(ServerWebExchange exchange, String methodParamValue) { 094 HttpMethod httpMethod = HttpMethod.resolve(methodParamValue.toUpperCase(Locale.ENGLISH)); 095 Assert.notNull(httpMethod, () -> "HttpMethod '" + methodParamValue + "' not supported"); 096 if (ALLOWED_METHODS.contains(httpMethod)) { 097 return exchange.mutate().request(builder -> builder.method(httpMethod)).build(); 098 } 099 else { 100 return exchange; 101 } 102 } 103 104}