修改ZuulHandlerMapping私有變量pathMatcher,重寫match方法,使url匹配兼容正則表達式。

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)

另外兩個自定義的配置類須要修改:

@ConditionalOnBean(ZuulProxyMarkerConfiguration.Marker.class)
//替換成:
@ConditionalOnBean(annotation=EnableZuulProxy.class)
 
@ConditionalOnBean(ZuulServerMarkerConfiguration.Marker.class)
替換成
@ConditionalOnBean(annotation=EnableZuulProxy.class)
 
 

 

PS:目前想到的方式就是這樣,若是你們有好的方式,歡迎留言。

相關文章
相關標籤/搜索