Skip to content

Commit cf86ecd

Browse files
committed
Avoid loss of body content in AbstractRequestLoggingFilter
Prior to this commit, the `ContentCachingRequestWrapper` class would cache the response content only if the reponse would be consumed using its InputStream. In case of a Form request, Spring MVC consumes the response using the `getParameter*` Servlet API methods. This causes the cached content to never be written. This commit makes the `ContentCachingResponseWrapper` write the request body to the cache buffer by using the `getParameter*` API, thus avoiding those issues. Issue: SPR-7913
1 parent 5fb6d6d commit cf86ecd

File tree

2 files changed

+116
-1
lines changed

2 files changed

+116
-1
lines changed

spring-web/src/main/java/org/springframework/web/util/ContentCachingRequestWrapper.java

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2014 the original author or authors.
2+
* Copyright 2002-2015 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -20,6 +20,14 @@
2020
import java.io.ByteArrayOutputStream;
2121
import java.io.IOException;
2222
import java.io.InputStreamReader;
23+
import java.io.UnsupportedEncodingException;
24+
import java.net.URLEncoder;
25+
import java.util.Arrays;
26+
import java.util.Enumeration;
27+
import java.util.Iterator;
28+
import java.util.List;
29+
import java.util.Map;
30+
2331
import javax.servlet.ServletInputStream;
2432
import javax.servlet.http.HttpServletRequest;
2533
import javax.servlet.http.HttpServletRequestWrapper;
@@ -36,6 +44,10 @@
3644
*/
3745
public class ContentCachingRequestWrapper extends HttpServletRequestWrapper {
3846

47+
private static final String FORM_CONTENT_TYPE = "application/x-www-form-urlencoded";
48+
49+
private static final String METHOD_POST = "POST";
50+
3951
private final ByteArrayOutputStream cachedContent;
4052

4153
private ServletInputStream inputStream;
@@ -80,9 +92,46 @@ public BufferedReader getReader() throws IOException {
8092
* Return the cached request content as a byte array.
8193
*/
8294
public byte[] getContentAsByteArray() {
95+
if(this.cachedContent.size() == 0 && isFormPost()) {
96+
writeRequestParamsToContent();
97+
}
8398
return this.cachedContent.toByteArray();
8499
}
85100

101+
private boolean isFormPost() {
102+
return (getContentType() != null && getContentType().contains(FORM_CONTENT_TYPE) &&
103+
METHOD_POST.equalsIgnoreCase(getMethod()));
104+
}
105+
106+
private void writeRequestParamsToContent() {
107+
try {
108+
if (this.cachedContent.size() == 0) {
109+
String requestEncoding = getCharacterEncoding();
110+
Map<String, String[]> form = getParameterMap();
111+
for (Iterator<String> nameIterator = form.keySet().iterator(); nameIterator.hasNext(); ) {
112+
String name = nameIterator.next();
113+
List<String> values = Arrays.asList(form.get(name));
114+
for (Iterator<String> valueIterator = values.iterator(); valueIterator.hasNext(); ) {
115+
String value = valueIterator.next();
116+
cachedContent.write(URLEncoder.encode(name, requestEncoding).getBytes());
117+
if (value != null) {
118+
cachedContent.write('=');
119+
cachedContent.write(URLEncoder.encode(value, requestEncoding).getBytes());
120+
if (valueIterator.hasNext()) {
121+
cachedContent.write('&');
122+
}
123+
}
124+
}
125+
if (nameIterator.hasNext()) {
126+
cachedContent.write('&');
127+
}
128+
}
129+
}
130+
}
131+
catch (IOException e) {
132+
throw new RuntimeException(e);
133+
}
134+
}
86135

87136
private class ContentCachingInputStream extends ServletInputStream {
88137

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
/*
2+
* Copyright 2002-2015 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+
package org.springframework.web.util;
17+
18+
import org.junit.Assert;
19+
import org.junit.Before;
20+
import org.junit.Test;
21+
22+
import org.springframework.mock.web.test.MockHttpServletRequest;
23+
import org.springframework.util.FileCopyUtils;
24+
25+
/**
26+
* @author Brian Clozel
27+
*/
28+
public class ContentCachingRequestWrapperTests {
29+
30+
protected static final String FORM_CONTENT_TYPE = "application/x-www-form-urlencoded";
31+
32+
protected static final String CHARSET = "UTF-8";
33+
34+
private MockHttpServletRequest request;
35+
36+
@Before
37+
public void setup() throws Exception {
38+
this.request = new MockHttpServletRequest();
39+
}
40+
41+
@Test
42+
public void cachedContent() throws Exception {
43+
this.request.setMethod("GET");
44+
this.request.setCharacterEncoding(CHARSET);
45+
this.request.setContent("Hello World".getBytes(CHARSET));
46+
47+
ContentCachingRequestWrapper wrapper = new ContentCachingRequestWrapper(this.request);
48+
byte[] response = FileCopyUtils.copyToByteArray(wrapper.getInputStream());
49+
Assert.assertArrayEquals(response, wrapper.getContentAsByteArray());
50+
}
51+
52+
@Test
53+
public void requestParams() throws Exception {
54+
this.request.setMethod("POST");
55+
this.request.setContentType(FORM_CONTENT_TYPE);
56+
this.request.setCharacterEncoding(CHARSET);
57+
this.request.setParameter("first", "value");
58+
this.request.setParameter("second", new String[] {"foo", "bar"});
59+
60+
ContentCachingRequestWrapper wrapper = new ContentCachingRequestWrapper(this.request);
61+
// getting request parameters will consume the request body
62+
Assert.assertFalse(wrapper.getParameterMap().isEmpty());
63+
Assert.assertEquals("first=value&second=foo&second=bar", new String(wrapper.getContentAsByteArray()));
64+
}
65+
66+
}

0 commit comments

Comments
 (0)