SpringCloud学习笔记(三)-网关Zuul

661 阅读5分钟

Zuul在SpringCloud中起到网关的作用,可用于请求路由转发、过滤、安全检查等作用,通过@EnableZuulProxy来开启网关的配置。

一、Zuul初始化

/**
 * 路由网关
 */
@EnableZuulProxy
@EnableEurekaClient
@SpringBootApplication(exclude={DataSourceAutoConfiguration.class})
@EnableSwagger2
public class RsmsZuulApplication {

	public static void main(String[] args) {
		SpringApplication.run(RsmsZuulApplication.class, args);
	}
}

该注解用于设置Zuul服务器断点,及配置一系列的前置或后置过滤器。可以看到在该注解中引入了ZuulProxyMarkerConfiguration类。

/**
 * Sets up a Zuul server endpoint and installs some reverse proxy filters in it, so it can
 * forward requests to backend servers. The backends can be registered manually through
 * configuration or via DiscoveryClient.
 *
 * @see EnableZuulServer for how to get a Zuul server without any proxying
 *
 * @author Spencer Gibb
 * @author Dave Syer
 * @author Biju Kunjummen
 */
@EnableCircuitBreaker
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(ZuulProxyMarkerConfiguration.class)
public @interface EnableZuulProxy {
}

继续跟踪ZuulProxyMarkerConfiguration类,通过注释可以看到,这个类的作用主要是通过配置的Marker(Bean)来激活ZuulProxyAutoConfiguration类,ZuulProxyAutoConfiguration是用于zuul自动装配的类,springcloud中大量使用这种装配方式,在系统启动时完成自动装配。

/**
 * Responsible for adding in a marker bean to trigger activation of 
 * {@link ZuulProxyAutoConfiguration}
 *
 * @author Biju Kunjummen
 */

@Configuration
public class ZuulProxyMarkerConfiguration {
	@Bean
	public Marker zuulProxyMarkerBean() {
		return new Marker();
	}
	class Marker {
	}
}

ZuulProxyAutoConfiguration配置如下 RibbonCommandFactoryConfiguration:用户处理负载均衡相关

@Configuration
@Import({ RibbonCommandFactoryConfiguration.RestClientRibbonConfiguration.class,
		RibbonCommandFactoryConfiguration.OkHttpRibbonConfiguration.class,
		RibbonCommandFactoryConfiguration.HttpClientRibbonConfiguration.class,
		HttpClientConfiguration.class })
@ConditionalOnBean(ZuulProxyMarkerConfiguration.Marker.class)
public class ZuulProxyAutoConfiguration extends ZuulServerAutoConfiguration {
...
}

ZuulProxyAutoConfiguration继承ZuulServerAutoConfiguration,ZuulServerAutoConfiguration定义了一系列的过滤器、控制器、监听器等。 其中比较重要的有zuulServlet。 1:初始化ZuulServlet ZuulServlet类似用SpringMVC的请求转发器DispatcherServlet,当有Http请求到来时,ZuulController会交给ZuulServlet处理。ZuulServlet在根据其所管理的Filter,按执行顺序执行Filter。zuulservlet的具体执行过程后续会单独讲解。

@Bean
    @ConditionalOnMissingBean(name = "zuulServlet")
    public ServletRegistrationBean zuulServlet() {
        ServletRegistrationBean<ZuulServlet> servlet = new ServletRegistrationBean<>(new ZuulServlet(),
                this.zuulProperties.getServletPattern());
        // The whole point of exposing this servlet is to provide a route that doesn't
        // buffer requests.
        servlet.addInitParameter("buffer-requests", "false");
        return servlet;
    }

2:路由定位器 定义了多路定位器和简单定位器。

@Bean
@Primary
public CompositeRouteLocator primaryRouteLocator(
			Collection<RouteLocator> routeLocators) {
		return new CompositeRouteLocator(routeLocators);
}

@Bean
@ConditionalOnMissingBean(SimpleRouteLocator.class)
public SimpleRouteLocator simpleRouteLocator() {
		return new SimpleRouteLocator(this.server.getServletPrefix(),
				this.zuulProperties);
}

SimpleRouteLocator定义路由获取的具体过程,如对前缀StripPrefix的处理、重试的配置、路由的刷新、路由规则的匹配等都是在这个类中完成。

protected Route getSimpleMatchingRoute(final String path) {
		if (log.isDebugEnabled()) {
			log.debug("Finding route for path: " + path);
		}

		// This is called for the initialization done in getRoutesMap()
		getRoutesMap();

		if (log.isDebugEnabled()) {
			log.debug("servletPath=" + this.dispatcherServletPath);
			log.debug("zuulServletPath=" + this.zuulServletPath);
			log.debug("RequestUtils.isDispatcherServletRequest()="
					+ RequestUtils.isDispatcherServletRequest());
			log.debug("RequestUtils.isZuulServletRequest()="
					+ RequestUtils.isZuulServletRequest());
		}

                //预处理URL路径
		String adjustedPath = adjustPath(path);
                //根据路径获取匹配的路由
		ZuulRoute route = getZuulRoute(adjustedPath);

		return getRoute(route, adjustedPath);
}

protected Route getRoute(ZuulRoute route, String path) {
		if (route == null) {
			return null;
		}
		if (log.isDebugEnabled()) {
			log.debug("route matched=" + route);
		}
		String targetPath = path;
		String prefix = this.properties.getPrefix();
                //处理前缀
		if (path.startsWith(prefix) && this.properties.isStripPrefix()) {
			targetPath = path.substring(prefix.length());
		}
		if (route.isStripPrefix()) {
			int index = route.getPath().indexOf("*") - 1;
			if (index > 0) {
				String routePrefix = route.getPath().substring(0, index);
				targetPath = targetPath.replaceFirst(routePrefix, "");
				prefix = prefix + routePrefix;
			}
		}
                //是否重试
		Boolean retryable = this.properties.getRetryable();
		if (route.getRetryable() != null) {
			retryable = route.getRetryable();
		}
		return new Route(route.getId(), targetPath, route.getLocation(), prefix,
				retryable,
				route.isCustomSensitiveHeaders() ? route.getSensitiveHeaders() : null, 
				route.isStripPrefix());
	}

3:控制器 Http请求被控制器拦截后交给ZuulServlet处理。

@Bean
public ZuulController zuulController() {
		return new ZuulController();
}

执行顺序越小会越先执行,所有pre过滤器执行完成,才会执行route过滤器,同样所有的post过滤器都会再route过滤器之后执行。(zuulFileter的compareTo方法会比较Order大小,执行过程中,从注册的过滤器列表中获取到相关类型的过滤器,根据优先级从小到大排序。)

大概整理了下zuul自带的一些过滤器

过滤器类型 名称 执行顺序 功能
pre ServletDetectionFilter -3 判断请求头来源是zuulServlet还是DispatcherServlet,决定后续的处理机制
pre FormBodyWrapperFilter -1 解析表单数据重新包装,提供给下游服务使用
pre DebugFilter 1 是否开启调试标志
pre Servlet30WrapperFilter -2 包装为符合servlet3.0规范的请求体
pre PreDecorationFilter 5 路由转发前基本信息配置
post SendResponseFilter 1000 处理正常的请求响应
error SendErrorFilter 0 错误请求响应,早起版本是post的一种,新版本单独列为一种
post LocationRewriteFilter 900 重写位置头信息为zuul url
route SendForwardFilter 500 处理内部转发
route RibbonRoutingFilter 10 使用Ribbon, Hystrix转发请求
route SimpleHostRoutingFilter 100 普通请求转发

二、ZuulServlet执行过程

前面说过ZuulServlet是Http请求的处理入口,类似用SpringMVC的请求转发器DispatcherServlet,当有Http请求到来时,会先交给ZuulServlet。 ZuulServlet继承HttpServlet,所以大概过程也是初始化-调用service()-销毁。 从service方法可以看出,按pre过滤器-route过滤器-postRoute顺序执行,如果出现错误会调用filterType为error的过滤器处理。 前置过滤器出现错误,会调用error过滤器处理后再交给post过滤器处理。 route过滤器出现错误,也是会先调用error过滤器处理再交给post过滤器处理。 而当post过滤器出现错误,直接调用error过滤器处理,结束流程。

public class ZuulServlet extends HttpServlet {

    private static final long serialVersionUID = -3374242278843351500L;
    private ZuulRunner zuulRunner;


    @Override
    public void init(ServletConfig config) throws ServletException {
        super.init(config);

        String bufferReqsStr = config.getInitParameter("buffer-requests");
        boolean bufferReqs = bufferReqsStr != null && bufferReqsStr.equals("true") ? true : false;

        zuulRunner = new ZuulRunner(bufferReqs);
    }

    @Override
    public void service(javax.servlet.ServletRequest servletRequest, javax.servlet.ServletResponse servletResponse) throws ServletException, IOException {
        try {
            init((HttpServletRequest) servletRequest, (HttpServletResponse) servletResponse);

            // Marks this request as having passed through the "Zuul engine", as opposed to servlets
            // explicitly bound in web.xml, for which requests will not have the same data attached
            RequestContext context = RequestContext.getCurrentContext();
            context.setZuulEngineRan();

            try {
                preRoute();
            } catch (ZuulException e) {
                error(e);
                postRoute();
                return;
            }
            try {
                route();
            } catch (ZuulException e) {
                error(e);
                postRoute();
                return;
            }
            try {
                postRoute();
            } catch (ZuulException e) {
                error(e);
                return;
            }

        } catch (Throwable e) {
            error(new ZuulException(e, 500, "UNHANDLED_EXCEPTION_" + e.getClass().getName()));
        } finally {
            RequestContext.getCurrentContext().unset();
        }
    }
...
}

所有过滤器的执行都是通过FilterProcessor来完成,这是过滤器执行的核心类,FilterProcessor是一个单例对象,会根据传入的过滤器类型,获取相关过滤器,根据order排序后依次执行。