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 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 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排序后依次执行。