There is a gateway module implemented by Netflix Zuul in the system, which is responsible for unified authentication and then transfers requests to the corresponding back-end module. After basic configuration, you only need to implement a Filter.

@slf4j @Component Public Class AccessTokenFilter extends ZuulFilter { @override public String filterType() {return "pre"; } @override public int filterOrder() {return 7; } @Override public Object run() { RequestContext requestContext = RequestContext.getCurrentContext(); HttpServletRequest request = requestContext.getRequest(); HttpServletResponse response = requestContext.getResponse(); String token = CookieUtils.getCookieValue("token", request); log.info("token={}", token); token = URLDecoder.decode(token, "UTF-8"); // validateToken Boolean valid = validateToken(token); If (! valid){ setFalseZuulResponse(requestContext); } return null; } /** * no routing, Direct response. * / private void setFalseZuulResponse (RequestContext RequestContext) {RequestContext. SetSendZuulResponse (false); requestContext.setResponseBody("error"); }}Copy the code

Everything was fine until one day something went wrong.

The environment

Spring Boot version:

<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> < version > 1.4.2. RELEASE < / version > < relativePath / > <! -- lookup parent from repository --> </parent>Copy the code

Spring Cloud Version:

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-dependencies</artifactId>
    <version>Brixton.SR5</version>
    <type>pom</type>
    <scope>import</scope>
</dependency>
Copy the code

The problem background

One day, you add an interface with a JSON string in the URL and discover that the request cannot reach the back end when accessing this interface. The gateway module threw an URISyntaxException.

Under Caused by: java.net.URISyntaxException: Illegal character in the query at index 65: http://10.201.169.146:8091/api/... /? param=%7B"a":"","b":"","c":""%7D at java.net.URI$Parser.fail(URI.java:2848) at java.net.URI$Parser.checkChars(URI.java:3021) at java.net.URI$Parser.parseHierarchical(URI.java:3111) at java.net.URI$Parser.parse(URI.java:3053) at java.net.URI.<init>(URI.java:588) at com.sun.jersey.api.uri.UriBuilderImpl.createURI(UriBuilderImpl.java:721)Copy the code

Then Google found out that this problem was also encountered by others. It said that this version of Zuul uses the Ribbon Client by default, so you can change it to HTTP Client.

@Bean public RibbonCommandFactory<? > ribbonCommandFactory( final SpringClientFactory clientFactory) { return new HttpClientRibbonCommandFactory(clientFactory); }Copy the code

This did solve the problem, but a new problem arose: java.io.IOException: Incomplete Parts occurred after the previous Multipart/form-data POST request was forwarded to the back-end server.

2018-10-09 19:04:22.591  WARN 12137 --- [qtp289592183-19] o.e.jetty.server.handler.ErrorHandler    : EXCEPTION 

org.springframework.web.util.NestedServletException: Request processing failed; nested exception is org.springframework.web.multipart.MultipartException: Could not parse multipart servlet request; nested exception is java.io.IOException: Incomplete parts
	at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:982)
	at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:872)
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:707)
	at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:846)
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:790)
	at org.eclipse.jetty.servlet.ServletHolder.handle(ServletHolder.java:845)
	at org.eclipse.jetty.servlet.ServletHandler.doHandle(ServletHandler.java:584)
	at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:143)
	at org.eclipse.jetty.security.SecurityHandler.handle(SecurityHandler.java:566)
	at org.eclipse.jetty.server.session.SessionHandler.doHandle(SessionHandler.java:226)
	at org.eclipse.jetty.server.handler.ContextHandler.doHandle(ContextHandler.java:1180)
	at org.eclipse.jetty.servlet.ServletHandler.doScope(ServletHandler.java:512)
	at org.eclipse.jetty.server.session.SessionHandler.doScope(SessionHandler.java:185)
	at org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:1112)
	at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:141)
	at org.eclipse.jetty.server.Dispatcher.forward(Dispatcher.java:199)
	at org.eclipse.jetty.server.Dispatcher.error(Dispatcher.java:79)
	at org.eclipse.jetty.server.handler.ErrorHandler.handle(ErrorHandler.java:94)
	at org.springframework.boot.context.embedded.jetty.JettyEmbeddedErrorHandler.handle(JettyEmbeddedErrorHandler.java:55)
	at org.eclipse.jetty.server.Response.sendError(Response.java:558)
	at org.eclipse.jetty.server.Response.sendError(Response.java:497)
	at org.eclipse.jetty.servlet.ServletHandler.doHandle(ServletHandler.java:651)
	at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:143)
	at org.eclipse.jetty.security.SecurityHandler.handle(SecurityHandler.java:548)
	at org.eclipse.jetty.server.session.SessionHandler.doHandle(SessionHandler.java:226)
	at org.eclipse.jetty.server.handler.ContextHandler.doHandle(ContextHandler.java:1180)
	at org.eclipse.jetty.servlet.ServletHandler.doScope(ServletHandler.java:512)
	at org.eclipse.jetty.server.session.SessionHandler.doScope(SessionHandler.java:185)
	at org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:1112)
	at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:141)
	at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:134)
	at org.eclipse.jetty.server.Server.handle(Server.java:534)
	at org.eclipse.jetty.server.HttpChannel.handle(HttpChannel.java:320)
	at org.eclipse.jetty.server.HttpConnection.onFillable(HttpConnection.java:251)
	at org.eclipse.jetty.io.AbstractConnection$ReadCallback.succeeded(AbstractConnection.java:273)
	at org.eclipse.jetty.io.FillInterest.fillable(FillInterest.java:95)
	at org.eclipse.jetty.io.SelectChannelEndPoint$2.run(SelectChannelEndPoint.java:93)
	at org.eclipse.jetty.util.thread.strategy.ExecuteProduceConsume.executeProduceConsume(ExecuteProduceConsume.java:303)
	at org.eclipse.jetty.util.thread.strategy.ExecuteProduceConsume.produceConsume(ExecuteProduceConsume.java:148)
	at org.eclipse.jetty.util.thread.strategy.ExecuteProduceConsume.run(ExecuteProduceConsume.java:136)
	at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:671)
	at org.eclipse.jetty.util.thread.QueuedThreadPool$2.run(QueuedThreadPool.java:589)
	at java.lang.Thread.run(Thread.java:745)
Caused by: org.springframework.web.multipart.MultipartException: Could not parse multipart servlet request; nested exception is java.io.IOException: Incomplete parts
	at org.springframework.web.multipart.support.StandardMultipartHttpServletRequest.parseRequest(StandardMultipartHttpServletRequest.java:111)
	at org.springframework.web.multipart.support.StandardMultipartHttpServletRequest.<init>(StandardMultipartHttpServletRequest.java:85)
	at org.springframework.web.multipart.support.StandardServletMultipartResolver.resolveMultipart(StandardServletMultipartResolver.java:76)
	at org.springframework.web.servlet.DispatcherServlet.checkMultipart(DispatcherServlet.java:1099)
	at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:932)
	at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:897)
	at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:970)
	... 42 common frames omitted
Caused by: java.io.IOException: Incomplete parts
	at org.eclipse.jetty.util.MultiPartInputStreamParser.parse(MultiPartInputStreamParser.java:781)
	at org.eclipse.jetty.util.MultiPartInputStreamParser.getParts(MultiPartInputStreamParser.java:422)
	at org.eclipse.jetty.server.Request.getParts(Request.java:2317)
	at org.eclipse.jetty.server.Request.extractMultipartParameters(Request.java:519)
	at org.eclipse.jetty.server.Request.extractContentParameters(Request.java:441)
	at org.eclipse.jetty.server.Request.getParameters(Request.java:365)
	at org.eclipse.jetty.server.Request.getParameter(Request.java:996)
	at org.springframework.web.filter.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:70)
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
	at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1699)
	at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:197)
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
	at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1699)
	at org.eclipse.jetty.servlet.ServletHandler.doHandle(ServletHandler.java:582)
	... 21 common frames omitted
Copy the code

The location of the exception is org. Eclipse. Jetty. Util. MultiPartInputStreamParser# parse.

Ultimately failed to locate the root cause of the problem. But the question is pretty clear: Use the default RibbonCommandFactory (namely RestClientRibbonCommandFactory) can handle multipart/request form, but is unable to process the URL contains a JSON, Whereas if you use HttpClientRibbonCommandFactory can handle a url contains JSON, but unable to forward the correct multipart request.


The problem is in route forwarding. Later, I thought of another way: modify the route forwarding logic to specify different RibbonCommandFactory according to the type of request?

The solution

Disable the default RibbonRoutingFilter.

zuul.RibbonRoutingFilter.route.disable: true
Copy the code

Then extend RibbonRoutingFilter to modify the default forwarding logic.

@Slf4j public class MyRibbonRoutingFilter extends RibbonRoutingFilter { @Autowired private RestClientRibbonCommandFactory restClientRibbonCommandFactory; @Autowired private HttpClientRibbonCommandFactory httpClientRibbonCommandFactory; public MyRibbonRoutingFilter(ProxyRequestHelper helper, RibbonCommandFactory<? > ribbonCommandFactory) { super(helper, ribbonCommandFactory); } public MyRibbonRoutingFilter(RibbonCommandFactory<? > ribbonCommandFactory) { super(ribbonCommandFactory); } protected ClientHttpResponse forward(RibbonCommandContext context) throws Exception { log.info("-------MyRibbonRoutingFilter forward--------"); Map<String, Object> info = this.helper.debug(context.getVerb(), context.getUri(), context.getHeaders(), context.getParams(), context.getRequestEntity()); RibbonCommandFactory rcf = this.restClientRibbonCommandFactory; if (! isMultipartForm()) { log.info("Not multipart/form request use HttpClientRibbonCommandFactory to handle url with json"); rcf = httpClientRibbonCommandFactory; } else { log.info("Multipart/form request use default"); } log.info("RibbonCommandFactory is " + rcf.getClass().getCanonicalName()); RibbonCommand command = rcf.create(context); try { ClientHttpResponse response = command.execute(); this.helper.appendDebug(info, response.getStatusCode().value(), response.getHeaders()); return response; } catch (HystrixRuntimeException ex) { return handleException(info, ex); } } private static boolean isMultipartForm() { RequestContext ctx = RequestContext.getCurrentContext(); HttpServletRequest request = ctx.getRequest(); String contentType = request.getContentType(); if (contentType == null) { return false; } try { MediaType mediaType = MediaType.valueOf(contentType); return MediaType.MULTIPART_FORM_DATA.includes(mediaType); } catch (InvalidMediaTypeException ex) { return false; }}}Copy the code

Of course, the two RibbonCommandFactory beans need to be configured.

@Configuration public class RibbonCommandFactoryConfig { @Bean public HttpClientRibbonCommandFactory ribbonCommandFactory(final SpringClientFactory clientFactory) { return new HttpClientRibbonCommandFactory(clientFactory); } @Bean public RestClientRibbonCommandFactory ribbonCommandFactory2(final SpringClientFactory clientFactory) { return new RestClientRibbonCommandFactory(clientFactory); }}Copy the code

With that out of the way, you can see that Zuul scales well.

reading

Github.com/Netflix/zuu…


The original address