Zuul默认使用的HTTP客户端是Apache HTTPClient,主要功能是路由转发和过滤器。zuul的核心是一系列的filters, 类似于java Servlet框架的Filter,或者AOP。zuul把请求路由到用户处理逻辑的过程中,这些filter参与一些过滤处理,比如Authentication;外围系统或者用户通过网关访问服务,网关通过注册中心找到对应提供服务的客户端(Ribbon获取服务),网关也需要到注册中心进行注册。
Zuul可以为同一注册中心中的其他服务的访问做反向代理(配置serviceId属性),也可以为外部服务访问做代理(配置URL属性),还可以对zuul服务本身的接口做代理(配置URL属性的值为forward关键字加路径)
搭建Zuul网关服务实现路由和过滤
1.引入Zuul依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
2.在启动类上添加@EnableZuulProxy注解
@SpringBootApplication
@EnableZuulProxy
@EnableEurekaClient
public class ZuulApplication {
public static void main(String[] args) {
SpringApplication.run(ZuulApplication.class, args);
}
}
3.自定义配置zuul服务的路由 (zuul默认以服务ID作为路由配置)
zuul默认情况下会以注册中心中的服务id作为路由设置,即如果请求是注册中心中的某个serviceId组成的URL就路由给对应的服务;zuul中的服务列表使用的是Ribbon实现的;
了解下zuul节点下的配置属性
zuul.prefix:api 配置在zuul节点下的属性,值为api 表示为所有URL加一个前缀
zuul.ignoredServices:"*" 表示禁用默认的路由配置,值为*表示禁用所有默认的路由,出去下面routes节点下配置的 路由外其他都无效。此属性也可以禁用特定的某个或多个默认的服务ID路由,多个服务ID用逗号隔开。
zuul.routes: 这个节点是路由配置开始的节点,其下可以定义多个配置组
zuul.routes.path: 指的是用户访问的URL
zuul.routes.serviceId / zuul.routes.url: 路由目标服务在Eureka中的服务实例ID或者服务的url地址
在目标服务集成Eureka的情况下自定义路由(目标服务使用serviceId配置)
routes节点后配置路由,,。
当然,目标服务也可以使用URL代替serviceId,只不过这种情况下要实现负载访问就要禁用Eureka的Ribbon,然以单独配置Ribbon并执行负载的多个服务ID,这种方式感觉并没有什么用。。。。
如下示例配置了两组路由,一组是customerURL,第二组是userURL。配置里有prefix前缀属性为api,所以第一组配置就是把用户访问地址以api/customer/*的请求路由到customer-service这个服务中;第二组配置就是把api/user/*的请求路由到user-service这个服务中。
server:
port: 8040
spring:
application:
name: zuul
# 配置Eureka地址
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/
# 构建路由地址
zuul:
#所有URL都要加api这个前缀
prefix:api
# 禁用默认的路由设置,使用此配置以后只有下面配置的两组路由才会生效
ignoredServices:"*"
routes:
# 这里可以自定义,只是一个唯一的标识
customerURL:
# 匹配的路由规则
path: /customer/**
# 路由的目标服务名
serviceId: customer-service
userURL:
# 匹配的路由规则
path: /user/**
# 路由的目标服务名
serviceId: user-service
上面这种配置中,如果某个服务有多个实例,以Eureka中集成Ribbon负载方式进行访问;如果使用URL配置目标服务的路由,就要单独配置Ribbon来实现负载:
server:
port: 8080
spring:
application:
name: zuul
# 配置eureka地址
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/
# 构建路由地址
zuul:
routes:
# 这里可以自定义
demo2:
# 匹配的路由规则
path: /demo/**
# 使用URL配置,不适用服务ID
url: http://127.0.0.1:8070
# 关闭使用eureka负载路由
ribbon:
eureka:
enabled: false
# 如果不使用eureka的话,需要自己定义路由的那个服务的其他负载服务
demo:
ribbon:
# 这里写你要路由的demo服务的所有负载服务请求地址,本项目只启动一个,因此只写一个
listOfServers: http://localhost:8090/
对于path属性的配置,要符合ant样式:
user/* 这个配置只对user/id这种/后一个值得url生效
user/** 这个配置可以以user/开头,任意个值得url生效。
不使用Eureka的情况下自定义配置路由(配置URL属性代替serviceId,无负载均衡)
由于没有Eureka注册中心,也就没有服务ID,只能使用URL地址配置服务的路由地址。
server:
port: 8080
spring:
application:
name: zuul
# 构建路由地址
zuul:
routes:
# 这里可以自定义
demo2:
# 匹配的路由规则
path: /demo/**
# 路由的目标地址
url: http://localhost:8090/
4.Zuul过滤器配置
默认情况下,Spring Cloud Zuul在请求路由时,会过滤掉请求头信息中的 一些敏感信息,防止它们被传递到下游的外部服务器。
zuul定义了四种标准过滤器类型,这四种过滤器分别对应着请求的生命周期
-
pre:路由请求前过滤。
-
post:路由请求后(此时已经走完目标服务程序了)过滤。
-
route:路由请求时过滤。
-
error:当上述三种过滤器抛出异常时,会走error过滤。
自定义并使用Zuul Filter
自定义过滤器很简单,只要继承ZuulFilter类并重写相应的方法,用@Component注解作为spring的bean使用即可。
ZuulFilter类提供了如下方法方便我们自定义和实现一个Zuul过滤器:
public String filterType():设置这个过滤器的类型是
public int filterOrder() :设置过滤器的优先级,多个同类型(filterType)ZuulFilter中哪个filter的此方法返回值最小优先级最高
public boolean shouldFilter() :决定此过滤器是否生效,返回true表示生效
public Object run() :过滤器的逻辑执行方法,在这个方法内调用doFiltrate方法
private boolean doFiltrate (HttpServletRequest request) :过滤逻辑,返回是否通过过滤(true或false)
下面是自定义一个pre类型的ZuulFilter的示例代码:
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.util.RequestBodyUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.PropertySource;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import java.util.Arrays;
import java.util.Random;
@Component
public class PreFilter extends ZuulFilter {
/**
* 定义过滤类型
* 【pre】路由请求前被调用过滤、
* 【post】后置过滤、
* 【error】错误过滤、
* 【route】路由请求时被调用
*/
@Override
public String filterType() {
// 设置这个过滤器的类型是 前置过滤器
return "pre";
}
/**
* 设置过滤器的优先级,如果服务中有多个同类型(filterType)的ZuulFilter
*
* 这个方法的值越小代表越先过滤
*/
@Override
public int filterOrder() {
return 0;
}
/**
* 决定此过滤器是否生效,返回true表示生效
*/
@Override
public boolean shouldFilter() {
return true;
}
/**
* 过滤器的逻辑执行方法,在这个方法内调用doFiltrate方法
*
* 注:当RequestContext.setSendZuulResponse(false);时表示过滤失败,zuul不对其进行路由
*/
@Override
public Object run() {
// 获取请求上下文
RequestContext requestContext = RequestContext.getCurrentContext();
// 获取请求
HttpServletRequest request = requestContext.getRequest();
if (doFiltrate(request)) {
// 验证通过
return null;
}
// 如果验证不通过,那么过滤该请求,不往下级服务去转发请求,到此结束
requestContext.setSendZuulResponse(false);
requestContext.setResponseStatusCode(HttpStatus.FORBIDDEN.value());
requestContext.setResponseBody(HttpStatus.FORBIDDEN.getReasonPhrase());
requestContext.getResponse().setContentType("text/html;charset=UTF-8");
return null;
}
/**
* 执行过滤逻辑,返回是否通过过滤结果
*/
private boolean doFiltrate (HttpServletRequest request) {
try {
/// 获取请求头
String who = request.getHeader("Authorization");
log.info(" requestHeader param 【Authorization】 is -> {} !", who);
/// 向请求头中添加信息
// requestContext.addZuulRequestHeader("");
// 获取请求体
RequestBodyUtil requestBodyUtil = new RequestBodyUtil(request);
String requestBody = requestBodyUtil.getBody();
log.info(" got requestBady -> {}", requestBody);
// TODO 由于是测试代码,这里随机返回 成功、失败
return new Random().nextBoolean();
} catch (Exception e) {
log.error(" zull authe occur error !", e);
return false;
}
}
}
禁用Zuul中默认启用的过滤器
上面提到了我们自己定义的filter如果不需要使用可以通过shouldFilter方法返回false实现对这个过滤器是禁用。但是Zuul默认启用了一些filter的bean,如果我们不想使用显然没办法通过修改方法来实现,这个时候我们可以通过配置yml来实现:
直接在配置文件中:zuul.<filtername>.<filtertype>.disable=true
例如:
zuul:
DebugFilter:
pre:
disable: true