参考:https://cloud.spring.io/spring-cloud-static/Edgware.SR5/multi/multi__router_and_filter_zuul.html
路由是微服务架构的组成部分。 例如,/ 可以映射到Web应用程序,/api/users 映射到用户服务,/api/shop 映射到商店服务。 Zuul(https://github.com/Netflix/zuul)是Netflix基于JVM的路由器和服务器端负载均衡器。
Netflix使用Zuul进行以下操作:
Zuul的规则引擎基本上允许以任何JVM语言编写规则和过滤器,内置支持Java和Groovy。
注:
1. 如何添加Zuul
要在项目中添加Zuul,请使用group id为 org.springframework.cloud 和 artifact id为 spring-cloud-starter-netflix-zuul 的starter。
2. 嵌入式Zuul反向代理
Spring Cloud创建了一个嵌入式Zuul代理,以简化常见的用例开发,如UI应用程序想要代理对一个或多个后端服务的调用。此功能对于用户界面代理其所需的后端服务非常有用,从而避免了为所有后端服务独立管理CORS和身份验证问题。
要启用它,使用@EnableZuulProxy注释Spring Boot主类,并将本地调用转发到相应的服务。按照惯例,具有ID“users”的服务将从位于 /users 的代理接收请求(带有前缀剥离)。代理使用Ribbon来定位要通过发现转发的实例,并且所有请求都在hystrix命令中执行,因此故障将显示在Hystrix指标中,并且一旦出现断路,代理将不会尝试联系服务。
注:Zuul starter不包含发现客户端,因此对于基于服务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",可以指定为ant风格的模式,因此“/ myusers/*”只匹配一个级别,但“/myusers/**”按层次匹配。
后端的位置可以被指定为“serviceId”(来自发现的服务)或“url”(用于物理位置),例如:application.yml
zuul:
routes:
users:
path: /myusers/**
url: http://example.com/users_service
这些简单的url-routes不会作为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: 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(
"(?^.+)-(?v.+$)",
"${version}/${name}");
}
这意味着serviceId “myusers-v1”将映射到路由“/v1/myusers/**”。 接受任何正则表达式,但所有命名组必须同时出现在servicePattern和routePattern中。 如果servicePattern与serviceId不匹配,则使用默认行为。 在上面的示例中,serviceId “myusers”将映射到路由“/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属性配置实际上绑定到 org.springframework.cloud.netflix.zuul.filters.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: /**
如果要使用属性文件,则 legacy 路径可能最终位于 users 路径前面,从而导致 users 路径无法访问。
3. Zuul Http客户端
zuul使用的默认HTTP客户端现在由Apache HTTP Client支持,而不是使用已经废弃的Ribbon RestClient。要使用RestClient或使用okhttp3.OkHttpClient,分别设置ribbon.restclient.enabled=true或ribbon.okhttp.enabled=true。如果要自定义Apache HTTP客户端或OK HTTP客户端,提供ClosableHttpClient或OkHttpClient类型的bean。
4. Cookie和敏感报头
在同一系统中的服务之间共享头部是可以的,但可能不希望敏感报头向下游泄漏到外部服务器。可以在路由配置中指定忽略的报头列表。 Cookie起着特殊的作用,因为它们在浏览器中具有明确定义的语义,并且它们总是被视为敏感的。如果代理的消费者是浏览器,那么下游服务的cookie也会给用户带来问题,因为它们都会混乱(所有下游服务看起来都来自同一个地方)。
如果对服务的设计非常小心,例如,如果只有一个下游服务设置了cookie,那么可以让它们从后端一直流到调用者。此外,如果代理设置了cookie并且所有后端服务都是同一系统的一部分,那么简单地共享它们就很自然(例如使用Spring Session将它们链接到某个共享状态)。除此之外,由下游服务设置的任何cookie对调用者来说可能不是很有用,因此建议(至少)将“Set-Cookie”和“Cookie”放入敏感报头中,这些报头用于不属于domain的路由。即使对于属于domain的路由,在允许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或authorization报头传递给后端,则必须执行此操作。 例:
application.yml
zuul:
routes:
users:
path: /myusers/**
sensitiveHeaders:
url: https://downstream
也可以通过设置zuul.sensitiveHeaders来全局设置敏感报头。如果在路由上设置了sensitiveHeaders,则会覆盖全局sensitiveHeaders设置。
5. 忽略报头
除了每个路由敏感报头之外,还可以为 zuul.ignoredHeaders 设置全局值,以便在与下游服务交互期间应丢弃的这些值(请求和响应)。默认情况下,如果Spring Security不在类路径上,则它们是空的,否则它们被初始化为Spring Security指定的一组“安全”报头(例如caching)。这种情况下的假设是,下游服务也可能添加这些报头,我们希望从代理获取这些值。如果Spring Security在类路径上,且不需要丢弃这些安全报头,可以将zuul.ignoreSecurityHeaders设置为false。如果在Spring Security中禁用了HTTP Security 响应报头并希望下游服务提供值,则此功能非常有用。
6. 管理端点
如果将@EnableZuulProxy与Spring Boot Actuator一起使用,将启用(默认情况下)两个额外的端点:
6.1 Routes端点
GET请求到 /routes 的路由端点将返回路由映射列表:
GET /routes
{
/stores/**: "http://localhost:8081"
}
可以通过将 ?format = details 查询字符串添加到 /routes 来请求其他路由详细信息。 这将产生以下输出:
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到 /routes 是一种强制立即更改的方法。
6.2 Filters端点
对于以GET方式请求到 /filters 的路由端点将按类型返回Zuul过滤器的映射。 对于映射中的每种过滤器类型,将找到该类型的所有过滤器及其详细信息的列表。
7. 扼杀模式和本地转发
迁移现有应用程序或API时的一个常见模式是“扼杀”旧端点,慢慢用不同的实现替换它们。 Zuul代理是一个有用的工具,因为可以使用它来处理来自旧端点的客户端的所有流量,但将一些请求重定向到新的端点。
配置示例:
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)。
注:忽略的模式不会被完全忽略,它们不会被代理处理(因此它们也可以在本地有效转发)。
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 "[email protected]" localhost:9999/zuul/simple/file
9. 查询字符串编码
处理传入请求时,将对查询参数进行解码,以便在Zuul过滤器中进行修改时可以获取到它们。 然后在路由过滤器中构建后端请求时重新编码它们。 如果使用Javascript的encodeURIComponent()方法编码,结果可能与原始输入不同。 虽然这在大多数情况下不会引起任何问题,但某些Web服务器可能会因复杂查询字符串的编码而变得挑剔。
要强制查询字符串的原始编码,可以将特殊标志传递给ZuulProperties,以便使用HttpServletRequest::getQueryString方法获取查询字符串:
application.ym
zuul:
forceOriginalQueryStringEncoding: true
注意:此特殊标志仅适用于SimpleHostRoutingFilter,并且无法使用RequestContext.getCurrentContext().setRequestQueryParams(someOverriddenParameters)轻松覆盖查询参数,因为查询字符串现在直接在原始HttpServletRequest上获取。
10. 直接嵌入Zuul
如果使用@EnableZuulServer(而不是@EnableZuulProxy),也可以在没有代理的情况下运行Zuul服务器,或者选择性地切换代理平台的各个部分。 与@EnableZuulProxy一样,添加到ZuulFilter类型的应用程序上的任何bean都将自动安装,但不会自动添加任何代理过滤器。
在这种情况下,仍然通过配置“zuul.routes.*”来指定进入Zuul服务器的路由,但是没有服务发现且没有代理,因此忽略“serviceId”和“url”设置。 例如:
application.yml。
zuul:
routes:
api: /api/**
将“/api/**”中的所有路径映射到Zuul过滤器链。
11. 禁用Zuul过滤器
Zuul for Spring Cloud在代理和服务器模式下都默认启用了许多ZuulFilter bean。 参考zuul filters包(org.springframework.cloud.netflix.zuul.filters.*)以获取已启用的过滤器。 如果要禁用一个,只需设置 zuul.
12. 为路由提供Hystrix回退
当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;
}
};
}
}
13. Zuul超时
13.1 服务发现配置
如果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。
注:
如果设置 hystrix.command.commandKey.execution.isolation.thread.timeoutInMilliseconds,其中commandKey是路由ID,或者设置hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds,无论ribbon.*属性怎么设置,这些值都将用于Hystrix超时。如果设置任何一个属性,则确保考虑好了Ribbon连接和读取超时以及可能发生的任何重试。
13.2 URL配置
如果通过指定URL配置了Zuul路由,则需要使用zuul.host.connect-timeout-millis和zuul.host.socket-timeout-millis。
14. 重写Location报头
如果Zuul面向Web应用程序,那么当Web应用程序通过3XX的http状态代码重定向时,可能需要重新编写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响应代码的位置标头,这可能不适用于所有情况,例如,如果用户重定向到外部URL。
15. Zuul开发人员指南
有关Zuul如何工作的一般概述,请参阅Zuul Wiki(https://github.com/Netflix/zuul/wiki/How-it-Works)。
Zuul请求生命周期(官方文档):
15.1 Zuul Servlet
Zuul是作为Servlet实现的。对于一般情况,Zuul嵌入到Spring Dispatch机制中。这允许Spring MVC控制路由。在这种情况下,Zuul配置为缓冲请求。如果需要在没有缓冲请求的情况下通过Zuul(例如,上传大型文件),则Servlet也安装在Spring Dispatcher之外。默认情况下,它位于/zuul。可以使用zuul.servlet-path属性更改此路径。
15.2 Zuul RequestContext
为了在过滤器之间传递信息,Zuul使用RequestContext。它的数据保存在特定于每个请求的ThreadLocal中。在何处路由请求,错误以及实际的HttpServletRequest和HttpServletResponse的信息都存储在那里。 RequestContext扩展了ConcurrentHashMap,因此任何东西都可以存储在上下文中。 FilterConstants包含Spring Cloud Netflix安装过滤器使用的key(稍后将详细介绍)。
15.3 @EnableZuulProxy与@EnableZuulServer
Spring Cloud Netflix安装了许多过滤器,根据使用哪个注解来启用Zuul。 @EnableZuulProxy是@EnableZuulServer的超集。换句话说,@EnableZuulProxy包含@EnableZuulServer安装的所有过滤器。 “代理”中的过滤器启用了路由功能。如果你想要一个“空白”Zuul,你应该使用@EnableZuulServer。
15.4 @EnableZuulServer过滤器
创建一个SimpleRouteLocator,用于从Spring Boot配置文件加载路由定义。
安装了以下过滤器(与普通的Spring Bean一样):
pre过滤器:
Route过滤器:
Post过滤器:
Error过滤器:
15.5 @EnableZuulProxy过滤器
创建DiscoveryClientRouteLocator,用于从DiscoveryClient(如Eureka)以及属性加载路径定义。为DiscoveryClient中的每个serviceId创建一个路由。随着新服务的添加,路由将被刷新。
除了上述过滤器之外,还安装了以下过滤器(与普通的Spring Bean一样):
pre过滤器:
Route过滤器:
15.6 自定义Zuul过滤器示例
下面的大多数示包含在Sample Zuul Filters项目(https://github.com/spring-cloud-samples/sample-zuul-filters)。还有一些操作该存储库中的请求或响应主体的示例。
15.7 如何编写Pre过滤器
Pre过滤器用于在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。
15.8 如何编写Route过滤器
Route过滤器在Pre过滤器之后运行,用于向其他服务发出请求。 这里的大部分工作是将请求和响应数据转换为客户端所需的模型。
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响应。 注:此过滤器可能存在bug,无法正常运行。
15.9 如何编写Post过滤器
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;
}
}
15.10 Zuul错误如何工作
如果在Zuul过滤器生命周期的任何部分期间抛出异常,则执行error过滤器。 仅当RequestContext.getThrowable()不为null时,才会运行SendErrorFilter。 然后,它在请求中设置特定的javax.servlet.error.*属性,并将请求转发到Spring Boot错误页面。
15.11 Zuul Eager应用程序上下文加载
Zuul内部使用Ribbon来调用远程URL,并且在第一次调用时,Spring Cloud会默认懒加载Ribbon客户端。 可以使用以下配置更改Zuul的此行为,将导致在应用程序启动时立即加载子Ribbon相关的应用程序上下文。
application.yml
zuul:
ribbon:
eager-load:
enabled: true