Spring Cloud Document翻译(九)--路由器和过滤器:Zuul

18.路由器和过滤器:Zuul

路由是微服务架构不可或缺的一部分。例如,/可以映射到您的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。

[注意]

ExecutionIsolationStrategy所有路由的默认Hystrix隔离模式()是SEMAPHORE。 如果首选隔离模式,则zuul.ribbonIsolationStrategy可以更改为THREAD

18.1如何包含Zuul

要在项目中包含Zuul,请使用组ID为ID org.springframework.cloud且工件ID为的starter spring-cloud-starter-netflix-zuul。有关使用当前Spring Cloud Release Train设置构建系统的详细信息,请参阅Spring Cloud Project页面

18.2嵌入式Zuul反向代理

Spring Cloud创建了一个嵌入式Zuul代理,以简化UI应用程序想要对一个或多个后端服务进行代理调用的常见用例的开发。此功能对于用户界面代理其所需的后端服务非常有用,从而无需为所有后端独立管理CORS和身份验证问题。

要启用它,请使用注释Spring Boot主类@EnableZuulProxy。这样做会导致本地呼叫转发到适当的服务。按照惯例,具有ID的服务users接收来自位于/users(带有前缀剥离)的代理的请求。代理使用功能区来查找要通过发现转发的实例。所有请求都在hystrix命令中执行,因此Hystrix指标中会出现故障。电路打开后,代理不会尝试联系该服务。

[注意]

Zuul启动器不包含发现客户端,因此,对于基于服务ID的路由,您还需要在类路径中提供其中一个(Eureka是一种选择)。

要跳过自动添加的服务,请设置zuul.ignored-services为服务ID模式列表。如果服务与忽略但仍包含在显式配置的路由映射中的模式匹配,则它是不带号的,如以下示例所示:

application.yml。 

<span style="color:#333333"><span style="color:#000000"><span style="color:#7f007f">zuul</span>:
 <span style="color:#7f007f">  ignoredServices</span>:<span style="color:#2a00ff">'*' </span>
<span style="color:#7f007f">  routes</span>:
 <span style="color:#7f007f">    users</span>:/ myusers / **</span></span>

 

在上面的例子中,所有的服务都将被忽略,除了users

要扩充或更改代理路由,可以添加外部配置,如下所示:

application.yml。 

<span style="color:#333333"><span style="color:#000000"><span style="color:#7f007f">zuul</span>:
 <span style="color:#7f007f">  routes</span>:
 <span style="color:#7f007f">    users</span>:/ myusers / **</span></span>

 

前面的示例表示HTTP调用将/myusers转发到users服务(例如/myusers/101转发到/101)。

要对路由进行更细粒度的控制,可以单独指定路径和serviceId,如下所示:

application.yml。 

<span style="color:#333333"><span style="color:#000000"><span style="color:#7f007f">zuul</span>:
 <span style="color:#7f007f">  routes</span>:
 <span style="color:#7f007f">    users</span>:
 <span style="color:#7f007f">      path</span>:/ myusers / **
 <span style="color:#7f007f">      serviceId</span>:users_service</span></span>

 

前面的示例表示HTTP调用将/myusers转发到users_service服务。路径必须具有path可以指定为ant样式的模式,因此/myusers/*只匹配一个级别,但是/myusers/**分层匹配。

后端的位置可以指定为a serviceId(用于发现服务)或a url(用于物理位置),如以下示例所示:

application.yml。 

<span style="color:#333333"><span style="color:#000000"><span style="color:#7f007f">zuul</span>:
 <span style="color:#7f007f">  routes</span>:
 <span style="color:#7f007f">    users</span>:<span style="color:#7f007f">      path</span>:/ myusers / **
 <span style="color:#7f007f">      url</span>:https :
 <span style="color:#7f007f">//example.com/users_service</span></span></span>

 

这些简单的url-routes不会作为a执行HystrixCommand,也不会使用Ribbon对多个URL进行负载平衡。要实现这些目标,您可以使用serviceId静态服务器列表指定a ,如下所示:

application.yml。 

<span style="color:#333333"><span style="color:#000000"><span style="color:#7f007f">zuul</span>:
 <span style="color:#7f007f">  routes</span>:
 <span style="color:#7f007f">    echo</span>:
 <span style="color:#7f007f">      path</span>:/ myusers / **
 <span style="color:#7f007f">      serviceId</span>:myusers-service
 <span style="color:#7f007f">      stripPrefix</span>:<span style="color:#7f0055"><strong>true</strong></span>

<span style="color:#7f007f">hystrix</span>:
 <span style="color:#7f007f">  command</span>:
 <span style="color:#7f007f">    myusers-service</span>:
 <span style="color:#7f007f">      execution</span>:
 <span style="color:#7f007f">        isolation</span>:
 <span style="color:#7f007f">          thread</span>:
 <span style="color:#7f007f">            timeoutInMilliseconds</span>:...

<span style="color:#7f007f">myusers-service</span>:
 <span style="color:#7f007f">  ribbon</span>:
 <span style="color:#7f007f">    NIWSServerListClassName</span>:com.netflix.loadbalancer.ConfigurationBasedServerList
 <span style="color:#7f007f">    listOfServers</span>:https://example1.com,http://example2.com
 <span style="color:#7f007f">    ConnectTimeout</span>:1000 
<span style="color:#7f007f">    ReadTimeout</span>:3000 
<span style="color:#7f007f">    MaxTotalHttpConnections</span>:500 
<span style="color:#7f007f">    MaxConnectionsPerHost</span>:100</span></span>

 

另一种方法是指定服务路由并为其配置Ribbon客户端serviceId(这样做需要在Ribbon中禁用Eureka支持 - 请参阅上面的更多信息),如以下示例所示:

application.yml。 

<span style="color:#333333"><span style="color:#000000"><span style="color:#7f007f">zuul</span>:
 <span style="color:#7f007f">  routes</span>:
 <span style="color:#7f007f">    users</span>:
 <span style="color:#7f007f">      path</span>:/ myusers / **
 <span style="color:#7f007f">      serviceId</span>:users

<span style="color:#7f007f">ribbon</span>:
 <span style="color:#7f007f">  eureka</span>:
 <span style="color:#7f007f">    enabled</span>:<span style="color:#7f0055"><strong>false</strong></span>

<span style="color:#7f007f">用户</span>:
 <span style="color:#7f007f">  ribbon</span>:
 <span style="color:#7f007f">    listOfServers</span>:example.com,google.com</span></span>

 

您可以使用提供serviceId路径和路线之间的约定regexmapper。它使用正则表达式命名组从中提取变量serviceId并将其注入路由模式,如以下示例所示:

ApplicationConfiguration.java。 

<span style="color:#333333"><span style="color:#000000"><em><span style="color:gray">@Bean</span></em>
<span style="color:#7f0055"><strong> public</strong></span> PatternServiceRouteMapper serviceRouteMapper(){
    <span style="color:#7f0055"><strong> return </strong></span> <span style="color:#7f0055"><strong>new</strong></span> PatternServiceRouteMapper(
        <span style="color:#2a00ff"> “?<name> ^。+) - (?<version> v。+ $)”</span>,
        <span style="color:#2a00ff"> “$ {version} / $ {name}”</span>);
}</span></span>

 

上面的示例是指一个serviceIdmyusers-v1映射来路由/v1/myusers/**。接受任何正则表达式,但所有命名组必须同时存在于servicePatternroutePattern。如果servicePattern不匹配a serviceId,则使用默认行为。在前面的例子中,一个serviceIdmyusers被映射到“/ myusers / **”路线(没有检测到版本)。默认情况下禁用此功能,仅适用于已发现的服务。

要为所有映射添加前缀,请设置zuul.prefix为值,例如/api。默认情况下,在请求转发之前,会从请求中删除代理前缀(您可以关闭此行为zuul.stripPrefix=false)。您还可以关闭从各个路由中剥离特定于服务的前缀,如以下示例所示:

application.yml。 

<span style="color:#333333"><span style="color:#000000"><span style="color:#7f007f">zuul</span>:
 <span style="color:#7f007f">  routes</span>:
 <span style="color:#7f007f">    users</span>:
 <span style="color:#7f007f">      path</span>:/ myusers / **
 <span style="color:#7f007f">      stripPrefix</span>:<span style="color:#7f0055"><strong>false</strong></span></span></span>

 

[注意]

zuul.stripPrefix仅适用于设置的前缀zuul.prefix。它对给定路径中定义的前缀没有任何影响path

在前面的例子中,请求/myusers/101被转发到/myusers/101users服务。

zuul.routes条目实际上结合到类型的对象ZuulProperties。如果查看该对象的属性,可以看到它还有一个retryable标志。将该标志设置true为让Ribbon客户端自动重试失败的请求。您还可以true在需要修改使用功能区客户端配置的重试操作的参数时将该标志设置为。

默认情况下,X-Forwarded-Host标头会添加到转发的请求中。要关闭它,请设置zuul.addProxyHeaders = false。默认情况下,前缀路径被剥离,对后端的请求会获取一个X-Forwarded-Prefix标头(/myusers在前面显示的示例中)。

如果设置了默认路由(/),则应用程序@EnableZuulProxy可以充当独立服务器。例如,zuul.route.home: /将所有流量(“/ **”)路由到“home”服务。

如果需要更细粒度的忽略,则可以指定要忽略的特定模式。这些模式在路径定位过程开始时进行评估,这意味着前缀应包含在模式中以保证匹配。忽略的模式跨越所有服务并取代任何其他路由规范。以下示例显示如何创建忽略的模式:

application.yml。 

<span style="color:#333333"><span style="color:#000000"><span style="color:#7f007f">zuul</span>:
 <span style="color:#7f007f">  ignoredPatterns</span>:/ ** / admin / **
 <span style="color:#7f007f">  routes</span>:
 <span style="color:#7f007f">    users</span>:/ myusers / **</span></span>

 

前面的例子是指所有的呼叫(例如/myusers/101)被转发到/101users服务。但是,电话包括/admin/不解决。

[警告]

如果您需要路由保留其订单,则需要使用YAML文件,因为使用属性文件时排序会丢失。以下示例显示了这样的YAML文件:

application.yml。 

<span style="color:#333333"><span style="color:#000000"><span style="color:#7f007f">zuul</span>:
 <span style="color:#7f007f">  routes</span>:
 <span style="color:#7f007f">    users</span>:
 <span style="color:#7f007f">      path</span>:/ myusers / **
 <span style="color:#7f007f">    legacy</span>:
 <span style="color:#7f007f">      path</span>:/ **</span></span>

 

如果您要使用属性文件,则legacy路径可能最终位于users 路径前面,从而使users路径无法访问。

18.3 Zuul Http客户端

Zuul使用的默认HTTP客户端现在由Apache HTTP Client而不是已弃用的功能区支持RestClient。分别使用RestClientokhttp3.OkHttpClient设置ribbon.restclient.enabled=trueribbon.okhttp.enabled=true。如果要自定义Apache HTTP客户端或OK HTTP客户端,请提供类型为ClosableHttpClient或的bean OkHttpClient

18.4 Cookie和敏感标题

您可以在同一系统中的服务之间共享标头,但您可能不希望敏感标头向下游泄漏到外部服务器。您可以在路由配置中指定忽略的标头列表。Cookie起着特殊的作用,因为它们在浏览器中具有明确定义的语义,并且它们始终被视为敏感。如果您的代理的消费者是浏览器,那么下游服务的cookie也会给用户带来问题,因为它们都混杂起来(所有下游服务看起来都来自同一个地方)。

如果您对服务的设计非常小心(例如,如果只有一个下游服务设置了cookie),您可以让它们从后端一直流到调用者。此外,如果您的代理设置了cookie并且所有后端服务都属于同一系统,则可以很自然地简单地共享它们(例如,使用Spring Session将它们链接到某个共享状态)。除此之外,也得到由下游服务设置任何cookie可能是没有用的给调用者,所以建议你做(至少)Set-Cookie,并Cookie转化为不属于域的一部分,路线敏感头。即使对于属于您的域的路由,在让Cookie和代理之间流动之前,请仔细考虑它的含义。

可以将敏感标头配置为每个路由的逗号分隔列表,如以下示例所示:

application.yml。 

<span style="color:#333333"><span style="color:#000000"><span style="color:#7f007f">zuul</span>:
 <span style="color:#7f007f">  routes</span>:
 <span style="color:#7f007f">    users</span>:
 <span style="color:#7f007f">      path</span>:/ myusers / **
 <span style="color:#7f007f">      sensitiveHeaders</span>:Cookie,Set-Cookie,Authorization
 <span style="color:#7f007f">      url</span>:https:// downstream</span></span>

 

[注意]

这是默认值sensitiveHeaders,因此除非您希望它不同,否则无需进行设置。这是Spring Cloud Netflix 1.1中的新功能(在1.0中,用户无法控制标题,并且所有Cookie都在两个方向上流动)。

sensitiveHeaders是一个黑名单,默认是不为空。因此,要使Zuul发送所有标头(除了ignored那些标头),您必须将其明确设置为空列表。如果要将cookie或授权标头传递给后端,则必须这样做。以下示例显示如何使用sensitiveHeaders

application.yml。 

<span style="color:#333333"><span style="color:#000000"><span style="color:#7f007f">zuul</span>:
 <span style="color:#7f007f">  routes</span>:
 <span style="color:#7f007f">    users</span>:
 <span style="color:#7f007f">      path</span>:/ myusers / **
 <span style="color:#7f007f">      sensitiveHeaders</span>:
 <span style="color:#7f007f">      url</span>:https:// downstream</span></span>

 

您还可以通过设置来设置敏感标头zuul.sensitiveHeaders。如果sensitiveHeaders在路径上设置,则它将覆盖全局sensitiveHeaders设置。

18.5忽略标题

除路由敏感标头外,您还可以设置一个全局值zuul.ignoredHeaders,该值在请求和下游服务交互期间应丢弃的值(请求和响应)。默认情况下,如果Spring Security不在类路径中,则它们为空。否则,它们被初始化为一组众所周知的“ 安全 ”头文件(例如,涉及缓存),如Spring Security所指定的那样。在这种情况下的假设是下游服务也可能添加这些头,但我们想要代理的值。要在Spring Security位于类路径时不丢弃这些众所周知的安全标头,您可以设置zuul.ignoreSecurityHeadersfalse。如果您在Spring Security中禁用了HTTP安全响应标头并希望下游服务提供的值,那么这样做会非常有用。

18.6管理端点

默认情况下,如果使用@EnableZuulProxySpring Boot Actuator,则启用另外两个端点:

  • 路线
  • 过滤器

18.6.1路由端点

对路由端点的GET /routes返回映射路由的列表:

GET /路线。 

<span style="color:#333333"><span style="color:#000000"><span style="color:#7f0055"><strong>{</strong></span> 
  / stores / **:<span style="color:#2a00ff">“http:// localhost:8081” </span>
<span style="color:#7f0055"><strong>}</strong></span></span></span>

 

可以通过添加?format=details查询字符串来请求其他路由详细信息/routes。这样做会产生以下输出:

GET /路线/细节。 

<span style="color:#333333"><span style="color:#000000"><span style="color:#7f0055"><strong>{ </strong></span>
  <span style="color:#2a00ff">“/ stores / **”</span>:<span style="color:#7f0055"><strong>{ </strong></span>
    <span style="color:#2a00ff">“id”</span>:<span style="color:#2a00ff">“stores” </span><span style="color:#7f0055"><strong>,</strong></span>
    <span style="color:#2a00ff">“fullPath”</span>:<span style="color:#2a00ff">“/ stores / **” </span><span style="color:#7f0055"><strong>,</strong></span>
    <span style="color:#2a00ff">“location”</span>:<span style="color:#2a00ff">“http:// localhost:8081” </span><span style="color:#7f0055"><strong>,</strong></span>
    <span style="color:#2a00ff">“path”</span>:<span style="color:#2a00ff">“/ **“ </span><span style="color:#7f0055"><strong>,</strong></span>
    <span style="color:#2a00ff">”前缀“</span>:<span style="color:#2a00ff">”/ stores“ </span><span style="color:#7f0055"><strong>,</strong></span>
    <span style="color:#2a00ff">”retryable“</span>:<span style="color:#7f0055"><strong>false </strong></span><span style="color:#7f0055"><strong>,</strong></span>
    <span style="color:#2a00ff">”customSensitiveHeaders“</span>:<span style="color:#7f0055"><strong>false </strong></span><span style="color:#7f0055"><strong>,</strong></span>
    <span style="color:#2a00ff">”prefixStripped“</span>:<span style="color:#7f0055"><strong>true </strong></span>
  <span style="color:#7f0055"><strong>} </strong></span>
<span style="color:#7f0055"><strong>}</strong></span></span></span>

 

POST/routes现有路线的力量刷新(例如,当在服务目录进行了变更)。您可以通过设置endpoints.routes.enabled为禁用此端点false

[注意]

路由应自动响应服务目录中的更改,但POSTto /routes是强制更改立即发生的方法。

18.6.2过滤器端点

GET到过滤器端点/filters返回按类型的Zuul过滤器的映射。对于地图中的每种过滤器类型,您将获得该类型的所有过滤器及其详细信息的列表。

18.7扼杀模式和本地前锋

迁移现有应用程序或API时的一个常见模式是“ 扼杀 ”旧端点,慢慢用不同的实现替换它们。Zuul代理是一个有用的工具,因为您可以使用它来处理来自旧端点的客户端的所有流量,但将一些请求重定向到新端点。

以下示例显示“ strangle ”方案的配置详细信息:

application.yml。 

<span style="color:#333333"><span style="color:#000000"><span style="color:#7f007f">zuul</span>:
 <span style="color:#7f007f">  routes</span>:
 <span style="color:#7f007f">    first</span>:
 <span style="color:#7f007f">      path</span>:/ first / **
 <span style="color:#7f007f">      url</span>:https
 :<span style="color:#7f007f">//first.example.com </span><span style="color:#7f007f">    second</span>:
 <span style="color:#7f007f">      path</span>:/ second / **
 <span style="color:#7f007f">      url</span>:forward:/ second
 <span style="color:#7f007f">    third</span>:
 <span style="color:#7f007f">      path</span>:/ third / **
 <span style="color:#7f007f">      url</span>:转发:/ 3 rd
 <span style="color:#7f007f">    legacy</span>:
 <span style="color:#7f007f">      path</span>:/ **
 <span style="color:#7f007f">      url</span>:https://legacy.example.com</span></span>

 

在前面的示例中,我们扼杀了“ 遗留 ”应用程序,该应用程序映射到与其他模式之一不匹配的所有请求。/first/**已使用外部URL将路径提取到新服务中。/second/**转发路径以便可以在本地处理它们(例如,使用普通的Spring @RequestMapping)。路径/third/**也会被转发但具有不同的前缀(/third/foo转发到/3rd/foo)。

[注意]

忽略的模式不会被完全忽略,它们只是不由代理处理(因此它们也可以在本地有效转发)。

18.8通过Zuul上传文件

如果您使用@EnableZuulProxy,您可以使用代理路径上传文件,只要文件很小,它就可以使用。对于大文件,有一个替代路径绕过Spring DispatcherServlet(以避免多部分处理)在“/ zuul / *”中。换句话说,如果你有zuul.routes.customers=/customers/**,那么你可以将POST大文件/zuul/customers/*。servlet路径通过外部化zuul.servletPath。如果代理路由引导您完成功能区负载平衡器,则极大文件也需要提升超时设置,如以下示例所示:

application.yml。 

<span style="color:#333333"><span style="color:#000000"><span style="color:#7f007f">hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds</span>:60000 
<span style="color:#7f007f">功能区</span>:
 <span style="color:#7f007f">  ConnectTimeout</span>:3000 
<span style="color:#7f007f">  ReadTimeout</span>:60000</span></span>

 

请注意,要使用大型文件进行流式处理,您需要在请求中使用分块编码(默认情况下某些浏览器不会这样做),如以下示例所示:

<span style="color:#333333"><span style="color:#000000">$卷曲-v -H <span style="color:#2a00ff">“传输编码:分块”</span> \
    -F <span style="color:#2a00ff">“ [email protected] ”</span> localhost:9999 / zuul / simple / file</span></span>

18.9查询字符串编码

处理传入请求时,将对查询参数进行解码,以便它们可用于Zuul过滤器中的可能修改。然后对它们进行重新编码,在路由过滤器中重建后端请求。如果(例如)使用Javascript encodeURIComponent()方法编码,结果可能与原始输入不同。虽然这在大多数情况下不会引起任何问题,但某些Web服务器可能会因复杂查询字符串的编码而变得挑剔。

要强制查询字符串的原始编码,可以传递一个特殊标志,ZuulProperties以便使用该HttpServletRequest::getQueryString方法按原样获取查询字符串,如以下示例所示:

application.yml。 

<span style="color:#333333"><span style="color:#000000"><span style="color:#7f007f">zuul</span>:
 <span style="color:#7f007f">  forceOriginalQueryStringEncoding</span>:<span style="color:#7f0055"><strong>true</strong></span></span></span>

 

[注意]

这个特殊标志只适用于SimpleHostRoutingFilter。此外,您无法轻松覆盖查询参数RequestContext.getCurrentContext().setRequestQueryParams(someOverriddenParameters),因为现在直接在原始查询字符串上提取查询字符串HttpServletRequest

18.10请求URI编码

处理传入请求时,请求URI在将它们与路由匹配之前进行解码。然后,在路由过滤器中重建后端请求时,将重新编码请求URI。如果您的URI包含编码的“/”字符,这可能会导致一些意外的行为。

要使用原始请求URI,可以将特殊标志传递给'ZuulProperties',以便URI将按原样采用该HttpServletRequest::getRequestURI方法,如以下示例所示:

application.yml。 

<span style="color:#333333"><span style="color:#000000"><span style="color:#7f007f">zuul</span>:
 <span style="color:#7f007f">  decodeUrl</span>:<span style="color:#7f0055"><strong>false</strong></span></span></span>

 

[注意]

如果使用requestURIRequestContext属性覆盖请求URI 并且此标志设置为false,则不会对请求上下文中设置的URL进行编码。您有责任确保URL已经编码。

18.11普通嵌入式Zuul

如果您使用@EnableZuulServer(而不是@EnableZuulProxy),您还可以运行Zuul服务器,而无需代理或有选择地切换代理平台的某些部分。您添加到类型的应用程序的任何bean都会ZuulFilter自动安装(与它们一样@EnableZuulProxy),但不会自动添加任何代理过滤器。

在这种情况下,仍然通过配置“zuul.routes。*”来指定进入Zuul服务器的路由,但是没有服务发现和代理。因此,将忽略“serviceId”和“url”设置。以下示例将“/ api / **”中的所有路径映射到Zuul过滤器链:

application.yml。 

<span style="color:#333333"><span style="color:#000000"><span style="color:#7f007f">zuul</span>:
 <span style="color:#7f007f">  routes</span>:
 <span style="color:#7f007f">    api</span>:/ api / **</span></span>

 

18.12禁用Zuul过滤器

Zuul for Spring Cloud ZuulFilter在代理和服务器模式下都默认启用了许多bean。有关可以启用的过滤器列表,请参阅Zuul过滤器包。如果要禁用其中一个,请设置zuul.<SimpleClassName>.<filterType>.disable=true。按照惯例,后面的包filters是Zuul过滤器类型。例如,要禁用org.springframework.cloud.netflix.zuul.filters.post.SendResponseFilter,请设置zuul.SendResponseFilter.post.disable=true

18.13为路由提供Hystrix后备

当Zuul中给定路径的电路跳闸时,您可以通过创建类型的bean来提供回退响应FallbackProvider。在此bean中,您需要指定回退所针对的路由ID,并提供ClientHttpResponse返回作为回退。以下示例显示了一个相对简单的FallbackProvider实现:

<span style="color:#333333"><span style="color:#000000"><span style="color:#7f0055"><strong>class</strong></span> MyFallbackProvider <span style="color:#7f0055"><strong>实现</strong></span> FallbackProvider {

    <em><span style="color:gray">@Override</span></em>
    <span style="color:#7f0055"><strong> public</strong></span> String getRoute(){
        <span style="color:#7f0055"><strong> return </strong></span> <span style="color:#2a00ff">“customers”</span> ;
    }

    <em><span style="color:gray">@覆盖</span></em>
    <span style="color:#7f0055"><strong>公共</strong></span> ClientHttpResponse fallbackResponse(字符串途径,<span style="color:#7f0055"><strong>最终</strong></span>的Throwable原因){
        <span style="color:#7f0055"><strong>如果</strong></span>(原因<span style="color:#7f0055"><strong>的instanceof</strong></span> HystrixTimeoutException){
            <span style="color:#7f0055"><strong>返回</strong></span>响应(HttpStatus.GATEWAY_TIMEOUT);
        } <span style="color:#7f0055"><strong>else</strong></span> {
             <span style="color:#7f0055"><strong>return</strong></span> response(HttpStatus.INTERNAL_SERVER_ERROR);
        }
    }

    <span style="color:#7f0055"><strong>private</strong></span> ClientHttpResponse响应(<span style="color:#7f0055"><strong>最终</strong></span> HttpStatus 状态){
         <span style="color:#7f0055"><strong>return </strong></span> <span style="color:#7f0055"><strong>new</strong></span> ClientHttpResponse(){ <em><span style="color:gray">@</span></em>
             Override <span style="color:#7f0055"><strong>public</strong></span> HttpStatus
             getStatusCode()<span style="color:#7f0055"><strong>throws</strong></span> IOException {
                 <span style="color:#7f0055"><strong>return</strong></span> status;
            }

            <em><span style="color:gray">@覆盖</span></em>
            <span style="color:#7f0055"><strong>公共</strong></span> <span style="color:#7f0055"><strong>INT</strong></span> getRawStatusCode()<span style="color:#7f0055"><strong>抛出</strong></span> IOException异常{
                <span style="color:#7f0055"><strong>返回</strong></span> status.value();
            }

            <em><span style="color:gray">@Override</span></em>
            <span style="color:#7f0055"><strong> public</strong></span> String getStatusText()<span style="color:#7f0055"><strong>抛出</strong></span> IOException {
                <span style="color:#7f0055"><strong> return</strong></span> status.getReasonPhrase();
            }

            <em><span style="color:gray">@Override</span></em>
            <span style="color:#7f0055"><strong> public </strong></span> <span style="color:#7f0055"><strong>void</strong></span> close(){
            }

            <em><span style="color:gray">@覆盖</span></em>
            <span style="color:#7f0055"><strong>公众</strong></span>的InputStream getBody()<span style="color:#7f0055"><strong>抛出</strong></span> IOException异常{
                <span style="color:#7f0055"><strong>返回</strong></span> <span style="color:#7f0055"><strong>新</strong></span> ByteArrayInputStream的(<span style="color:#2a00ff"> “回退”</span> .getBytes());
            }

            <em><span style="color:gray">@覆盖</span></em>
            <span style="color:#7f0055"><strong>公共</strong></span> HttpHeaders getHeaders(){
                HttpHeaders headers = <span style="color:#7f0055"><strong>new</strong></span> HttpHeaders();
                headers.setContentType(MediaType.APPLICATION_JSON);
                <span style="color:#7f0055"><strong>返回</strong></span>标题;
            }
        };
    }
}</span></span>

以下示例显示了上一个示例的路由配置可能如何显示:

<span style="color:#333333"><span style="color:#000000"><span style="color:#7f007f">zuul</span>:
 <span style="color:#7f007f">  routes</span>:
 <span style="color:#7f007f">    customers</span>:/ customers / **</span></span>

如果要为所有路由提供默认回退,可以创建类型的bean FallbackProvider并使getRoute方法返回*null,如以下示例所示:

<span style="color:#333333"><span style="color:#000000"><span style="color:#7f0055"><strong>类</strong></span> MyFallbackProvider <span style="color:#7f0055"><strong>实现</strong></span> FallbackProvider {
     <em><span style="color:gray">@覆盖</span></em>
    <span style="color:#7f0055"><strong>公共</strong></span>字符串getRoute(){
         <span style="color:#7f0055"><strong>回报</strong></span> <span style="color:#2a00ff">“*”</span> ;
    }

    <em><span style="color:gray">@覆盖</span></em>
    <span style="color:#7f0055"><strong>公共</strong></span> ClientHttpResponse fallbackResponse(字符串路线,抛出的Throwable){
        <span style="color:#7f0055"><strong>返回</strong></span> <span style="color:#7f0055"><strong>新</strong></span> ClientHttpResponse(){
            <em><span style="color:gray"> @覆盖</span></em>
            <span style="color:#7f0055"><strong>公共</strong></span>的HTTPStatus getStatusCode()<span style="color:#7f0055"><strong>抛出</strong></span> IOException异常{
                <span style="color:#7f0055"><strong>返回</strong></span> HttpStatus.OK;
            }

            <em><span style="color:gray">@覆盖</span></em>
            <span style="color:#7f0055"><strong>公共</strong></span> <span style="color:#7f0055"><strong>INT</strong></span> getRawStatusCode()<span style="color:#7f0055"><strong>抛出</strong></span> IOException异常{
                <span style="color:#7f0055"><strong>返回</strong></span> 200 ;
            }

            <em><span style="color:gray">@Override</span></em>
            <span style="color:#7f0055"><strong> public</strong></span> String getStatusText()<span style="color:#7f0055"><strong>抛出</strong></span> IOException {
                <span style="color:#7f0055"><strong> return </strong></span> <span style="color:#2a00ff">“OK”</span> ;
            }

            <em><span style="color:gray">@Override</span></em>
            <span style="color:#7f0055"><strong> public </strong></span> <span style="color:#7f0055"><strong>void</strong></span> close(){

            }

            <em><span style="color:gray">@覆盖</span></em>
            <span style="color:#7f0055"><strong>公众</strong></span>的InputStream getBody()<span style="color:#7f0055"><strong>抛出</strong></span> IOException异常{
                <span style="color:#7f0055"><strong>返回</strong></span> <span style="color:#7f0055"><strong>新</strong></span> ByteArrayInputStream的(<span style="color:#2a00ff"> “回退”</span> .getBytes());
            }

            <em><span style="color:gray">@覆盖</span></em>
            <span style="color:#7f0055"><strong>公共</strong></span> HttpHeaders getHeaders(){
                HttpHeaders headers = <span style="color:#7f0055"><strong>new</strong></span> HttpHeaders();
                headers.setContentType(MediaType.APPLICATION_JSON);
                <span style="color:#7f0055"><strong>返回</strong></span>标题;
            }
        };
    }
}</span></span>

18.14 Zuul超时

如果要为通过Zuul代理的请求配置套接字超时和读取超时,则根据您的配置有两个选项:

  • 如果Zuul使用服务发现,则需要使用ribbon.ReadTimeoutribbon.SocketTimeout功能区属性配置这些超时 。

如果您通过指定URL配置了Zuul路由,则需要使用 zuul.host.connect-timeout-milliszuul.host.socket-timeout-millis

18.15重写Location标题

如果Zuul面向Web应用程序,则Location当Web应用程序通过HTTP状态代码重定向时,您可能需要重新编写标头3XX。否则,浏览器会重定向到Web应用程序的URL而不是Zuul URL。您可以配置LocationRewriteFilterZuul过滤器以将Location标头重新写入Zuul的URL。它还会添加剥离的全局和路由特定前缀。以下示例使用Spring配置文件添加过滤器:

<span style="color:#333333"><span style="color:#000000"><span style="color:#7f0055"><strong>import</strong></span> org.springframework.cloud.netflix.zuul.filters.post.LocationRewriteFilter;
...

<em><span style="color:gray">@Configuration </span></em>
<em><span style="color:gray">@EnableZuulProxy</span></em>
<span style="color:#7f0055"><strong> public </strong></span> <span style="color:#7f0055"><strong>class</strong></span> ZuulConfig {
    <em><span style="color:gray"> @Bean</span></em>
    <span style="color:#7f0055"><strong> public</strong></span> LocationRewriteFilter locationRewriteFilter(){
        <span style="color:#7f0055"><strong> return </strong></span> <span style="color:#7f0055"><strong>new</strong></span> LocationRewriteFilter();
    }
}</span></span>
[警告] 警告

仔细使用此过滤器。过滤器作用于Location所有3XX响应代码的标头,这可能不适用于所有情况,例如将用户重定向到外部URL时。

18.16启用跨源请求

默认情况下,Zuul将所有跨源请求(CORS)路由到服务。如果你想要Zuul来处理这些请求,可以通过提供自定义WebMvcConfigurerbean 来完成:

<span style="color:#333333"><span style="color:#000000"><em><span style="color:gray">@Bean</span></em>
<span style="color:#7f0055"><strong> public</strong></span> WebMvcConfigurer corsConfigurer(){
    <span style="color:#7f0055"><strong> return </strong></span> <span style="color:#7f0055"><strong>new</strong></span> WebMvcConfigurer(){
        <span style="color:#7f0055"><strong> public </strong></span> <span style="color:#7f0055"><strong>void</strong></span> addCorsMappings(CorsRegistry registry){
            registry.addMapping(<span style="color:#2a00ff">“/ path-1 / **”</span>)
                    .allowedOrigins(<span style="color:#2a00ff">“https://allowed-origin.com”</span>)
                    .allowedMethods(<span style="color:#2a00ff">“GET”</span>,<span style="color:#2a00ff">“POST”</span>);
        }
    };
}</span></span>

在上面的示例中,我们允许GETPOST方法从https://allowed-origin.com开头发送跨源请求到端点path-1。您可以使用/**映射将CORS配置应用于特定路径模式,也可以全局应用于整个应用程序。您可以自定义属性:allowedOriginsallowedMethodsallowedHeadersexposedHeadersallowCredentialsmaxAge通过此配置。

18.17度量标准

对于路由请求时可能发生的任何故障,Zuul将在Actuator指标端点下提供指标。点击即可查看这些指标/actuator/metrics。度量标准将具有格式的名称ZUUL::EXCEPTION:errorCause:statusCode

18.18 Zuul开发人员指南

有关Zuul如何工作的一般概述,请参阅Zuul Wiki

18.18.1 Zuul Servlet

Zuul是作为Servlet实现的。对于一般情况,Zuul嵌入到Spring Dispatch机制中。这让Spring MVC可以控制路由。在这种情况下,Zuul缓冲请求。如果需要在没有缓冲请求的情况下通过Zuul(例如,对于大型文件上载),Servlet也会安装在Spring Dispatcher之外。默认情况下,servlet的地址为/zuul。可以使用zuul.servlet-path属性更改此路径。

18.18.2 Zuul RequestContext

为了在过滤器之间传递信息,Zuul使用了RequestContext。其数据保存在ThreadLocal每个请求的特定数据中。有关在何处路由请求,错误和实际信息HttpServletRequest以及HttpServletResponse存储在那里的信息。在RequestContext扩展ConcurrentHashMap,所以什么都可以存储在上下文。FilterConstants包含Spring Cloud Netflix安装的过滤器使用的密钥(稍后将详细介绍)。

18.18.3  @EnableZuulProxyvs.@EnableZuulServer

Spring Cloud Netflix安装了许多过滤器,具体取决于使用哪个注释来启用Zuul。@EnableZuulProxy是一个超集@EnableZuulServer。换句话说,@EnableZuulProxy包含安装的所有过滤器@EnableZuulServer。“ 代理 ”中的其他过滤器启用路由功能。如果你想要一个“ 空白 ” Zuul,你应该使用@EnableZuulServer

18.18.4  @EnableZuulServer过滤器

@EnableZuulServer创建一个SimpleRouteLocator从Spring Boot配置文件加载路由定义。

安装以下过滤器(与普通的Spring Bean一样):

  • 预过滤器:

    • ServletDetectionFilter:检测请求是否通过Spring Dispatcher。设置一个键为的布尔值FilterConstants.IS_DISPATCHER_SERVLET_REQUEST_KEY
    • FormBodyWrapperFilter:解析表单数据并为下游请求重新编码。
    • DebugFilter:如果debug请求参数被设置,设置RequestContext.setDebugRouting()RequestContext.setDebugRequest()true。*路线过滤器:
    • SendForwardFilter:使用Servlet转发请求RequestDispatcher。转发位置存储在RequestContext属性中FilterConstants.FORWARD_TO_KEY。这对于转发到当前应用程序中的端点很有用。
  • 过滤后:

    • SendResponseFilter:将代理请求的响应写入当前响应。
  • 错误过滤器:

    • SendErrorFilter/error如果RequestContext.getThrowable()不为null,则转发到(默认情况下)。您可以/error通过设置error.path属性来更改默认转发路径()。

18.18.5  @EnableZuulProxy过滤器

创建一个DiscoveryClientRouteLocatorDiscoveryClient(例如Eureka)以及属性加载路径定义的方法。路线为每个创建serviceId从所述DiscoveryClient。添加新服务后,将刷新路由。

除了前面描述的过滤器之外,还安装了以下过滤器(与普通的Spring Bean一样):

  • 预过滤器:

    • PreDecorationFilter:根据提供的内容确定路由的位置和方式RouteLocator。它还为下游请求设置各种与代理相关的标头。
  • 路线过滤器:

    • RibbonRoutingFilter:使用Ribbon,Hystrix和可插入HTTP客户端发送请求。可在RequestContext属性中找到服务ID FilterConstants.SERVICE_ID_KEY。此过滤器可以使用不同的HTTP客户端:

      • Apache HttpClient:默认客户端。
      • Squareup OkHttpClientv3:通过com.squareup.okhttp3:okhttp在类路径和设置上设置库来启用ribbon.okhttp.enabled=true
      • Netflix Ribbon HTTP客户端:通过设置启用ribbon.restclient.enabled=true。此客户端具有限制,包括它不支持PATCH方法,但它也具有内置重试。
    • SimpleHostRoutingFilter:通过Apache HttpClient向预定URL发送请求。网址可在中找到RequestContext.getRouteHost()

18.18.6自定义Zuul过滤器示例

下面的大多数“如何写”示例包括Sample Zuul Filters项目。还有一些操作该存储库中的请求或响应主体的示例。

本节包括以下示例:

如何编写预过滤器

预过滤器RequestContext在下游过滤器中设置数据。主要用例是设置路由过滤器所需的信息。以下示例显示了Zuul预过滤器:

<span style="color:#333333"><span style="color:#000000"><span style="color:#7f0055"><strong>公共</strong></span> <span style="color:#7f0055"><strong>类</strong></span> QueryParamPreFilter <span style="color:#7f0055"><strong>延伸</strong></span> ZuulFilter {
	 <em><span style="color:gray">@覆盖</span></em>
	<span style="color:#7f0055"><strong>公共</strong></span> <span style="color:#7f0055"><strong>INT</strong></span> filterOrder(){
		 <span style="color:#7f0055"><strong>返回</strong></span> PRE_DECORATION_FILTER_ORDER - 1 ; <span style="color:#3f5f5f"><em>//在PreDecoration之前运行</em></span>
	}

	<em><span style="color:gray">@Override</span></em>
	<span style="color:#7f0055"><strong> public</strong></span> String filterType(){
		<span style="color:#7f0055"><strong> return</strong></span> PRE_TYPE;
	}

	<em><span style="color:gray">@覆盖</span></em>
	<span style="color:#7f0055"><strong>公共</strong></span> <span style="color:#7f0055"><strong>布尔</strong></span> shouldFilter(){
		RequestContext ctx = RequestContext.getCurrentContext();
		<span style="color:#7f0055"><strong>return</strong></span>!ctx.containsKey(FORWARD_TO_KEY)<span style="color:#3f5f5f"><em>//过滤器已经转发了</em></span> 
				&&!ctx.containsKey(SERVICE_ID_KEY); <span style="color:#3f5f5f"><em>//过滤器已经确定了serviceId</em></span>
	}
    <em><span style="color:gray">@Override</span></em>
    <span style="color:#7f0055"><strong> public</strong></span> Object run(){
        RequestContext ctx = RequestContext.getCurrentContext();
		HttpServletRequest request = ctx.getRequest();
		<span style="color:#7f0055"><strong>if</strong></span>(request.getParameter(<span style="color:#2a00ff">“sample”</span>)!= null){
		     <span style="color:#3f5f5f"><em>//将serviceId放在`</em></span> 
    		RequestContext`中ctx.put(SERVICE_ID_KEY,request.getParameter(<span style="color:#2a00ff">“foo”</span>));
    	}
        <span style="color:#7f0055"><strong>return</strong></span> null;
    }
}</span></span>

前面的过滤器SERVICE_ID_KEYsample请求参数填充。在实践中,您不应该进行这种直接映射。相反,应该从相应的值中查找服务ID sample

现在SERVICE_ID_KEY已填充,PreDecorationFilter不运行并RibbonRoutingFilter运行。

[小费]

如果您想要路由到完整的URL,请致电ctx.setRouteHost(url)

要修改路由过滤器转发的路径,请设置REQUEST_URI_KEY

如何编写路由过滤器

路由过滤器在预过滤器后运行并向其他服务发出请求。这里的大部分工作是将请求和响应数据转换为客户端所需的模型。以下示例显示了Zuul路由过滤器:

<span style="color:#333333"><span style="color:#000000"><span style="color:#7f0055"><strong>公共</strong></span> <span style="color:#7f0055"><strong>类</strong></span> OkHttpRoutingFilter <span style="color:#7f0055"><strong>扩展了</strong></span> ZuulFilter {
	 <em><span style="color:gray">@Autowired</span></em>
	<span style="color:#7f0055"><strong> private</strong></span> ProxyRequestHelper helper;

	<em><span style="color:gray">@Override</span></em>
	<span style="color:#7f0055"><strong> public</strong></span> String filterType(){
		<span style="color:#7f0055"><strong> return</strong></span> ROUTE_TYPE;
	}

	<em><span style="color:gray">@覆盖</span></em>
	<span style="color:#7f0055"><strong>公共</strong></span> <span style="color:#7f0055"><strong>INT</strong></span> filterOrder(){
		<span style="color:#7f0055"><strong>返回</strong></span> SIMPLE_HOST_ROUTING_FILTER_ORDER - 1 ;
	}

	<em><span style="color:gray">@覆盖</span></em>
	<span style="color:#7f0055"><strong>公共</strong></span> <span style="color:#7f0055"><strong>布尔</strong></span> shouldFilter(){
		<span style="color:#7f0055"><strong>返回</strong></span> RequestContext.getCurrentContext()。getRouteHost()!= NULL
				&& RequestContext.getCurrentContext()。sendZuulResponse();
	}

    <em><span style="color:gray">@Override</span></em>
    <span style="color:#7f0055"><strong> public</strong></span> Object run(){
		OkHttpClient httpClient = <span style="color:#7f0055"><strong>new</strong></span> OkHttpClient.Builder()
				 <span style="color:#3f5f5f"><em>//自定义</em></span>
				。建立();

		RequestContext context = RequestContext.getCurrentContext();
		HttpServletRequest request = context.getRequest();

		String method = request.getMethod();

		String uri = <span style="color:#7f0055"><strong>this</strong></span> .helper.buildZuulRequestURI(request);

		Headers.Builder headers = <span style="color:#7f0055"><strong>new</strong></span> Headers.Builder();
		枚举<String> headerNames = request.getHeaderNames();
		<span style="color:#7f0055"><strong>while</strong></span>(headerNames.hasMoreElements()){
			String name = headerNames.nextElement();
			枚举<String> values = request.getHeaders(name);

			<span style="color:#7f0055"><strong>while</strong></span>(values.hasMoreElements()){
				String value = values.nextElement();
				headers.add(name,value);
			}
		}

		InputStream inputStream = request.getInputStream();

		RequestBody requestBody = null;
		<span style="color:#7f0055"><strong>if</strong></span>(inputStream!= null && HttpMethod.permitsRequestBody(method)){
			MediaType mediaType = null;
			<span style="color:#7f0055"><strong>if</strong></span>(headers.get(<span style="color:#2a00ff">“Content-Type”</span>)!= null){
				mediaType = MediaType.parse(headers.get(<span style="color:#2a00ff">“Content-Type”</span>));
			}
			requestBody = RequestBody.create(mediaType,StreamUtils.copyToByteArray(inputStream));
		}

		Request.Builder builder = <span style="color:#7f0055"><strong>new</strong></span> Request.Builder()
				.headers(headers.build())
				.URL(URI)
				.method(method,requestBody);

		响应响应= httpClient.newCall(builder.build())。execute();

		LinkedMultiValueMap <String,String> responseHeaders = <span style="color:#7f0055"><strong>new</strong></span> LinkedMultiValueMap <>();

		<span style="color:#7f0055"><strong>for</strong></span>(Map.Entry <String,List <String >> entry:response.headers()。toMultimap()。entrySet()){
			responseHeaders.put(entry.getKey(),entry.getValue());
		}

		<span style="color:#7f0055"><strong>这个</strong></span> .helper.setResponse(response.code(),response.body()。byteStream(),
				responseHeaders响应);
		context.setRouteHost(NULL); <span style="color:#3f5f5f"><em>//阻止SimpleHostRoutingFilter运行</em></span>
		<span style="color:#7f0055"><strong>return</strong></span> null;
    }
}</span></span>

前面的过滤器将Servlet请求信息转换为OkHttp3请求信息,执行HTTP请求,并将OkHttp3响应信息转换为Servlet响应。

如何编写后置过滤器

后置过滤器通常会操纵响应。下面过滤器添加一个随机UUID作为X-Sample标头:

<span style="color:#333333"><span style="color:#000000"><span style="color:#7f0055"><strong>公共</strong></span> <span style="color:#7f0055"><strong>类</strong></span> AddResponseHeaderFilter <span style="color:#7f0055"><strong>延伸</strong></span> ZuulFilter {
	 <em><span style="color:gray">@覆盖</span></em>
	<span style="color:#7f0055"><strong>公共</strong></span>字符串过滤式(){
		 <span style="color:#7f0055"><strong>返回</strong></span> POST_TYPE;
	}

	<em><span style="color:gray">@覆盖</span></em>
	<span style="color:#7f0055"><strong>公共</strong></span> <span style="color:#7f0055"><strong>INT</strong></span> filterOrder(){
		<span style="color:#7f0055"><strong>返回</strong></span> SEND_RESPONSE_FILTER_ORDER - 1 ;
	}

	<em><span style="color:gray">@覆盖</span></em>
	<span style="color:#7f0055"><strong>公共</strong></span> <span style="color:#7f0055"><strong>布尔</strong></span> shouldFilter(){
		<span style="color:#7f0055"><strong>返回</strong></span>真;
	}

	<em><span style="color:gray">@Override</span></em>
	<span style="color:#7f0055"><strong> public</strong></span> Object run(){
		RequestContext context = RequestContext.getCurrentContext();
    	HttpServletResponse servletResponse = context.getResponse();
		servletResponse.addHeader(<span style="color:#2a00ff">“X-Sample”</span>,UUID.randomUUID()。toString());
		<span style="color:#7f0055"><strong>return</strong></span> null;
	}
}</span></span>
[注意]

其他操作(例如转换响应体)要复杂得多且计算量大。

18.18.7 Zuul错误如何工作

如果在Zuul过滤器生命周期的任何部分期间抛出异常,则执行错误过滤器。SendErrorFilter只有在RequestContext.getThrowable()没有的情况下才会运行null。然后,它javax.servlet.error.*在请求中设置特定属性,并将请求转发到Spring Boot错误页面。

18.18.8 Zuul Eager应用程序上下文加载

Zuul内部使用Ribbon来调用远程URL。默认情况下,Spring Cloud在第一次调用时会延迟加载Ribbon客户端。可以使用以下配置更改Zuul的此行为,这会导致在应用程序启动时急切加载与子功能区相关的应用程序上下文。以下示例显示如何启用预先加载:

application.yml。 

<span style="color:#333333"><span style="color:#000000">zuul:
  带:
    渴望负荷:
      启用:true</span></span>
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章