Convert form-encoded request to byte[] for proxy
With just a bit more hackery on the Zuul request wrapper we can mask off the input stream and content lengths, and fix them so they contain the expected content. Doesn't work (yet) for multipart content. Fixes gh-109
This commit is contained in:
@@ -4,8 +4,8 @@ require 'erb'
|
||||
options = {:mkdirs => true, :safe => :unsafe, :attributes => 'linkcss'}
|
||||
|
||||
guard 'shell' do
|
||||
watch(/^src\/[A-Za-z].*\.adoc$/) {|m|
|
||||
Asciidoctor.load_file('src/main/asciidoc/README.adoc', :to_file => './README.adoc', safe: :safe, parse: false, attributes: 'allow-uri-read')
|
||||
Asciidoctor.render_file('src/main/asciidoc/spring-cloud-netflix.adoc', options.merge(:to_dir => 'target/generated-docs'))
|
||||
watch(/^docs\/[A-Za-z].*\.adoc$/) {|m|
|
||||
Asciidoctor.load_file('docs/src/main/asciidoc/README.adoc', :to_file => './README.adoc', safe: :safe, parse: false, attributes: 'allow-uri-read')
|
||||
Asciidoctor.render_file('docs/src/main/asciidoc/spring-cloud-netflix.adoc', options.merge(:to_dir => 'target/generated-docs'))
|
||||
}
|
||||
end
|
||||
|
||||
@@ -9,6 +9,7 @@ import org.springframework.cloud.context.scope.refresh.RefreshScopeRefreshedEven
|
||||
import org.springframework.cloud.netflix.zuul.filters.post.SendErrorFilter;
|
||||
import org.springframework.cloud.netflix.zuul.filters.post.SendResponseFilter;
|
||||
import org.springframework.cloud.netflix.zuul.filters.pre.DebugFilter;
|
||||
import org.springframework.cloud.netflix.zuul.filters.pre.FormBodyWrapperFilter;
|
||||
import org.springframework.cloud.netflix.zuul.filters.pre.Servlet30WrapperFilter;
|
||||
import org.springframework.context.ApplicationEvent;
|
||||
import org.springframework.context.ApplicationListener;
|
||||
@@ -80,6 +81,11 @@ public class ZuulConfiguration {
|
||||
}
|
||||
|
||||
// pre filters
|
||||
@Bean
|
||||
public FormBodyWrapperFilter formBodyWrapperFilter() {
|
||||
return new FormBodyWrapperFilter();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public DebugFilter debugFilter() {
|
||||
return new DebugFilter();
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
package org.springframework.cloud.netflix.zuul.filters.pre;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import javax.servlet.ServletInputStream;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.apache.commons.io.Charsets;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
import com.google.common.base.Throwables;
|
||||
import com.netflix.zuul.ZuulFilter;
|
||||
import com.netflix.zuul.context.RequestContext;
|
||||
import com.netflix.zuul.http.HttpServletRequestWrapper;
|
||||
import com.netflix.zuul.http.ServletInputStreamWrapper;
|
||||
|
||||
/**
|
||||
* @author Spencer Gibb
|
||||
*/
|
||||
public class FormBodyWrapperFilter extends ZuulFilter {
|
||||
protected Field requestField = null;
|
||||
|
||||
public FormBodyWrapperFilter() {
|
||||
requestField = ReflectionUtils.findField(HttpServletRequestWrapper.class, "req",
|
||||
HttpServletRequest.class);
|
||||
Assert.notNull(requestField, "HttpServletRequestWrapper.req field not found");
|
||||
requestField.setAccessible(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String filterType() {
|
||||
return "pre";
|
||||
}
|
||||
|
||||
@Override
|
||||
public int filterOrder() {
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldFilter() {
|
||||
RequestContext ctx = RequestContext.getCurrentContext();
|
||||
HttpServletRequest request = ctx.getRequest();
|
||||
if (MediaType.APPLICATION_FORM_URLENCODED_VALUE.equals(request.getContentType())) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object run() {
|
||||
RequestContext ctx = RequestContext.getCurrentContext();
|
||||
HttpServletRequest request = ctx.getRequest();
|
||||
if (request instanceof HttpServletRequestWrapper) {
|
||||
try {
|
||||
HttpServletRequest wrapped = (HttpServletRequest) requestField.get(request);
|
||||
requestField.set(request, new FormBodyRequestWrapper(wrapped));
|
||||
}
|
||||
catch (IllegalAccessException e) {
|
||||
Throwables.propagate(e);
|
||||
}
|
||||
}
|
||||
else {
|
||||
ctx.setRequest(new FormBodyRequestWrapper(request));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private class FormBodyRequestWrapper extends HttpServletRequestWrapper {
|
||||
|
||||
private HttpServletRequest request;
|
||||
private byte[] contentData;
|
||||
|
||||
public FormBodyRequestWrapper(HttpServletRequest request) {
|
||||
super(request);
|
||||
this.request = request;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getContentLength() {
|
||||
if (contentData == null) {
|
||||
contentData = buildContentData();
|
||||
}
|
||||
return contentData.length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ServletInputStream getInputStream() throws IOException {
|
||||
if (RequestContext.getCurrentContext().isChunkedRequestBody()) {
|
||||
return request.getInputStream();
|
||||
}
|
||||
else {
|
||||
if (contentData == null) {
|
||||
contentData = buildContentData();
|
||||
}
|
||||
return new ServletInputStreamWrapper(contentData);
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] buildContentData() {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
for (Entry<String, String[]> entry : request.getParameterMap().entrySet()) {
|
||||
for (String value : entry.getValue()) {
|
||||
if (builder.length() != 0) {
|
||||
builder.append("&");
|
||||
}
|
||||
builder.append(entry.getKey()).append("=").append(value);
|
||||
}
|
||||
}
|
||||
return builder.toString().getBytes(Charsets.UTF_8);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -59,7 +59,7 @@ public class Servlet30WrapperFilter extends ZuulFilter {
|
||||
return null;
|
||||
}
|
||||
|
||||
class Servlet30RequestWrapper extends HttpServletRequestWrapper {
|
||||
private class Servlet30RequestWrapper extends HttpServletRequestWrapper {
|
||||
private HttpServletRequest request;
|
||||
|
||||
Servlet30RequestWrapper(HttpServletRequest request) {
|
||||
|
||||
@@ -0,0 +1,127 @@
|
||||
package org.springframework.cloud.netflix.zuul;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.boot.test.IntegrationTest;
|
||||
import org.springframework.boot.test.SpringApplicationConfiguration;
|
||||
import org.springframework.boot.test.TestRestTemplate;
|
||||
import org.springframework.cloud.netflix.ribbon.RibbonClient;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.http.HttpEntity;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
import org.springframework.test.context.web.WebAppConfiguration;
|
||||
import org.springframework.util.LinkedMultiValueMap;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import com.netflix.appinfo.EurekaInstanceConfig;
|
||||
import com.netflix.loadbalancer.BaseLoadBalancer;
|
||||
import com.netflix.loadbalancer.ILoadBalancer;
|
||||
import com.netflix.loadbalancer.Server;
|
||||
import com.netflix.zuul.ZuulFilter;
|
||||
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@SpringApplicationConfiguration(classes = FormZuulProxyApplication.class)
|
||||
@WebAppConfiguration
|
||||
@IntegrationTest({ "server.port: 0",
|
||||
"zuul.routes.simple: /simple/**" })
|
||||
@DirtiesContext
|
||||
public class FormZuulProxyApplicationTests {
|
||||
|
||||
@Value("${local.server.port}")
|
||||
private int port;
|
||||
|
||||
@Autowired
|
||||
private ProxyRouteLocator routes;
|
||||
|
||||
@Autowired
|
||||
private RoutesEndpoint endpoint;
|
||||
|
||||
@Test
|
||||
public void postWithForm() {
|
||||
MultiValueMap<String, String> form = new LinkedMultiValueMap<String, String>();
|
||||
form.set("foo", "bar");
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
|
||||
ResponseEntity<String> result = new TestRestTemplate().exchange(
|
||||
"http://localhost:" + port + "/simple", HttpMethod.POST,
|
||||
new HttpEntity<MultiValueMap<String,String>>(form, headers), String.class);
|
||||
assertEquals(HttpStatus.OK, result.getStatusCode());
|
||||
assertEquals("Posted! {foo=[bar]}", result.getBody());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//Don't use @SpringBootApplication because we don't want to component scan
|
||||
@Configuration
|
||||
@EnableAutoConfiguration
|
||||
@RestController
|
||||
@EnableZuulProxy
|
||||
@RibbonClient(name = "simple", configuration = FormRibbonClientConfiguration.class)
|
||||
class FormZuulProxyApplication {
|
||||
|
||||
@RequestMapping(value = "/", method = RequestMethod.POST)
|
||||
public String delete(@RequestBody MultiValueMap<String, String> form) {
|
||||
return "Posted! " + form;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ZuulFilter sampleFilter() {
|
||||
return new ZuulFilter() {
|
||||
@Override
|
||||
public String filterType() {
|
||||
return "pre";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldFilter() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object run() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int filterOrder() {
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(SampleZuulProxyApplication.class, args);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//Load balancer with fixed server list for "simple" pointing to localhost
|
||||
@Configuration
|
||||
class FormRibbonClientConfiguration {
|
||||
@Bean
|
||||
public ILoadBalancer ribbonLoadBalancer(EurekaInstanceConfig instance) {
|
||||
BaseLoadBalancer balancer = new BaseLoadBalancer();
|
||||
balancer.setServersList(Arrays.asList(new Server("localhost", instance
|
||||
.getNonSecurePort())));
|
||||
return balancer;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user