SpringCloud Netflix复习之Zuul

写作背景

本文是继复习了Eureka、Ribbon、OpenFeign、Hystrix之后SpringCloud系列的微服务网关组件部分。
SpringCloud Zuul虽然现在用的比较少基本都被SpringCloud Gateway替代了但是因为公司的老项目中还是用的Zuul因此有必要再复习下。
本文的写作思路主要包括以下几个方面来主要是实战和源码验证

  1. Zuul是什么
  2. Zuul的核心功能
  3. 上手实战
  4. 从源码角度验证下核心功能

Zuul是什么

Zuul本身是Netflix公司研发的网关组件1.x的版本底层是基于阻塞式IOZuul 2.x版本基于Netty性能也很高SpringCloud Zuul就是SpringCloud官方基于Zuul做了一层封装。说到网关那么先回想一下之前的实战中没有网关写在fc-service-portal和fc-service-screen里的接口要想访问他们这两个服务里的接口需要切换对应的服务的ip和端口。这还只是个示例真正生产环境中微服务的个数是很多的不可能让前端访问后端的接口要记住那么多的host那前端哥们会疯掉。
网关就是让前端所有请求都先路由到网关由网关和RibbonHystrix等整合决定访问路由下游具体哪一个服务实例。如果网关要做高可用其实很简单只需要把网关部署多个实例然后用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-serverfc-gateway-zuulfc-service-portalfc-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)
默认不配置ribbonhystrix的超时时间是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拦截器servletfilter加入一些内置的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;
	}

其实就是再任何阶段如果有异常打印出异常日志在控制台然后将异常信息输出到浏览器中去。

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6
标签: Spring