Zuul网关是系统的唯一对外的入口,介于客户端和服务器端之间的中间层,处理非业务功能 提供路由请求、鉴权、监控、缓存、限流等功能。
- 主流的网关:
Zuul、kong(基于Nginx的API gateway)、nginx+lua(是一个高性能的HTTP和反向代理服务器,lua是脚本语言,让Nginx执行Lua脚本,并且高并发、非阻塞的处理各种请求)
利用Zuul搭建服务网关:
- 搭建网关服务,引入依赖“spring-cloud-starter-netflix-eureka-client”和“spring-cloud-starter-netflix-zuul”
(如果是使用Idea的Spring Initializr来搭建,则选中eureka client和zuul即可) - 在application.yml中配置服务端口,如8070,以及注册中心
server:
port: 8070
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/
- 在启动类中贴上@EnableZuulProxy注解,并启动
默认的启动访问网关的url访问网关的端口号,并且拼接上对应的资源路径,且在资源路径前加上应用的服务名称
如:
http://localhost:8070/product-server/api/v1/product/get?id=2
http://localhost:8070/order-server/api/v1/order/save?userId=1&productId=2
网关的配置
路由规则
默认的路由规则是使用服务名去访问,如“order-server”,“product-server”等。需要修改,那么就需要加上zuul.routes
zuul:
routes:
order-server-route: -- 这个可以随便写
path: /order/**
serviceId: order-server
这时候默认的“order-server”还是可以进行访问,如果不想要默认的访问,可以配置 zuul.routes.ignoredPatterns:=/*-server/**
zuul:
routes:
ignoredPatterns: /*-server/**
order-server-route:
path: /order/**
serviceId: order-server
product-server-route:
path: /product/**
serviceId: product-server
过滤请求头的问题
默认"Cookie", “Set-Cookie”, "Authorization"等值会被网关拦截,从而传到服务里面的是null值,在ZuulProperties中是默认配置的
private Set<String> sensitiveHeaders = new LinkedHashSet<>(Arrays.asList("Cookie", "Set-Cookie", "Authorization"));
如果需要取消,可以在配置文件中配置
zuul:
sensitive-headers: -- 配置为空,就相当于不拦截这些属性
Zuul的工作原理
可以参考 https://github.com/Netflix/zuul/wiki/How-it-Works
ZuulServlet
Zuul服务其实就是以Servlet启动,也就是说,其底层是通过Servlet,在其service方法中可以看到执行流程逻辑,前置过滤器,路由过滤器,后置过滤器依次执行。最终是通过FilterProcessor
执行类来进行过滤器的控制,调用,以及结果的操作运算。
上文说到Zuul提供路由请求、鉴权、监控、缓存、限流等功能(这些功能的启用与配置可以在网上直接搜索),而实现这些功能原理是过滤器
- At the center of Zuul is a series of Filters that are capable of performing a range of actions during the routing of HTTP requests and responses.
Zuul的核心实质是一系列的过滤器,能够在HTTP请求和响应的路由过程中执行一系列操作
ZuulFilter:
通过观察runFilter()这个方法,可以发现fiter的执行操作。是先调用shouldFilter()方法来判断是否需要过滤,后才执行run()方法调用filter。
模拟实现鉴权登录
假设有两个服务
order-server (需要登录)
product-server (不需要登录)
@Component //一定要贴这个注解,将其交给Spirng管理。 ZuulFilters的注册是通过从Spring容器里拿到所有继承了ZuulFilter的实例
public class AuthZuulFilter extends ZuulFilter {
@Override
public String filterType() {
return FilterConstants.PRE_TYPE;
}
@Override
public int filterOrder() {
return 1;
}
@Override
public boolean shouldFilter() {
RequestContext currentContext = RequestContext.getCurrentContext();
HttpServletRequest request = currentContext.getRequest();
if(request.getRequestURI().indexOf("order")>0){
System.out.println(request.getRequestURI());
return true;
}
return false;
}
@Override
public Object rn() throws ZuulException {
RequestContext currentContext = RequestContext.getCurrentContext();
HttpServletRequest request = currentContext.getRequest();
String token = request.getHeader("token");
if(StringUtils.isEmpty(token)){
token = request.getParameter("token");
}
if(StringUtils.isEmpty(token)){
currentContext.setSendZuulResponse(false); // 执行结果 response为false
currentContext.setResponseStatusCode(HttpStatus.UNAUTHORIZED.value()); //未授权
}
return null;
}
}
总结:
- ZuulFilter的加载过程:
首先,进行过滤器的加载,这个过程是ZuulServerAutoConfiguration的bean对象ZuulFilterInitializer来完成的。
而其中,程序首先从Spring中拿到继承了ZuulFilter抽象类的实例
@Configuration
protected static class ZuulFilterConfiguration {
@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);
}
}
通过Spirng拿到所有的过滤器,然后以type区分,也就是前置过滤,路由过滤还是后置过滤等。
其次,将过滤器放到FilterRegistry工厂里,这是一个单例对象,利用一个ConcurrentHashMap来储存所有的过滤器。并在第一次请求时,拿到所有的过滤器,放到FilterLoader中缓存hashFiltersByType中。
- ZuulFilter的执行过程:
- 依次执行preFilter、routeFilter、postFilter
- 每种Filter的执行,都是由FilterProcessor发起的,首先拿到全部该种类的过滤器,再循环一个个过滤器执行。
- 每次执行Filter,都会先判断是否需要过滤,判断的逻辑写在shouldFilter()中,如果需要,最后执行run方法进行过滤逻辑。
- 真正的调用发起是在RibbonRoutingFilter中,其type为 “route” , 其run方法执行了forward来发起远程调用,用的其实也就是Ribbon方式。注意,RibbonRoutingFilter的shouldFilter()方法会根据RequestContext的Response的sendZuulResponse是否为true,是才执行该过滤器,即发起调用,否则就执行跳过。所以上面设置了该属性为false,就不会发起调用。
- 调用结果保存到RquestContext中的response中。