SpringCloud Netflix复习之Zuul

news2024/12/22 19:45:17

文章目录

    • 写作背景
    • 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,因此有必要再复习下。
本文的写作思路主要包括以下几个方面来,主要是实战和源码验证

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

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;
	}

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/164597.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

C++入门--list

目录 list的介绍&#xff1a; list的构造&#xff1a; 遍历&#xff1a; reverse、sort、unique list的模拟实现&#xff1a; 反向迭代器&#xff1a; list与vector的比较&#xff1a; list的介绍&#xff1a; list是序列容器&#xff0c;允许在序列内的任何位置执行O(…

RocketMQ源码(19)—Broker处理DefaultMQPushConsumer发起的拉取消息请求源码【一万字】

基于RocketMQ release-4.9.3&#xff0c;深入的介绍了Broker处理DefaultMQPushConsumer发起的拉取消息请求源码。 此前我们学习了RocketMQ源码(18)—DefaultMQPushConsumer消费者发起拉取消息请求源码。我们知道consumer在发送了拉取消息请求的时候&#xff0c;请求的Code为PUL…

【 JavaScript编程详解 -1 】什么是JavaScript ?

文章目录简介Java与JavaScript的不同Javascrpt可以做什么JavaScript的构成为什么可以在浏览器中运行如何将 JavaScript 代码添加到网站&#xff1f;方法1- \<script\>标签内嵌JavaScript方法1- \<script\>标签引入外部JavaScript文件方法3- 内联 JavaScript编辑器推…

蛇形矩阵(简单明了的方法)

T112524 【数组2】蛇形矩阵 题目来源 AC代码 #include<stdio.h>int main() {int arr[111][111];int n 0;scanf("%d",&n);int temp 0;int sum 1;while(temp<(2*n-2)){for(int i0;i<n;i){for(int j0;j<n;j){if((ij) temp){if(temp%2 0){arr[…

基于FPGA的时间数字转换(TDC)设计(三)

1.多相位TDC计时测试 以下为多相位TDC计时的测试。图1为多相位TDC计时的测试框图,利用信号发生器,产生两路同相位、具有固定延时的脉冲信号,一路作为Start信号,另外一路作为Stop信号。由于分辨率为312.5ps,因此以312.5ps为步进,对Stop信号进行延时,扫描一个周期,通过UA…

红米 12C earth Fastboot 线刷包 root TWRP 刷入magisk recovery卡刷

红米 12C earth Fastboot 线刷包 root TWRP 刷入magisk recovery卡刷 红米 12C (earth) 国行版 Fastboot 线刷包 & Recovery 卡刷包 ROM 红米 12C earth Fastboot 线刷包 root TWRP 刷入magisk recovery卡刷下载 红米 12C 稳定版 Fastboot 线刷包 要安装国行版 红米 12C F…

对笔试使用《剑指offer》吧(第十天)

跟着博主一起刷题 这里使用的是题库&#xff1a; https://leetcode.cn/problem-list/xb9nqhhg/?page1 目录剑指 Offer 62. 圆圈中最后剩下的数字剑指 Offer 64. 求12…n剑指 Offer 65. 不用加减乘除做加法剑指 Offer 62. 圆圈中最后剩下的数字 剑指 Offer 62. 圆圈中最后剩下的…

【BP靶场portswigger-客户端12】跨站点请求伪造CSRF-12个实验(全)

前言&#xff1a; 介绍&#xff1a; 博主&#xff1a;网络安全领域狂热爱好者&#xff08;承诺在CSDN永久无偿分享文章&#xff09;。 殊荣&#xff1a;CSDN网络安全领域优质创作者&#xff0c;2022年双十一业务安全保卫战-某厂第一名&#xff0c;某厂特邀数字业务安全研究员&…

YOLO家族系列模型的演变:从v1到v8(上)

YOLO V8已经在本月发布了&#xff0c;我们这篇文章的目的是对整个YOLO家族进行比较分析。了解架构的演变可以更好地知道哪些改进提高了性能&#xff0c;并且明确哪些版本是基于那些版本的改进&#xff0c;因为YOLO的版本和变体的命名是目前来说最乱的&#xff0c;希望看完这篇文…

Android 深入系统完全讲解(18)

3 su Root 的相关代码原理 在 Android4.4 的时候&#xff0c;我们经常能够看到 Root Apk&#xff0c;作为发烧友操作这个才算是真的会 Android。那么学习 su 的代码&#xff0c;就是非常有意义的事情。这一节来说下 su 是如何管理权限 的&#xff0c;如何调用授权 Apk&#xf…

撒贝宁搭档某网红,直播带货人气破十万,央视主持人能接私活吗

随着互联网的发展&#xff0c;直播已经司空见惯&#xff0c;直播带货更是成为家常便饭&#xff0c;尤其是那些有了名气的人。就在不久前&#xff0c;央视著名主持人撒贝宁&#xff0c;也开启了自己的直播带货首秀&#xff0c;不得不说央视主持人还是多才多艺。 在很短时间内&am…

SpringBoot 自定义拦截器

SpringBoot 自定义拦截器 目录SpringBoot 自定义拦截器一、自定义拦截器二、编写控制器三、添加拦截器对象&#xff0c;注入到容器的配置类中另一种写法四、最后application运行一、自定义拦截器 创建登录拦截器 com/bjpowernode/springbootinterceptor02/interceptor LoginI…

改进YOLOv7系列:结合丰富的梯度流信息模块,来自YOLOv8的核心模块

&#x1f4a1;统一使用 YOLOv7 代码框架&#xff0c;结合不同模块来构建不同的YOLO目标检测模型。&#x1f31f;本项目包含大量的改进方式,降低改进难度,改进点包含【Backbone特征主干】、【Neck特征融合】、【Head检测头】、【注意力机制】、【IoU损失函数】、【NMS】、【Loss…

【Linux操作系统】进程优先级和进程切换

文章目录一.进程优先级1.三段论谈优先级2.PRI和NI二.进程切换1.进程的四个特性2.上下文数据保护和恢复一.进程优先级 1.三段论谈优先级 什么是优先级&#xff1f;它等同于权限吗&#xff1f; 定义: cpu资源分配的先后顺序&#xff0c;就是指进程的优先权&#xff08;priority…

SpringBoot + MDC 实现全链路调用日志跟踪

写在前面MDC介绍MDC使用MDC 存在的问题解决MDC存在的问题写在前面通过本文将了解到什么是MDC、MDC应用中存在的问题、如何解决存在的问题基于 Spring Boot MyBatis Plus Vue & Element 实现的后台管理系统 用户小程序&#xff0c;支持 RBAC 动态权限、多租户、数据权限、…

Shader踩坑笔记UV操作

一、UV坐标范围0-1 // 坐标范围 0-1&#xff0c;原点在画布左下角 vec2 uv FRAGCOORD.xy / iResolution.xy; 有两个变量 1、FRAGCOORD是godot引擎自带的变量表示纹理坐标 2、iResolution我自定义的变量&#xff0c;输入画布尺寸 使用uniform关键词可以创建自定义变量 比…

戴尔电脑怎么录屏?这6个方法教你轻松录屏

无论您是需要录制屏幕活动以创建在线发布的演示文稿、录制网络研讨会的屏幕以供日后参考&#xff0c;还是出于任何其他原因——如果您使用的是戴尔或类似 PC&#xff0c;您有多种选择。那么&#xff0c;让我们来谈谈如何在戴尔笔记本电脑上进行屏幕记录。 在戴尔笔记本电脑上录…

机器人介绍、应用、前景

机器人介绍、应用、前景1 介绍1.1 定义1.2 作用1.3 发展历程1.4 分类1.5 三大顾虑1.6 前景2 种类工业机器臂协作机械臂工业移动机器人复合机器人扫地机器人服务机器人机器狗人形机器人无人机3 技术3.1 机器人学分类3.2 功能分类3.2.1 感知3.2.2 决策3.2.3 执行AGV减震机构减速机…

经典问题:Python实现生产者消费者模式的多线程爬虫

Python实现生产者消费者模式的多线程爬虫1. 多组件的Pipeline技术架构2. 生产者消费者爬虫的架构3.多线程数据通信的queue.Queue4. 代码编写实现生产者消费者爬虫1. 多组件的Pipeline技术架构 复杂的事情一般都不会一下子做完&#xff0c;而是会分很多中间步骤一步步完成。 …

二十七、Kubernetes中DaemonSet(DS)控制器详解

1、概述 在kubernetes中&#xff0c;有很多类型的pod控制器&#xff0c;每种都有自己的适合的场景&#xff0c;常见的有下面这些&#xff1a; ReplicationController&#xff1a;比较原始的pod控制器&#xff0c;已经被废弃&#xff0c;由ReplicaSet替代 ReplicaSet&#xff…