基于zuul实现自定义路由源码分析
ZuulFilter定义
通过继承ZuulFilter
我们可以定义一个新的过滤器,如下
public class IpAddressFilter extends ZuulFilter {
@Autowired
private IGatewayService iGatewayService;
@Override
public String filterType() {
// pre类型的过滤器
return PRE_TYPE;
}
@Override
public int filterOrder() {
// 排序
return 1;
}
@Override
public boolean shouldFilter() {
return true;
}
@Override
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
String ip = ctx.getRequest().getRemoteAddr();
Set<String> blackList = ConcurrentCache.getBlackSet();
Set<String> whiteList = ConcurrentCache.getWhiteSet();
blackList.removeAll(whiteList);
// 在黑名单中禁用
if (StringUtils.isNotBlank(ip)&& blackList.contains(ip)) {
ctx.setSendZuulResponse(false);
ctx.setResponseBody("Suspected flooding attack, IP blocked");
ctx.setResponseStatusCode(HttpStatus.FORBIDDEN.value());
ctx.addZuulResponseHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_UTF8_VALUE);
return null;
}
return null;
}
}
ZuulFilter
中实现了compareTo()
方法,根据它的值决定同类型的filter的执行顺序。compareTo()
方法如下:
public int compareTo(ZuulFilter filter) {
return Integer.compare(this.filterOrder(), filter.filterOrder());
}
注册ZuulFilter到spring容器中
ZuulFilter
可以通过@Component
,也可以通过@Bean
实例化来纳入spring的生命周期中。
@Configuration
public class FilterConfig {
@Bean
public IpAddressFilter addIpAddressFilter() {
return new IpAddressFilter();
}
}
ZuulServerAutoConfiguration
中自动装配了filter
,被spring实例化出来的所有的ZuulFilter
都会被自动装配到Map中。
@Configuration
protected static class ZuulFilterConfiguration {
// 根据类型,自动装配ZuulFilter到Map对象中
@Autowired
private Map<String, ZuulFilter> filters;
@Bean
public ZuulFilterInitializer zuulFilterInitializer(
CounterFactory counterFactory, TracerFactory tracerFactory) {
FilterLoader filterLoader = FilterLoader.getInstance();
// 单例模式
FilterRegistry filterRegistry = FilterRegistry.instance();
return new ZuulFilterInitializer(this.filters, counterFactory, tracerFactory, filterLoader, filterRegistry);
}
}
上面的代码会调用ZuulFilterInitializer
的构造方法。
ZuulFilterInitializer
中的contextInitialized()
开启了@PostConstruct
注解,在构造方法完成时,容器会调用contextInitialized()
方法(注意:ZuulFilterInitializer对象要由spring管理才会调用到@PostConstruct),将所有的filter保存到filterRegistry
中,filterRegistry
是一个单例对象。
说明:PostConstruct 注释用于在依赖关系注入完成之后需要执行的方法上
contextInitialized()
方法如下:
@PostConstruct
public void contextInitialized() {
log.info("Starting filter initializer");
TracerFactory.initialize(tracerFactory);
CounterFactory.initialize(counterFactory);
for (Map.Entry<String, ZuulFilter> entry : this.filters.entrySet()) {
// 保存filter
filterRegistry.put(entry.getKey(), entry.getValue());
}
}
自定义路由转发规则
ZuulProxyAutoConfiguration
类中注册了RouteLocator
的bean
,@Bean
会按照类型,自动注入RouteLocator的实现类。
@Bean
public PreDecorationFilter preDecorationFilter(RouteLocator routeLocator,
ProxyRequestHelper proxyRequestHelper) {
return new PreDecorationFilter(routeLocator, this.server.getServletPrefix(),
this.zuulProperties, proxyRequestHelper);
}
RouteLocator
实例化
@Configuration
public class AppConfig{
//.....省略....
@Bean(value = "discoveryRouteLocator")
public DiscoveryClientRouteLocator discoveryClientRouteLocator(ServerProperties server, DiscoveryClient discovery, ZuulProperties properties,ServiceInstance localInstance) {
return new CustomRouteLocator(server.getServletPath(), discovery,properties,localInstance);
}
}
CustomRouteLocator
实现自定义路由的功能,类如下。
public class CustomRouteLocator extends DiscoveryClientRouteLocator {
// ....省略....
@Override
// 重写
public Route getMatchingRoute(String path) {
// ....省略....
//可以从数据库中读取路由规则,并进行处理
}
// 重写
@Override
protected LinkedHashMap<String, ZuulRoute> locateRoutes() {
// ....省略....
}
}
Servlet初始化
为什么通过访问网关可以自动跳转到zuul中,其实是通过servlet的实现的,该servlet对根路径/
进行过滤。下面说明servlet的初始化内容。
ZuulServerAutoConfiguration
类中定义了ZuulController
@Bean
public ZuulController zuulController() {
return new ZuulController();
}
ZuulController
继承了ServletWrappingController
类
public class ZuulController extends ServletWrappingController {
public ZuulController() {
// 设置类为ZuulServlet
setServletClass(ZuulServlet.class);
setServletName("zuul");
setSupportedMethods((String[]) null);
}
@Override
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
try {
return super.handleRequestInternal(request, response);
}
finally {
RequestContext.getCurrentContext().unset();
}
}
}
ServletWrappingController
对ZuulServlet
进行实例化
@Override
public void afterPropertiesSet() throws Exception {
if (this.servletClass == null) {
throw new IllegalArgumentException("'servletClass' is required");
}
if (this.servletName == null) {
this.servletName = this.beanName;
}
// 实例化
this.servletInstance = this.servletClass.newInstance();
// 调用servlet的init方法
this.servletInstance.init(new DelegatingServletConfig());
}
当访问一个url的时候,服务请求会跳转到ZuulController
中,执行handleRequest()
方法。
@Override
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
try {
// 调用父类的handleRequestInternal方法
return super.handleRequestInternal(request, response);
}
finally {
RequestContext.getCurrentContext().unset();
}
}
handleRequestInternal()
方法如下:
protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response)
throws Exception {
this.servletInstance.service(request, response);
return null;
}
servletInstance
即ZuulServlet
的实例,上面的方法最终调用ZuulServlet
中的service()
方法。
service()
方法如下:
@Override
public void service(javax.servlet.ServletRequest servletRequest, javax.servlet.ServletResponse servletResponse) throws ServletException, IOException {
try {
init((HttpServletRequest) servletRequest, (HttpServletResponse) servletResponse);
RequestContext context = RequestContext.getCurrentContext();
context.setZuulEngineRan();
try {
// pre过滤器
preRoute();
} catch (ZuulException e) {
error(e);
postRoute();
return;
}
try {
// route过滤器
route();
} catch (ZuulException e) {
error(e);
postRoute();
return;
}
try {
// post过滤器
postRoute();
} catch (ZuulException e) {
error(e);
return;
}
} catch (Throwable e) {
error(new ZuulException(e, 500, "UNHANDLED_EXCEPTION_" + e.getClass().getName()));
} finally {
RequestContext.getCurrentContext().unset();
}
}
根据上面的源码我们知道,一个请求到来的时候,就要经历preRoute、route、postRoute几个阶段,用官方的图来说明
网关请求执行的过程
根据第上面的内容,我们知道,当通过网关对服务进行请求的时候,要经历preRoute,route、postRoute阶段,这里以以preRoute()
方法为例,对路由的处理过程进行说明。
preRoute()
方法如下:
void preRoute() throws ZuulException {
zuulRunner.preRoute();
}
ZuulRunner
中的preRoute()
方法如下:
public void preRoute() throws ZuulException {
FilterProcessor.getInstance().preRoute();
}
FilterProcessor
是一个单例模式,FilterProcessor
中的preRoute()
方法如下:
public void preRoute() throws ZuulException {
try {
// 运行pre过滤器
runFilters("pre");
} catch (ZuulException e) {
throw e;
} catch (Throwable e) {
throw new ZuulException(e, 500, "UNCAUGHT_EXCEPTION_IN_PRE_FILTER_" + e.getClass().getName());
}
}
执行过滤器,runFilters()
方法如下:
public Object runFilters(String sType) throws Throwable {
if (RequestContext.getCurrentContext().debugRouting()) {
Debug.addRoutingDebug("Invoking {" + sType + "} type filters");
}
boolean bResult = false;
// 根据过滤器类型,获取过滤器列表。
List<ZuulFilter> list = FilterLoader.getInstance().getFiltersByType(sType);
if (list != null) {
// 依次调用过滤器
for (int i = 0; i < list.size(); i++) {
ZuulFilter zuulFilter = list.get(i);
// 过滤器处理过程
Object result = processZuulFilter(zuulFilter);
if (result != null && result instanceof Boolean) {
bResult |= ((Boolean) result);
}
}
}
return bResult;
}
根据类型获取过滤器列表,getFiltersByType()
方法如下:
public List<ZuulFilter> getFiltersByType(String filterType) {
List<ZuulFilter> list = hashFiltersByType.get(filterType);
if (list != null) return list;
list = new ArrayList<ZuulFilter>();
// 获取所有的过滤器
Collection<ZuulFilter> filters = filterRegistry.getAllFilters();
for (Iterator<ZuulFilter> iterator = filters.iterator(); iterator.hasNext(); ) {
ZuulFilter filter = iterator.next();
// 取得filterType的类型列表
if (filter.filterType().equals(filterType)) {
list.add(filter);
}
}
// 对filter进行排序
Collections.sort(list); // sort by priority
// 保存列表
hashFiltersByType.putIfAbsent(filterType, list);
return list;
}
FilterRegistry
类是一个单例模式,getAllFilters()
方法如下
public class FilterRegistry {
private static final FilterRegistry INSTANCE = new FilterRegistry();
// ....省略....
public Collection<ZuulFilter> getAllFilters() {
return this.filters.values();
}
}
过滤器的处理方法processZuulFilter()
如下:
public Object processZuulFilter(ZuulFilter filter) throws ZuulException {
RequestContext ctx = RequestContext.getCurrentContext();
boolean bDebug = ctx.debugRouting();
final String metricPrefix = "zuul.filter-";
long execTime = 0;
String filterName = "";
try {
long ltime = System.currentTimeMillis();
filterName = filter.getClass().getSimpleName();
// ....省略....
// 运行filter
ZuulFilterResult result = filter.runFilter();
ExecutionStatus s = result.getStatus();
execTime = System.currentTimeMillis() - ltime;
// .....省略....
usageNotifier.notify(filter, s);
return o;
} catch (Throwable e) {
// .....省略.....
}
}
runFilter()
方法如下,:
public ZuulFilterResult runFilter() {
ZuulFilterResult zr = new ZuulFilterResult();
if (!isFilterDisabled()) {
// 判断过滤器是否需要执行
if (shouldFilter()) {
Tracer t = TracerFactory.instance().startMicroTracer("ZUUL::" + this.getClass().getSimpleName());
try {
// 调用filter的run方法。
Object res = run();
zr = new ZuulFilterResult(res, ExecutionStatus.SUCCESS);
} catch (Throwable e) {
// ....省略....
} finally {
t.stopAndLog();
}
} else {
zr = new ZuulFilterResult(ExecutionStatus.SKIPPED);
}
}
return zr;
}
最终调用到各ZuulFilter
中的run()
方法。
路由查找
pre
类型的PreDecorationFilter
过滤器,用来进行路由规则的匹配
如下:
执行后,上下文内容中的内容如下,加入了requestURI
访问服务
根据下图可以知道,真正访问服务的是route阶段。如下:
对于正常的服务,比如:/xxx/service_name
是通过RibbonRoutingFilter
实现对服务的负载均衡访问,它的run()
方法如下:
public Object run() {
RequestContext context = RequestContext.getCurrentContext();
this.helper.addIgnoredHeaders();
try {
RibbonCommandContext commandContext = buildCommandContext(context);
ClientHttpResponse response = forward(commandContext);
setResponse(response);
return response;
}
catch (ZuulException ex) {
throw new ZuulRuntimeException(ex);
}
catch (Exception ex) {
throw new ZuulRuntimeException(ex);
}
}
如果是固定的url链接,如:http://www.abc.com/xxx/service_name
这种,则是通过SendForwardFilter
过滤器实现转发。它的run()
方法如下:
public Object run() {
try {
RequestContext ctx = RequestContext.getCurrentContext();
String path = (String) ctx.get(FORWARD_TO_KEY);
RequestDispatcher dispatcher = ctx.getRequest().getRequestDispatcher(path);
if (dispatcher != null) {
ctx.set(SEND_FORWARD_FILTER_RAN, true);
if (!ctx.getResponse().isCommitted()) {
// url转发
dispatcher.forward(ctx.getRequest(), ctx.getResponse());
ctx.getResponse().flushBuffer();
}
}
}
catch (Exception ex) {
ReflectionUtils.rethrowRuntimeException(ex);
}
return null;
}