路由是微服务架构不可或缺的一部分。例如,/可能被映射到您的web应用程序,/api/users被映射到用户服务,/api/shop被映射到商店服务。 Zuul是Netflix的基于JVM的路由器和服务器端负载平衡器。
Netflix将Zuul用于以下用途:
认证方式
见解
压力测试
金丝雀测试
动态路由
服务迁移
减载
安全
静态响应处理
主动/主动流量管理
Zuul的规则引擎可使用几乎所有JVM语言编写规则和过滤器,并内置对Java和Groovy的支持。
[注意]
配置属性zuul.max.host.connections已被两个新属性zuul.host.maxTotalConnections和zuul.host.maxPerRouteConnections取代,它们分别默认为200和20。
[注意]
所有路由的默认Hystrix隔离模式(ExecutionIsolationStrategy)为SEMAPHORE。如果首选该隔离模式,则可以将zuul.ribbonIsolationStrategy更改为THREAD。
1如何包含Zuul
要将Zuul包含在您的项目中,请使用组ID为org.springframework.cloud和工件ID为spring-cloud-starter-netflix-zuul的启动程序。有关使用当前Spring Cloud版本Train设置构建系统的详细信息,请参见Spring Cloud项目页面。
2嵌入式Zuul反向代理
Spring Cloud已创建嵌入式Zuul代理,以简化UI应用程序要对一个或多个后端服务进行代理调用的常见用例的开发。此功能对于用户界面代理所需的后端服务很有用,从而避免了为所有后端独立管理CORS和身份验证问题的需求。
要启用它,请用@EnableZuulProxy注释Spring Boot主类。这样做会导致将本地呼叫转发到适当的服务。按照约定,ID为users的服务从位于/users的代理接收请求(前缀被去除)。代理使用Ribbon来定位要通过发现转发到的实例。所有请求均在hystrix命令中执行,因此失败以Hystrix指标显示。一旦电路断开,代理就不会尝试与服务联系。
[注意]
Zuul入门程序不包含发现客户端,因此,对于基于服务ID的路由,您还需要在类路径上提供其中之一(Eureka是一种选择)。
要跳过自动添加服务的过程,请将zuul.ignored-services设置为服务ID模式的列表。如果服务与被忽略但已包含在显式配置的路由映射中的模式匹配,则将其忽略,如以下示例所示:
application.yml。
zuul:
ignoredServices: '*'
routes:
users: /myusers/**
在前面的示例中,除 users 外,所有服务均被忽略。
要增加或更改代理路由,可以添加外部配置,如下所示:
application.yml。
zuul:
routes:
users: /myusers/**
前面的示例意味着对/myusers的HTTP调用被转发到users服务(例如,/myusers/101被转发到/101)。
要对路由进行更细粒度的控制,可以分别指定路径和serviceId,如下所示:
application.yml。
zuul:
routes:
users:
path: /myusers/**
serviceId: users_service
前面的示例意味着对/myusers的HTTP调用将转发到users_service服务。路由必须具有可以指定为蚂蚁样式模式的path,因此/myusers/*仅匹配一个级别,而/myusers/**则分层匹配。
后端的位置可以指定为serviceId(用于发现服务)或url(用于物理位置),如以下示例所示:
application.yml。
zuul:
routes:
users:
path: /myusers/**
url: https://example.com/users_service
这些简单的url路由不会作为HystrixCommand来执行,也不会使用Ribbon对多个URL进行负载均衡。为了实现这些目标,可以使用静态服务器列表指定一个serviceId,如下所示:
application.yml。
zuul:
routes:
echo:
path: /myusers/**
serviceId: myusers-service
stripPrefix: true
hystrix:
command:
myusers-service:
execution:
isolation:
thread:
timeoutInMilliseconds: ...
myusers-service:
ribbon:
NIWSServerListClassName: com.netflix.loadbalancer.ConfigurationBasedServerList
listOfServers: https://example1.com,http://example2.com
ConnectTimeout: 1000
ReadTimeout: 3000
MaxTotalHttpConnections: 500
MaxConnectionsPerHost: 100
另一种方法是指定服务路由并为serviceId配置Ribbon客户端(这样做需要在Ribbon中禁用Eureka支持- 有关更多信息,请参见上文),如下所示例:
application.yml。
zuul:
routes:
users:
path: /myusers/**
serviceId: users
ribbon:
eureka:
enabled: false
users:
ribbon:
listOfServers: example.com,google.com
您可以使用regexmapper在serviceId和路由之间提供约定。它使用正则表达式命名组从serviceId中提取变量,并将其注入到路由模式中,如以下示例所示:
ApplicationConfiguration.java。
@Bean
public PatternServiceRouteMapper serviceRouteMapper() {
return new PatternServiceRouteMapper(
"(?<name>^.+)-(?<version>v.+$)",
"${version}/${name}");
}
前面的示例意味着myusers-v1中的serviceId被映射到路由/v1/myusers/**。可以接受任何正则表达式,但是所有命名组必须同时存在于servicePattern和routePattern中。如果servicePattern与serviceId不匹配,则使用默认行为。在前面的示例中,myusers中的serviceId被映射到“ / myusers / **”路由(未检测到版本)。默认情况下,此功能是禁用的,仅适用于发现的服务。
要为所有映射添加前缀,请将zuul.prefix设置为一个值,例如/api。默认情况下,在转发请求之前,将从请求中删除代理前缀(您可以使用zuul.stripPrefix=false将此行为关闭)。您还可以关闭从单个路由中剥离特定于服务的前缀,如以下示例所示:
application.yml。
zuul:
routes:
users:
path: /myusers/**
stripPrefix: false
[注意]
zuul.stripPrefix仅适用于zuul.prefix中设置的前缀。它对给定路由的path中定义的前缀没有任何影响。
在前面的示例中,对/myusers/101的请求被转发到users服务上的/myusers/101。
zuul.routes条目实际上绑定到类型为ZuulProperties的对象。如果查看该对象的属性,则可以看到它也有一个retryable标志。将该标志设置为true,以使Ribbon客户端自动重试失败的请求。当您需要修改使用Ribbon客户端配置的重试操作的参数时,也可以将该标志设置为true。
默认情况下,X-Forwarded-Host标头被添加到转发的请求中。要关闭它,请设置zuul.addProxyHeaders = false。默认情况下,前缀路径被剥离,并且后端请求使用X-Forwarded-Prefix标头(在前面显示的示例中为/myusers)。
如果设置默认路由(/),则带有@EnableZuulProxy的应用程序可以充当独立服务器。例如,zuul.route.home: /会将所有流量(“ / **”)路由到“ home”服务。
如果需要更细粒度的忽略,则可以指定要忽略的特定模式。这些模式在路线定位过程开始时进行评估,这意味着模式中应包含前缀以保证匹配。被忽略的模式跨越所有服务,并取代任何其他路由规范。以下示例显示了如何创建忽略的模式:
application.yml。
zuul:
ignoredPatterns: /**/admin/**
routes:
users: /myusers/**
前面的示例意味着所有呼叫(例如/myusers/101)都被转发到users服务上的/101。但是,包括/admin/在内的呼叫无法解决。
[警告]
如果您需要保留路由的顺序,则需要使用YAML文件,因为使用属性文件时顺序会丢失。以下示例显示了这样的YAML文件:
application.yml。
zuul:
routes:
users:
path: /myusers/**
legacy:
path: /**
如果要使用属性文件,则legacy路径可能最终位于users路径的前面,从而导致users路径不可访问。
3 Zuul HTTP客户端
Zuul使用的默认HTTP客户端现在由Apache HTTP客户端支持,而不是已弃用的Ribbon RestClient。要使用RestClient或okhttp3.OkHttpClient,请分别设置ribbon.restclient.enabled=true或ribbon.okhttp.enabled=true。如果要自定义Apache HTTP客户端或OK HTTP客户端,请提供类型为ClosableHttpClient或OkHttpClient的bean。
4 Cookie和敏感标题
您可以在同一系统中的服务之间共享标头,但您可能不希望敏感标头泄漏到下游到外部服务器中。您可以在路由配置中指定忽略的标头列表。Cookies发挥着特殊的作用,因为它们在浏览器中具有定义明确的语义,并且始终将它们视为敏感内容。如果代理的使用者是浏览器,那么下游服务的cookie也会给用户带来麻烦,因为它们都混杂在一起(所有下游服务看起来都来自同一位置)。
如果您对服务的设计很谨慎(例如,如果只有一个下游服务设置cookie),则可以让它们从后端一直流到调用者。另外,如果您的代理设置了cookie,并且您的所有后端服务都在同一系统中,则很自然地简单地共享它们(例如,使用Spring Session将它们链接到某些共享状态)。除此之外,由下游服务设置的任何cookie可能对调用者都无用,因此建议您将(至少)Set-Cookie和Cookie设置为敏感的标头,用于那些没有使用的路由您网域的一部分。即使对于属于您网域的路由,在让Cookie在它们和代理之间流动之前,也应仔细考虑其含义。
可以将敏感头配置为每个路由的逗号分隔列表,如以下示例所示:
application.yml。
zuul:
routes:
users:
path: /myusers/**
sensitiveHeaders: Cookie,Set-Cookie,Authorization
url: https://downstream
[注意]
这是sensitiveHeaders的默认值,因此除非您希望其与众不同,否则无需进行设置。这是Spring Cloud Netflix 1.1中的新功能(在1.0中,用户无法控制标题,并且所有cookie都双向流动)。
sensitiveHeaders是黑名单,默认值不为空。因此,要使Zuul发送所有标头(ignored除外),必须将其显式设置为空列表。如果要将Cookie或授权标头传递到后端,则必须这样做。以下示例显示如何使用sensitiveHeaders:
application.yml。
zuul:
routes:
users:
path: /myusers/**
sensitiveHeaders:
url: https://downstream
您还可以通过设置zuul.sensitiveHeaders来设置敏感标题。如果在路由上设置了sensitiveHeaders,它将覆盖全局sensitiveHeaders设置。
5忽略标题
除了路由敏感的标头之外,您还可以为与下游服务交互期间应丢弃的值(请求和响应)设置一个名为zuul.ignoredHeaders的全局值。默认情况下,如果Spring Security不在类路径中,则它们为空。否则,它们将初始化为Spring Security指定的一组众所周知的“ 安全性 ”标头(例如,涉及缓存)。在这种情况下的假设是,下游服务也可以添加这些标头,但是我们需要来自代理的值。要在类路径上有Spring Security时不丢弃这些众所周知的安全标头,可以将zuul.ignoreSecurityHeaders设置为false。如果您在Spring Security中禁用了HTTP安全响应标头,并希望由下游服务提供值,则这样做很有用。
6管理端点
默认情况下,如果将@EnableZuulProxy与Spring Boot Actuator结合使用,则将启用两个附加端点:
路线
筛选器
6.1路由端点
在/routes处的路由端点的GET返回已映射路由的列表:
GET /路线。
{
/stores/**: "http://localhost:8081"
}
可以通过将?format=details查询字符串添加到/routes来请求其他路由详细信息。这样做会产生以下输出:
获取/ routes / details。
{
"/stores/**": {
"id": "stores",
"fullPath": "/stores/**",
"location": "http://localhost:8081",
"path": "/**",
"prefix": "/stores",
"retryable": false,
"customSensitiveHeaders": false,
"prefixStripped": true
}
}
POST至/routes强制刷新现有路由(例如,当服务目录中发生更改时)。您可以通过将endpoints.routes.enabled设置为false来禁用此端点。
[注意]
路由应该自动响应服务目录中的更改,但是从POST到/routes是强制更改立即进行的一种方法。
6.2过滤器端点
过滤器端点/filters的GET按类型返回Zuul过滤器的映射。对于地图中的每种过滤器类型,您将获得该类型的所有过滤器的列表以及它们的详细信息。
7扼杀模式和局部Forwards
迁移现有应用程序或API时,常见的模式是“ 勒死 ”旧的端点,并用不同的实现方式慢慢替换它们。Zuul代理是一个有用的工具,因为您可以使用它来处理来自旧端点的客户端的所有流量,但可以将一些请求重定向到新请求。
以下示例显示“ 扼杀 ”方案的配置详细信息:
application.yml。
zuul:
routes:
first:
path: /first/**
url: https://first.example.com
second:
path: /second/**
url: forward:/second
third:
path: /third/**
url: forward:/3rd
legacy:
path: /**
url: https://legacy.example.com
在前面的示例中,我们扼杀了“ legacy ”应用程序,该应用程序映射到与其他模式之一不匹配的所有请求。/first/**中的路径已使用外部URL提取到新服务中。/second/**中的路径被转发,以便可以在本地处理(例如,使用普通Spring @RequestMapping)。/third/**中的路径也被转发,但是前缀不同(/third/foo被转发到/3rd/foo)。
[注意]
被忽略的模式不会被完全忽略,它们不会由代理处理(因此它们也可以在本地有效转发)。
8通过Zuul上传文件
如果使用@EnableZuulProxy,则可以使用代理路径上载文件,只要文件很小,它就可以正常工作。对于大文件,有一个替代路径可以绕过“ / zuul / *”中的Spring DispatcherServlet(以避免进行多部分处理)。换句话说,如果您拥有zuul.routes.customers=/customers/**,则可以将POST大文件复制到/zuul/customers/*。Servlet路径通过zuul.servletPath外部化。如果代理路由将您带到Ribbon负载均衡器,则超大文件也需要提高超时设置,如以下示例所示:
application.yml。
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds: 60000
ribbon:
ConnectTimeout: 3000
ReadTimeout: 60000
请注意,要使流技术处理大文件,您需要在请求中使用分块编码(某些浏览器默认不这样做),如以下示例所示:
$ curl -v -H "Transfer-Encoding: chunked" \
-F "file=@mylarge.iso" localhost:9999/zuul/simple/file
9查询字符串编码
在处理传入请求时,查询参数将被解码,以便可以在Zuul过滤器中进行修改。然后将它们重新编码,在路由过滤器中重建后端请求。例如,如果结果是使用Javascript的encodeURIComponent()方法编码的,则结果可能不同于原始输入。虽然这在大多数情况下不会引起问题,但某些web服务器可能对复杂查询字符串的编码很挑剔。
要强制对查询字符串进行原始编码,可以将特殊标志传递给ZuulProperties,以便使用HttpServletRequest::getQueryString方法按原样使用查询字符串,如以下示例所示:
application.yml。
zuul:
forceOriginalQueryStringEncoding: true
[注意]
该特殊标志仅适用于SimpleHostRoutingFilter。另外,您松开了使用RequestContext.getCurrentContext().setRequestQueryParams(someOverriddenParameters)轻松覆盖查询参数的功能,因为现在直接在原始HttpServletRequest上获取了查询字符串。
10请求URI编码
在处理传入请求时,在将请求URI与路由匹配之前,先对其进行解码。然后在路由过滤器中重建后端请求时,将对请求URI进行重新编码。如果您的URI包含编码的“ /”字符,则可能导致某些意外行为。
要使用原始请求URI,可以将特殊标志传递给'ZuulProperties',以便使用HttpServletRequest::getRequestURI方法按原样使用URI,如以下示例所示:
application.yml。
zuul:
decodeUrl: false
[注意]
如果使用requestURI RequestContext属性覆盖请求URI,并且此标志设置为false,则将不对在请求上下文中设置的URL进行编码。确保URL已被编码是您的责任。
11普通嵌入式Zuul
如果使用@EnableZuulServer(而不是@EnableZuulProxy),则也可以运行Zuul服务器而不进行代理或有选择地打开代理平台的某些部分。您添加到类型为ZuulFilter的应用程序中的所有beans都会自动安装(与@EnableZuulProxy一样),但是不会自动添加任何代理过滤器。
在这种情况下,仍然可以通过配置“ zuul.routes。*”来指定进入Zuul服务器的路由,但是没有服务发现也没有代理。因此,“ serviceId”和“ url”设置将被忽略。以下示例将“ / api / **”中的所有路径映射到Zuul过滤器链:
application.yml。
zuul:
routes:
api: /api/**
12禁用Zuul过滤器
Spring Cloud的Zuul带有多个ZuulFilter beans,默认情况下在代理和服务器模式下都启用。有关可以启用的过滤器列表,请参见Zuul过滤器包。如果要禁用一个,请设置zuul.<SimpleClassName>.<filterType>.disable=true。按照惯例,filters之后的软件包是Zuul过滤器类型。例如,要禁用org.springframework.cloud.netflix.zuul.filters.post.SendResponseFilter,请设置zuul.SendResponseFilter.post.disable=true。
13为路线提供Hystrix后备
当Zuul中给定路由的电路跳闸时,可以通过创建类型为FallbackProvider的bean提供回退响应。在此bean中,您需要指定回退的路由ID,并提供一个ClientHttpResponse作为回退的路由。以下示例显示了一个相对简单的FallbackProvider实现:
class MyFallbackProvider implements FallbackProvider {
@Override
public String getRoute() {
return "customers";
}
@Override
public ClientHttpResponse fallbackResponse(String route, final Throwable cause) {
if (cause instanceof HystrixTimeoutException) {
return response(HttpStatus.GATEWAY_TIMEOUT);
} else {
return response(HttpStatus.INTERNAL_SERVER_ERROR);
}
}
private ClientHttpResponse response(final HttpStatus status) {
return new ClientHttpResponse() {
@Override
public HttpStatus getStatusCode() throws IOException {
return status;
}
@Override
public int getRawStatusCode() throws IOException {
return status.value();
}
@Override
public String getStatusText() throws IOException {
return status.getReasonPhrase();
}
@Override
public void close() {
}
@Override
public InputStream getBody() throws IOException {
return new ByteArrayInputStream("fallback".getBytes());
}
@Override
public HttpHeaders getHeaders() {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
return headers;
}
};
}
}
以下示例显示了上一个示例的路由配置可能如何显示:
zuul:
routes:
customers: /customers/**
如果您想为所有路由提供默认后备,则可以创建类型为FallbackProvider的bean,并让getRoute方法返回*或null,如以下示例:
class MyFallbackProvider implements FallbackProvider {
@Override
public String getRoute() {
return "*";
}
@Override
public ClientHttpResponse fallbackResponse(String route, Throwable throwable) {
return new ClientHttpResponse() {
@Override
public HttpStatus getStatusCode() throws IOException {
return HttpStatus.OK;
}
@Override
public int getRawStatusCode() throws IOException {
return 200;
}
@Override
public String getStatusText() throws IOException {
return "OK";
}
@Override
public void close() {
}
@Override
public InputStream getBody() throws IOException {
return new ByteArrayInputStream("fallback".getBytes());
}
@Override
public HttpHeaders getHeaders() {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
return headers;
}
};
}
}
14 Zuul超时
如果要为通过Zuul代理的请求配置套接字超时和读取超时,则根据您的配置,有两个选项:
如果Zuul使用服务发现,则需要使用ribbon.ReadTimeout和ribbon.SocketTimeout Ribbon属性配置这些超时。
如果通过指定URL配置了Zuul路由,则需要使用zuul.host.connect-timeout-millis和zuul.host.socket-timeout-millis。
15重写Location标头
如果Zuul在web应用程序的前面,则当web应用程序通过HTTP状态代码3XX重定向时,您可能需要重新编写Location标头。否则,浏览器将重定向到web应用程序的URL,而不是Zuul URL。您可以配置LocationRewriteFilter Zuul过滤器,将Location标头重写为Zuul的URL。它还添加回去的全局前缀和特定于路由的前缀。以下示例通过使用Spring配置文件添加过滤器:
import org.springframework.cloud.netflix.zuul.filters.post.LocationRewriteFilter;
@Configuration
@EnableZuulProxy
public class ZuulConfig {
@Bean
public LocationRewriteFilter locationRewriteFilter() {
return new LocationRewriteFilter();
}
}
[警告]警告
小心使用此过滤器。筛选器作用于所有3XX响应代码的Location头,这可能并不适用于所有情况,例如将用户重定向到外部URL时。
16启用跨源请求
默认情况下,Zuul将所有跨源请求(CORS)路由到服务。如果您希望Zuul处理这些请求,可以通过提供自定义WebMvcConfigurer bean来完成:
@Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurer() {
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/path-1/**")
.allowedOrigins("https://allowed-origin.com")
.allowedMethods("GET", "POST");
}
};
}
在上面的示例中,我们允许https://allowed-origin.com中的GET和POST方法将跨域请求发送到以path-1开头的端点。您可以使用/**映射将CORS配置应用于特定的路径模式或整个应用程序的全局路径。您可以通过此配置来自定义属性:allowedOrigins,allowedMethods,allowedHeaders,exposedHeaders,allowCredentials和maxAge。
17指标
Zuul将在执行器指标终结点下提供指标,以解决路由请求时可能发生的任何故障。可以通过点击/actuator/metrics来查看这些指标。指标的名称格式为ZUUL::EXCEPTION:errorCause:statusCode。
18 Zuul开发人员指南
有关Zuul的工作原理的一般概述
1 Zuul Servlet
Zuul被实现为Servlet。对于一般情况,Zuul已嵌入Spring调度机制中。这使Spring MVC可以控制路由。在这种情况下,Zuul缓冲请求。如果需要通过Zuul而不缓冲请求(例如,用于大文件上传),则Servlet也将安装在Spring Dispatcher之外。缺省情况下,该servlet的地址为/zuul。可以使用zuul.servlet-path属性更改此路径。
2 Zuul RequestContext
要在过滤器之间传递信息,Zuul使用RequestContext。其数据保存在每个请求专用的ThreadLocal中。有关在何处路由请求,错误以及实际的HttpServletRequest和HttpServletResponse的信息存储在此处。RequestContext扩展了ConcurrentHashMap,因此任何内容都可以存储在上下文中。FilterConstants包含Spring Cloud Netflix安装的过滤器使用的密钥(稍后会详细介绍)。
3 @EnableZuulProxy与@EnableZuulServer
Spring Cloud Netflix安装了许多过滤器,具体取决于启用了Zuul的注释。@EnableZuulProxy是@EnableZuulServer的超集。换句话说,@EnableZuulProxy包含@EnableZuulServer安装的所有筛选器。“ 代理 ”中的其他过滤器启用路由功能。如果您想使用“ 空白 ” Zuul,则应使用@EnableZuulServer。
4 @EnableZuulServer过滤器
@EnableZuulServer创建一个SimpleRouteLocator,该文件从Spring Boot配置文件中加载路由定义。
已安装以下过滤器(按常规方式Spring Beans):
前置过滤器:
ServletDetectionFilter:检测请求是否通过Spring分派器进行。设置键为FilterConstants.IS_DISPATCHER_SERVLET_REQUEST_KEY的布尔值。
FormBodyWrapperFilter:解析表单数据并为下游请求重新编码。
DebugFilter:如果设置了debug请求参数,则将RequestContext.setDebugRouting()和RequestContext.setDebugRequest()设置为true。*路由过滤器:
SendForwardFilter:使用Servlet RequestDispatcher的Forwards请求。转发位置存储在RequestContext属性FilterConstants.FORWARD_TO_KEY中。这对于转发到当前应用程序中的端点很有用。
帖子过滤器:
SendResponseFilter:将代理请求的响应写入当前响应。
错误过滤器:
SendErrorFilter:如果RequestContext.getThrowable()不为空,则Forwards至/error(默认)。您可以通过设置error.path属性来更改默认转发路径(/error)。
5 @EnableZuulProxy过滤器
创建一个DiscoveryClientRouteLocator,它从DiscoveryClient(例如Eureka)以及属性中加载路由定义。从DiscoveryClient为每个serviceId创建一条路由。添加新服务后,将刷新路由。
除了前面描述的过滤器之外,还安装了以下过滤器(常规Spring Beans):
前置过滤器:
PreDecorationFilter:根据提供的RouteLocator确定路线和路线。它还为下游请求设置了各种与代理相关的标头。
路线过滤器:
RibbonRoutingFilter:使用Ribbon,Hystrix和可插拔的HTTP客户端发送请求。在RequestContext属性FilterConstants.SERVICE_ID_KEY中可以找到服务ID。此过滤器可以使用不同的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。可在RequestContext.getRouteHost()中找到URL。
6自定义Zuul过滤器示例
下面的大多数“如何编写”示例都包含在示例Zuul过滤器项目中。在该存储库中也有一些处理请求或响应正文的示例。
本节包括以下示例:
名为“如何编写预过滤器”的部分
名为“如何编写路由过滤器”的部分
名为“如何编写后置过滤器”的部分
如何编写前置过滤器
前置过滤器可在RequestContext中设置数据,以便在下游的过滤器中使用。主要用例是设置路由过滤器所需的信息。以下示例显示了Zuul前置过滤器:
public class QueryParamPreFilter extends ZuulFilter {
@Override
public int filterOrder() {
return PRE_DECORATION_FILTER_ORDER - 1; // run before PreDecoration
}
@Override
public String filterType() {
return PRE_TYPE;
}
@Override
public boolean shouldFilter() {
RequestContext ctx = RequestContext.getCurrentContext();
return !ctx.containsKey(FORWARD_TO_KEY) // a filter has already forwarded
&& !ctx.containsKey(SERVICE_ID_KEY); // a filter has already determined serviceId
}
@Override
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
if (request.getParameter("sample") != null) {
// put the serviceId in `RequestContext`
ctx.put(SERVICE_ID_KEY, request.getParameter("foo"));
}
return null;
}
}
前面的过滤器从sample请求参数中填充SERVICE_ID_KEY。实际上,您不应该执行这种直接映射。而是应从sample的值中查找服务ID。
现在已填充SERVICE_ID_KEY,PreDecorationFilter将不运行,而RibbonRoutingFilter将运行。
[提示]
如果要路由到完整URL,请致电ctx.setRouteHost(url)。
要修改路由过滤器转发到的路径,请设置REQUEST_URI_KEY。
如何编写路由过滤器
路由过滤器在预过滤器之后运行,并向其他服务发出请求。这里的许多工作是将请求和响应数据与客户端所需的模型相互转换。以下示例显示了Zuul路由过滤器:
public class OkHttpRoutingFilter extends ZuulFilter {
@Autowired
private ProxyRequestHelper helper;
@Override
public String filterType() {
return ROUTE_TYPE;
}
@Override
public int filterOrder() {
return SIMPLE_HOST_ROUTING_FILTER_ORDER - 1;
}
@Override
public boolean shouldFilter() {
return RequestContext.getCurrentContext().getRouteHost() != null
&& RequestContext.getCurrentContext().sendZuulResponse();
}
@Override
public Object run() {
OkHttpClient httpClient = new OkHttpClient.Builder()
// customize
.build();
RequestContext context = RequestContext.getCurrentContext();
HttpServletRequest request = context.getRequest();
String method = request.getMethod();
String uri = this.helper.buildZuulRequestURI(request);
Headers.Builder headers = new Headers.Builder();
Enumeration<String> headerNames = request.getHeaderNames();
while (headerNames.hasMoreElements()) {
String name = headerNames.nextElement();
Enumeration<String> values = request.getHeaders(name);
while (values.hasMoreElements()) {
String value = values.nextElement();
headers.add(name, value);
}
}
InputStream inputStream = request.getInputStream();
RequestBody requestBody = null;
if (inputStream != null && HttpMethod.permitsRequestBody(method)) {
MediaType mediaType = null;
if (headers.get("Content-Type") != null) {
mediaType = MediaType.parse(headers.get("Content-Type"));
}
requestBody = RequestBody.create(mediaType, StreamUtils.copyToByteArray(inputStream));
}
Request.Builder builder = new Request.Builder()
.headers(headers.build())
.url(uri)
.method(method, requestBody);
Response response = httpClient.newCall(builder.build()).execute();
LinkedMultiValueMap<String, String> responseHeaders = new LinkedMultiValueMap<>();
for (Map.Entry<String, List<String>> entry : response.headers().toMultimap().entrySet()) {
responseHeaders.put(entry.getKey(), entry.getValue());
}
this.helper.setResponse(response.code(), response.body().byteStream(),
responseHeaders);
context.setRouteHost(null); // prevent SimpleHostRoutingFilter from running
return null;
}
}
前面的过滤器将Servlet请求信息转换为OkHttp3请求信息,执行HTTP请求,并将OkHttp3响应信息转换为Servlet响应。
如何编写帖子过滤器
后置过滤器通常操纵响应。以下过滤器将随机UUID添加为X-Sample标头:
public class AddResponseHeaderFilter extends ZuulFilter {
@Override
public String filterType() {
return POST_TYPE;
}
@Override
public int filterOrder() {
return SEND_RESPONSE_FILTER_ORDER - 1;
}
@Override
public boolean shouldFilter() {
return true;
}
@Override
public Object run() {
RequestContext context = RequestContext.getCurrentContext();
HttpServletResponse servletResponse = context.getResponse();
servletResponse.addHeader("X-Sample", UUID.randomUUID().toString());
return null;
}
}
[注意]
其他操作,例如转换响应主体,则更加复杂且计算量大。
7 Zuul错误的工作方式
如果在Zuul过滤器生命周期的任何部分抛出异常,则将执行错误过滤器。仅当RequestContext.getThrowable()不是null时才运行SendErrorFilter。然后,它在请求中设置特定的javax.servlet.error.*属性,并将请求转发到Spring Boot错误页面。
8 Zuul急切的应用程序上下文加载
Zuul内部使用Ribbon来调用远程URL。默认情况下,Ribbon客户端在第一次调用时被Spring Cloud延迟加载。可以使用以下配置为Zuul更改此行为,这会导致在应用程序启动时急于加载与子Ribbon相关的应用程序上下文。以下示例显示了如何启用即时加载:
application.yml。
zuul:
ribbon:
eager-load:
enabled: true