Spring Cloud Netflix之路由器和过滤器: Zuul

本文详细介绍了Spring Cloud Netflix组件Zuul的使用,包括如何引入Zuul,设置嵌入式Zuul反向代理,以及Zuul的路由、过滤器、HTTP客户端、Cookie与敏感头部管理、忽略头部、管理端点等多个方面的配置和功能。Zuul作为一个路由器和服务器端负载均衡器,可用于身份验证、洞察、压力测试等场景。文章还探讨了Zuul的开发者指南,提供了编写自定义过滤器的示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

路由是微服务体系结构的一个组成部分。例如,/可能映射到web应用程序,/api/users映射到用户服务,/api/shop映射到商店服务。Zuul是Netflix基于JVM的路由器和服务器端负载均衡器。

Netflix使用Zuul做以下用途:

  • 身份验证
  • 洞察
  • 压力测试

  • 金丝雀测试
  • 动态路由
  • 服务迁移
  • 甩负荷
  • 安全性
  • 静态响应处理
  • 主动/主动交通管理

zuul的规则引擎允许基本上用任何JVM语言编写规则和过滤器,并且内置了对Java和Groovy的支持。

配置属性zuul.max.host.connections已被两个新属性zuul.host.maxTotalConnections and zuul.host.maxPerRouteConnections, 默认值分别为200和20。

所有路由的默认Hystrix隔离模式(ExecutionIsolationStrategy)是信号量。如果首选THREAD隔离模式,zuul.ribbonIsolationStrategy可被设置为THREAD。

怎样引入ZUUL

 group org.springframework.cloud and artifact id spring-cloud-starter-netflix-zuul

嵌入式Zuul反向代理

Spring Cloud创建了一个嵌入式Zuul代理,以简化一个非常常见的用例的开发,在这个用例中,UI应用程序希望代理调用一个或多个后端服务。对于用户界面来说,该特性非常有用,可以代理到所需的后端服务,避免了独立管理所有后端的CORS和身份验证问题。

要启用它,请使用@EnableZuulProxy注释Spring启动主类,并将本地调用转发到适当的服务。按照惯例,ID为“users”的服务将从位于/users的代理接收请求(去掉前缀)。代理使用Ribbon定位要通过discovery转发的实例,所有请求都在hystrix命令中执行,因此失败将显示在hystrix metrics中,一旦电路打开,代理将不会尝试联系服务。

要跳过自动添加服务,请设置zuul.ignored-serviceso哦服务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”服务。路由必须有一个可以指定为ant样式模式的“路径”,因此“/myusers/*”只匹配一个级别,而“/myusers/**”则是分层匹配的。

后端位置可以指定为“serviceId”(用于来自discovery的服务)或“url”(用于物理位置),例如:

 zuul:
  routes:
    users:
      path: /myusers/**
      url: http://example.com/users_service

这些简单的url路由不会作为一个HystrixCommand执行,也不会用Ribbon加载多个url。要实现这一点,可以使用静态服务器列表指定serviceId:

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: http://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和路由之间提供约定。它使用名为groups的正则表达式从serviceId中提取变量并将它们注入到路由模式中。

ApplicationConfiguration.java. 

@Bean
public PatternServiceRouteMapper serviceRouteMapper() {
    return new PatternServiceRouteMapper(
        "(?<name>^.+)-(?<version>v.+$)",
        "${version}/${name}");
}

这意味着serviceId“myusers-v1”将映射到路由“/v1/myusers/**”。可以接受任何正则表达式,但是所有命名组必须同时出现在servicePattern和routePattern中。如果servicePattern与serviceId不匹配,则使用默认行为。在上面的示例中,serviceId“myusers”将映射到route“/myusers/**”(未检测到版本),该特性在默认情况下是禁用的,只适用于已发现的服务。

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

application.yml. 

 zuul:
  routes:
    users:
      path: /myusers/**
      stripPrefix: false

zuul.stripPrefix只适用于zuul.prefix中设置的前缀。它对给定路由路径中定义的前缀没有任何影响。在本例中,对“/myusers/101”的请求将被转发到“users”服务上的“/myusers/101”。

zuul.routes条目实际上绑定到ZuulProperties类型的对象。如果您查看该对象的属性,您将看到它还有一个“retryable”标志。

将该标志设置为“true”,以便Ribbon客户机自动重试失败的请求。(如果需要,可以使用Ribbon客户机配置修改重试操作的参数)。

默认情况下,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文件,因为使用属性文件将丢失顺序。例如:

application.yml. 

 zuul:
  routes:
    users:
      path: /myusers/**
    legacy:
      path: /**

如果要使用属性文件,遗留路径可能会出现在用户路径前面,从而导致无法访问用户路径。

Zuul Http Client

zuul使用的默认HTTP客户端现在由Apache HTTP客户端而不是废弃的Ribbon RestClient支持。要使用RestClient 或者 okhttp3.OkHttpClient,分别设备ribbon.restclient.enabled=true或者ribbon.okhttp.enabled=true。如果您想自定义Apache HTTP客户端或OK HTTP客户端,请提供ClosableHttpClient或OkHttpClient类型的bean。

Cookies and Sensitive Headers

在同一个系统中的服务之间共享消息头是可以的,但是您可能不希望敏感的消息头向下泄漏到外部服务器。您可以在路由配置中指定被忽略的标题列表。cookie扮演着特殊的角色,因为它们在浏览器中具有定义良好的语义,而且它们总是被视为敏感的。如果您的代理的使用者是浏览器,那么下游服务的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发送所有的标头(“忽略”的标头除外),您必须显式地将它设置为空列表。如果希望将cookie或授权标头传递到后端,这是必要的。例子:

application.yml. 

 zuul:
  routes:
    users:
      path: /myusers/**
      sensitiveHeaders:
      url: https://downstream

还可以通过设置zuul.sensitiveHeaders全局设置敏感标头。如果在路由上设置了sensitiveHeaders ,这将覆盖全局sensitiveHeaders 设置。

Ignored Headers

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

管理端点

如果您使用@EnableZuulProxy与 Spring Boot Actuator一起使用,您将(默认情况下)启用两个额外端点:

  • Routes
  • Filters

 Routes Endpoint

A GET to the routes endpoint at /routes将返回映射的路由列表:

GET /routes. 

{
  /stores/**: "http://localhost:8081"
}

可以通过向/routes添加?format=details查询字符串来请求其他路由细节。这将产生以下输出:

GET /routes?format=details. 

{
  "/stores/**": {
    "id": "stores",
    "fullPath": "/stores/**",
    "location": "http://localhost:8081",
    "path": "/**",
    "prefix": "/stores",
    "retryable": false,
    "customSensitiveHeaders": false,
    "prefixStripped": true
  }
}

POST将强制更新现有路由(例如,在服务目录中有更改的情况下)。可以通过设置禁用此端点:endpoints.routes.enabled=false。

路由应该自动响应服务目录中的更改,但是POST to /routes是一种强制立即进行更改的方法。

9.6.2 Filters Endpoint

A GET /filters的过滤器端点将按类型返回Zuul过滤器的映射。For each filter type in the map, you will find a list of all the filters of that type, along with their details.

9.7 Strangulation Patterns and Local Forwards

示例配置:

application.yml. 

zuul:
  routes:
    first:
      path: /first/**
      url: http://first.example.com
    second:
      path: /second/**
      url: forward:/second
    third:
      path: /third/**
      url: forward:/3rd
    legacy:
      path: /**
      url: http://legacy.example.com

在这个例子中,我们扼杀了映射到所有不匹配其他模式的请求的“legacy”应用程序。/first/**中的路径已被提取到具有外部URL的新服务中。并且/second/**中的路径被转发,这样它们就可以在本地处理,例如使用普通的Spring @RequestMapping。/third/**中的路径也被转发,但前缀不同(即/third/foo被转发到/3rd/foo)。

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

9.8 Uploading Files through Zuul

如果你@EnableZuulProxy,你可以使用代理路径上传文件,只要文件很小,它就可以工作。对于大文件,在“/zuul/*”中有一个替代路径绕过Spring DispatcherServlet(以避免多部分处理)。即如果zuul.routes.customers=/customers/** 然后你可以发布大文件到“/zuul/customers/*”。servlet路径通过zuul.servletPath路径外部化。如果代理路由通过一个带负载均衡器(Ribbon load balancer),那么非常大的文件也需要提高超时设置。

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.9 Query String Encoding

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

为了强制查询字符串的原始编码,可以向ZuulProperties传递一个特殊的标志,以便将查询字符串作为HttpServletRequest::getQueryString方法:

application.yml. 

 zuul:
  forceOriginalQueryStringEncoding: true

注意:

这个特殊的标志只适用于SimpleHostRoutingFilter,并且您失去了使用RequestContext.getCurrentContext(). setrequestqueryparams (someOverriddenParameters)轻松覆盖查询参数的能力,因为查询字符串现在直接从原始HttpServletRequest获取。

9.10 Plain Embedded Zuul

如果使用@EnableZuulServer(而不是@EnableZuulProxy),还可以在不使用代理的情况下运行Zuul服务器,或者有选择地打开代理平台的部分。您添加到ZuulFilter类型的应用程序中的任何bean都将自动安装,就像@EnableZuulProxy一样,但是不会自动添加任何代理过滤器。

在这种情况下,进入Zuul服务器的路由仍然是通过配置“zuul.routes.*”指定的,但没有服务发现和代理,因此忽略“serviceId”和“url”设置。例如:

application.yml. 

 zuul:
  routes:
    api: /api/**

将“/api/**”中的所有路径映射到Zuul过滤器链。

9.11 Disable Zuul Filters

针对Spring Cloud的Zuul附带了许多在代理和服务器模式下默认启用的ZuulFilter bean。请参阅zuul filters包,以获得可能启用的过滤器。如果您想禁用一个,只需设置zuul.<SimpleClassName>.<filterType>.disable=true。按照惯例,过滤器之后的包是Zuul过滤器类型。例如,要禁用org.springframework.cloud.netflix.zuul.filters.post.SendResponseFilter,设置zuul.SendResponseFilter.post.disable=true。

9.12 Providing Hystrix Fallbacks For Routes

当Zuul给定路由的电路被触发时,您可以通过创建ZuulFallbackProvider类型的bean来提供回退响应。在这个bean中,您需要指定回退用于的路由ID,并提供一个ClientHttpResponse作为回退返回。下面是一个非常简单的ZuulFallbackProvider实现。

class MyFallbackProvider implements ZuulFallbackProvider {
    @Override
    public String getRoute() {
        return "customers";
    }

    @Override
    public ClientHttpResponse fallbackResponse() {
        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;
            }
        };
    }
}

这就是路由配置的样子。

zuul:
  routes:
    customers: /customers/**

如果希望为所有路由提供默认回退,那么可以创建ZuulFallbackProvider类型的bean,并让getRoute方法返回*或null。

class MyFallbackProvider implements ZuulFallbackProvider {
    @Override
    public String getRoute() {
        return "*";
    }

    @Override
    public ClientHttpResponse fallbackResponse() {
        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;
            }
        };
    }
}

如果您想根据失败的原因选择响应,请使用FallbackProvider,它将在未来的版本中替换ZuulFallbackProvder。

class MyFallbackProvider implements FallbackProvider {

    @Override
    public String getRoute() {
        return "*";
    }

    @Override
    public ClientHttpResponse fallbackResponse(final Throwable cause) {
        if (cause instanceof HystrixTimeoutException) {
            return response(HttpStatus.GATEWAY_TIMEOUT);
        } else {
            return fallbackResponse();
        }
    }

    @Override
    public ClientHttpResponse fallbackResponse() {
        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;
            }
        };
    }
}

9.13 Zuul Timeouts

9.13.1 Service Discovery Configuration

如果Zuul使用服务发现,您需要关注两个超时:Hystrix超时(因为默认情况下所有路由都包装在Hystrix命令中)和Ribbon超时。

Hystrix超时需要考虑Ribbon读取和连接超时以及该服务将发生的重试总数。默认情况下,Spring Cloud Zuul将尽力为您计算Hystrix超时,除非您显式指定Hystrix超时。

Hystrix超时计算公式如下:

(ribbon.ConnectTimeout + ribbon.ReadTimeout) * (ribbon.MaxAutoRetries + 1) * (ribbon.MaxAutoRetriesNextServer + 1)

例如,如果在应用程序属性中设置以下属性:

application.yml. 

ribbon:
  ReadTimeout:100
  ConnectTimeout:500
  MaxAutoRetries:1
  MaxAutoRetriesNextServer:1

然后将Hystrix超时(在本例中为所有路由)设置为2400ms。

您可以使用service.ribbon.*为各个路由配置Hystrix超时属性。

如果您选择不配置上述属性,那么将使用默认值,因此默认的Hystrix超时将设置为4000ms。

如果你设置hystrix.command.commandKey.execution.isolation.thread.timeoutInMilliseconds,如果您设置hystrix.command.commandKey.execution.isolation.thread.timeoutInMilliseconds,其中commandKey是路由id,或者设置hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds,这些值将用于Hystrix超时,而不管您为ribbon设置了什么ribbon.*属性。如果您设置了这些属性中的任何一个,您将负责确保它考虑到Ribbon connect和read超时以及可能发生的重试。

9.13.2 URL Configuration

如果您通过指定url配置了Zuul路由,那么您将需要使用这些url,zuul.host.connect-timeout-millis和zuul.host.socket-timeout-millis。

9.14 Rewriting Location header

如果Zuul是web应用程序的前端,那么当web应用程序通过3XX的http状态代码重定向时,可能需要重写位置标头,否则浏览器最终将重定向到web应用程序的url,而不是Zuul url。可以将LocationRewriteFilter Zuul过滤器配置为将位置标头重新写入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响应代码的位置标头,这在所有场景中可能都不合适,比如用户重定向到外部URL。

9.15 Zuul Developer Guide

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

9.15.1 The Zuul Servlet

Zuul是作为Servlet实现的。对于一般情况,Zuul被嵌入到Spring分派机制中。这允许Spring MVC控制路由。在这种情况下,Zuul被配置为缓冲请求。如果需要在没有缓冲请求的情况下遍历Zuul(例如,对于大文件上传),Servlet也安装在Spring Dispatcher之外。默认情况下,它位于/zuul。此路径可以使用zuul.servlet-path属性。

9.15.2 Zuul RequestContext

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

9.15.3 @EnableZuulProxy vs. @EnableZuulServer

Netflix安装了许多过滤器,根据这些过滤器使用注解来启用Zuul。@EnableZuulProxy是@EnableZuulServer的超集。换句话说,@EnableZuulProxy包含@EnableZuulServer安装的所有过滤器。“代理”中的附加过滤器启用路由功能。如果您想要一个“空白”Zuul,您应该使用@EnableZuulServer。

9.15.4 @EnableZuulServer Filters

创建一个SimpleRouteLocator,从Spring引导配置文件加载路由定义。

安装了以下过滤器(作为普通Spring bean):

前置过滤器:

  • ServletDetectionFilter: ServletDetectionFilter:检测请求是否通过Spring Dispatcher发出。使用键fFilterConstants.IS_DISPATCHER_SERVLET_REQUEST_KEY设置布尔值。
  • FormBodyWrapperFilter: Parses form data and reencodes it for downstream requests.
  • DebugFilter: if the debug request parameter is set, this filter sets RequestContext.setDebugRouting() and RequestContext.setDebugRequest() to true.

路由过滤器:

  • SendForwardFilter: 这个过滤器使用Servlet RequestDispatcher转发请求。转发位置存储在RequestContext属性FilterConstants.FORWARD_TO_KEY中。这对于转发到当前应用程序中的端点非常有用。

后置过滤器:

  •  SendResponseFilter: 将代理请求的响应写入当前响应。

 Error 过滤器:

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

 9.15.5 @EnableZuulProxy Filters

创建一个DiscoveryClientRouteLocator,它从DiscoveryClient(如Eureka)和属性加载路由定义。为DiscoveryClient中的每个serviceId创建一个路由。随着新服务的添加,路由将被刷新。除了上述过滤器外,还安装了以下过滤器(作为普通Spring bean):

前置过滤器:

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

 路由过滤器:

  • RibbonRoutingFilter: 这个过滤器使用Ribbon、Hystrix和可插入的HTTP客户端发送请求。服务ids在RequestContext属性FilterConstants.SERVICE_ID_KEY中找到。这个过滤器可以使用不同的HTTP客户端。它们是:
  1. Apache HttpClient. 这是默认客户端。
  2. Squareup OkHttpClient v3.  classpath下有com.squareup.okhttp3:okhttp库及设置ribbon.okhttp.enabled=true启用
  3. Netflix Ribbon HTTP client. 通过设置ribbon.restclient.enabled=true启用。这个客户端有一些限制,比如它不支持PATCH 方法,但也有内置的重试功能。

 9.15.6 Custom Zuul Filter examples

下面的“如何编写”示例大部分都包含了Zuul过滤器示例项目。还可以在存储库中操作请求或响应主体。

9.15.7 How to Write a Pre Filter

前置过滤器用于在RequestContext中设置用于下游过滤器的数据。主要用例是设置路由过滤器所需的信息。

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("foo") != null) {
		    // put the serviceId in `RequestContext`
    		ctx.put(SERVICE_ID_KEY, request.getParameter("foo"));
    	}
        return null;
    }
}

上面的过滤器从foo请求参数填充SERVICE_ID_KEY。实际上,进行这种直接映射并不是一个好主意,但是应该从foo的值查找服务id。

现在已经填充了SERVICE_ID_KEY, PreDecorationFilter将不会运行,RibbonRoutingFilter将运行。如果您想要路由到完整的URL,可以调用ctx.setRouteHost(URL)。

要修改路由筛选器转发的路径,请设置REQUEST_URI_KEY。

9.15.8 How to Write a Route Filter

路由筛选器在预筛选器之后运行,用于向其他服务发出请求。这里的大部分工作是将请求和响应数据转换为客户端所需的模型。

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响应。警告:此过滤器可能有错误,不能正常工作。

9.15.9 How to Write a Post Filter

Post过滤器通常操作响应。在下面的过滤器中,我们添加一个随机UUID作为X-Foo头。其他操作,例如转换响应体,要复杂得多,而且计算密集得多。

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-Foo", UUID.randomUUID().toString());
		return null;
	}
}

9.15.10 How Zuul Errors Work

如果在Zuul筛选器生命周期的任何部分中抛出异常,则执行错误筛选器。SendErrorFilter只在RequestContext.getThrowable()不为空时才运行。然后设置特定的javax.servlet.error.*属性,并将请求转发到Spring引导错误页面。

9.15.11 Zuul Eager Application Context Loading

Zuul内部使用Ribbon调用远程url,默认情况下,Ribbon客户端在第一次调用时由Spring Cloud延迟加载。可以使用以下配置为Zuul更改此行为,并将导致在应用程序启动时急切加载与子功能区相关的应用程序上下文。

application.yml. 

zuul:
  ribbon:
    eager-load:
      enabled: true

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值