路由是一个微服务架构体系中必须的一部分,比如说,“/” 映射你的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.
注意:
zuul.max.host.connections 配置属性已经被 zuul.host.maxTotalConnections 和 zuul.host.maxPerRouteConnections 替代了,它们的默认值分别是 200 和 20.
默认所有路由的 Hystrix 隔离模式为 “SEMAPHORE”, 可以修改 zuul.ribbonIsolationStrategy 为 “THREAD” 将隔离级别改为 “THREAD” 模式。
9.1 在项目中使用zuul
在项目中引入group 为 org.springframework.cloud , artifact id 为 spring-cloud-starter-zuul 的 starter 就可以使用zuul了,要使用最新的spring cloud 来构建你的项目请看 Spring Cloud Project page.
9.2 嵌入式zuul做反向代理
为了便于前端UI应用调用一个或多个后端服务,Spring Cloud 创造了嵌入式的zuul代理。当一个用户接口需要调用它所需的后端服务的时候,这一特性是非常有用的。它还避免了对所有的后端服务的跨域和认证问题进行独立的管理。
可以通过在Spring Boot的主类上加上@EnableZuulProxy注解来开启zuul,它会将请求转向指定的服务上,默认情况下,service ID 为“user”的服务会映射到 “/user”(去除前缀) 的请求上.这个代理通过注册中心使用Ribbon去定位一个服务实例。所有请求都在 hystrix command下执行,所以调用失败会被Hystrix 统计,当熔断器打开的时候,它就不会再去尝试重新调用服务。
注意:
* Zuul 的starter 包里面不包含注册中心的客户端,所以如果你想基于service IDs去做路由还需要将注册中心对应的客户端的包放在项目的classpath里(比如说Eureka)。
可以通过设置 zuul.ignored-services 忽略匹配模式的值来避免自动添加被代理服务,如果一个服务匹配上了 zuul.ignored-services 的值,同时也匹配上了路由规则,那么它不会被忽略。比如说:
application.yml
zuul:
ignoredServices: '*'
routes:
users: /myusers/**
在这个例子中,除”users”服务以外,所有服务都会被忽略。可以添加外部的配置去改变代理路由规则:
application.yml
zuul:
routes:
users: /myusers/**
这个规则意味着所有以”/myusers”开头的http请求都将转向请求”users”服务(例如: “/myusers/101″转向”/101”).
你还可以通过单独的指定path和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
These simple url-routes don’t get executed as a HystrixCommand nor can you loadbalance multiple URLs with Ribbon. To achieve this, specify a service-route and configure a Ribbon client for the serviceId (this currently requires disabling Eureka support in Ribbon: see above for more information), e.g.
上面这些路由都不能像 HystrixCommand 一样去执行,也不能使用Ribbon对多个匹配服务实例进行负载均衡调用。为了使用这些特性,我们可以指定一个 service-route 形式的路由然后为这个”serviceId”配置Ribbon客户端(这是需要取消Eureka在Ribbon中的支持:看第6章的相关信息):
application.yml
zuul:
routes:
users:
path: /myusers/**
serviceId: users
ribbon:
eureka:
enabled: false
users:
ribbon:
listOfServers: example.com,google.com
默认你可以使用正则映射serviceId和路由规则,它利用正则表达式从serviceId里面去提取姓名分组信息然后注入到一个路由规则.
ApplicationConfiguration.java.
@Bean
public PatternServiceRouteMapper serviceRouteMapper() {
return new PatternServiceRouteMapper(
"(?^.+)-(?v.+$)",
"${version}/${name}");
}
这个意味着”myusers-v1″的serviceId将被映射到路由”/v1/myusers/**”上去,只要姓名分组信息都存在于服务模式和路由模式的任何正则表达式都可以被正确识别,如果服务模式没有匹配到一个serviceId,就会应用默认的匹配规则,在上个例子中,serviceId为”myusers”的服务将被映射到”/myusers/**”路由上去(没检测到版本信息).这一特性默认是关闭的,而且只能应用在有注册中心的项目中.
为了给所有的映射地址加上前缀, 可以给 zuul.prefix 设置一个值,比如说 “/api”,默认情况下这个代理前缀会从在请求转发时被删除掉(可以通过设置 zuul.stripPrefix=false 来关闭).你也可以单独的为个别路由关闭这一功能.比如说:
application.yml.
zuul:
routes:
users:
path: /myusers/**
stripPrefix: false
zuul.stripPrefix only applies to the prefix set in zuul.prefix. It does not have any effect on prefixes defined within a given route’s path.
注意:
* zuul.stripPrefix 属性仅仅适用于 zuul.prefix 的前缀, 不会影响任何给定的route路径的前缀.
在这个例子中, “/myusers/101” 的请求将会被转向 “users” 服务的 “/myusers/101” 地址.
这个 zuul.routes 入口实际上绑定了一个 ZuulProperties 类型的对象, 如果你看这个对象的属性你将会发现它还有一个 “retryable” 属性, 设置为”true” 可以使用Ribbon的请求失败重试机制(如果你需要改变这个retry操作的参数你需要配置Ribbon的客户端)
每个请求header里面默认会附带一个 “X-Forwarded-Host”属性, 可以通过设置 zuul.addProxyHeaders=false 来关闭它. 虽然路径的前缀默认会被删除的,但是请求后端的时候header里面还是会附带一个”X-Forwarded-Prefix”属性(如上的”/myusers”示例).
如果你给带有@EnableZuulProxy注解的应用设置一个默认的路由”/”, 那么它还可以作为一个单独的服务. 例如: “zuul.route.home: /” , 这个会转发所有的请求(和”/**”一样)到”home”这个服务上去.
如果需要细粒度的忽略匹配,你可以指定一个忽略匹配模式.这些模式在路由地址前被匹配,这意味着路由前缀需要包含在忽略匹配模式里面才可以保证匹配的到,全局忽略模式会取代指定服务的忽略模式:
application.yml.
zuul:
ignoredPatterns: /**/admin/**
routes:
users: /myusers/**
这个例子中所有像”/muyusers/101″的请求 将被转发到 “/101” 的 “user” 服务上面, 但是如果有包含 “/admin/” 的请求将被忽略
注意:
* 如果你使用properties类型的文件而不是YAML类型的文件去保存你的路由,会导致部分路由丢失,访问不到:
application.yml.
zuul:
routes:
users:
path: /myusers/**
legacy:
path: /**
如果这些配置你放在一个properties类型的文件里面, legacy的路由配置会覆盖 users的配置导致users服务不能访问.
9.3 Zuul 使用的http client
现在zuul用Apache http client替换了已被删除的Ribbon的RestClient作为默认的http client.如果你仍想用RestClient或者okhttp3.OkHttpClient你需要分别设置 ribbon.restclient.enabled=true 和 ribbon.okhttp.enabled=true
9.4 Cookies和敏感头设置
在同一个系统中共享请求头信息是可以的,但是你可能不希望有些敏感头信息泄露给外部的服务,你可以为你的路由配置信息指定一个忽略头部信息的列表来解决这个问题. Cookies因为在浏览器中定义了重要的信息所以起一个特殊的角色,默认都会被作为敏感信息处理,如果被代理的消费者是浏览器,那么如果被代理服务都获取到了这些cookies还会因为Cookies混淆的问题给用户带来一些问题(所有后端服务都像是来自于一个地方).
如果你设计你的微服务时非常仔细.比如说: 后端服务里面只有一个需要设置cookies, 那么你可以让这些Cookies从后端服务一路流向这个调用者.同样的话,如果你给你代理的所有后端服务中相同的部分服务都设置cookies,这些服务可以自然简单的共享cookies(可以使用Spring Session将它们联系起来保证共享状态的一致性). 除此之外, 被下游服务设置的cookies对调用者来说不太可能非常有用. 所以建议你将”Set-Cookie”和”Cookie”(至少一个)放入路由中不属于同一个域里面的服务的敏感头信息设置里面,即使对于路由服务来说它和后端服务也是处于同一个域里面,仔细想想这样的做法对前面使得cookies一直在流动的做法有什么意义.
这个敏感头用一个逗号隔开的列表表示,例如:
application.yml.
zuul:
routes:
users:
path: /myusers/**
sensitiveHeaders: Cookie,Set-Cookie,Authorization
url: https://downstream
[Note]
this is the default value for sensitiveHeaders, so you don’t need to set it unless you want it to be different. N.B. this is new in Spring Cloud Netflix 1.1 (in 1.0 the user had no control over headers and all cookies flow in both directions).
注意:
* 这是sensitiveHeaders的默认设置,如果不想改变它的话你可以不用设置它,注意,这是Spring Cloud Netflix 1.1版本 的新特性(1.0版本中用户不能控制请求头和cookies的流动方向).
The sensitiveHeaders are a blacklist and the default is not empty, so to make Zuul send all headers (except the “ignored” ones) you would have to explicitly set it to the empty list. This is necessary if you want to pass cookie or authorization headers to your back end. Example:
sensitiveHeaders 是一个默认不为空的黑名单列表, 所以如果你想要发送所有的cookies信息和认证头信息(除了忽略的headers)给后端服务,你需要设置这个列表为空,例如:
application.yml.
zuul:
routes:
users:
path: /myusers/**
sensitiveHeaders:
url: https://downstream
9.5 忽略头信息
相对于对每个路由都设置敏感头信息,你可以通过 zuul.ignoredHeaders 设置一个全局的值来取消与下游服务的headers交互(包括请求和响应).默认这个值是空的,如果你项目中没有使用Spring Security, 否则Spring Security会在请求头里面带一个”security”的头信息(例如调用caching时) , 假设这种情况下下游的服务可能会添加这些头信息, 并且我们想要从代理服务中得到这些头信息.我们只需设置 zuul.ignoreSecurityHeaders 的值为 false 即可,当你想要下游的服务去代替Spring Security响应HTTP Security 响应头时这是非常有用的.
9.6 路由端点
当你同时在项目中使用了 @EnableZuulProxy 注解和 Spring Boot Actuator 时默认情况下你会开启一个额外的端点, 可以通过HTTP的方式调用这个 “/routes” 地址, GET方式访问会返回映射的路由信息列表,POST方式访问将强制刷新已经存在的路由信息(万一在服务目录里面已经被改动了).你可以通过设置endpoints.routes.enabled 为false 来关闭这个端点.
注意:
* 虽然当服务目录里面路由信息改变时,它是会自动刷新的,但是POST访问”/routes”端点会使这个改变立即生效.
9.7 扼杀模式和本地转发(译者注:个人觉得应该叫分流)
迁移现有的应用程序或api的一种常见模式是扼杀老的路由端点,用不同的实现慢慢去替换他们。Zuul代理是个非常有用的工具去处理这个问题,因为它可以处理所有的客户端对老端点的路由问题,并且能对一些请求进行重定向到新程序或新API上。例如下面的配置:
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 Zuul上传文件
如果你开启了@EnableZuulProxy 注解,那么你可以用代理的路径去上传不是太大的文件,对于大文件上传,有一个可替代的路径, “/zuul/**”可以绕过Spring DispatcherServlet (避免大文件的处理),有个这样的路由 “zuul.routes.customers=/customers/**” ,那么你可以POST请求大文件到 “/zuul/customers/**”这个路径上,这个路径是通过zuul.servletPath 指定的外部化路由,如果你是通过ribbon的负载均衡进行代理的话上传大文件的同时也需要提高路由超时时间:
application.yml.
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds: 60000
ribbon:
ConnectTimeout: 3000
ReadTimeout: 60000
注意,对于用流来处理大文件,你需要使用在请求中使用chunked 编码(一些浏览器默认不那样做),比如这个命令:
$ curl -v -H "Transfer-Encoding: chunked" -F "file=@mylarge.iso" localhost:9999/zuul/simple/file
9.9 查询字符串编码
当处理即将到来的请求时,查询参数将被解码以遍在Zuul的过滤器中进行处理,然后通过路由转发时再对他进行重编码。如果编码时使用的是javascript的encodeURIComponent()方法,那么重编码的结果可能会不同,大多数情况下这样做并不会造成什么问题,但是有些web服务器对请求的参数是严格需要编码的。
为了强制给查询参数进行原始的编码,可以通过传递给ZuulProperties 一个特殊的标志然后通过HttpServletRequest::getQueryString 方法获取查询参数:
application.yml.
zuul:
forceOriginalQueryStringEncoding: true
注意,这个特殊的标志只在SimpleHostRoutingFilter 启用的时候才有用,HttpServletRequest 获取到这个查询参数的时候你可以通过RequestContext.getCurrentContext().setRequestQueryParams(someOverriddenParameters) 方法来覆盖查询参数。
9.10 纯嵌入式Zuul
你也可以不使用代理只跑一个纯zuul服务,或者用其他可选的代理工具,如果你使用@EnableZuulServer注解(替换@EnableZuulProxy),这个应用中添加的所有ZuulFilter类型的bean都不会自动被安装,而@EnableZuulProxy可以。
这种情况下这个Zuul服务仍然通过 “zuul.routes.*” 配置路由信息,但是不会有注册中心的服务发现和代理功能,”serviceId”和”url”设置的值会自动被忽略,例如:
application.yml.
zuul:
routes:
api: /api/**
将会映射所有”/api/**”的路径到Zuul 过滤链里面。
9.11 取消Zuul的过滤器
Zuul 的 Spring Cloud 版本默认会启用一些ZuulFilter bean无论是server模式还是proxy模式,可以看zuul filters包下可用的filters,如果你想关闭其中的一个,你只需配置 zuul…disable=true 就行了,默认的,filters包下的filter是zuul Filter类型,比如说关闭org.springframework.cloud.netflix.zuul.filters.post.SendResponseFilter 这个filter, 只需配置 zuul.SendResponseFilter.post.disable=true 即可。
9.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/**
如果你想提供一个默认的回调函数给所有的路由,你可以在 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;
}
};
}
}
9.13 Zuul 开发指南
对于zuul是怎样工作的,请移步Zuul Wiki
9.13.1 Zuul servlet
Zuul作为一种Servlet的实现,通常来讲,它嵌入到了Spring Dispatch的机制中,这一机制允许Spring MVC控制路由过程,在这种情况下,如果有需要在没有缓冲请求的情况下通过Zuul的话(例如大文件上传),Zuul也可以配置为缓冲请求,Servlet也可以绕过Spring Dispatcher 被访问,默认是在 /zuul 下,这个路径可以通过 zuul.servlet-path 设置.
9.13.2 Zuul RequestContext
Zuul使用RequestContext 在filters中传递信息,这些信息保存在每个请求的ThreadLocal副本中,包括路由的目的地,路由错误信息,还有HttpServletRequest和HttpServletResponse。RequestContext继承自ConcurrentHashMap,所以在context中可以储存任何东西,FilterConstants这个常量类包含Spring Cloud Netflix 自带的filters的key(稍后将详细介绍这些)。
9.13.3 @EnableZuulProxy注解和@EnableZuulServer注解的比较
Spring Cloud Netflix 在你以不同的注解使用Zuul时会自动安装一些filter. @EnableZuulProxy注解是@EnableZuulServer注解的超集,换句话说,@EnableZuulProxy注解包含所有的在@EnableZuulServer注解中自带的filters,它还开启了函数式路由功能。所以如果你想使用一个纯净的Zuul,你应该使用@EnableZuulServer注解。
9.13.4 @EnableZuulServer注解的过滤器
SimpleRouteLocator 类用来从Spring Boot中加载路由配置信息
以下过滤器将被装配(和正常的Spring Beans 一样):
Pre 型过滤器:
* ServletDetectionFilter: 检测请求是不是通过Spring Dispatcher或者ZuulFilter来的,会给 FilterConstants.ISDISPATCHERSERVLETREQUESTKEY 常量赋予一个booean值
FormBodyWrapperFilter: 解析表单数据然后重编码请求给下游应用。
DebugFilter: 检测请求参数中是否含有Debug,如果有的话,会给RequestContext.setDebugRouting() 和 RequestContext.setDebugRequest() 设为 true
Route 型过滤器:
* SendForwardFilter: 这个过滤器使用Servlet的RequestDispatcher对请求进行转发,转发的地址储存在RequestContext 的FilterConstants.FORWARDTOKEY 属性中,这在当前应用中进行本地转发是非常有用的。
POST 型过滤器:
* SendResponseFilter: 给代理的请求添加响应体到当前的响应体中
Error 型过滤器:
* SendErrorFilter: 如果RequestContext.getThrowable() 方法返回不为null,则会重定向到 /error 这个端点,这个端点可以通过 error.path 来指定
9.13.5 @EnableZuulProxy 注解的过滤器
DiscoveryClientRouteLocator 类用来从注册中心(例如Eureka)加载路由配置信息,和从配置文件加载一样。每个serviceId都是一个路由,每当services增加时,路由将会刷新。
和上述的filters一样,以下过滤器将被装配(和正常的Spring Beans 一样):
Pre 型过滤器:
* PreDecorationFilter: 这个过滤器基于提供的RouteLocator对象来决定路由的方式和路由的目的地,它还会给下游的请求设置和代理相关的请求头。
Route 型过滤器:
* RibbonRoutingFilter: 这个 filter 需要用到 Ribbon,Hystrix 和可插入式的 HTTP 客户端去发送请求,在RequestContext的FilterConstants.SERVICEIDKEY属性可以获取到service id列表,这个filter还可以使用不同的Http客户端。包括:Apache HttpClient(默认的),Squareup OkHttpClient v3 需要将com.squareup.okhttp3:okhttp库放入classpath中并且配置ribbon.okhttp.enabled=true 才能启用它,Netflix Ribbon HTTP client,可以通过配置ribbon.restclient.enabled=true启用它,但是他又部分限制,不如说不支持PATCH类型的请求方法,但是有内置的重试机制。
* SimpleHostRoutingFilter: 这个filter通过Apache Client给预先定义的URLs发送请求,可以通过RequestContext.getRouteHost()方法获取URLs。
9.13.6 自定义Zuul filter示例
下列”How to Write”的大部分例子都在 “Sample Zuul Filters” 这个仓库里面,在那个仓库里面还有操作 “请求/响应” 体的例子。
9.13.7 How to Write 一个 Pre 型过滤器
Pre 型过滤器是用来给下游的过滤器设置数据的,数据储存在RequestContext里里面,主要是用来给 route 型过滤器设置所需信息的。
public class QueryParamPreFilter extends ZuulFilter {
@Override
public int filterOrder() {
return PREDECORATIONFILTER_ORDER - 1; // filter优先级,数字越低等级越高
}
@Override
public String filterType() {
return PRE_TYPE;
}
@Override
public boolean shouldFilter() {
RequestContext ctx = RequestContext.getCurrentContext();
return !ctx.containsKey(FORWARD_TO_KEY) // 判断filter是否被转发
&& !ctx.containsKey(SERVICE_ID_KEY); // 判断filter是否定义了serviceId
}
@Override
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
if (request.getParameter("foo") != null) {
// 将serviceId 信息放入 `RequestContext`
ctx.put(SERVICE_ID_KEY, request.getParameter("foo"));
}
return null;
}
}
上面的这个filter给SERVICEIDKEY 填充了请求参数的“foo”的值,事实上,这样直接给这个SERVICEIDKEY映射值不是个好主意,但是serviceId 确实应该从 “foo”参数中动态获取。
现在这个SERVICEIDKEY有值了,RibbonRoutingFilter将会代替PreDecorationFilter去运行,如果你想要如路由到一个绝对地址,你可以调用 ctx.setRouteHost(url)方法。
可以通过给 REQUESTURIKEY 设置值去改变转发路由的地址。
9.13.8 How to Write 一个 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<String, String> responseHeaders = new LinkedMultiValueMap<>();
for (Map.Entry<String, List> 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;
}
}
这个filter将Servlet请求信息转换成OkHttp3请求信息,并执行HTTP请求,然后将OkHttp3响应信息再转换成Servlet响应信息。警告: 这个filter可能有bugs存在并且它不是正确的函数。
9.13.9 How to Write 一个post类型的过滤器
Post 类型的 filters 就是对响应体的操作了,在下面的filter,我们给 X-foo 信息头增加了一个随机的UUID值。还可以将响应体变得更复杂和难以计算。
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.13.10 Zuul对错误的处理
如果在zuul过滤器的生命周期内发生异常的话,会执行error类型的过滤器,SendErrorFilter会根据RequestContext.getThrowable()方法是否为空来启用,然后会给request设置指定的javax.servlet.error.*属性并转向Spring Boot的错误处理页面
9.13.11 Zuul 应用上下文饿汉式加载
Zuul 内部使用Ribbon执行远程调用,Spring Cloud第一次调用的时候Ribbon clients默认执行的是懒加载机制,这一默认的行为可以通过下列的配置改变,改变后可以导致Ribbon在应用启动的时候就执行饿加载。
application.yml.
zuul:
ribbon:
eager-load:
enabled: true
原创文章,作者:ItWorker,如若转载,请注明出处:https://blog.ytso.com/98836.html