Skip to content

Commit 124d4c8

Browse files
committed
Support for Servlet request params with HTTP DELETE
This commit adds FormContentFilter, which is the same as the HttpPutFormContentFilter but also supports DELETE. The HttpPutFormContentFilter is now deprecated. Issue: SPR-16874
1 parent 7396309 commit 124d4c8

File tree

6 files changed

+418
-227
lines changed

6 files changed

+418
-227
lines changed

spring-test/src/test/java/org/springframework/test/web/servlet/samples/spr/FormContentTests.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,10 @@
2323
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
2424
import org.springframework.web.bind.annotation.PutMapping;
2525
import org.springframework.web.bind.annotation.RestController;
26-
import org.springframework.web.filter.HttpPutFormContentFilter;
26+
import org.springframework.web.filter.FormContentFilter;
2727

28-
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put;
29-
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
28+
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
29+
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
3030

3131
/**
3232
* Test for issues related to form content.
@@ -39,7 +39,7 @@ public class FormContentTests {
3939
public void formContentIsNotDuplicated() throws Exception {
4040

4141
MockMvc mockMvc = MockMvcBuilders.standaloneSetup(new Spr15753Controller())
42-
.addFilter(new HttpPutFormContentFilter())
42+
.addFilter(new FormContentFilter())
4343
.build();
4444

4545
mockMvc.perform(put("/").content("d1=a&d2=s").contentType(MediaType.APPLICATION_FORM_URLENCODED))
Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
/*
2+
* Copyright 2002-2018 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.web.filter;
18+
19+
import java.io.IOException;
20+
import java.io.InputStream;
21+
import java.nio.charset.Charset;
22+
import java.util.ArrayList;
23+
import java.util.Arrays;
24+
import java.util.Collections;
25+
import java.util.Enumeration;
26+
import java.util.LinkedHashMap;
27+
import java.util.LinkedHashSet;
28+
import java.util.List;
29+
import java.util.Map;
30+
import java.util.Set;
31+
import javax.servlet.FilterChain;
32+
import javax.servlet.ServletException;
33+
import javax.servlet.http.HttpServletRequest;
34+
import javax.servlet.http.HttpServletRequestWrapper;
35+
import javax.servlet.http.HttpServletResponse;
36+
37+
import org.springframework.http.HttpInputMessage;
38+
import org.springframework.http.MediaType;
39+
import org.springframework.http.converter.FormHttpMessageConverter;
40+
import org.springframework.http.converter.support.AllEncompassingFormHttpMessageConverter;
41+
import org.springframework.http.server.ServletServerHttpRequest;
42+
import org.springframework.lang.Nullable;
43+
import org.springframework.util.Assert;
44+
import org.springframework.util.MultiValueMap;
45+
import org.springframework.util.StringUtils;
46+
47+
/**
48+
* {@code Filter} that parses form data for HTTP PUT, PATCH, and DELETE requests
49+
* and exposes it as Servlet request parameters. By default the Servlet spec
50+
* only requires this for HTTP POST.
51+
*
52+
* @author Rossen Stoyanchev
53+
* @since 5.1
54+
*/
55+
public class FormContentFilter extends OncePerRequestFilter {
56+
57+
private static final List<String> HTTP_METHODS = Arrays.asList("PUT", "PATCH", "DELETE");
58+
59+
60+
private FormHttpMessageConverter formConverter = new AllEncompassingFormHttpMessageConverter();
61+
62+
63+
/**
64+
* Set the converter to use for parsing form content.
65+
* <p>By default this is an instance of {@link AllEncompassingFormHttpMessageConverter}.
66+
*/
67+
public void setFormConverter(FormHttpMessageConverter converter) {
68+
Assert.notNull(converter, "FormHttpMessageConverter is required.");
69+
this.formConverter = converter;
70+
}
71+
72+
public FormHttpMessageConverter getFormConverter() {
73+
return this.formConverter;
74+
}
75+
76+
/**
77+
* The default character set to use for reading form data.
78+
* This is a shortcut for:<br>
79+
* {@code getFormConverter.setCharset(charset)}.
80+
*/
81+
public void setCharset(Charset charset) {
82+
this.formConverter.setCharset(charset);
83+
}
84+
85+
86+
@Override
87+
protected void doFilterInternal(final HttpServletRequest request, HttpServletResponse response,
88+
FilterChain filterChain) throws ServletException, IOException {
89+
90+
MultiValueMap<String, String> params = parseIfNecessary(request);
91+
92+
if (params != null && !params.isEmpty()) {
93+
filterChain.doFilter(new FormContentRequestWrapper(request, params), response);
94+
}
95+
else {
96+
filterChain.doFilter(request, response);
97+
}
98+
}
99+
100+
@Nullable
101+
private MultiValueMap<String, String> parseIfNecessary(HttpServletRequest request) throws IOException {
102+
103+
if (!shouldParse(request)) {
104+
return null;
105+
}
106+
107+
HttpInputMessage inputMessage = new ServletServerHttpRequest(request) {
108+
109+
@Override
110+
public InputStream getBody() throws IOException {
111+
return request.getInputStream();
112+
}
113+
};
114+
115+
return this.formConverter.read(null, inputMessage);
116+
}
117+
118+
private boolean shouldParse(HttpServletRequest request) {
119+
if (!HTTP_METHODS.contains(request.getMethod())) {
120+
return false;
121+
}
122+
try {
123+
MediaType mediaType = MediaType.parseMediaType(request.getContentType());
124+
return MediaType.APPLICATION_FORM_URLENCODED.includes(mediaType);
125+
}
126+
catch (IllegalArgumentException ex) {
127+
return false;
128+
}
129+
}
130+
131+
132+
private static class FormContentRequestWrapper extends HttpServletRequestWrapper {
133+
134+
private MultiValueMap<String, String> formParams;
135+
136+
public FormContentRequestWrapper(HttpServletRequest request, MultiValueMap<String, String> params) {
137+
super(request);
138+
this.formParams = params;
139+
}
140+
141+
@Override
142+
@Nullable
143+
public String getParameter(String name) {
144+
String queryStringValue = super.getParameter(name);
145+
String formValue = this.formParams.getFirst(name);
146+
return (queryStringValue != null ? queryStringValue : formValue);
147+
}
148+
149+
@Override
150+
public Map<String, String[]> getParameterMap() {
151+
Map<String, String[]> result = new LinkedHashMap<>();
152+
Enumeration<String> names = getParameterNames();
153+
while (names.hasMoreElements()) {
154+
String name = names.nextElement();
155+
result.put(name, getParameterValues(name));
156+
}
157+
return result;
158+
}
159+
160+
@Override
161+
public Enumeration<String> getParameterNames() {
162+
Set<String> names = new LinkedHashSet<>();
163+
names.addAll(Collections.list(super.getParameterNames()));
164+
names.addAll(this.formParams.keySet());
165+
return Collections.enumeration(names);
166+
}
167+
168+
@Override
169+
@Nullable
170+
public String[] getParameterValues(String name) {
171+
String[] parameterValues = super.getParameterValues(name);
172+
List<String> formParam = this.formParams.get(name);
173+
if (formParam == null) {
174+
return parameterValues;
175+
}
176+
if (parameterValues == null || getQueryString() == null) {
177+
return StringUtils.toStringArray(formParam);
178+
}
179+
else {
180+
List<String> result = new ArrayList<>(parameterValues.length + formParam.size());
181+
result.addAll(Arrays.asList(parameterValues));
182+
result.addAll(formParam);
183+
return StringUtils.toStringArray(result);
184+
}
185+
}
186+
}
187+
188+
}

spring-web/src/main/java/org/springframework/web/filter/HttpPutFormContentFilter.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,15 +58,18 @@
5858
*
5959
* @author Rossen Stoyanchev
6060
* @since 3.1
61+
* @deprecated as of 5.1 in favor of {@link FormContentFilter} which is the same
62+
* but also handles DELETE.
6163
*/
64+
@Deprecated
6265
public class HttpPutFormContentFilter extends OncePerRequestFilter {
6366

6467
private FormHttpMessageConverter formConverter = new AllEncompassingFormHttpMessageConverter();
6568

6669

6770
/**
6871
* Set the converter to use for parsing form content.
69-
* <p>By default this is an instnace of {@link AllEncompassingFormHttpMessageConverter}.
72+
* <p>By default this is an instance of {@link AllEncompassingFormHttpMessageConverter}.
7073
*/
7174
public void setFormConverter(FormHttpMessageConverter converter) {
7275
Assert.notNull(converter, "FormHttpMessageConverter is required.");

0 commit comments

Comments
 (0)