18. Router and Filter: Zuul-Part III. Spring Cloud Netflix
版本Finchley.SR1
路由是微服务架构不可或缺的一部分。例如,/
可以映射到您的Web应用程序,/api/users
映射到用户服务,/api/shop
映射到商店服务。 Zuul是Netflix的基于JVM的路由器和服务器端负载均衡器。
Netflix使用Zuul进行以下操作:
* 认证 Authentication
* 洞察 Insights
* 压力测试 Stress Testing
* 金丝雀测试 Canary Testing
* 动态路由 Dynamic Routing
* 服务迁移 Service Migration
* 负载脱落 Load Shedding
* 安全 Security
* 静态响应处理 Static Response handling
* 主动/主动流量管理 Active/Active traffic management
Zuul的规则引擎允许规则和过滤器基本上以任何JVM语言编写,内置支持Java和Groovy。
NOTE:
The configuration property zuul.max.host.connections has been replaced by two new properties, zuul.host.maxTotalConnections and zuul.host.maxPerRouteConnections, which default to 200 and 20 respectively.The default Hystrix isolation pattern (ExecutionIsolationStrategy) for all routes is SEMAPHORE. zuul.ribbonIsolationStrategy can be changed to THREAD if that isolation pattern is preferred.
要在项目中包含Zuul,请使用组ID为IDorg.springframework.cloud
ID为的starter spring-cloud-starter-netflix-zuul
。See the Spring Cloud Project page for details on setting up your build system with the current Spring Cloud Release Train.
Spring Cloud创建了一个嵌入式Zuul代理,以简化UI应用程序想要对一个或多个后端服务进行代理调用的常见用例的开发。此功能对于用户界面代理其所需的后端服务非常有用,从而无需为所有后端独立管理CORS和身份验证问题。
要使用这个功能,添加一个注解搞定@EnableZuulProxy
,这样处理,本地的请求会被转发到合适的服务上去处理。按照惯例,一个服务ID为users
接收来自位于/users
(带有前缀剥离)的代理的请求.All requests are executed in a hystrix command,因此Hystrix metrics出现故障,电路打开后,代理不会尝试联系该服务。
the Zuul starter does not include a discovery client, so, for routes based on service IDs, you need to provide one of those on the classpath as well (Eureka is one choice).
要跳过自动添加的服务,请设置zuul.ignored-services
为服务ID模式列表。如果服务与忽略但仍包含在显式配置的路由映射中的模式匹配,则它也会被处理的,如以下示例所示:
zuul:
ignoredServices: '*'
routes:
users: /myusers/**
在上面的例子,所有自动发现的服务都会被忽略,只有users
可以被使用。
表示HTTP调用将/myusers
转发到users
服务(例如/myusers/101
转发到users这个服务中的/101
)。
要对路由进行更细粒度的控制,可以单独指定路径和serviceId,如下所示:
zuul:
routes:
users:
path:/ myusers / ** ##使用ant匹配模式 两个星代表多层结构
serviceId:users_service
前面的示例表示HTTP调用将/myusers
转发到users_service
服务。路径必须具有path可以指定为ANT风格模式的路径,因此/myusers/*
仅匹配一个级别,但是按/myusers/**
层次匹配。
后端的位置(反向代理,真实的处理地址)可以指定为a serviceId
(对于发现服务)或url
(对于物理位置),如以下示例所示:
zuul:
routes:
users:
path:/ myusers / **
url:http://example.com/users_service ##一个真实的地址
但是这样简单( url-routes)的处理,不会执行HystrixCommand
,nor do they load-balance multiple URLs with Ribbon(也不会使用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
另一种方法是指定服务路由并为其配置Ribbon客户端serviceId(这样做需要在Ribbon中禁用Eureka支持 - 请参阅上面的更多信息),如以下示例所示:
zuul:
routes:
users:
path: /myusers/**
serviceId: users
ribbon:
eureka:
enabled: false
users:
ribbon:
listOfServers: example.com,google.com
You can provide a convention between serviceId and routes
by using regexmapper
. 它使用正则表达式命名组从serviceId中提取变量并将它们注入路由模式,如以下示例所示:
@Bean
public PatternServiceRouteMapper serviceRouteMapper() {
return new PatternServiceRouteMapper(
"(?^.+)-(?v.+$)" ,
"${version}/${name}");
}
上面的示例是指一个serviceId
的myusers-v1
映射来路由/v1/myusers/**
。接受任何正则表达式,但所有命名组必须同时存在于servicePattern
和routePattern
。如果servicePattern
不匹配 serviceId
,则使用默认行为。在前面的例子中,一个serviceIdmyusers
被映射到“/ myusers / **
”路线(没有检测到版本)。默认情况下禁用此功能,仅适用于已发现的服务。(This feature is disabled by default and only applies to discovered services.)
要为所有映射添加前缀,请设置zuul.prefix
为值,例如/api
。默认情况下,代理前缀会在请求转发之前从请求中删除(您可以关闭此行为zuul.stripPrefix=false
),您还可以关闭从各个路由中剥离特定于服务的前缀,如以下示例所示:
zuul:
routes:
users:
path: /myusers/**
stripPrefix: false
NOTE
zuul.stripPrefix
only applies to the prefix set inzuul.prefix
. It does not have any effect on prefixes defined within a givenroute’s path
.
In the preceding example, requests to /myusers/101
are forwarded to/myusers/101
on the users service.
The zuul.routes
entries actually bind to an object of type ZuulProperties
. If you look at the properties of that object, you can see that it also has a retryable
flag. Set that flag to true to have the Ribbon client automatically retry failed requests. You can also set that flag to true when you need to modify the parameters of the retry operations that use the Ribbon client configuration. 可以参考ZuulProperties这个类,配置重试机制。
By default, the X-Forwarded-Host
header is added to the forwarded requests. To turn it off, set zuul.addProxyHeaders = false
. By default, the prefix path is stripped, and the request to the back end picks up a X-Forwarded-Prefix
header (/myusers in the examples shown earlier).转发过程值增加参数的开关
如果需要更细粒度的忽略,则可以指定要忽略的特定模式。这些模式在路径定位过程开始时进行评估,这意味着前缀应包含在模式中以保证匹配。忽略的模式跨越所有服务并取代任何其他路由规范。以下示例显示如何创建忽略的模式:(If more fine-grained ignoring is needed, you can specify specific patterns to ignore. These patterns are evaluated at the start of the route location process, which means prefixes should be included in the pattern to warrant a match. Ignored patterns span all services and supersede any other route specification. The following example shows how to create ignored patterns:),大概的意思就是:如果我就是不匹配某些路径,对于某些路径不处理,这个优先级相当的高!
zuul:
ignoredPatterns: /**/admin/**
routes:
users: /myusers/**
The preceding example means that all calls (such as /myusers/101
) are forwarded to /101
on theusers
service. However, calls including /admin/
do not resolve.
Note 如果你要保持路径匹配有顺序必须使用yaml文件格式
If you need your routes to have their order preserved, you need to use a YAML file, as the ordering is lost when using a properties file. The following example shows such a YAML file:
zuul:
routes:
users:
path: /myusers/**
legacy:
path: /**
If you were to use a properties file, the legacy path might end up in front of the users path, rendering the users path unreachable.(使用属性文件的话,users永远也没法被访问啦。)
The default HTTP client used by Zuul is now backed by theApache HTTP Client
instead of the deprecatedRibbon RestClient
. To useRestClient or okhttp3.OkHttpClient
, set ribbon.restclient.enabled=true or ribbon.okhttp.enabled=true
, respectively. If you would like to customize the Apache HTTP client or the OK HT TP client, provide a bean of type ClosableHttpClient or OkHttpClient.
您可以在同一系统中的服务之间共享标头,但您可能不希望敏感标头向下游泄漏到外部服务器。您可以在路由配置中指定忽略的标头列表。Cookie起着特殊的作用,因为它们在浏览器中具有良好定义的语义,并且它们始终被视为敏感。如果您的代理的消费者是浏览器,那么下游服务的cookie也会给用户带来问题,因为它们都混杂起来(所有下游服务看起来都来自同一个地方)。
如果您对服务的设计非常小心(例如,如果只有一个下游服务设置了cookie),您可以让它们从后端一直流到调用者。此外,如果您的代理设置了cookie并且所有后端服务都是同一系统的一部分,那么简单地共享它们就很自然(例如,使用Spring Session将它们链接到某个共享状态)。除此之外,也得到由下游服务设置任何cookie可能是没有用的给调用者,所以建议你做(至少)Set-Cookie,并Cookie转化为不属于域的一部分,路线敏感头。即使是属于您域名的路由,也要在让cookie和代理之间流动之前仔细考虑它的含义。
敏感头可以被配置使用逗号分隔在每一个路由中 :
zuul:
routes:
users:
path: /myusers/**
sensitiveHeaders: Cookie,Set-Cookie,Authorization
url: https://downstream
This is the default value for sensitiveHeaders, so you need not set it unless you want it to be different. This is new in Spring Cloud Netflix 1.1 (in 1.0, the user had no control over headers, and all cookies flowed in both directions). 上面的设置的是默认的值,如果不是特别说明,无需进行特别的设置哦。
该sensitiveHeaders
是一个黑名单,默认是不为空。因此,要使Zuul发送所有标头(除了ignored那些标头),您必须将其明确设置为空列表。如果要将cookie或授权标头传递到后端,则必须这样做。以下示例显示如何使用sensitiveHeaders:
zuul:
routes:
users:
path:/ myusers / **
sensitiveHeaders:
url:https:// downstream
您还可以通过设置来设置敏感标头zuul.sensitiveHeaders。如果sensitiveHeaders在路由上设置,则它将覆盖全局sensitiveHeaders设置.
In addition to the route-sensitive headers, you can set a global value called zuul.ignoredHeaders
for values (both request and response) that should be discarded during interactions with downstream services. By default, if Spring Security is not on the classpath, these are empty. Otherwise, they are initialized to a set of well known “security” headers (for example, involving caching) as specified by Spring Security. The assumption in this case is that the downstream services might add these headers, too, but we want the values from the proxy. To not discard these well known security headers when Spring Security is on the classpath, you can set zuul.ignoreSecurityHeaders to false. Doing so can be useful if you disabled the HTTP Security response headers in Spring Security and want the values provided by downstream services. 简单一句话,对于请求和响应的head头都要去掉。
默认情况下,如果使用@EnableZuulProxySpring Boot Actuator,则启用另外两个端点,监控信息监测:
management.endpoints.routes.enabled
to false.迁移现有应用程序或API时的一种常见模式是“扼杀”旧端点,慢慢用不同的实现替换它们。Zuul代理是一个有用的工具,因为您可以使用它来处理来自旧端点的客户端的所有流量,但将一些请求重定向到新的端点。
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
在前面的示例中,我们扼杀了“遗留”应用程序(we are strangle the “legacy” application),该应用程序映射到与其他模式之一不匹配的所有请求。/first/**
已使用外部URL将路径提取到新服务中。/second/**
转发路径以便可以在本地处理它们(例如,使用普通的Spring @RequestMapping)。路径/third/**
也会被转发但具有不同的前缀(/third/foo
转发到/3rd/foo
)。
Note
忽略的模式不会被完全忽略,它们只是不由代理处理(因此它们也可以在本地有效转发)。
### 18.8 Uploading Files through Zuul(上传大文件)
如果您使用@EnableZuulProxy
,您可以使用代理路径上传文件,只要文件很小,它就可以工作。对于大型文件,有一个替代路径绕过Spring DispatcherServlet(以避免多部分处理)在“/ zuul / *
”中。换句话说,如果你有zuul.routes.customers=/customers/**
,那么你可以将POST大文件/zuul/customers/*
。servlet路径通过外部化zuul.servletPath
。If the proxy route takes you through a Ribbon load balancer, extremely large files also require elevated timeout settings, as shown in the following example:(也需要提升超时设置)
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
### 18.9查询字符串编码
处理传入请求时,将对查询参数进行解码,以便它们可用于Zuul过滤器中的可能修改。然后对它们进行重新编码,在路由过滤器中重建后端请求。如果(例如)使用Javascript encodeURIComponent()方法编码,结果可能与原始输入不同。虽然这在大多数情况下不会引起任何问题,但某些Web服务器可能会因复杂查询字符串的编码而变得挑剔。
要强制查询字符串的原始编码,可以传递一个特殊标志,ZuulProperties以便使用该HttpServletRequest::getQueryString方法按原样获取查询字符串,如以下示例所示:
zuul:
forceOriginalQueryStringEncoding:true
NOTE
这个特殊标志只适用于SimpleHostRoutingFilter。此外,您无法轻松覆盖查询参数RequestContext.getCurrentContext().setRequestQueryParams(someOverriddenParameters),因为现在直接在原始查询字符串上提取查询字符串HttpServletRequest。
如果您使用@EnableZuulServer(而不是@EnableZuulProxy)
,您还可以运行Zuul服务器而无需代理或有选择地切换代理平台的某些部分。您添加到类型的应用程序的任何bean都会ZuulFilter自动安装(与它们一样@EnableZuulProxy),但不会自动添加任何代理过滤器。
在这种情况下,仍然通过配置“zuul.routes。*”来指定进入Zuul服务器的路由,但是没有服务发现和代理。因此,忽略“serviceId”和“url”设置。以下示例将“/ api / **
”中的所有路径映射到Zuul过滤器链:
zuul:
routes:
api: /api/**
Zuul for Spring Cloud comes with a number of ZuulFilter beans enabled by default in both proxy and server mode. See the Zuul filters package for the list of filters that you can enable. If you want to disable one, set zuul...disable=true. By convention, the package after filters is the Zuul filter type. For example to disable org.springframework.cloud.netflix.zuul.filters.post.SendResponseFilter, set zuul.SendResponseFilter.post.disable=true.
当Zuul中给定路径的电路跳闸时,您可以通过创建类型的bean来提供回退响应FallbackProvider。在此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/**
想针对所有的router: If you would like to provide a default fallback for all routes, you can create a bean of type FallbackProvider and have the getRoute method return * or null
, as shown in the following example:
如果要为通过Zuul代理的请求配置套接字超时和读取超时,则有两种选择,具体取决于您的配置:
1、 如果Zuul使用服务发现(If Zuul uses service discovery),则需要使用ribbon.ReadTimeout和ribbon.SocketTimeout功能区属性配置这些超时 。
2、 If you have configured Zuul routes by specifying URLs, you need to use zuul.host.connect-timeout-millis and zuul.host.socket-timeout-millis.(对于特定的URL配置超时)
如果Zuul面向Web应用程序,则Location当Web应用程序通过HTTP状态代码重定向时,您可能需要重新编写标头3XX。否则,浏览器会重定向到Web应用程序的URL而不是Zuul URL。您可以配置LocationRewriteFilterZuul过滤器以将Location标头重新写入Zuul的URL。它还会添加剥离的全局和路由特定前缀。以下示例使用Spring配置文件添加过滤器:
@Configuration
@EnableZuulProxy
public class ZuulConfig {
@Bean
public LocationRewriteFilter locationRewriteFilter() {
return new LocationRewriteFilter();
}
}
NOTE
仔细使用此过滤器。过滤器作用于Location所有3XX响应代码的标头,这可能不适用于所有情况,例如将用户重定向到外部URL时。
Zuul will provide metrics under the Actuator metrics endpoint for any failures that might occur when routing requests. These metrics can be viewed by hitting /actuator/metrics. The metrics will have a name that has the format ZUUL::EXCEPTION:errorCause:statusCode.(对于路由请求时可能发生的任何故障,Zuul将在Actuator指标端点下提供指标。点击即可查看这些指标/actuator/metrics。度量标准将具有格式的名称 ZUUL::EXCEPTION:errorCause:statusCode。)
有关Zuul如何工作的一般概述,请参阅Zuul Wiki。
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安装的过滤器使用的密钥(稍后将详细介绍)。
Spring Cloud Netflix安装了许多过滤器,具体取决于使用哪个注释来启用Zuul。@EnableZuulProxy是一个超集@EnableZuulServer。换句话说,@EnableZuulProxy包含安装的所有过滤器@EnableZuulServer。“代理”中的其他过滤器启用路由功能。如果你想要一个“空白”Zuul,你应该使用@EnableZuulServer。(Spring Cloud Netflix installs a number of filters, depending on which annotation was used to enable Zuul. @EnableZuulProxy is a superset of @EnableZuulServer. In other words, @EnableZuulProxy contains all the filters installed by @EnableZuulServer. The additional filters in the “proxy” enable routing functionality. If you want a “blank” Zuul, you should use @EnableZuulServer.)
@EnableZuulServer创建一个SimpleRouteLocator从Spring Boot配置文件加载路由定义。
The following filters are installed (as normal Spring Beans):
预过滤器:
ServletDetectionFilter:检测请求是否通过Spring Dispatcher。设置一个键为的布尔值FilterConstants.IS_DISPATCHER_SERVLET_REQUEST_KEY。(Detects whether the request is through the Spring Dispatcher. Sets a boolean with a key of )
FormBodyWrapperFilter:解析表单数据并为下游请求重新编码。(Parses form data and re-encodes it for downstream requests.)
DebugFilter:如果debug请求参数被设置,设置RequestContext.setDebugRouting()和RequestContext.setDebugRequest()到true。*路线过滤器:(If the debug request parameter is set, sets RequestContext.setDebugRouting() and RequestContext.setDebugRequest() to true. *Route filters:)
SendForwardFilter:使用Servlet转发请求RequestDispatcher。转发位置存储在RequestContext属性中FilterConstants.FORWARD_TO_KEY。这对于转发到当前应用程序中的端点非常有用。(Forwards requests by using the Servlet RequestDispatcher. The forwarding location is stored in the RequestContext attribute, FilterConstants.FORWARD_TO_KEY. This is useful for forwarding to endpoints in the current application.)
过滤后:
SendResponseFilter:将代理请求的响应写入当前响应。(Writes responses from proxied requests to the current response.)
错误过滤器:
SendErrorFilter:/error如果RequestContext.getThrowable()不为null,则转发到(默认情况下)。您可以/error通过设置error.path属性来更改默认转发路径(Forwards to /error (by default) if RequestContext.getThrowable() is not null. You can change the default forwarding path (/error) by setting the error.path property.
)。
创建一个DiscoveryClientRouteLocator从DiscoveryClient(例如Eureka)以及属性加载路径定义的路径。路线为每个创建serviceId从所述DiscoveryClient。添加新服务后,将刷新路由。
除了前面描述的过滤器之外,还安装了以下过滤器(与普通的Spring Bean一样):
预过滤器:
PreDecorationFilter:根据提供的内容确定路由的位置和方式RouteLocator。它还为下游请求设置各种与代理相关的标头。( Determines where and how to route, depending on the supplied RouteLocator. It also sets various proxy-related headers for downstream requests.)
Route filters:
RibbonRoutingFilter:使用Ribbon,Hystrix和可插入的HTTP客户端发送请求。可在RequestContext属性中找到服务ID FilterConstants.SERVICE_ID_KEY。(Uses Ribbon, Hystrix, and pluggable HTTP clients to send requests. Service IDs are found in the RequestContext attribute, 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()。
自定义过滤器例子
How to Write a Pre Filter
Pre filters set up data in the RequestContext for use in filters downstream. The main use case is to set information required for route filters. The following example shows a Zuul pre filter:
RequestContext 相等于共享数据中心,让所有的Filter共享
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;
}
}
If you want to route to a full URL, call ctx.setRouteHost(url) instead.
要修改路由过滤器转发的路径,请设置REQUEST_URI_KEY。(To modify the path to which routing filters forward, set the REQUEST_URI_KEY )
How to Write a Route Filter
路由过滤器在预过滤器之后运行并向其他服务发出请求。这里的大部分工作是将请求和响应数据转换为客户端所需的模型。以下示例显示了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 headerNames = request.getHeaderNames();
while (headerNames.hasMoreElements()) {
String name = headerNames.nextElement();
Enumeration 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 responseHeaders = new LinkedMultiValueMap<>();
for (Map.Entry> 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响应。
How to Write a Post Filter
Post filters typically manipulate the response. The following filter adds a random UUID as the X-Sample header:
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;
}
}
其他操作(例如转换响应体)要复杂得多且计算量大。
If an exception is thrown during any portion of the Zuul filter lifecycle, the error filters are executed. The SendErrorFilter is only run if RequestContext.getThrowable() is not null. It then sets specific javax.servlet.error.* attributes in the request and forwards the request to the Spring Boot error page.
Zuul内部使用Ribbon来调用远程URL。默认情况下,Spring Cloud在第一次调用时会延迟加载Ribbon客户端。可以使用以下配置更改Zuul的此行为,这会导致在应用程序启动时急切加载与子功能区相关的应用程序上下文。以下示例显示如何启用预先加载:
zuul:
ribbon:
eager-load:
enabled:true