本文为《Spring Cloud微服务实战》一书的摘要总结
spring-cloud-starter-netflix-zuul
依赖:<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-zuulartifactId>
dependency>
该模块下不仅包含了NetflixZuul的核心依赖zuul-core
,还包括一下依赖:
spring-boot-starter-web
spring-boot-starter-actuator
spring-cloud-starter-netflix-hystrix
spring-cloud-starter-netflix-ribbon
spring-cloud-starter-netflix-archaius
在应用主类使用@EnableZuulProxy
注解开启Zuul的API网关服务功能
在配置文件配置应用名称、端口等信息。
网关应用没有与成服务治理一起使用,需要手动指定服务的url:
zuul:
routes:
serverA:
path: /server-a/**
url: http://localhost:8080/
上面的配置定义了发往API网关服务的请求中,所有符合/server-a/** 规则的访问都将被路由转发到http://localhost:8080/ 地址上。其中zuul.rootes.serverA中的serverA是路由名称,可以随便定义;在.properties类型的配置文件中,一组path与url的路由名称要相同,如:
zuul.routes.serverA.path=/server-a/**
zuul.routes.serverA.url=http://localhost:8080/ #如果没有该行配置,那么符合、server-a/**的请求不会被路由转发
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
dependency>
指定serviceId 而不是 url。
eureka:
client:
service-url:
defaultZone: http://192.168.1.6:1111/eureka/ #将自己注册为服务
instance:
prefer-ip-address: true
zuul:
routes:
UserServer:
path: /user-server/**
#url: http://localhost:8080/ 不再使用url参数
serviceId: CONSUMER # 指定服务名
FeignServer:
path: /feign-server/**
serviceId: FEIGN-CONSUMER
我们可以通过继承com.netflix.zuul.ZuulFilter
并重写它的方法来实现对请求的过滤:
public class AccessFilter extends ZuulFilter { //继承com.netflix.zuul.ZuulFilter
private static Logger logger = Logger.getLogger("AccessFilter");
//过滤器类型,它决定过滤器在哪个生命周期执行
@Override
public String filterType() {
return "pre";
}
//过滤器执行顺序
@Override
public int filterOrder() {
return 0;
}
//判断该过滤器是否需要被执行
@Override
public boolean shouldFilter() {
return true;
}
//过滤器的具体逻辑
@Override
public Object run() throws ZuulException {
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
logger.info("send " + request.getMethod() + "to " + request.getRequestURL().toString());
Object accessToken = request.getParameter("accessToken");
if (accessToken == null){
logger.warning("access token is empty");
ctx.setSendZuulResponse(false); //false:不对该请求进行路由
ctx.setResponseStatusCode(401); //设置状态码
return null;
}
logger.info("access token ok");
return null;
}
}
然后我们创建AccessFilter
Bean,启动网关服务后,该过滤器生效。
zuul:
routes:
serverA:
path: /server-a/**
url: http://localhost:8080/
zuul:
routes:
serverA:
path: /server-a/**
serviceId: SERVER-A #指定serviceId
ribbon:
eureka:
enabled: false #不使用eureka服务治理服务
SERVER-A: # 对应前面配置的serviceId的值
ribbon:
listOfServers: # 手工维护服务列表
- http://localhost:8080/
- http://localhost:8081/
zuul:
routes:
UserServer:
path: /user-server/**
serviceId: CONSUMER
除了使用path和serviceId之外,还有一种更简便的配置方式:zuul.routes.=
zuul:
routes:
CONSUMER: /user-server/**
当zuul.routes下没有path属性时就要明白用的是这种配置方式了。
当我们为Spring cloud Zuul构建的API网关服务引入Spring Cloud Eureka之后,它为Eureka中的 每个服务 都自动创建一个默认路由规则:path会使用serviceId配置的服务名作为前缀。即:
zuul:
routes:
: >/** #路径全为小写
我们也可以通过zuul.ignored-services参数来设置一个服务名表达式来定义不自动创建路由的规则:
zuul:
ingnord-services: user-* #以"user-"开头的服务都不会自动创建默认的路由规则
我们可以创建PatternServiceRouteMapper
Bean来自定义服务与路由映射的生成关系(serviceId与path的关系):
@Bean
public PatternServiceRouteMapper patternServiceRouteMapper(){
return new PatternServiceRouteMapper(
"?(^.+)-(?v.+$)" , //<任意多的任意字符>-
"${version}/$"
);
}
构造函数的第一个参数用来匹配服务名称是否符合自定义规则的正则表达式,第二个参数则是根据服务名中定义的内容转化出的路径表达式规则。
如果我们有一个服务的serviceId为:userServer-v1,那么Zuul会自动配置如下路由:
zuul:
routes:
userServer-v1: /v1/userserver/**
正则表达式
在Zuul中,路由匹配的路径表达式采用了Ant风格定义
通配符 | 说明 |
---|---|
? | 匹配任意单个字符 |
* | 匹配任意数量的字符 |
** | 匹配任意数量的字符,支持多级目录 |
当一个请求路径与多个path匹配时,Zuul将通过线性遍历的方式,使用第一个匹配到请求路径的路由规则。而所有的路由规则都存在一个LinkedHashMap
中,所以我们配置路由规则的顺序会影响到请求路径的路由。而使用.properties文件的配置内容无法保证有序,我们应该使用YAML文件来配置。
zuul:
ignored-patterns: /**/hello/**
上面的配置将导致任何路径中有/hello/的请求都不会被API网关路由。
zuul:
prefix: /api
routes:
UserServer:
path: /user-server/**
serviceId: CONSUMER
上面的路由规则为:/api/user-server/**
我们还可以设置stripPrefix为false:
zuul:
prefix: /api
stripPrefix: false
routes:
UserServer:
path: /user-server/**
serviceId: CONSUMER
此时,上面的路由规则为:/api/user-server/** ;同时也会在调用具体的CONSUMER服务是加上前缀/api,即:如果CONSUMER服务的真实地址为:localhost:8080,那么API网关会路由到localhost:8080/api/
注意在使用prefix时,要避免将prefix设置为与现有path中前缀相同,如存在路由规则:/api/user-server/** ,如果再设置前缀为/api,这条路由规则将无法路由
在通过使用path与url的配置方式时,可以使用forward来指定需要跳转的服务器资源路径
zuul:
routes:
server-a:
path: /server-a/**
url: forward:/local
如果网关的地址为:localhost:8080,那么请求http://localhost:8080/server-a/hello将会被转发到http://localhost:8080/local/hello。
默认情况下,Spring Cloud Zuul在请求路由时,会过滤掉HTTP请求头信息中的敏感信息,防止它们被传递到下游的外部服务器。
敏感的头信息包括:Cookie,Set-Cookie,Authorization三个属性,我们可以通过zuul.sensitiveHeaders参数定义。
当我们需要将这些敏感的信息传递给下游外部服务器时,我们需要设置zuul.sensitiveHeaders
zuul:
sensitive-headers: #将改参数设置为空,所有请求的头信息都将传递下去
或者:
zuul:
routes:
server-a:
customSensitiveHeasers: true # 对指定路由开启自定义路由
sensitiveHeaders : #敏感头设置为空
当我们解决了Cookie问题后,我们可以通过网关来访问并登陆到我们的Web应用了,但是这个时候会出现另一个问题:登陆成功后跳转到的页面URL是具体Web应用实例的地址,而不是通过网关的路由地址。我们可以通过设置auul.addHostHeader=true
,使得网关在进行路由转发前为请求设置Host头信息,以标识最初的服务端请求地址。
Zuul天生拥有线程隔离和断路器额自我保护能力,以及对服务调用的客户端负载均衡的能力。
但是当我们使用path与url的映射关系来配置路由规则时,由于路由转发的请求不会采用HystrixCommand来包装,所以这类路由请求没有线程隔离和断路器保护,也不会有负载均衡的能力。
我们在使用Zuul搭建API网管的时候,可以通过Hystrix和Ribbon的参数来调整路由请求的这种超时时间等配置。
过滤器是Zuul实现API网关功能的核心部件,它的路由映射、请求和转发都是通过几个过滤器实现的。
Spring Cloud Zuul中实现的顾虑器必须包含4个基本的特征:过滤类型,执行顺序,执行条件,具体操作。这实际上就是com.netflix.zuul.ZuulFilter
抽象类定义的4个抽象方法:
public abstract class ZuulFilter implements IZuulFilter, Comparable<ZuulFilter> {
/*
该函数需要返回一个字符串来代表过滤器的类型,这个类型就是HTTP请求过程中定义的各个阶段
pre:可以在请求被路有钱被调用
routing:在路由请求时被调用
post:在routing和error过滤器之后被调用
error:处理请求时发生错误时被调用
*/
public abstract String filterType();
/*
通过int值来确定过滤器的执行顺序
数值越小优先级越高
*/
public abstract int filterOrder();
}
public interface IZuulFilter {
/*
返回一个Boolean值来判断该过滤器是否要执行。
我们可以通过该方法来确定过滤器的有效范围
*/
boolean shouldFilter();
/*
过滤器的具体逻辑
*/
Object run() throws ZuulException;
}
Spring Cloud Zuul在HTTP请求生命周期的各个阶段默认实现了一批核心过滤器,它们定义在org.springframework.cloud.netflix.zuul.filters
包下
过滤器 | 优先级 | 作用 |
---|---|---|
ServletDetectionFilter |
-3 | 最先被执行的过滤器,总是会被执行,主要用来检测当前的请求是通过Spring的DispatcherServlet 处理运行的,还是通过ZuulServlet 来处理运行的,处理结果会以Boolean类型存在请求上下文的isDispatcherServletRequest 参数中 |
Servlet30WrapperFilter |
-2 | 第二个被执行的顾虑器,总是会被执行,主要是为了将原始的HttpServletRequest 包装成Servlet30RequestWrapper 对象 |
FormBodyWrapperFilter |
-1 | 过滤器对两种请求生效:一种是Content-Type为application/x-www-form-urlencoded的请求;另一种是Content-Type为multipart/form-data并且是有Spring的DispatcherServlet 处理的请求。该过滤器的主要作用是将符合要求的请求体包装成FormBodyRequestWrapper 对象 |
DebugFIlter |
1 | 该过滤器会根据配置参数zuul.debug.request 和请求中的debug参数来决定是否执行。它的主要作用是将当前请求上下文的debugRouting 和debugRequest 参数设置为true |
PreDecorationFilter |
5 | 判断当前请求上线文中是否存在forward.to和serviceId参数,如果不存在就会执行具体过滤器操作————为当前请求做一些预处理,如进行路由规则的匹配、在请求上下文中设置该请求的基本信息以及路由匹配结果等一些设置信息。 |
过滤器 | 优先级 | 作用 |
---|---|---|
RibbonRoutingFilter |
10 | routing阶段第一个执行的过滤器,该过滤器只对当前请求上下文中存在serviceId 参数的请求进行处理,它的处理逻辑就是面向服务路由的核心,通过使用Ribbon和Hystrix来向服务实例发起请求,并将服务实例的请求结果返回。 |
SimpleHostRoutingFilter |
100 | 该顾虑器只处理请求上下文中存在routeHost参数的请求,即只对通过url配置路由规则的请求生效。操作逻辑就是直接向routeHost参数的物理地址发起请求,请求是使用httpclient包实现的,所以这类请求没有线程隔离和断路器保护。 |
SendForwardFilter |
500 | 只对上下文中存在forward.to参数的请求进行处理,即用来处理路由规则中的forward本地跳转配置 |
过滤器 | 优先级 | 作用 |
---|---|---|
SendErrorFilter |
0 | post阶段第一个执行的过滤器,该过滤器仅在请求上下文中包含error.status_code 参数并且还没有被该过滤器处理过的时候执行。过滤器的处理逻辑就是利用请求上下文中的错误信息来组成一个forward到API网关的/error错误端点的请求产生错误响应。 |
SendResponseFilter |
1000 | 是post最后阶段执行的过滤器。该过滤器会检查当前请求上下文是否包含请求响应相关的头信息、响应数据流或是响应体,只有包含其中之一的时候才执行。执行的逻辑就是利用请求上下文中的响应信息来组织需要发送回客户端的相应内容。 |
Spring Cloud Zuul 默认实现了SendErrorFilter
这个"error"类型的过滤器,所有其他类型过滤器在执行过程中抛出错误,都会在SendErrorFilter
过滤器中进行处理,而处理的逻辑只是简单的在控制台打印出异常的栈信息,设置状态码为500,跳转到Spring boot默认的错误页面。
我们可以继承SendErrorFilter
,然后覆盖它shouldFilter
方法来限制处理
后面再补充