路由是微服务体系结构的一个组成部分。例如,/
可以映射到您的 Web 应用程序,/api/users
映射到用户服务,并将/api/shop
映射到商店服务。Zuul 是 Netflix 的基于 JVM 的路由器和服务器端负载均衡器。
一 简单使用
1.1 快速启动
1 加入相关依赖
新建一个 spring cloud 工程,在项目的 pom 文件里加入以下依赖
1 | <dependency> |
2 设置配置文件
1 | server: |
上述配置是将 zuul 也作为一个微服务注册到 eureka 注册中心中。
Zuul 启动器不包括发现客户端,因此对于基于服务 ID 的路由,您还需要在类路径中提供其中一个路由(例如 Eureka)。
3 新建启动类
在项目中的任意一个@Configuration
注解类的下面添加上 @EnableZuulProxy
注解即可
1 | @SpringBootApplication |
在默认情况下,zuul 能够自动添加路由,启动上述程序后,zuul 即可进行请求转发了。
1.2 路由映射
1.2.1 传统路由方式
在项目的配置文件中增加以下配置:
1 | zuul.routes.service-a.path=/service-a/** |
改配置定义了发往 API 网关服务的请求中,所有符合 /service-a/**
规则的访问都被路由转发到http://localhost:8080/
的地址上。也就是说,当我们访问 http://localhost:9000/service-a/aa
的时候,请求会被转发到 http://localhost:8080/aa
提供的服务上。其中,配置属性zuul.routes.service-a.path
中的service-a
部分为路由的名字,可以任意定义,但是一组 path
和url
映射关系的路由名要相同。
1.2.2 面向服务的路由
1 增加依赖
1 | <dependency> |
2 增加配置属性
1 | eureka: |
3 修改启动类
1 | @SpringBootApplication |
在默认情况下,zuul 会将 /service-a/**
的请求转发到服务名为 service-a
的注册服务的服务接口中,将 /service-b/**
的请求转发到服务名为 service-b
的注册服务的服务接口中。
要跳过自动添加的服务,请将zuul.ignored-services
设置为服务 ID 模式列表。 如果服务与忽略但仍包含在显式配置的路由映射中的模式匹配,则它是不带号的,如以下示例所示:
1 | zuul: |
在此示例中,除 users
之外 之外,所有服务都被忽略。
要扩充或更改代理路由,可以添加如下所示的外部配置:
1 | zuul: |
这意味着对/myusers
的 http 请求转发到users
服务(例如/myusers/101
转发到users
服务中的/101
)。
要获得对路由的更细粒度的控制,您可以独立地指定路径和 serviceId:
1 | zuul: |
前面的示例意味着对/myusers
的 HTTP 调用将转发到users_service
服务。 路径必须具有可以指定为 ant 样式模式的路径,因此/myusers/*
仅匹配一个级别,但/myusers/**
是分层匹配的。
后端的位置可以指定为 serviceId(用于发现服务)或 url(用于物理位置),如以下示例所示:
1 | zuul: |
要为所有映射添加前缀,请将zuul.prefix
设置为值,例如/api
。 默认情况下,在转发请求之前,会从请求中删除代理前缀(您可以使用zuul.stripPrefix = false
关闭此行为)。 您还可以关闭从各个路由中剥离特定于服务的前缀,如以下示例所示:
1 | zuul: |
zuul.stripPrefix
仅适用于zuul.prefix
中设置的前缀。它对给定路由 path 中定义的前缀有影响。
在本示例中,对/myusers/101
的请求将转发到users
服务的/myusers/101
上。
zuul.routes
条目实际上绑定到类型为ZuulProperties
的对象。如果您查看该对象的属性,您将看到它还具有“可重试”标志。将该标志设置为true
使 Ribbon 客户端自动重试失败的请求(如果需要,可以使用 Ribbon 客户端配置修改重试操作的参数)。
默认情况下,将X-Forwarded-Host
标头添加到转发的请求中。zuul.addProxyHeaders = false
可关闭此行为。默认情况下,前缀路径被删除,对后端的请求会拾取一个标题X-Forwarded-Prefix
(上述示例中的/myusers
)。
如果您设置默认路由(/
),则@EnableZuulProxy
的应用程序可以作为独立服务器,例如zuul.route.home: /
将路由所有流量(即/**
)到home
服务。
如果需要更细粒度的忽略,可以指定要忽略的特定模式。在路由位置处理开始时评估这些模式,这意味着前缀应包含在模式中以保证匹配。忽略的模式跨越所有服务,并取代任何其他路由规范。
1 | zuul: |
这意味着诸如/myusers/101
的所有请求将被转发到users
服务上的/101
。但是包含/admin /
的请求除外。
如果您需要路由保留配置顺序,则需要使用 YAML 文件,因为使用属性文件时排序会丢失。 以下示例显示了这样的 YAML 文件
二 Cookie 和敏感头
默认情况下,Spring Cloud Zuul 在请求路由时,会过滤掉 HTTP 请求头信息中的一些敏感信息,防止它们被传递到下游的外部服务器,默认的敏感头信息通过zuul.sensitiveHeaders
参数定义。包括Cookie
、Set-Cookie
、 Authortzation
三个属性。
所以,我们在开发 Web 项目时常用的Cookie
在 Spring Cloud Zuul 网关中默认是不会传递的,这就会引发一个常见的问题: 如果我们要将使用了 Spring Security、 Shiro 等安全框架构建的 Web 应用通过 Spring Cloud Zuul 构建的网关来进行路由时,由于 Cookie 信息无法传递,我们的 Web 应用将无法实现登录和签权。为了解决这个问题,配置的方法有很多。
- 通过设置全局参数为空来覆盖默认值,具体如下:
1 | zuul.sensitiveHeaders= |
- 通过指定路由的参数来配置
1 | # 方法一:对指定路由开启自定义敏感头 |
可以将敏感请求头配置为每个路由的逗号分隔列表,如以下示例所示:
1 | zuul: |
这是sensitiveHeaders
的默认值,因此您不需要设置它,除非您希望它不同。注意这是 Spring Cloud Netflix 1.1 中的新功能(1.0 中,用户无法控制标题,所有 Cookie 都在两个方向上流动)。
sensitiveHeaders
是一个黑名单,默认值不为空,所以要使 Zuul 发送所有标题(“被忽略”除外),您必须将其显式设置为空列表。如果您要将 Cookie 或授权标头传递到后端,这是必要的。例:
1 | zuul: |
也可以通过设置zuul.sensitiveHeaders
来全局设置敏感标题。如果在路由上设置sensitiveHeaders
,则将覆盖全局sensitiveHeaders
设置。
忽略请求头
除路由敏感标头外,您还可以为与下游服务交互期间应丢弃的值(请求和响应)设置名为zuul.ignoredHeaders
的全局值。 默认情况下,如果 Spring Security 不在类路径中,则它们为空。 否则,它们被初始化为一组众所周知的“安全”头文件(例如,涉及缓存),如 Spring Security 所指定的那样。
在这种情况下的假设是下游服务也可能添加这些头,但我们想要代理的值。 要在 Spring Security 位于类路径上时不丢弃这些众所周知的安全标头,可以将zuul.ignoreSecurityHeaders
设置为false
。 如果您在 Spring Security 中禁用了 HTTP 安全响应标头并希望下游服务提供的值,那么这样做会非常有用。
下面是一个示例配置
1 | zuul: |
三 路线端点
默认情况下,如果将@EnableZuulProxy
与Spring Boot Actuator
一起使用,则启用另外两个端点:
路由端点
通过 GET
请求访问 /routes
时可以获取到所有的路由列表
1 | { |
还可以通过将?format = details
查询字符串添加到/routes
来请求其他路由详细信息。 这样做会产生以下输出:
1 | { |
此外,/routes
的POST
请求强制刷新现有路由。
可以通过将
endpoints.routes.enabled
设置为false
来禁用此端点。
过滤器端点
发送一个 GET 请求到 /filters
可以获取到 zuul 中所有的过滤器列表。
如果使用
@EnableZuulServer
(而不是@EnableZuulProxy
),您还可以运行 Zuul 服务器,而无需代理或有选择地切换代理平台的各个部分。 您添加到 ZuulFilter 类型的应用程序的任何 bean 都会自动安装(与@EnableZuulProxy
一样),但不会自动添加任何代理过滤器。
Spring Cloud Netflix 安装了许多过滤器,具体取决于使用哪个注释来启用 Zuul。 @EnableZuulProxy
是@EnableZuulServer
的超集。 换句话说,@ EnableZuulProxy
包含@EnableZuulServer
安装的所有过滤器。 “代理”中的其他过滤器启用路由功能。 如果你想要一个“空白”Zuul,你应该使用@EnableZuulServer
。
1 | zuul: |
在这种情况下,仍然通过配置zuul.routes.*
来指定进入 Zuul 服务器的路由,但是没有服务发现和代理。 因此,将忽略serviceId
和url
设置。 以下示例将/ api / **
中的所有路径映射到 Zuul 过滤器链
四 进阶配置
4.1 文件上传
如果您使用@EnableZuulProxy
,您可以直接使用代理路径上传小文件。 对于大型文件,有一个替代路径/zuul/*
绕过 Spring 中的 DispatcherServlet。 换句话说,如果你有zuul.routes.customers = /customers/**
,那么你可以通过 POST 请求/zuul/customers/*
上传大文件。 servlet 路径中的 /zuul
可以通过zuul.servletPath
进行自定义设置。
如果代理路由引导您完成功能区负载平衡器,则极大文件也需要提升超时设置,如以下示例所示:
1 | # 设置API网关中路由转发请求的 HystrixCommand 执行超时时间,单位为毫秒 |
4.2 全局回退
示例代码如下
1 | @Component |
4.3 zuul 获取上下文
Zuul 是通过 Servlet 实现的。 对于一般情况,Zuul 嵌入到 Spring Dispatch 机制中。 这让 Spring MVC 可以控制路由。 在这种情况下,Zuul 缓冲请求。 如果需要在没有缓冲请求的情况下通过 Zuul(例如,对于大型文件上载),Servlet 也会安装在 Spring Dispatcher 之外。 默认情况下,servlet 的地址为/zuul
。 可以使用zuul.servlet-path
属性更改此路径。
为了在过滤器之间传递信息,Zuul 使用RequestContext
。 它的数据保存在特定于每个请求的ThreadLocal
中。 有关在何处路由请求,错误以及实际的HttpServletRequest
和HttpServletResponse
的信息都存储在那里。 RequestContext
扩展了ConcurrentHashMap
,因此任何东西都可以存储在上下文中。 FilterConstants
包含 Spring Cloud Netflix 安装的过滤器使用的密钥。
例如
1 | RequestContext ctx = RequestContext.getCurrentContext(); |
4.4 zuul 过滤器
4.4.1 @EnableZuulServer
支持的过滤器
@EnableZuulServer
用于从 Spring Boot 配置文件加载的路由定义创建一个SimpleRouteLocator
。
默认支持的过滤器有:
前置过滤器:
ServletDetectionFilter
:执行顺序为-3,检测当前请求是否通过 Spring 的DispatcherServlet
还是通过ZuulServlert
来处理运行的,它的检测结果保存在FilterConstants.IS_DISPATCHER_SERVLET_REQUEST_KEY
的参数中。
一般情况下,发送到网关的外部请求都会被
DispatcherServlet
处理,除了/zuul/*
的请求会绕过DispatcherServlet
,直接使用ZuulServlert
处理,主要用于处理大文件上传的情况。可以通过 RequestUtils 工具获取访问源头
FormBodyWrapperFilter
:执行顺序为-1,解析表单数据并为下游请求重新编码,将符合要求的请求体包装成FormBodyWrapperWrapper
对象。
DebugFilter
:执行顺序为 1,如果设置了debug
请求参数,则将RequestContext.setDebugRouting()
和RequestContext.setDebugRequest()
设置为true.*
路由过滤器SendForwardFilter
:执行顺序为 500,使用Servlet RequestDispatcher
转发请求。转发位置存储在RequestContext
中,获取值的键为FilterConstants.FORWARD_TO_KEY
。
后置过滤器
SendResponseFilter
:执行顺序为 1000 ,将代理请求的响应写入当前响应。
错误过滤器:
SendErrorFilter
:执行顺序为 0,如果RequestContext.getThrowable()
不为null
,则转发到/ error
(默认情况下)。您可以通过设置error.path
属性来更改默认转发路径(/ error
)。
如果在 Zuul 过滤器生命周期的任何部分期间抛出异常,则执行错误过滤器。 仅当RequestContext.getThrowable()
不为null
时,才会运行SendErrorFilter
。 然后,它在请求中设置特定的javax.servlet.error.*
属性,并将请求转发到 Spring Boot 错误页面。
4.4.2 @EnableZuulProxy
支持的过滤器
创建DiscoveryClientRouteLocator
,用于从DiscoveryClient
(例如Eureka
)以及属性加载路径定义。 为DiscoveryClient
中的每个serviceId
创建一个路由。 添加新服务后,将刷新路由。
除了前面描述的过滤器之外,还支持以下过滤器(与普通的 Spring Bean 一样):
前置过滤器
PreDecorationFilter
:根据提供的RouteLocator
确定路由的位置和方式。 它还为下游请求设置各种与代理相关的请求头。
路由过滤器
RibbonRoutingFilter
:执行顺序为 10,使用Ribbon
,Hystrix
和可插入 HTTP 客户端发送请求。 服务 ID 位于RequestContext
属性FilterConstants.SERVICE_ID_KEY
中。 此过滤器可以使用不同的 HTTP 客户端:
Apache HttpClient
:默认客户端。Squareup OkHttpClient
v3:通过在类路径上设置com.squareup.okhttp3:okhttp
库并设置ribbon.okhttp.enabled = true
来启用。Netflix Ribbon HTTP
客户端:通过设置ribbon.restclient.enabled = true
启用。 此客户端具有限制,包括它不支持 PATCH 方法,但它也具有内置重试。
SimpleHostRoutingFilter
:通过 Apache HttpClient 向预定 URL 发送请求。 URL 位于RequestContext.getRouteHost()
中。
4.4.3 自定义过滤器
1 | public class QueryParamPreFilter extends ZuulFilter { |
前面的过滤器从样本请求参数填充SERVICE_ID_KEY
。 在实践中,您不应该进行这种直接映射。 相反,应该从样本的值中查找服务 ID。
现在已填充SERVICE_ID_KEY
,PreDecorationFilter
不会运行并且RibbonRoutingFilter
会运行。
4.5 跨域支持
默认情况下,Zuul 将所有跨源请求(CORS)路由到服务。 如果你想要 Zuul 来处理这些请求,可以通过提供自定义WebMvcConfigurer
bean 来完成:
1 | @Bean |
在上面的示例中,我们允许来自https://allowed-origin.com
的 GET 和 POST 方法将跨源请求发送到以 path-1 开头的端点。 您可以使用/ **
映射将 CORS 配置应用于特定路径模式或全局应用于整个应用程序。 您可以通过此配置自定义属性:allowedOrigins
,allowedMethods
,allowedHeaders
,exposedHeaders
,allowCredentials
和maxAge
。
注意 ,如果微服务里也设置了允许跨域,需要在 zuul 增加配置
1 | zuul.sensitive-headers=Access-Control-Allow-Origin |
即将跨域设置不传递到后面的微服务中。