Fix issue with servlet request wrappers in Zuul
Because of the way that a FormBodyServletRequestWrapper was implemented (extending the Zuul servlet 2.5 wrapper) it could barf at runtime if anyone called its servlet 3.0 methods. The fix for that was to extract our Servlet30RequestWrapper and extend that instead. Also tweaked the DebugFilter a bit so it doesn't try and display the whole payload. Probably speeds up file uploads a bit but the fact that we have to store the whole request body in memory is going to kill us eventually. See gh-254
This commit is contained in:
@@ -226,9 +226,14 @@ public class ProxyRequestHelper {
|
||||
|
||||
private void debugRequestEntity(Map<String, Object> info, InputStream inputStream)
|
||||
throws IOException {
|
||||
if (RequestContext.getCurrentContext().isChunkedRequestBody()) {
|
||||
info.put("body", "<chunked>");
|
||||
return;
|
||||
}
|
||||
String entity = IOUtils.toString(inputStream);
|
||||
if (StringUtils.hasText(entity)) {
|
||||
info.put("body", entity);
|
||||
info.put("body", entity.length() <= 4096 ? entity : entity.substring(0, 4096)
|
||||
+ "<truncated>");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -108,7 +108,7 @@ public class FormBodyWrapperFilter extends ZuulFilter {
|
||||
return null;
|
||||
}
|
||||
|
||||
private class FormBodyRequestWrapper extends HttpServletRequestWrapper {
|
||||
private class FormBodyRequestWrapper extends Servlet30RequestWrapper {
|
||||
|
||||
private HttpServletRequest request;
|
||||
|
||||
@@ -135,6 +135,9 @@ public class FormBodyWrapperFilter extends ZuulFilter {
|
||||
|
||||
@Override
|
||||
public int getContentLength() {
|
||||
if (super.getContentLength() <= 0) {
|
||||
return super.getContentLength();
|
||||
}
|
||||
if (this.contentData == null) {
|
||||
buildContentData();
|
||||
}
|
||||
@@ -143,15 +146,10 @@ public class FormBodyWrapperFilter extends ZuulFilter {
|
||||
|
||||
@Override
|
||||
public ServletInputStream getInputStream() throws IOException {
|
||||
if (RequestContext.getCurrentContext().isChunkedRequestBody()) {
|
||||
return this.request.getInputStream();
|
||||
}
|
||||
else {
|
||||
if (this.contentData == null) {
|
||||
buildContentData();
|
||||
}
|
||||
return new ServletInputStreamWrapper(this.contentData);
|
||||
if (this.contentData == null) {
|
||||
buildContentData();
|
||||
}
|
||||
return new ServletInputStreamWrapper(this.contentData);
|
||||
}
|
||||
|
||||
private synchronized void buildContentData() {
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
/*
|
||||
* Copyright 2013-2015 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.netflix.zuul.filters.pre;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
|
||||
import javax.servlet.AsyncContext;
|
||||
import javax.servlet.DispatcherType;
|
||||
import javax.servlet.ServletContext;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.ServletRequest;
|
||||
import javax.servlet.ServletResponse;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.servlet.http.Part;
|
||||
|
||||
import com.netflix.zuul.http.HttpServletRequestWrapper;
|
||||
|
||||
class Servlet30RequestWrapper extends HttpServletRequestWrapper {
|
||||
private HttpServletRequest request;
|
||||
|
||||
Servlet30RequestWrapper(HttpServletRequest request) {
|
||||
super(request);
|
||||
this.request = request;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean authenticate(HttpServletResponse response) throws IOException,
|
||||
ServletException {
|
||||
return this.request.authenticate(response);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void login(String username, String password) throws ServletException {
|
||||
this.request.login(username, password);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void logout() throws ServletException {
|
||||
this.request.logout();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Part> getParts() throws IOException, IllegalStateException,
|
||||
ServletException {
|
||||
return this.request.getParts();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Part getPart(String name) throws IOException, IllegalStateException,
|
||||
ServletException {
|
||||
return this.request.getPart(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ServletContext getServletContext() {
|
||||
return this.request.getServletContext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public AsyncContext startAsync() {
|
||||
return this.request.startAsync();
|
||||
}
|
||||
|
||||
@Override
|
||||
public AsyncContext startAsync(ServletRequest servletRequest,
|
||||
ServletResponse servletResponse) {
|
||||
return this.request.startAsync(servletRequest, servletResponse);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAsyncStarted() {
|
||||
try {
|
||||
return this.request.isAsyncStarted();
|
||||
}
|
||||
catch (Throwable e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAsyncSupported() {
|
||||
try {
|
||||
return this.request.isAsyncSupported();
|
||||
}
|
||||
catch (Throwable e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public AsyncContext getAsyncContext() {
|
||||
return this.request.getAsyncContext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public DispatcherType getDispatcherType() {
|
||||
return this.request.getDispatcherType();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -16,19 +16,9 @@
|
||||
|
||||
package org.springframework.cloud.netflix.zuul.filters.pre;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.Collection;
|
||||
|
||||
import javax.servlet.AsyncContext;
|
||||
import javax.servlet.DispatcherType;
|
||||
import javax.servlet.ServletContext;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.ServletRequest;
|
||||
import javax.servlet.ServletResponse;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.servlet.http.Part;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
@@ -82,88 +72,4 @@ public class Servlet30WrapperFilter extends ZuulFilter {
|
||||
return null;
|
||||
}
|
||||
|
||||
private class Servlet30RequestWrapper extends HttpServletRequestWrapper {
|
||||
private HttpServletRequest request;
|
||||
|
||||
Servlet30RequestWrapper(HttpServletRequest request) {
|
||||
super(request);
|
||||
this.request = request;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean authenticate(HttpServletResponse response) throws IOException,
|
||||
ServletException {
|
||||
return this.request.authenticate(response);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void login(String username, String password) throws ServletException {
|
||||
this.request.login(username, password);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void logout() throws ServletException {
|
||||
this.request.logout();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Part> getParts() throws IOException, IllegalStateException,
|
||||
ServletException {
|
||||
return this.request.getParts();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Part getPart(String name) throws IOException, IllegalStateException,
|
||||
ServletException {
|
||||
return this.request.getPart(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ServletContext getServletContext() {
|
||||
return this.request.getServletContext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public AsyncContext startAsync() {
|
||||
return this.request.startAsync();
|
||||
}
|
||||
|
||||
@Override
|
||||
public AsyncContext startAsync(ServletRequest servletRequest,
|
||||
ServletResponse servletResponse) {
|
||||
return this.request.startAsync(servletRequest, servletResponse);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAsyncStarted() {
|
||||
try {
|
||||
return this.request.isAsyncStarted();
|
||||
}
|
||||
catch (Throwable e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAsyncSupported() {
|
||||
try {
|
||||
return this.request.isAsyncSupported();
|
||||
}
|
||||
catch (Throwable e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public AsyncContext getAsyncContext() {
|
||||
return this.request.getAsyncContext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public DispatcherType getDispatcherType() {
|
||||
return this.request.getDispatcherType();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -17,13 +17,19 @@
|
||||
package org.springframework.cloud.netflix.zuul;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.actuate.trace.InMemoryTraceRepository;
|
||||
import org.springframework.boot.actuate.trace.TraceRepository;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.boot.builder.SpringApplicationBuilder;
|
||||
import org.springframework.boot.test.IntegrationTest;
|
||||
import org.springframework.boot.test.SpringApplicationConfiguration;
|
||||
import org.springframework.boot.test.TestRestTemplate;
|
||||
@@ -58,7 +64,7 @@ import static org.junit.Assert.assertEquals;
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@SpringApplicationConfiguration(classes = FormZuulProxyApplication.class)
|
||||
@WebAppConfiguration
|
||||
@IntegrationTest({ "server.port: 0", "zuul.routes.simple: /simple/**" })
|
||||
@IntegrationTest({ "server.port:0", "zuul.routes.simple:/simple/**" })
|
||||
@DirtiesContext
|
||||
public class FormZuulProxyApplicationTests {
|
||||
|
||||
@@ -72,7 +78,7 @@ public class FormZuulProxyApplicationTests {
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
|
||||
ResponseEntity<String> result = new TestRestTemplate().exchange(
|
||||
"http://localhost:" + this.port + "/simple", HttpMethod.POST,
|
||||
"http://localhost:" + this.port + "/simple/form", HttpMethod.POST,
|
||||
new HttpEntity<MultiValueMap<String, String>>(form, headers),
|
||||
String.class);
|
||||
assertEquals(HttpStatus.OK, result.getStatusCode());
|
||||
@@ -86,7 +92,7 @@ public class FormZuulProxyApplicationTests {
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setContentType(MediaType.MULTIPART_FORM_DATA);
|
||||
ResponseEntity<String> result = new TestRestTemplate().exchange(
|
||||
"http://localhost:" + this.port + "/simple", HttpMethod.POST,
|
||||
"http://localhost:" + this.port + "/simple/form", HttpMethod.POST,
|
||||
new HttpEntity<MultiValueMap<String, String>>(form, headers),
|
||||
String.class);
|
||||
assertEquals(HttpStatus.OK, result.getStatusCode());
|
||||
@@ -118,7 +124,7 @@ public class FormZuulProxyApplicationTests {
|
||||
headers.setContentType(MediaType
|
||||
.valueOf(MediaType.APPLICATION_FORM_URLENCODED_VALUE + "; charset=UTF-8"));
|
||||
ResponseEntity<String> result = new TestRestTemplate().exchange(
|
||||
"http://localhost:" + this.port + "/simple", HttpMethod.POST,
|
||||
"http://localhost:" + this.port + "/simple/form", HttpMethod.POST,
|
||||
new HttpEntity<MultiValueMap<String, String>>(form, headers),
|
||||
String.class);
|
||||
assertEquals(HttpStatus.OK, result.getStatusCode());
|
||||
@@ -132,9 +138,10 @@ public class FormZuulProxyApplicationTests {
|
||||
@RestController
|
||||
@EnableZuulProxy
|
||||
@RibbonClient(name = "simple", configuration = FormRibbonClientConfiguration.class)
|
||||
@Slf4j
|
||||
class FormZuulProxyApplication {
|
||||
|
||||
@RequestMapping(value = "/", method = RequestMethod.POST)
|
||||
@RequestMapping(value = "/form", method = RequestMethod.POST)
|
||||
public String accept(@RequestParam MultiValueMap<String, String> form)
|
||||
throws IOException {
|
||||
return "Posted! " + form;
|
||||
@@ -144,7 +151,22 @@ class FormZuulProxyApplication {
|
||||
@RequestMapping(value = "/file", method = RequestMethod.POST)
|
||||
public String file(@RequestParam(required = false) MultipartFile file)
|
||||
throws IOException {
|
||||
return "Posted! " + (file == null ? "" : new String(file.getBytes()));
|
||||
byte[] bytes = new byte[0];
|
||||
if (file != null) {
|
||||
if (file.getSize() > 1024) {
|
||||
bytes = new byte[1024];
|
||||
InputStream inputStream = file.getInputStream();
|
||||
inputStream.read(bytes);
|
||||
byte[] buffer = new byte[1024 * 1024 * 10];
|
||||
while (inputStream.read(buffer) >= 0) {
|
||||
log.info("Read more bytes");
|
||||
}
|
||||
}
|
||||
else {
|
||||
bytes = file.getBytes();
|
||||
}
|
||||
}
|
||||
return "Posted! " + new String(bytes);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@@ -174,8 +196,23 @@ class FormZuulProxyApplication {
|
||||
};
|
||||
}
|
||||
|
||||
@Bean
|
||||
public TraceRepository traceRepository() {
|
||||
return new InMemoryTraceRepository() {
|
||||
@Override
|
||||
public void add(Map<String, Object> map) {
|
||||
if (map.containsKey("body")) {
|
||||
map.get("body");
|
||||
}
|
||||
super.add(map);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(FormZuulProxyApplication.class, args);
|
||||
new SpringApplicationBuilder(FormZuulProxyApplication.class).properties(
|
||||
"zuul.routes.simple:/simple/**", "multipart.maxFileSize:4096MB",
|
||||
"multipart.maxRequestSize:4096MB").run(args);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
<html>
|
||||
<body>
|
||||
<form method="POST" enctype="multipart/form-data"
|
||||
action="/simple/file">
|
||||
File to upload: <input type="file" name="file"><br /> Name: <input
|
||||
type="text" name="name"><br /> <br /> <input type="submit"
|
||||
value="Upload"> Press here to upload the file via proxy!
|
||||
</form>
|
||||
<form method="POST" enctype="multipart/form-data"
|
||||
action="/file">
|
||||
File to upload: <input type="file" name="file"><br /> Name: <input
|
||||
type="text" name="name"><br /> <br /> <input type="submit"
|
||||
value="Upload"> Press here to upload the file directly!
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user