文章目录
- 写作背景
- 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;
	}
其实就是再任何阶段如果有异常,打印出异常日志,在控制台,然后将异常信息输出到浏览器中去。





















