zoukankan      html  css  js  c++  java
  • 修改ZuulHandlerMapping私有变量pathMatcher,重写match方法,使url匹配兼容正则表达式。

    一、起源

    在mocksever中引入了spring cloud zuul做代理转发,如果请求的url和配置的mock规则匹配(精确匹配和模糊匹配),忽略,不做转发。如果mock配置的url和请求url是完全匹配的,没有问题。例如,请求url:http://localhost:8080/mock/test/query/12345

    mock配置:

    url response
    /mock/test/query/12435 {"ret_code":"A00000"}

    但是如果mock的配置url包含正则表达式:/mock/test/query/(d.*),ZuulHandlerMapping类中的isIgnoredPath返回false,因为该方法调用的是AntPathMatcher的match方法,而AntPathMatcher的匹配规则是通配符匹配,无法识别正则表达式。

    AntPathMatcher基本规则:

    1、? 匹配一个字符(除过操作系统默认的文件分隔符)
    2、* 匹配0个或多个字符
    3、**匹配0个或多个目录
    4、{spring:[a-z]+} 将正则表达式[a-z]+匹配到的值,赋值给名为 spring 的路径变量。

    二、方案:

    1、重写isIgnoredPath方法   ---------无法实现,isIgnoredPath是私有方法。

    2、重写match方法  -----------可以实现,math是接口PathMatcher的方法。

    三:具体实现

    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:目前想到的方式就是这样,如果大家有好的方式,欢迎留言。




  • 相关阅读:
    Linux启动或禁止SSH用户及IP的登录,只允许密钥验证登录模式
    emacs 入门教程,菜单汉化,配置文件等杂乱文章
    bzoj3376/poj1988[Usaco2004 Open]Cube Stacking 方块游戏 — 带权并查集
    NOIP复习篇
    HiHocoder 1036 : Trie图 AC自动机
    (皇后移动类)八数码难题引发的搜索思考及总结
    POJ 水题(刷题)进阶
    [TJOI2010] 中位数
    小球和盒子的问题
    [洛谷P2785] 物理1(phsic1)-磁通量
  • 原文地址:https://www.cnblogs.com/Eric-zhao/p/10315720.html
Copyright © 2011-2022 走看看