路由是微服务架构中的一个组成部分。
例如【/】可能是你映射到web应用的路径,【/api/user】可能是你映射到user服务的路径,【/api/shop】可能是
映射到shop服务的路径。
Zuul是一款出自于Netflix基于JVM的服务器端的负载均衡器。
Netflix在用Zuul做这些事情:
Spring Cloud 已经建立了Zuul代理,用来适用前端应用程序想要通过代理来访问一个或者多个后端服务。
这个特点对于用户接口代理访问到后端服务非常有用。避免了跨域问题并且可以使认证独立于后端服务。
如果要启用Zuul反向代理,可以在Spring Boot应用程序的主类中添加注解@EnableZuulProxy。 这样做
可以使本地的调用请求路由到相应的服务。按照惯例,一个users服务从代理那里接收/users(使用前缀剥离服务)
的请求Zuul代理使用Ribbon通过服务发现来定位具体的服务。所有的请求在hystrix command中执行,所以失败
的请求可以在Hystrix监控中获取到。一旦断路器打开,Zuul代理将不会尝试连接该服务。
注:Zuul starter 没有包含服务发现客户端,因此,为了实现基于服务id的路由,最好提供服务发现的客户端
(Eureka是其中之一的选择)
如果服务要跳过Zuul的反向代理,可在属性【zuul.ignored-services】中设置跳过的服务ID列表。
如果某个服务和需要跳过的服务列表匹配,但是又明确的设置在了路由map中,该服务将不会跳过Zuul的代理。
下述例子就是这样的效果:
application.yml
zuul:
ignoredServices: '*'
routes:
users: /myusers/**
上述例子中,所有的服务都将跳过Zuul的代理,除了users服务。
如果要添加或者改变代理路由,可以将配置信息配置成如下所示:
application.yml
zuul:
routes:
users: /myusers/**
上述配置意味着如果HTTP请求/myusers将会转发到users服务(例:/myusers/101将会转发到users服务的/101)
如果想要通过路由获取到更好的粒度控制,则可以独立的指定path和serviceId,配置如下:
application.yml
zuul:
routes:
users:
path: /myusers/**
serviceId: users_service
上述配置意味着如果HTTP请求/myusers将会转发到serviceId为users_service的服务中去。
此场景中必须指定ant-style风格的格式。所以/myusers/*只匹配一级路径,/myusers/**可匹配多级路径。
后台服务的位置可以通过serviceId(通过服务发现获取)指定,也可以通过url指定,配置如下:
application.yml
zuul:
routes:
users:
path: /myusers/**
url: http://example.com/users_service
上述所有的这些路由例子都不会以HystrixCommand的方式执行,有多个url的时候也不会使用Ribbon做负载均衡。
如果要实现这些目标,可以指定一个静态服务的list,配置如下:
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
也可以使用正则表达式在serviceId和如路由之间指定一个规则。从serviceId中提取变量并且将它们注入到路由中,进而使用有规律的表达式来定义groups。例子如下:
ApplicationConfiguration.java
@Bean
public PatternServiceRouteMapper serviceRouteMapper() {
return new PatternServiceRouteMapper(
"(?^.+)-(?v.+$)" ,
"${version}/${name}");
}
上述例子意味着serviceId为myusers-v1的服务将指向地址/v1/myusers/**.任何正则表达式都可被接受,但是任何被命名的groups必须存在于servicePattern和routePattern中。如果servicePattern不能匹配serviceId,将使用默认的操作。上述例子中,myusers的serviceId映射到了/myusers/**的路由中(没有检测到版本号)。这个功能默认是禁用的,只有在服务被发现时才有这个功能。
增加一个映射的前缀,可以设置zuul.prefix属性值,例如/api。默认情况下,Zuul在请求转发前会将这个前缀丢弃(可以通过设置zuul.stripPrefix=false来关掉这个功能)。也可以在个别的路由中特定服务中关掉丢弃映射前缀的功能。例如:
application.yml
zuul:
routes:
users:
path: /myusers/**
stripPrefix: false
注:zuul.stripPrefix只有在设置了zuul.prefix时才会起作用。它不会对路由中设置的path产生任何影响。
在上述例子中,请求/myusers/101将转发到users服务的/myusers/101路径中。
事实上,zuul.routes中的属性会绑定到ZuulProperties的对象中。如果你去看那个对象的属性,里面有一个retryable的标志。如果设置那个标志为true,则Ribbon的客户端会自动重试失败的请求。当你需要修改Ribbon客户端的重试参数配置时你也可以将这个标记设置为true。
默认情况下,X-Forward-Host头部属性会加到转发的请求中。如果要关闭这个属性,可设置zuul.addProxyHeaders=false.
默认情况下,前缀路径在转发时将丢弃,并且后台请求中会增加X-Forward-Prefix头部属性(如上述例子中的/myusers)。
如果设置默认的路由路径(/),被@EnableZuulProxy注解的应用程序将类似一个独立的服务器。例如,zuul.route.home:/将会路由home服务的/**路径。
如果需要将忽略路径控制到更好的颗粒度,可以指定一些特别的pattern。这些pattern将会在处理路由位置开始时处理,这意味着前缀应该包含在pattenr中以保证匹配。忽略的模式跨越所有服务并取代任何其他路由规范。下面的例子将展示如何创建一个忽略pattern:
application.yml
zuul:
ignoredPatterns: /**/admin/**
routes:
users: /myusers/**
上面的例子意味着请求如/myusers/101将转发到users服务的/101路径中,然而如果请求路径中包含/admin/将不会被转发。
如果你需要保证路由的顺序,你需要使用YAML文件。使用properties文件将丢失顺序访问的功能。下面的例子就是这样一个YAML文件:
application.yml
zuul:
routes:
users:
path: /myusers/**
legacy:
path: /**
如果使用properties文件,legacy的路径有可能变成优先于users的路径处理,最后导致users服务不可达。
Zuul默认使用的HTTP client是 Apache HTTP client而不是丢弃的Ribbon的RestClient。如果要使用RestClient或者okhttp3.OkHttpClient,分别设置 ribbon.restclient.enabled=true 或者 ribbon.okhttp.enabled=true即可。如果你想自定义Apache HTTP client或者OK HTTP client,则需要提供ClosableHttpClient或者OkHttpClient的bean类型。
在同一个系统的不同服务之间你可以共享头部信息,但是可能你不想将某些敏感头部信息泄露给下游的外部服务器。这种情况下可以指定忽略的头部信息作为路由配置的一部分。Cookies扮演了一个特殊的角色,因为它们能在浏览器中很好的定义语义,并且经常当做处理过的敏感信息使用。如果你的代理的消费者是浏览器,对于用户来说给下游服务使用的Cookies可能是个问题,因为它们都被搞得乱七八糟(所有的下游服务认为Cookies都是来自于一个地方)。
如果你充分的设计了你的服务(比如,只有一个下游服务设置了Cookies),你也许可以使他们从后端服务一直流转到调用者。当然,如果你的代理设置了Cookies并且你所有的后端服务都是同一个系统的一部分,可以很自然和简单的共享他们(例如使用Spring Session让他们维持在共享的状态)。除此之外, 对于调用者来说,下游服务队任何cookie的get和set操作似乎都没有多大用处,所以推荐至少将Set-Cookie和Cookie放入属于域的一部分的路由的敏感头部中。即使该路由属于域的一部分,在让cookie流转与服务和代理之前,请仔细考虑那将意味着什么。
每个路由的敏感头部可以通过逗号隔开字符串来设置,下面的例子就是这样一个YAML文件:
application.yml
zuul:
routes:
users:
path: /myusers/**
sensitiveHeaders: Cookie,Set-Cookie,Authorization
url: https://downstream
这个是sensitiveHeaders的默认值,你可以不需要设置它除非你想要一个和默认值不一样的值。
这个在Spring Cloud Netflix 1.1中修改的。(在1.0中,用户无法控制头部,并且所有的cookies都流向两个方向。).
sensitiveHeaders是黑名单,并且默认不为空。因此,如果要让Zuul发送所有的头部(除了忽略掉的),你必须指定设置一个空的列表。如果你想传递cookie或者authorization头部给你的后台服务这么做是必要的。下面的例子展示了如何使用sensitiveHeaders:
application.yml
zuul:
routes:
users:
path: /myusers/**
sensitiveHeaders:
url: https://downstream
你也可以通过zuul.sensitiveHeaders来设置sensitive headers。如果sensitiveHeaders设置在一个路由中,那么它将会覆盖全局的sensitiveHeaders设置。
在附加在路由头部的敏感头部信息中,你可以设置一个全局属性叫zuul.ignoredHeaders
,在和下游服务交互的时候为request和response丢弃敏感头部信息。默认情况下,如果spring Security 不在classpath中,这个属性值是空的。否则,会被初始化成大家都知道被spring security指定的安全头部。在某种情况下,假设下游服务也可以添加这些敏感头部,但是我们又需要从代理中获取这些敏感头部;当spring security在classpath中的情况下,为了不丢弃这些安全头部信息,可以设置zuul.ignoredHeaders
为false
。如果您在Spring security 中禁用了HTTP安全响应头,并且希望下游服务提供值,那么这样做很有用。
默认情况下,如果你使用了@EnableZuulProxy注解,你就启用了以下两个附加的endpoints:
{
/stores/**: "http://localhost:8081"
}
{
"/stores/**": {
"id": "stores",
"fullPath": "/stores/**",
"location": "http://localhost:8081",
"path": "/**",
"prefix": "/stores",
"retryable": false,
"customSensitiveHeaders": false,
"prefixStripped": true
}
}
endpoints.routes.enabled=false
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)。
PS: 上述例子中被忽略的路径不是真的被忽略了,只是没有通过Zuul转发而已。(所以它们仍能跳转到本地服务中)
DispatcherServlet
(避免multipart 处理)就是"/zuul/*"。也就是说,如果你配置了路由zuul.routes.customers=/customers/**
, 你就可以提交大文件给/zuul/customers/*
。servlet路径是取道zuul.servletPath通过的。如果代理如有通过一个Ribbon负载均衡器,非常大的文件必须提高timeout的设置,例子如下: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
当处理正在进来的请求时,查询参数(query params)将被解码,所以在Zuul filters中如果有需要才被修改。然后它们将被重新编码,在路由过滤器(route filters)中重建后端请求。如果是用Javascript的encodeURIComponent方法来编码,结果可能和原始的输入有所不同。在大多数情况下,这没有问题,不过有些web服务器对编码比较复杂的查询参数比较挑剔。
强制执行查询字符串的原始编码,可以往ZuulProperties中设置一个特殊的标记,这样查询字符串就可以与HttpServletRequest::getQueryString方法一起使用。 例子如下:
application.yml
zuul:
forceOriginalQueryStringEncoding: true
PS:这个特殊标记只在SimpleHostRoutingFilter中有效果。因此你也丢失了使用RequestContext.getCurrentContext().setRequestQueryParams(someOverriddenParameters)
轻松重写查询参数的能力,因为查询字符串现在直接从原始的HttpServletRequest中获取。
当处理正在进来的请求时,请求URI将在匹配路由前进行解码。此后将在路由过滤器(route filters)中重建后端请求的时候被重新编码。如果你的URI中包含"/"这将出现一些不可预知的行为。
如果要使用原始的请求URI(request URI),可以往ZuulProperties中设置一个特殊的标记, 这样查询字符串就可以与HttpServletRequest::getRequstURI方法一起使用。 例子如下:
application.yml
zuul:
decodeUrl: false
PS:如果你使用RequestContext属性覆盖了请求URI,并且这个标记被设置成了false,请求内容中的url将不会被编码。确认URL已经被编码了将会是你的责任。
如果你使用@EnableZuulServer(而不是@EnableZuulProxy),你仍然能运行Zuul server,而无需代理或选择性地打开代理平台的某些部分。应用中的任何Zuul filter类型的将被自动装载(与@EnableZuulProxy相同),但没有任何代理过滤器将被自动装载。
在这个例子中,装入Zuul server中欧冠的路由仍然使用"zuul.routes.*"配置指定,但是已经没有服务发现和代理。因此,“serviceId”和“url”配置将被忽略,以下示例将“/api/**”中的所有路径映射到Zuul的过滤器链路中:
application.yml
zuul:
routes:
api: /api/**
zuul...disable=true
即可。在Zuul的路由中服务链路被中断,你可以创建一个Fallbackprovider类型的bean来提供一个备用的响应(fallback response)。在这个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/**
如果你想为所有的路由提供一个默认的fallback,你可以创建一个FallbackProvider类型的bean,并且在getRoute方法中返回*
或者null
, 如下所示:
class MyFallbackProvider implements FallbackProvider {
@Override
public String getRoute() {
return "*";
}
@Override
public ClientHttpResponse fallbackResponse(String route, Throwable throwable) {
return new ClientHttpResponse() {
@Override
public HttpStatus getStatusCode() throws IOException {
return HttpStatus.OK;
}
@Override
public int getRawStatusCode() throws IOException {
return 200;
}
@Override
public String getStatusText() throws IOException {
return "OK";
}
@Override
public void close() {
}
@Override
public InputStream getBody() throws IOException {
return new ByteArrayInputStream("fallback".getBytes());
}
@Override
public HttpHeaders getHeaders() {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
return headers;
}
};
}
}
如果你想配置通过Zuul的socket和代理请求的超时时间,你有两个选项可供配置:
ribbon.ReadTimeout
和ribbon.SocketTimeout
属性。zuul.host.connect-timeout-millis
和uul.host.socket-timeout-millis
。如果Zuul处理的是一个web应用程序,当web应用程序通过http状态码3XX
跳转时,你可能需要重写Location头部信息。否则,浏览器将重定向到Zuul的Url而不是web应用程序的url了。你可以设置一个LocationRewriteFilter为Zuul的url重写Location头部。当然,它还添加了剥离的全局前缀和路由特定前缀。下面的例子展示了使用一个Spring Configuration文件来增加一个filter:
import org.springframework.cloud.netflix.zuul.filters.post.LocationRewriteFilter;
...
@Configuration
@EnableZuulProxy
public class ZuulConfig {
@Bean
public LocationRewriteFilter locationRewriteFilter() {
return new LocationRewriteFilter();
}
}
PS:请谨慎使用该filter,此filger作用在所有请求返回状态码为3XX的Location头部中,也许适用于所有的场景,比如重定向到外部的url中。
默认情况下,Zuul路由所有的跨域请求(CORS)到下游服务中。如果你想要Zuul处理特定的跨域请求,你可以使用自定义的WebMvcConfigurer
bean:
@Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurer() {
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/path-1/**")
.allowedOrigins("http://allowed-origin.com")
.allowedMethods("GET", "POST");
}
};
}
/path-1/
开头的服务节点中。你可以将你的CORS配置应用到具体的path表达式中,或者是为整个应用程序准备的全局配置,使用/**
表达式。Zuul在路由请求时,将会为任何在请求时可能发生的失败提供监控指标。这些指标可以通过请求/actuator/metrics
来获取。这些指标将会有名字和这样的格式:ZUUL::EXCEPTION:errorCause:statusCode.
请查看Zuul Wiki
Zuul被当做一个servlet来实现。在一般场景下,Zuul被嵌入到Spring的Dispatch机制。这让Spring MVC在路由的控制之下,在这个场景中,Zuul缓冲所有请求。如果需要Zuul不缓冲请求(比如,大文件上传),Servlet也可安装在Spring Dispather之外。默认情况下,selvet的地址是/zuul,这个路径可以使用zuul.servlet-path属性来修改。
Zuul使用RequestContext在过滤器间传递数据。数据保存在为每个request指定的ThreadLocal中。
有关路由请求的位置信息,errors和实际的HttpServletRequest和HttpServletResponse都保存在那里。RequestContext继承自ConCurrentHashMap,所以任何东西都可以保存在那里。FilterContants包含了被安装在SpringCloud Netflix中的所有过滤器的使用的keys(以后再谈这些)。
Spring Cloud Netflix 安装了一些过滤器,依赖于启用Zuul的注解。@EnableZuulProxy是@EnableZuulServer的超集。也就是说,@EnableZuulProxy包含了所有被@EnableZuulServer安装的过滤器。在“proxy”的额外过滤器开启了路由功能。如果你想要一个“空白”的Zuul,你应该使用@EnableZuulServer。
@EnableZuulServer 创建了一个从Spring Boot配置文件中加载路由定义的SimpleRouteLocator。
下面的过滤器被装载了(作为普通的Spring Beans):
前置过滤器(Pre filters):
路由过滤器(Route filters):
后置过滤器(Post filters):
Error filters:
创建一个DiscoveryClientRouteLocator用来从DiscoveryClient(比如说Eureka)以及properties文件中加载路由定义,这是一个为从DiscoveryClient的每个ServiceId创建的路由。如果新的服务加入进来,路由将会被刷新。
此外,为了让过滤器被更早的发现,装载了以下的过滤器(作为普通的Spring Beans):
前置过滤器(Pre filters):
路由过滤器(Route filters):
大部分的怎么编写过滤器的实例在Sample Zuul Filters工程中可以找到。
在这个工程中当然也可以找到操纵请求或者响应内容的实例。
这部分包含以下实例:
怎么编写 Pre Filter:
前置过滤器把数据装载在RequestContext中供下游的过滤器使用。主要的使用场景是为路由过滤器设置必须的信息。下面是一个Zuul前置过滤器的例子:
public class QueryParamPreFilter extends ZuulFilter {
@Override
public int filterOrder() {
return PRE_DECORATION_FILTER_ORDER - 1; // run before PreDecoration
}
@Override
public String filterType() {
return PRE_TYPE;
}
@Override
public boolean shouldFilter() {
RequestContext ctx = RequestContext.getCurrentContext();
return !ctx.containsKey(FORWARD_TO_KEY) // a filter has already forwarded
&& !ctx.containsKey(SERVICE_ID_KEY); // a filter has already determined serviceId
}
@Override
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
if (request.getParameter("sample") != null) {
// put the serviceId in `RequestContext`
ctx.put(SERVICE_ID_KEY, request.getParameter("foo"));
}
return null;
}
}
上述过滤器从request参数中填充SERVICE_ID_KEY。在实际应用中,你不应该做这种直接的映射,Service ID应该从sample中查找。
现在SERVICE_ID_KEY被修改了,PreDecorationFilter将不会执行,RibbonRoutingFilter将会被执行。
PS:如果你想路由到全路径URL,可以调用ctx.setRouteHost(url)。想要修改路由过滤器的跳转,可以设置REQUEST_URI_KEY的值。
怎么编写 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<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的响应信息。
怎么编写 Post Filter:
后置过滤器一般操作请求的响应信息。下面的例子在请求头部信息中增加了一个UUID作为X-Sample头部值:
public class AddResponseHeaderFilter extends ZuulFilter {
@Override
public String filterType() {
return POST_TYPE;
}
@Override
public int filterOrder() {
return SEND_RESPONSE_FILTER_ORDER - 1;
}
@Override
public boolean shouldFilter() {
return true;
}
@Override
public Object run() {
RequestContext context = RequestContext.getCurrentContext();
HttpServletResponse servletResponse = context.getResponse();
servletResponse.addHeader("X-Sample", UUID.randomUUID().toString());
return null;
}
}
PS:其他的操作,比如转换和加工请求响应内容,要复杂的多,计算量也大得多。
如果一个异常在Zuul过滤器的任何生命周期内被抛出,error过滤器将被执行。SendErrorFilter只有在RequestContext.getThrowable()不会null的时候才会被执行,这时将会在设置指定的javax.servlet.error.*属性到请求中,并且将请求转发到SpringBoot的error page中。
Zuul内部使用Ribbon调用远程的URLs。默认情况下,SpringCloud第一次调用的Ribbon客户端的时候使用懒加载的方式。可以通过设置以下的配置来修改这种行为,配置之后的结果就是在程序启动时就会加载相关的Ribbon上下文:
zuul:
ribbon:
eager-load:
enabled: true
参考文档: