Zuul路由源码
Zuul对于动态路由的实现,默认是从配置文件里面去拿到相关的配置数据的。
比如在 application.yml
里面做如下配置
zuul:
routes:
service1:
path: /api/service1/**
serviceId: service1
github:
path: /github/**
url: https://github.com/
ZuulProperties
@ConfigurationProperties("zuul")
public class ZuulProperties {
public static final List<String> SECURITY_HEADERS = Arrays.asList("Pragma", "Cache-Control", "X-Frame-Options", "X-Content-Type-Options", "X-XSS-Protection", "Expires");
private String prefix = "";
private boolean stripPrefix = true;
private Boolean retryable = false;
private Map<String, ZuulProperties.ZuulRoute> routes = new LinkedHashMap();
private boolean addProxyHeaders = true;
private boolean addHostHeader = false;
private Set<String> ignoredServices = new LinkedHashSet();
private Set<String> ignoredPatterns = new LinkedHashSet();
private Set<String> ignoredHeaders = new LinkedHashSet();
private boolean ignoreSecurityHeaders = true;
private boolean forceOriginalQueryStringEncoding = false;
private String servletPath = "/zuul";
private boolean ignoreLocalService = true;
private ZuulProperties.Host host = new ZuulProperties.Host();
private boolean traceRequestBody = true;
private boolean removeSemicolonContent = true;
private Set<String> sensitiveHeaders = new LinkedHashSet(Arrays.asList("Cookie", "Set-Cookie", "Authorization"));
private boolean sslHostnameValidationEnabled = true;
private ExecutionIsolationStrategy ribbonIsolationStrategy;
private ZuulProperties.HystrixSemaphore semaphore;
private ZuulProperties.HystrixThreadPool threadPool;
......
......
public static class ZuulRoute {
private String id;
private String path;
private String serviceId;
private String url;
private boolean stripPrefix = true;
private Boolean retryable;
private Set<String> sensitiveHeaders = new LinkedHashSet();
private boolean customSensitiveHeaders = false;
......
......
}
}
路由信息配置前置过滤器
public class PreDecorationFilter extends ZuulFilter {
public PreDecorationFilter(RouteLocator routeLocator, String dispatcherServletPath, ZuulProperties properties, ProxyRequestHelper proxyRequestHelper) {
this.routeLocator = routeLocator;
this.properties = properties;
this.urlPathHelper.setRemoveSemicolonContent(properties.isRemoveSemicolonContent());
this.dispatcherServletPath = dispatcherServletPath;
this.proxyRequestHelper = proxyRequestHelper;
}
@Override
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
// 根据request提取requestURI
String requestURI = this.urlPathHelper.getPathWithinApplication(ctx.getRequest());
Route route = this.routeLocator.getMatchingRoute(requestURI);
String location;
String xforwardedfor;
String remoteAddr;
if (route != null) {
location = route.getLocation();
if (location != null) {
ctx.put("requestURI", route.getPath());
ctx.put("proxy", route.getId());
if (!route.isCustomSensitiveHeaders()) {
this.proxyRequestHelper.addIgnoredHeaders((String[])this.properties.getSensitiveHeaders().toArray(new String[0]));
} else {
this.proxyRequestHelper.addIgnoredHeaders((String[])route.getSensitiveHeaders().toArray(new String[0]));
}
if (route.getRetryable() != null) {
ctx.put("retryable", route.getRetryable());
}
if (!location.startsWith("http:") && !location.startsWith("https:")) {
if (location.startsWith("forward:")) {
ctx.set("forward.to", StringUtils.cleanPath(location.substring("forward:".length()) + route.getPath()));
ctx.setRouteHost((URL)null);
return null;
}
// 排除 http: 、 https: 、 forward:
ctx.set("serviceId", location);
ctx.setRouteHost((URL)null);
ctx.addOriginResponseHeader("X-Zuul-ServiceId", location);
} else { // http: 或 https:
ctx.setRouteHost(this.getUrl(location));
ctx.addOriginResponseHeader("X-Zuul-Service", location);
}
if (this.properties.isAddProxyHeaders()) {
this.addProxyHeaders(ctx, route);
xforwardedfor = ctx.getRequest().getHeader("X-Forwarded-For");
remoteAddr = ctx.getRequest().getRemoteAddr();
if (xforwardedfor == null) {
xforwardedfor = remoteAddr;
} else if (!xforwardedfor.contains(remoteAddr)) {
xforwardedfor = xforwardedfor + ", " + remoteAddr;
}
ctx.addZuulRequestHeader("X-Forwarded-For", xforwardedfor);
}
if (this.properties.isAddHostHeader()) {
ctx.addZuulRequestHeader("Host", this.toHostHeader(ctx.getRequest()));
}
}
} else {
log.warn("No route found for uri: " + requestURI);
xforwardedfor = this.dispatcherServletPath;
if (RequestUtils.isZuulServletRequest()) {
log.debug("zuulServletPath=" + this.properties.getServletPath());
location = requestURI.replaceFirst(this.properties.getServletPath(), "");
log.debug("Replaced Zuul servlet path:" + location);
} else {
log.debug("dispatcherServletPath=" + this.dispatcherServletPath);
location = requestURI.replaceFirst(this.dispatcherServletPath, "");
log.debug("Replaced DispatcherServlet servlet path:" + location);
}
if (!location.startsWith("/")) {
location = "/" + location;
}
remoteAddr = xforwardedfor + location;
remoteAddr = remoteAddr.replaceAll("//", "/");
ctx.set("forward.to", remoteAddr);
}
return null;
}
}
############# 分割线 ##################
this.routeLocator.getMatchingRoute(requestURI) 是重点,根据请求的URL获取Route,再根据Route的location
是否http:、https:、forward: 前缀来设置属性。
例如访问
http://location:8080/service1/echo
-> Route(id='service1', fullPath='/service1/echo', path='/echo', location='service1', prefix='/service1')
http://location:8080/github/echo
-> Route(id='github', fullPath='/github/echo', path='/echo', location='https://github.com/', prefix='/github')
路由定位
PreDecorationFilter
中通过 RouteLocator
根据URL获取Route。
动态路由可以通过扩展 RouteLocator
来完成。
public interface RouteLocator {
Collection<String> getIgnoredPaths();
List<Route> getRoutes();
Route getMatchingRoute(String path);
}
### RouteLocator主要能力有: 根据path获取Route 和 获取所有的Route
RefreshableRouteLocator
public interface RefreshableRouteLocator extends RouteLocator {
void refresh();
}
SimpleRouteLocator
简单的路由定位器,路由信息来自 ZuulProperties
属性配置类, locateRoutes()
是定位路由的核心,从 ZuulProperties
中加载路由数据。
protected Map<String, ZuulRoute> locateRoutes() {
// routesMap 存储路由信息
LinkedHashMap<String, ZuulRoute> routesMap = new LinkedHashMap();
Iterator var2 = this.properties.getRoutes().values().iterator();
// 查找路由信息
while(var2.hasNext()) {
ZuulRoute route = (ZuulRoute)var2.next();
routesMap.put(route.getPath(), route);
}
return routesMap;
}
DiscoveryClientRouteLocator
基于DiscoveryClient,路由数据来自 properties
中的静态配置和 DiscoveryClient
从注册中心获取的数据。DiscoveryClientRouteLocator
拥有几个重要能力: 动态添加路由,刷新路由,获取路由信息(用途不大)
public class DiscoveryClientRouteLocator extends SimpleRouteLocator implements RefreshableRouteLocator {
public DiscoveryClientRouteLocator(String servletPath, DiscoveryClient discovery, ZuulProperties properties, ServiceInstance localServiceInstance) {
super(servletPath, properties);
if (properties.isIgnoreLocalService() && localServiceInstance != null) {
String localServiceId = localServiceInstance.getServiceId();
if (!properties.getIgnoredServices().contains(localServiceId)) {
properties.getIgnoredServices().add(localServiceId);
}
}
this.serviceRouteMapper = new SimpleServiceRouteMapper();
this.discovery = discovery;
this.properties = properties;
}
public void addRoute(String path, String location) {
this.properties.getRoutes().put(path, new ZuulRoute(path, location));
this.refresh();
}
public void addRoute(ZuulRoute route) {
this.properties.getRoutes().put(route.getPath(), route);
this.refresh();
}
protected LinkedHashMap<String, ZuulRoute> locateRoutes() {
LinkedHashMap<String, ZuulRoute> routesMap = new LinkedHashMap();
// 通过父类 SimpleRouteLocator获取静态路由信息
routesMap.putAll(super.locateRoutes());
LinkedHashMap values;
Iterator var3;
String path;
if (this.discovery != null) {
values = new LinkedHashMap();
var3 = routesMap.values().iterator();
while(var3.hasNext()) {
ZuulRoute route = (ZuulRoute)var3.next();
path = route.getServiceId();
if (path == null) {
path = route.getId();
}
if (path != null) {
values.put(path, route);
}
}
// 通过DiscoveryClient获取路由信息
List<String> services = this.discovery.getServices();
String[] ignored = (String[])this.properties.getIgnoredServices().toArray(new String[0]);
Iterator var13 = services.iterator();
while(var13.hasNext()) {
String serviceId = (String)var13.next();
String key = "/" + this.mapRouteToService(serviceId) + "/**";
if (values.containsKey(serviceId) && ((ZuulRoute)values.get(serviceId)).getUrl() == null) {
ZuulRoute staticRoute = (ZuulRoute)values.get(serviceId);
if (!StringUtils.hasText(staticRoute.getLocation())) {
staticRoute.setLocation(serviceId);
}
}
if (!PatternMatchUtils.simpleMatch(ignored, serviceId) && !routesMap.containsKey(key)) {
routesMap.put(key, new ZuulRoute(key, serviceId));
}
}
}
if (routesMap.get("/**") != null) {
ZuulRoute defaultRoute = (ZuulRoute)routesMap.get("/**");
routesMap.remove("/**");
routesMap.put("/**", defaultRoute);
}
values = new LinkedHashMap();
Entry entry;
for(var3 = routesMap.entrySet().iterator(); var3.hasNext(); values.put(path, entry.getValue())) {
entry = (Entry)var3.next();
path = (String)entry.getKey();
if (!path.startsWith("/")) {
path = "/" + path;
}
if (StringUtils.hasText(this.properties.getPrefix())) {
path = this.properties.getPrefix() + path;
if (!path.startsWith("/")) {
path = "/" + path;
}
}
}
return values;
}
// 刷新时会调用 locateRoute()
public void refresh() {
this.doRefresh();
}
}
以service1为例,配置 /api/service1/** -> service1, 存储的路由信息为:
/api/service1/** --> service1 # 根据静态路由配置生成的路由规则
/service1/** --> service1 # 利用DiscoveryClient提取后根据默认规则生成的路由信息(用处不大)
CompositeRouteLocator
具备组合多个 RouteLocator
的能力,用 Collection
存储多个 RouteLocator
,调用 getRoutes()
、 getMatchingRoute()
、 refresh()
都会逐个调用每一个 RouteLocator
相应的方法。
public class CompositeRouteLocator implements RefreshableRouteLocator {
private final Collection<? extends RouteLocator> routeLocators;
private ArrayList<RouteLocator> rl;
public CompositeRouteLocator(Collection<? extends RouteLocator> routeLocators) {
Assert.notNull(routeLocators, "'routeLocators' must not be null");
this.rl = new ArrayList(routeLocators);
AnnotationAwareOrderComparator.sort(this.rl);
this.routeLocators = this.rl;
}
public List<Route> getRoutes() {
List<Route> route = new ArrayList();
Iterator var2 = this.routeLocators.iterator();
while(var2.hasNext()) {
RouteLocator locator = (RouteLocator)var2.next();
route.addAll(locator.getRoutes());
}
return route;
}
public Route getMatchingRoute(String path) {
Iterator var2 = this.routeLocators.iterator();
Route route;
do {
if (!var2.hasNext()) {
return null;
}
RouteLocator locator = (RouteLocator)var2.next();
route = locator.getMatchingRoute(path);
} while(route == null);
return route;
}
public void refresh() {
Iterator var1 = this.routeLocators.iterator();
while(var1.hasNext()) {
RouteLocator locator = (RouteLocator)var1.next();
if (locator instanceof RefreshableRouteLocator) {
((RefreshableRouteLocator)locator).refresh();
}
}
}
}
Zuul动态路由实现
数据表实现
id | path | service_id | url | retryable | enable | strip_prefix | api_name
主键 路径 服务名称 url 可重试 是否可用 带前缀 接口名称
CREATE TABLE `gateway_api_route` (
`id` varchar(50) NOT NULL,
`path` varchar(255) NOT NULL,
`service_id` varchar(50) DEFAULT NULL,
`url` varchar(255) DEFAULT NULL,
`retryable` tinyint(1) DEFAULT NULL,
`enabled` tinyint(1) NOT NULL,
`strip_prefix` int(11) DEFAULT NULL,
`api_name` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
INSERT INTO gateway_api_route (id, path, service_id, retryable, strip_prefix, url, enabled)
VALUES ('order-service', '/order/**', 'order-service',0,1, NULL, 1);
异步路由定位器
import org.springframework.beans.BeanUtils;
import org.springframework.cloud.netflix.zuul.filters.RefreshableRouteLocator;
import org.springframework.cloud.netflix.zuul.filters.SimpleRouteLocator;
import org.springframework.cloud.netflix.zuul.filters.ZuulProperties;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.util.StringUtils;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
public class DynamicRouteLocator extends SimpleRouteLocator implements RefreshableRouteLocator {
private JdbcTemplate jdbcTemplate;
private ZuulProperties properties;
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
public DynamicRouteLocator(String servletPath, ZuulProperties properties) {
super(servletPath, properties);
this.properties = properties;
}
@Override
public void refresh() {
doRefresh();
}
@Override
protected Map<String, ZuulProperties.ZuulRoute> locateRoutes() {
LinkedHashMap<String, ZuulProperties.ZuulRoute> routesMap = new LinkedHashMap<>();
// 加载application.yml中的路由表
routesMap.putAll(super.locateRoutes());
// 加载db中的路由表
routesMap.putAll(locateRoutesFromDB());
// 统一处理一下路由path的格式
LinkedHashMap<String, ZuulProperties.ZuulRoute> values = new LinkedHashMap<>();
for (Map.Entry<String, ZuulProperties.ZuulRoute> entry : routesMap.entrySet()) {
String path = entry.getKey();
if (!path.startsWith("/")) {
path = "/" + path;
}
if (StringUtils.hasText(this.properties.getPrefix())) {
path = this.properties.getPrefix() + path;
if (!path.startsWith("/")) {
path = "/" + path;
}
}
values.put(path, entry.getValue());
}
System.out.println("路由表:" + values);
return values;
}
private Map<String, ZuulProperties.ZuulRoute> locateRoutesFromDB() {
// routesMap 存储路由信息
Map<String, ZuulProperties.ZuulRoute> routes = new LinkedHashMap<>();
// 从数据表里获取启用的路由服务
List<GatewayApiRoute> results = jdbcTemplate.query(
"select * from gateway_api_route where enabled = true ",
new BeanPropertyRowMapper<>(GatewayApiRoute.class));
for (GatewayApiRoute result : results) {
if (StringUtils.isEmpty(result.getPath()) ) {
continue;
}
if (StringUtils.isEmpty(result.getServiceId()) && StringUtils.isEmpty(result.getUrl())) {
continue;
}
ZuulProperties.ZuulRoute zuulRoute = new ZuulProperties.ZuulRoute();
try {
BeanUtils.copyProperties(result, zuulRoute);
} catch (Exception e) {
e.printStackTrace();
}
routes.put(zuulRoute.getPath(), zuulRoute);
}
return routes;
}
}
public class GatewayApiRoute {
private String id;
private String path;
private String serviceId;
private String url;
private boolean stripPrefix = true;
private Boolean retryable;
private Boolean enabled;
}
@Configuration
public class DynamicRouteConfiguration {
@Autowired
private ZuulProperties zuulProperties;
@Autowired
private ServerProperties server;
@Autowired
private JdbcTemplate jdbcTemplate;
@Bean
public DynamicRouteLocator routeLocator() {
DynamicRouteLocator routeLocator = new DynamicRouteLocator(
this.server.getServletPrefix(), this.zuulProperties);
routeLocator.setJdbcTemplate(jdbcTemplate);
return routeLocator;
}
}
定时刷新路由信息
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.netflix.zuul.RoutesRefreshedEvent;
import org.springframework.cloud.netflix.zuul.filters.RouteLocator;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
@Component
@Configuration
@EnableScheduling
public class RefreshRouteTask {
@Autowired
private ApplicationEventPublisher publisher;
@Autowired
private RouteLocator routeLocator;
@Scheduled(fixedRate = 5000)
private void refreshRoute() {
System.out.println("定时刷新路由表");
RoutesRefreshedEvent routesRefreshedEvent = new RoutesRefreshedEvent(routeLocator);
publisher.publishEvent(routesRefreshedEvent);
}
}