文章目录
- 写作背景
- Zuul是什么
- Zuul的核心功能
- 上手实战
- SpringCloud中Zuul如何使用
- 自定义过滤器
- 配置全局Fallback降级
- Zuul请求头Ribbon等其他参数配置
- 过滤敏感请求头参数配置
- 开启Ribbon懒加载和Ribbon超时配置
- 开启Hystrix超时配置(一般不配置没啥用)
- 源码部分
- 请求入口ZuulServlet注入Spring容器的源码
- Zuul各种过滤器源码
- 6个pre过滤器(5个内置一个我自定义)
- 解析请求URI匹配路由规则的PreDecorationFilter过滤器源码
- 3个Route过滤器
- 真正发送请求的RibbonRoutingFilter过滤器源码
- 1个内置post过滤器SendResponseFilter
- 报错阶段的SendErrorFilter过滤器
写作背景
本文是继复习了Eureka、Ribbon、OpenFeign、Hystrix之后,SpringCloud系列的微服务网关组件部分。
SpringCloud Zuul虽然现在用的比较少,基本都被SpringCloud Gateway替代了,但是因为公司的老项目中还是用的Zuul,因此有必要再复习下。
本文的写作思路主要包括以下几个方面来,主要是实战和源码验证
- Zuul是什么?
- Zuul的核心功能
- 上手实战
- 从源码角度验证下核心功能
Zuul是什么
Zuul本身是Netflix公司研发的网关组件,1.x的版本底层是基于阻塞式IO(Zuul 2.x版本基于Netty性能也很高)SpringCloud Zuul就是SpringCloud官方基于Zuul做了一层封装。说到网关,那么先回想一下之前的实战中没有网关,写在fc-service-portal和fc-service-screen里的接口,要想访问他们这两个服务里的接口,需要切换对应的服务的ip和端口。这还只是个示例,真正生产环境中,微服务的个数是很多的,不可能让前端访问后端的接口要记住那么多的host,那前端哥们会疯掉。
网关就是让前端所有请求都先路由到网关,由网关和Ribbon,Hystrix等整合决定访问路由下游具体哪一个服务实例。如果网关要做高可用,其实很简单,只需要把网关部署多个实例,然后用Nginx做负载均衡,此时的架构就是
前端 => 负载均衡 => 网关 => 后端服务
Zuul的核心功能
Zuul在SpringCloud中的角色定位就是请求路由,然后解析请求URI,再根据你application.yml里配置的路由规则,进行路由匹配,然后将请求封装一下,基于Eureka + Ribbon实现服务的负载均衡,基于Hystirx包裹实际的请求,完成熔断降级就转发到对应的服务。
然后就是有很多过滤器
1、执行请求前阶段的pre过滤器
ServletDetectionFilter
Servlet30WrapperFilter
FromBodyWrapperFilter
DebugFilter
PreDecorationFilter
2、请求路由阶段的routing过滤器
RibbonRoutingFilter
SimpleHostRoutingFilter
SendForwardFilter
3、执行请求后返回结果前阶段的post过滤器
SendResponseFilter
4、执行错误阶段的error过滤器
SendErrorFilter
上手实战
SpringCloud中Zuul如何使用
新建一个fc-gateway-zuul的工程项目
1、pom.xml引入坐标依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
2、启动类增加@EnableZuulProxy注解开启Zuul功能
@SpringBootApplication
@EnableZuulProxy
public class FcGatewayZuulApplication {
public static void main(String[] args) {
SpringApplication.run(FcGatewayZuulApplication.class, args);
}
}
3、配置文件application配置路由到fc-service-portal里的
server:
port: 8000
spring:
application:
name: fc-gateway-zuul
#eureka相关配置
eureka:
client:
service-url:
defaultZone: http://root:123456@localhost:8761/eureka/
instance:
#显示的微服务名称
instance-id: ms-fc-gateway-zuul-8000
#eureka客户端向服务端发送心跳时间默认30s
lease-renewal-interval-in-seconds: 10
#Eureka服务器在接收到实例的最后一次发出的心跳后,需要等待多久才可以将此实例删除,默认为90秒
lease-expiration-duration-in-seconds: 30
#路由配置
zuul:
routes:
#主要用来唯一标识一个path的,一般用服务名
fc-service-portal:
path: /portal/**
启动ureka-server,fc-gateway-zuul,fc-service-portal,fc-service-screen
然后通过fc-gateway-zuul来访问fc-service-portal的一个接口
http://localhost:8000/portal/getUser3/2?age=27
看接口访问的结果,是预期的
说明fc-gateway-portal的路由效果起到了。
自定义过滤器
自定义一个继承ZuulFilter的MyZuulFilter类
@Slf4j
public class MyZuulFilter extends ZuulFilter {
@Override
public String filterType() {
//在哪个阶段执行
return FilterConstants.PRE_TYPE;
}
@Override
public int filterOrder() {
//数字越小优先级越大
return 1;
}
@Override
public boolean shouldFilter() {
//是否需要执行过滤器,默认是false
return true;
}
@Override
public Object run() throws ZuulException {
log.info("执行MyZuulFilter逻辑");
return null;
}
}
然后注入到Spring容器
@Configuration
public class MyZuulFilterConfig {
@Bean
public MyZuulFilter zuulFilter() {
return new MyZuulFilter();
}
}
我们启动服务,然后访问一个接口看看效果
http://localhost:8000/portal/getPortByFeign
配置全局Fallback降级
Zuul与Ribbon整合会用到RibbonRoutingFilter过滤器,转发的时候会用Hystrix包裹请求,如果请求失败会执行fallback逻辑。
定义一个实现了FallbackProvider接口的MyFallbackProvider类,然后通过@Bean注入到Spring容器。
import com.netflix.hystrix.exception.HystrixTimeoutException;
import org.springframework.cloud.netflix.zuul.filters.route.FallbackProvider;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.client.ClientHttpResponse;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
public class MyFallbackProvider implements FallbackProvider {
@Override
public String getRoute() {
//也可以针对某一个服务配置,比如fc-service-portal
return "*";
}
@Override
public ClientHttpResponse fallbackResponse(String route, Throwable cause) {
//这里可以根据实际的异常做不同的处理,hystrix默认超时时间是1s
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 HttpStatus.BAD_REQUEST.getReasonPhrase();
}
@Override
public void close() {
}
@Override
public InputStream getBody() throws IOException {
return new ByteArrayInputStream(("fallback:" + MyFallbackProvider.this.getRoute()).getBytes());
}
@Override
public HttpHeaders getHeaders() {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
return headers;
}
};
}
}
注入Spring容器
@Configuration
public class HystrixFallbackConfig {
@Bean
public FallbackProvider myFallbackProvider() {
return new MyFallbackProvider();
}
}
如果Ribbon和Hystrix的超时时间都不配置,那么默认1s超时熔断,可以测试下,我们让接口睡眠1s
@GetMapping("/getPortByFeign")
public int getPortByFeign() throws InterruptedException {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
log.info("请求头的token参数值:{}", request.getHeader("token"));
//睡1s测试hystrix默认超时时间
Thread.sleep(1000);
return screenFeignClient.getPort();
}
然后我们在fc-gateway-zuul里配置Hystrisx的超时时间为4s,为什么是4s因为Ribbon默认超时配置如下,计算出来hystrix至少是4s(ConnectTimeout+ReadTimeout) * (MaxAutoRetries+1)*(MaxAutoRetriesNextServer + 1) ,如果你设置比4s小,那么再请求失败的情况也只接熔断的,因为在Ribbon的重试时间内
ribbon:
#请求连接超时时间
ConnectTimeout: 1000
#请求处理超时时间
ReadTimeout: 1000
#对当前选中实例重试次数,不包括第⼀次调⽤
MaxAutoRetries: 0
#切换实例的重试次数
MaxAutoRetriesNextServer: 1
hystrix配置如下
hystrix:
command:
default:
execution:
isolation:
thread:
#熔断超时设置,默认为1s,不配置第一次很容易就熔断了
timeoutInMilliseconds: 4000
然后重启服务再试一下,发现还是进熔断了,这是为啥?
因为Ribbon的读超时就是1s,上面说过Ribbon和Hystrix的超时配置都是有效的,那么实际上项目里面只需要配置Ribbon的超时配置就可以了,Hystrix主要来配置断路器等参数优化(其实一般Hystrix的参数用默认就够了)
这时候要想成功就修改fc-gateway-zuul的Ribbon的超时参数
#ribbon的超时配置
ribbon:
ReadTimeout: 5000
ConnectTimeout: 2000
MaxAutoRetries: 1
MaxAutoRetriesNextServer: 3
修改完重启服务再访问一次
Zuul请求头Ribbon等其他参数配置
过滤敏感请求头参数配置
zuul.sensitiveHeaders配置忽略敏感请求头,例如如下配置意思是从网关Zuul的请求转发到下游服务比如fc-service-portal时,如果请求头有token会被忽略掉不往下游携带。
#路由配置
zuul:
#敏感请求头忽略参数
sensitiveHeaders:
- token
加个日志测试一下,访问如下接口
http://localhost:8000/portal/getPortByFeign
看日志
可以看到请求头里的token参数没了。
开启Ribbon懒加载和Ribbon超时配置
zuul:
ribbon:
eager-load:
#开启ribbon预加载,默认是false也就是第一次请求zuul才会初始化ribbon客户端
enabled: true
#ribbon的超时配置
ribbon:
ReadTimeout: 5000
ConnectTimeout: 2000
MaxAutoRetries: 1
MaxAutoRetriesNextServer: 3
开启Hystrix超时配置(一般不配置没啥用)
hystrix:
command:
fc-service-portal:
execution:
isolation:
thread:
timeoutInMilliseconds: 3000
hystrix的超时时间计算公式如下:
(ribbon.ConnectTimeout + ribbon.ReadTimeout) * (ribbon.MaxAutoRetries + 1) * (ribbon.MaxAutoRetriesNextServer + 1)
默认不配置ribbon,hystrix的超时时间是4s
因为默认的ribbon配置如下
ribbon:
ReadTimeout: 1000
ConnectTimeout: 1000
MaxAutoRetries: 0
MaxAutoRetriesNextServer: 1
(1 + 1) * 1* 2 = 4s
我们测试一下,写一个接口,睡眠4s看会不会进入熔断
@GetMapping("/getPort")
public int getPort() throws InterruptedException {
Thread.sleep(4000);
return restTemplate.getForObject("http://fc-service-screen/getPort", Integer.class);
}
从网关访问接口
进入熔断了
从日志里也可以看的出来是超时时间配置的问题,在Ribbon重试的时间内Hystrix超时了,换句话说,如果Ribbon和Hystrix两个都配置了超时时间,那么两个时间都有效,这一点和Feign与Ribbon都配置了超时时间以Feign超时为准不一样。
源码部分
请求入口ZuulServlet注入Spring容器的源码
Zuul的源码入口还得是从启动类上的@EnableZuulProxy注解入手
/**
* Sets up a Zuul server endpoint and installs some reverse proxy filters in it, so it can
* forward requests to backend servers. The backends can be registered manually through
* configuration or via DiscoveryClient.
*/
@EnableCircuitBreaker
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(ZuulProxyMarkerConfiguration.class)
public @interface EnableZuulProxy {
}
看注解上的注释的意思是这个@EnableZuulProxy注解其实干了两件事情,
第一件事情就是启用一个zuul server,所有的http请求,都会被他给拦截;
第二件事情,就是给那个zuul server(拦截器,servlet,filter)加入一些内置的filter,过滤器。
然后上面@Import导入了一个ZuulProxyMarkerConfiguration的配置类
/**
* Responsible for adding in a marker bean to trigger activation of
* {@link ZuulProxyAutoConfiguration}
*/
@Configuration(proxyBeanMethods = false)
public class ZuulProxyMarkerConfiguration {
@Bean
public Marker zuulProxyMarkerBean() {
return new Marker();
}
class Marker {
}
}
注释上还友情给了ZuulProxyAutoConfiguration的连接,我们进去看下
@Configuration(proxyBeanMethods = false)
@Import({ RibbonCommandFactoryConfiguration.RestClientRibbonConfiguration.class,
RibbonCommandFactoryConfiguration.OkHttpRibbonConfiguration.class,
RibbonCommandFactoryConfiguration.HttpClientRibbonConfiguration.class,
HttpClientConfiguration.class })
//看到这个没,也就是说ZuulProxyAutoConfiguration的自动装配条件是Spirng容器中需要有ZuulProxyMarkerConfiguration.Marker的bean。
@ConditionalOnBean(ZuulProxyMarkerConfiguration.Marker.class)
public class ZuulProxyAutoConfiguration extends ZuulServerAutoConfiguration {
@Autowired(required = false)
private List<RibbonRequestCustomizer> requestCustomizers = Collections.emptyList();
@Autowired(required = false)
private Registration registration;
@Autowired
private DiscoveryClient discovery;
@Autowired
private ServiceRouteMapper serviceRouteMapper;
@Override
public HasFeatures zuulFeature() {
return HasFeatures.namedFeature("Zuul (Discovery)",
ZuulProxyAutoConfiguration.class);
}
@Bean
@ConditionalOnMissingBean(DiscoveryClientRouteLocator.class)
public DiscoveryClientRouteLocator discoveryRouteLocator() {
return new DiscoveryClientRouteLocator(this.server.getServlet().getContextPath(),
this.discovery, this.zuulProperties, this.serviceRouteMapper,
this.registration);
}
// pre filters
@Bean
@ConditionalOnMissingBean(PreDecorationFilter.class)
public PreDecorationFilter preDecorationFilter(RouteLocator routeLocator,
ProxyRequestHelper proxyRequestHelper) {
return new PreDecorationFilter(routeLocator,
this.server.getServlet().getContextPath(), this.zuulProperties,
proxyRequestHelper);
}
// route filters
@Bean
@ConditionalOnMissingBean(RibbonRoutingFilter.class)
public RibbonRoutingFilter ribbonRoutingFilter(ProxyRequestHelper helper,
RibbonCommandFactory<?> ribbonCommandFactory) {
RibbonRoutingFilter filter = new RibbonRoutingFilter(helper, ribbonCommandFactory,
this.requestCustomizers);
return filter;
}
@Bean
@ConditionalOnMissingBean({ SimpleHostRoutingFilter.class,
CloseableHttpClient.class })
public SimpleHostRoutingFilter simpleHostRoutingFilter(ProxyRequestHelper helper,
ZuulProperties zuulProperties,
ApacheHttpClientConnectionManagerFactory connectionManagerFactory,
ApacheHttpClientFactory httpClientFactory) {
return new SimpleHostRoutingFilter(helper, zuulProperties,
connectionManagerFactory, httpClientFactory);
}
@Bean
@ConditionalOnMissingBean({ SimpleHostRoutingFilter.class })
public SimpleHostRoutingFilter simpleHostRoutingFilter2(ProxyRequestHelper helper,
ZuulProperties zuulProperties, CloseableHttpClient httpClient) {
return new SimpleHostRoutingFilter(helper, zuulProperties, httpClient);
}
@Bean
@ConditionalOnMissingBean(ServiceRouteMapper.class)
public ServiceRouteMapper serviceRouteMapper() {
return new SimpleServiceRouteMapper();
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingClass("org.springframework.boot.actuate.health.Health")
protected static class NoActuatorConfiguration {
@Bean
public ProxyRequestHelper proxyRequestHelper(ZuulProperties zuulProperties) {
ProxyRequestHelper helper = new ProxyRequestHelper(zuulProperties);
return helper;
}
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(Health.class)
protected static class EndpointConfiguration {
@Autowired(required = false)
private HttpTraceRepository traces;
@Bean
@ConditionalOnEnabledEndpoint
public RoutesEndpoint routesEndpoint(RouteLocator routeLocator) {
return new RoutesEndpoint(routeLocator);
}
@ConditionalOnEnabledEndpoint
@Bean
public FiltersEndpoint filtersEndpoint() {
FilterRegistry filterRegistry = FilterRegistry.instance();
return new FiltersEndpoint(filterRegistry);
}
@Bean
public ProxyRequestHelper proxyRequestHelper(ZuulProperties zuulProperties) {
TraceProxyRequestHelper helper = new TraceProxyRequestHelper(zuulProperties);
if (this.traces != null) {
helper.setTraces(this.traces);
}
return helper;
}
}
}
主要就是初始化了默认的一堆过滤器。我们看父类ZuulServerAutoConfiguration
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties({ ZuulProperties.class })
@ConditionalOnClass({ ZuulServlet.class, ZuulServletFilter.class })
@ConditionalOnBean(ZuulServerMarkerConfiguration.Marker.class)
public class ZuulServerAutoConfiguration {
}
看到ZuulServlet和ZuulServletFilter,这两个东西是Java Web应用里的东西,注册到例如Tomcat容器里负责拦截所有请求的。
@Bean
@ConditionalOnMissingBean(name = "zuulServlet")
@ConditionalOnProperty(name = "zuul.use-filter", havingValue = "false",
matchIfMissing = true)
public ServletRegistrationBean zuulServlet() {
ServletRegistrationBean<ZuulServlet> servlet = new ServletRegistrationBean<>(
new ZuulServlet(), this.zuulProperties.getServletPattern());
// The whole point of exposing this servlet is to provide a route that doesn't
// buffer requests.
servlet.addInitParameter("buffer-requests", "false");
return servlet;
}
@Bean
@ConditionalOnMissingBean(name = "zuulServletFilter")
@ConditionalOnProperty(name = "zuul.use-filter", havingValue = "true",
matchIfMissing = false)
public FilterRegistrationBean zuulServletFilter() {
final FilterRegistrationBean<ZuulServletFilter> filterRegistration = new FilterRegistrationBean<>();
filterRegistration.setUrlPatterns(
Collections.singleton(this.zuulProperties.getServletPattern()));
filterRegistration.setFilter(new ZuulServletFilter());
filterRegistration.setOrder(Ordered.LOWEST_PRECEDENCE);
// The whole point of exposing this servlet is to provide a route that doesn't
// buffer requests.
filterRegistration.addInitParameter("buffer-requests", "false");
return filterRegistration;
}
Zuul各种过滤器源码
我们现在ZuulServlet的service()方法打上断点,然后请求接口debug看下
com.netflix.zuul.http.ZuulServlet#service
@Override
public void service(javax.servlet.ServletRequest servletRequest, javax.servlet.ServletResponse servletResponse) throws ServletException, IOException {
try {
init((HttpServletRequest) servletRequest, (HttpServletResponse) servletResponse);
//请求的上下文
RequestContext context = RequestContext.getCurrentContext();
context.setZuulEngineRan();
try {
//先执行pre过滤器
preRoute();
} catch (ZuulException e) {
//如果出错执行error过滤器
error(e);
//然后执行post过滤器
postRoute();
return;
}
try {
//如果pre过滤器没报错,接着执行route过滤器
route();
} catch (ZuulException e) {
//如果出错执行error过滤器
error(e);
//然后执行post过滤器
postRoute();
return;
}
try {
//最后执行post过滤器
postRoute();
} catch (ZuulException e) {
//如果出错执行error过滤器
error(e);
return;
}
} catch (Throwable e) {
error(new ZuulException(e, 500, "UNHANDLED_EXCEPTION_" + e.getClass().getName()));
} finally {
RequestContext.getCurrentContext().unset();
}
}
6个pre过滤器(5个内置一个我自定义)
先看这个
public void preRoute() throws ZuulException {
FilterProcessor.getInstance().preRoute();
}
直接跟进去看FilterProcessor#preRoute
public void preRoute() throws ZuulException {
try {
//硬编码传进去一个pre字符串,看名字是执行pre过滤器
runFilters("pre");
} catch (ZuulException e) {
throw e;
} catch (Throwable e) {
throw new ZuulException(e, 500, "UNCAUGHT_EXCEPTION_IN_PRE_FILTER_" + e.getClass().getName());
}
}
跟进去看这个runFilters
public Object runFilters(String sType) throws Throwable {
boolean bResult = false;
//先获取所有pre过滤器
List<ZuulFilter> list = FilterLoader.getInstance().getFiltersByType(sType);
if (list != null) {
//循环执行
for (int i = 0; i < list.size(); i++) {
ZuulFilter zuulFilter = list.get(i);
//看这个方法
Object result = processZuulFilter(zuulFilter);
if (result != null && result instanceof Boolean) {
bResult |= ((Boolean) result);
}
}
}
return bResult;
}
找出了6个pre过滤器如下所示
processZuulFilter(zuulFilter)这个方法里面直接调用的是下面这个方法
com.netflix.zuul.ZuulFilter#runFilter
public ZuulFilterResult runFilter() {
ZuulFilterResult zr = new ZuulFilterResult();
if (!isFilterDisabled()) {
if (shouldFilter()) {
Tracer t = TracerFactory.instance().startMicroTracer("ZUUL::" + this.getClass().getSimpleName());
try {
//真正执行的是ZuulFilter的run()方法
Object res = run();
zr = new ZuulFilterResult(res, ExecutionStatus.SUCCESS);
} catch (Throwable e) {
t.setName("ZUUL::" + this.getClass().getSimpleName() + " failed");
zr = new ZuulFilterResult(ExecutionStatus.FAILED);
zr.setException(e);
} finally {
t.stopAndLog();
}
} else {
zr = new ZuulFilterResult(ExecutionStatus.SKIPPED);
}
}
return zr;
}
解析请求URI匹配路由规则的PreDecorationFilter过滤器源码
上面pre过滤器内置的有5个,我们主要看PreDecorationFilter,解析请求URI和路由规则匹配就是这个过滤器里完成的。从上面截图看到它是排在我MyZuulFilter顺序后面的,MyZuulFilter自定义的顺序order是1,看下它的,它的值是5,说明值越大优先级越低
@Override
public int filterOrder() {
//默认PRE_DECORATION_FILTER_ORDER = 5;
return PRE_DECORATION_FILTER_ORDER;
}
我们重点看下PreDecorationFilter的run方法
@Override
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
//解析请求URI
final String requestURI = this.urlPathHelper
.getPathWithinApplication(ctx.getRequest());
//匹配路由规则,就是把你写在application.yml里zuul.routes开头的规则来匹配
Route route = this.routeLocator.getMatchingRoute(requestURI);
if (route != null) {
我们打个断点看下
3个Route过滤器
获取route过滤器列表的方法和上面pre一样,就是getFiltersByType传入route字符串
可以看到有3个route过滤器
RibbonRoutingFilter => 将请求转发到服务的
SimpleHostRoutingFilter => 将请求转发到某个url地址的
SendForwardFilter => 将请求转发到zuul网关服务自己的一个接口上去
真正发送请求的RibbonRoutingFilter过滤器源码
我们看下RibbonRoutingFilter#run
@Override
public Object run() {
RequestContext context = RequestContext.getCurrentContext();
this.helper.addIgnoredHeaders();
try {
RibbonCommandContext commandContext = buildCommandContext(context);
ClientHttpResponse response = forward(commandContext);
setResponse(response);
return response;
}
}
protected RibbonCommandContext buildCommandContext(RequestContext context) {
HttpServletRequest request = context.getRequest();
MultiValueMap<String, String> headers = this.helper
.buildZuulRequestHeaders(request);
MultiValueMap<String, String> params = this.helper
.buildZuulRequestQueryParams(request);
String verb = getVerb(request);
InputStream requestEntity = getRequestBody(request);
if (request.getContentLength() < 0 && !verb.equalsIgnoreCase("GET")) {
context.setChunkedRequestBody();
}
String serviceId = (String) context.get(SERVICE_ID_KEY);
Boolean retryable = (Boolean) context.get(RETRYABLE_KEY);
Object loadBalancerKey = context.get(LOAD_BALANCER_KEY);
String uri = this.helper.buildZuulRequestURI(request);
// remove double slashes
uri = uri.replace("//", "/");
long contentLength = useServlet31 ? request.getContentLengthLong()
: request.getContentLength();
return new RibbonCommandContext(serviceId, verb, uri, retryable, headers, params,
requestEntity, this.requestCustomizers, contentLength, loadBalancerKey);
}
protected ClientHttpResponse forward(RibbonCommandContext context) throws Exception {
Map<String, Object> info = this.helper.debug(context.getMethod(),
context.getUri(), context.getHeaders(), context.getParams(),
context.getRequestEntity());
RibbonCommand command = this.ribbonCommandFactory.create(context);
try {
ClientHttpResponse response = command.execute();
this.helper.appendDebug(info, response.getRawStatusCode(),
response.getHeaders());
return response;
}
catch (HystrixRuntimeException ex) {
return handleException(info, ex);
}
}
我们打个断点看看
构造的RibbonCommand其实就是HttpClientRibbonCommand(继承自HystrixCommand),里面实现了run()逻辑,实现了这个command要发送的请求的核心逻辑。
HttpClientRibbonCommand,他核心的一点就是自己设置的那个run()方法,封装了自己核心的业务逻辑,发送一个请求出去
@Override
protected ClientHttpResponse run() throws Exception {
final RequestContext context = RequestContext.getCurrentContext();
RQ request = createRequest();
RS response;
boolean retryableClient = this.client instanceof AbstractLoadBalancingClient
&& ((AbstractLoadBalancingClient) this.client)
.isClientRetryable((ContextAwareRequest) request);
if (retryableClient) {
response = this.client.execute(request, config);
} else {
response = this.client.executeWithLoadBalancer(request, config);
}
...
return new RibbonHttpResponse(response);
}
这块代码有点眼熟,跟LoadBalancerFeignClient有点像。
1个内置post过滤器SendResponseFilter
我们直接看SendResponseFilter的run()方法
@Override
public Object run() {
try {
addResponseHeaders();
writeResponse();
}
catch (Exception ex) {
ReflectionUtils.rethrowRuntimeException(ex);
}
return null;
}
看方法吗,像是网响应头里加点东西,然后把响应流回写到浏览器
private void writeResponse(InputStream zin, OutputStream out) throws Exception {
byte[] bytes = buffers.get();
int bytesRead = -1;
while ((bytesRead = zin.read(bytes)) != -1) {
out.write(bytes, 0, bytesRead);
}
}
报错阶段的SendErrorFilter过滤器
之前的pre阶段、route阶段、post阶段,任何一个阶段抛出了异常,都会执行SendErrorFilter,我们直接看它的run方法
@Override
public Object run() {
try {
RequestContext ctx = RequestContext.getCurrentContext();
ExceptionHolder exception = findZuulException(ctx.getThrowable());
HttpServletRequest request = ctx.getRequest();
...
//错误路径,默认是/error可以配置文件通过error.path指定
RequestDispatcher dispatcher = request.getRequestDispatcher(this.errorPath);
if (dispatcher != null) {
ctx.set(SEND_ERROR_FILTER_RAN, true);
if (!ctx.getResponse().isCommitted()) {
ctx.setResponseStatusCode(exception.getStatusCode());
dispatcher.forward(request, ctx.getResponse());
}
}
}
catch (Exception ex) {
ReflectionUtils.rethrowRuntimeException(ex);
}
return null;
}
其实就是再任何阶段如果有异常,打印出异常日志,在控制台,然后将异常信息输出到浏览器中去。