1、起源java
在mocksever中引入了spring cloud zuul作代理轉發,若是請求的url和配置的mock規則匹配(精確匹配和模糊匹配),忽略,不作轉發。若是mock配置的url和請求url是徹底匹配的,沒有問題。例如,請求url:http://localhost:8080/mock/test/query/12345web
mock配置:正則表達式
url | response |
/mock/test/query/12435 | {"ret_code":"A00000"} |
可是若是mock的配置url包含正則表達式:/mock/test/query/(\d.*),ZuulHandlerMapping類中的isIgnoredPath返回false,由於該方法調用的是AntPathMatcher的match方法,而AntPathMatcher的匹配規則是通配符匹配,沒法識別正則表達式。spring
AntPathMatcher基本規則:express
一、? 匹配一個字符(除過操做系統默認的文件分隔符)
二、* 匹配0個或多個字符
三、**匹配0個或多個目錄
四、{spring:[a-z]+} 將正則表達式[a-z]+匹配到的值,賦值給名爲 spring 的路徑變量。apache
2、方案:app
一、重寫isIgnoredPath方法 ---------沒法實現,isIgnoredPath是私有方法。cors
二、重寫match方法 -----------能夠實現,math是接口PathMatcher的方法。less
三:具體實現ide
ZuulHandlerMapping源碼:
/* * Copyright 2013-2017 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.web; import java.util.Collection; import javax.servlet.http.HttpServletRequest; import org.springframework.boot.autoconfigure.web.ErrorController; import org.springframework.cloud.netflix.zuul.filters.RefreshableRouteLocator; import org.springframework.cloud.netflix.zuul.filters.Route; import org.springframework.cloud.netflix.zuul.filters.RouteLocator; import org.springframework.util.AntPathMatcher; import org.springframework.util.PathMatcher; import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.servlet.HandlerExecutionChain; import org.springframework.web.servlet.handler.AbstractUrlHandlerMapping; import com.netflix.zuul.context.RequestContext; /** * MVC HandlerMapping that maps incoming request paths to remote services. * * @author Spencer Gibb * @author Dave Syer * @author João Salavessa * @author Biju Kunjummen */ public class ZuulHandlerMapping extends AbstractUrlHandlerMapping { private final RouteLocator routeLocator; private final ZuulController zuul; private ErrorController errorController; private PathMatcher pathMatcher = new AntPathMatcher(); private volatile boolean dirty = true; public ZuulHandlerMapping(RouteLocator routeLocator, ZuulController zuul) { this.routeLocator = routeLocator; this.zuul = zuul; setOrder(-200); } @Override protected HandlerExecutionChain getCorsHandlerExecutionChain(HttpServletRequest request, HandlerExecutionChain chain, CorsConfiguration config) { if (config == null) { // Allow CORS requests to go to the backend return chain; } return super.getCorsHandlerExecutionChain(request, chain, config); } public void setErrorController(ErrorController errorController) { this.errorController = errorController; } public void setDirty(boolean dirty) { this.dirty = dirty; if (this.routeLocator instanceof RefreshableRouteLocator) { ((RefreshableRouteLocator) this.routeLocator).refresh(); } } @Override protected Object lookupHandler(String urlPath, HttpServletRequest request) throws Exception { if (this.errorController != null && urlPath.equals(this.errorController.getErrorPath())) { return null; } if (isIgnoredPath(urlPath, this.routeLocator.getIgnoredPaths())) return null; RequestContext ctx = RequestContext.getCurrentContext(); if (ctx.containsKey("forward.to")) { return null; } if (this.dirty) { synchronized (this) { if (this.dirty) { registerHandlers(); this.dirty = false; } } } return super.lookupHandler(urlPath, request); } private boolean isIgnoredPath(String urlPath, Collection<String> ignored) { if (ignored != null) { for (String ignoredPath : ignored) { //pathMatcher 是私有變量,並且被初始化爲AntPathMatcher對象 if (this.pathMatcher.match(ignoredPath, urlPath)) { return true; } } } return false; } private void registerHandlers() { Collection<Route> routes = this.routeLocator.getRoutes(); if (routes.isEmpty()) { this.logger.warn("No routes found from RouteLocator"); } else { for (Route route : routes) { registerHandler(route.getFullPath(), this.zuul); } } } }
pathMatcher 是私有變量,並且被初始化爲AntPathMatcher對象,假設咱們寫一個接口PathMatcher的實現類,重寫match方法,可是沒有辦法在isIgnoredPath中被調用到。
想到的是在ZuulHandlerMapping注入到容器的時候,把私有屬性pathMatcher初始化成咱們定義的新的實現類。
第一步:
修改注入ZuulHandlerMapping的配置類ZuulServerAutoConfiguration、ZuulProxyAutoConfiguration,新建2個類,copy上面2個類的代碼,從新命名,例如命名爲:
CusZuulServerAutoConfiguration、CusZuulProxyAutoConfiguration。把依賴的類也copy源碼新建一個類,放到同一個包下,最後的目錄結構以下:
圈出來的三個類都是須要的依賴類
第二步:
新建接口PathMatcher的實現類,重寫math方法:
import org.springframework.util.AntPathMatcher; import org.springframework.util.Assert; import org.springframework.util.PathMatcher; import org.springframework.util.StringUtils; import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.regex.Matcher; import java.util.regex.Pattern; public class RePathMatcher extends AntPathMatcher implements PathMatcher { @Override public boolean match(String pattern, String path) { if(super.match(pattern,path )){ return true; } else { pattern = pattern.replaceAll("\\*\\*","(.*)" ); Pattern rePattern = Pattern.compile(pattern); Matcher matcher = rePattern.matcher(path); if (matcher.matches()) { return true; } return false; } } }
說明:先調用父類AntPathMatcher中的match方法,若是匹配到了就返回true,若是沒有匹配到,就走正則,這裏面須要先把pattern中的**替換成正則表達式,由於在配置的轉發規則都是下面這樣的形式:
path | host |
/tech/** | http://tech.com |
若是不替換,**沒法被識別會拋異常。替換後就是正常的正則匹配了。
第三步:
使用反射修改ZuulHandlerMapping中的pathMatcher屬性。
修改咱們新建的配置類CusZuulServerAutoConfiguration中ZuulHandlerMapping注入的代碼:
@Bean public ZuulHandlerMapping zuulHandlerMapping(RouteLocator routes) { ZuulHandlerMapping mapping = new ZuulHandlerMapping(routes, zuulController()); try { Class<?> clazz = Class.forName("org.springframework.cloud.netflix.zuul.web.ZuulHandlerMapping"); Field[] fs = clazz.getDeclaredFields(); for (Field field : fs) { field.setAccessible(true); if(field.getName().equals("pathMatcher")){ field.set(mapping, new RePathMatcher()); } } } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } mapping.setErrorController(this.errorController); return mapping; }
第四步:在SpringbootApplication中屏蔽ZuulProxyAutoConfiguration
@SpringBootApplication(exclude = ZuulProxyAutoConfiguration.class)
另外兩個自定義的配置類須要修改:
PS:目前想到的方式就是這樣,若是你們有好的方式,歡迎留言。