zuul source code analysis

zuul source code analysis

The dependency of zuul and springboot integration

   <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
        </dependency>

Seeing the starter, the first reaction is the automatic assembly of springboot?
We can see the content of the file in the spring.factories file under the zuul package

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.netflix.zuul.ZuulServerAutoConfiguration,\
org.springframework.cloud.netflix.zuul.ZuulProxyAutoConfiguration

You can see the two classes of configuration, ZuulServerAutoConfiguration and ZuulProxyAutoConfiguration,
insert image description here
insert image description here
the difference between these two classes is the effective condition

@ConditionalOnBean(ZuulProxyMarkerConfiguration.Marker.class)
@ConditionalOnBean(ZuulServerMarkerConfiguration.Marker.class)

When the zuul gateway is in use, the @EnableZuulProxy annotation is generally added to the startup class.
Click to see it

@EnableCircuitBreaker
@Target({
    
    ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Import({
    
    ZuulProxyMarkerConfiguration.class})
public @interface EnableZuulProxy {
    
    
}

Imported the ZuulProxyMarkerConfiguration class for us
insert image description here
and only did one more thing is to import mark for us to make the startup take effect

So the question is what is the difference between ZuulServerAutoConfiguration and ZuulProxyAutoConfiguration

insert image description here
The static internal class ZuulFilterConfiguration of ZuulServerAutoConfiguration

@Configuration(
        proxyBeanMethods = false
    )
    protected static class ZuulFilterConfiguration {
    
    
        @Autowired
        private Map<String, ZuulFilter> filters;

        protected ZuulFilterConfiguration() {
    
    
        }

        @Bean
        public ZuulFilterInitializer zuulFilterInitializer(CounterFactory counterFactory, TracerFactory tracerFactory) {
    
    
            FilterLoader filterLoader = FilterLoader.getInstance();
            FilterRegistry filterRegistry = FilterRegistry.instance();
            return new ZuulFilterInitializer(this.filters, counterFactory, tracerFactory, filterLoader, filterRegistry);
        }
    }

The above code is to initialize the filter

in

      @Autowired
        private Map<String, ZuulFilter> filters;

All filters that inherit ZuulFilter and are managed by spring will be added in, which is implemented by spring

Determination of the request interceptor
The next question is, when I use postman to request zuul, which class will be processed?
insert image description here
The framework uses springMvc to process requests. Anyone who is familiar with the source code of springMvc knows that the request is processed by DispatcherServlet. Here I made a breakpoint on the
insert image description here
DispatcherServlet.doDispatch method
insert image description here
.
insert image description here
Injected into ZuulHandlerMapping, ZuulController

ZuulController
insert image description here
ZuulController source code found to inherit ServletWrappingController
, so search for what ServletWrappingController does
insert image description here

ServletWrappingController will intercept the request and hand it over to the internally packaged servlet for processing.
Finally, all requests will be intercepted by ZuulController and processed by the internally packaged ZuulServlet

After requesting to the handleRequest method of ZuulController,
insert image description here
this servletInstance instance will be called, which is the service method
of ZuulServlet processing each http request

  public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
    
    
        try {
    
    
            this.init((HttpServletRequest)servletRequest, (HttpServletResponse)servletResponse);
            RequestContext context = RequestContext.getCurrentContext();
            context.setZuulEngineRan();

            try {
    
    
                this.preRoute();
            } catch (ZuulException var13) {
    
    
                this.error(var13);
                this.postRoute();
                return;
            }

            try {
    
    
                this.route();
            } catch (ZuulException var12) {
    
    
                this.error(var12);
                this.postRoute();
                return;
            }

            try {
    
    
                this.postRoute();
            } catch (ZuulException var11) {
    
    
                this.error(var11);
            }
        } catch (Throwable var14) {
    
    
            this.error(new ZuulException(var14, 500, "UNHANDLED_EXCEPTION_" + var14.getClass().getName()));
        } finally {
    
    
            RequestContext.getCurrentContext().unset();
        }
    }

Let's look at the preRoute method

   public void preRoute() throws ZuulException {
    
    
        try {
    
    
            this.runFilters("pre");
        } catch (ZuulException var2) {
    
    
            throw var2;
        } catch (Throwable var3) {
    
    
            throw new ZuulException(var3, 500, "UNCAUGHT_EXCEPTION_IN_PRE_FILTER_" + var3.getClass().getName());
        }
    }

runFilters method
insert image description here

The main thing is to get all pre type filters and sort them,
then use the processZuulFilter method

processZuulFilter method

public Object processZuulFilter(ZuulFilter filter) throws ZuulException {
    
    
        RequestContext ctx = RequestContext.getCurrentContext();
        boolean bDebug = ctx.debugRouting();
        String metricPrefix = "zuul.filter-";
        long execTime = 0L;
        String filterName = "";

        try {
    
    
            long ltime = System.currentTimeMillis();
            filterName = filter.getClass().getSimpleName();
            RequestContext copy = null;
            Object o = null;
            Throwable t = null;
            if (bDebug) {
    
    
                Debug.addRoutingDebug("Filter " + filter.filterType() + " " + filter.filterOrder() + " " + filterName);
                copy = ctx.copy();
            }

            ZuulFilterResult result = filter.runFilter();
            ExecutionStatus s = result.getStatus();
            execTime = System.currentTimeMillis() - ltime;
            switch (s) {
    
    
                case FAILED:
                    t = result.getException();
                    ctx.addFilterExecutionSummary(filterName, ExecutionStatus.FAILED.name(), execTime);
                    break;
                case SUCCESS:
                    o = result.getResult();
                    ctx.addFilterExecutionSummary(filterName, ExecutionStatus.SUCCESS.name(), execTime);
                    if (bDebug) {
    
    
                        Debug.addRoutingDebug("Filter {" + filterName + " TYPE:" + filter.filterType() + " ORDER:" + filter.filterOrder() + "} Execution time = " + execTime + "ms");
                        Debug.compareContextState(filterName, copy);
                    }
            }

            if (t != null) {
    
    
                throw t;
            } else {
    
    
                this.usageNotifier.notify(filter, s);
                return o;
            }
        } catch (Throwable var15) {
    
    
            if (bDebug) {
    
    
                Debug.addRoutingDebug("Running Filter failed " + filterName + " type:" + filter.filterType() + " order:" + filter.filterOrder() + " " + var15.getMessage());
            }

            this.usageNotifier.notify(filter, ExecutionStatus.FAILED);
            if (var15 instanceof ZuulException) {
    
    
                throw (ZuulException)var15;
            } else {
    
    
                ZuulException ex = new ZuulException(var15, "Filter threw Exception", 500, filter.filterType() + ":" + filterName);
                ctx.addFilterExecutionSummary(filterName, ExecutionStatus.FAILED.name(), execTime);
                throw ex;
            }
        }
    }
**最重要就是调用runfliter方法**
 public ZuulFilterResult runFilter() {
    
    
        ZuulFilterResult zr = new ZuulFilterResult();
        if (!this.isFilterDisabled()) {
    
    
            if (this.shouldFilter()) {
    
    
                Tracer t = TracerFactory.instance().startMicroTracer("ZUUL::" + this.getClass().getSimpleName());

                try {
    
    
                    Object res = this.run();
                    zr = new ZuulFilterResult(res, ExecutionStatus.SUCCESS);
                } catch (Throwable var7) {
    
    
                    t.setName("ZUUL::" + this.getClass().getSimpleName() + " failed");
                    zr = new ZuulFilterResult(ExecutionStatus.FAILED);
                    zr.setException(var7);
                } finally {
    
    
                    t.stopAndLog();
                }
            } else {
    
    
                zr = new ZuulFilterResult(ExecutionStatus.SKIPPED);
            }
        }
	

The run method of loop execution filter
executes the preRoute() method. If an exception occurs, the filters of type error and post are executed in sequence. The logic is the same as that of the pre filter, the difference is that the filter types are different; otherwise, the route( ), postRoute() method.

Internal main filter execution logic

pre type
SevletDetectionFilter: decide whether to execute the request by ZuulServlet or DispatchServlet.

@Override
public Object run() {
    
    
	RequestContext ctx = RequestContext.getCurrentContext();
	HttpServletRequest request = ctx.getRequest();
	if (!(request instanceof HttpServletRequestWrapper)
			&& isDispatcherServletRequest(request)) {
    
    
        //由DispatchServlet执行请求时,HttpServletRequest没有被包装过并且Attributes中包含
        //DispatchServlet上下文参数
		ctx.set(IS_DISPATCHER_SERVLET_REQUEST_KEY, true);
	}
	else {
    
    
        //由ZuulServlet执行请求
		ctx.set(IS_DISPATCHER_SERVLET_REQUEST_KEY, false);
	}
	return null;
}

FormBodyWrapperFilter: form data parsing filter

@Override
public boolean shouldFilter() {
    
    
	RequestContext ctx = RequestContext.getCurrentContext();
	HttpServletRequest request = ctx.getRequest();
	String contentType = request.getContentType();
	//GET 请求不执行
	if (contentType == null) {
    
    
		return false;
	}
    //仅处理表单数据和在DispatchServlet中的Multipart数据。
	try {
    
    
		MediaType mediaType = MediaType.valueOf(contentType);
		return MediaType.APPLICATION_FORM_URLENCODED.includes(mediaType)
			|| (isDispatcherServletRequest(request)
					&& MediaType.MULTIPART_FORM_DATA.includes(mediaType));
	}
	catch (InvalidMediaTypeException ex) {
    
    
		return false;
	}
}

@Override
public Object run() {
    
    
	RequestContext ctx = RequestContext.getCurrentContext();
	HttpServletRequest request = ctx.getRequest();
	FormBodyRequestWrapper wrapper = null;
	if (request instanceof HttpServletRequestWrapper) {
    
    
		HttpServletRequest wrapped = (HttpServletRequest) ReflectionUtils
				.getField(this.requestField, request);
        //FormBodyRequestWrapper其实就是将request中的表单数据提取出来
		wrapper = new FormBodyRequestWrapper(wrapped);
        //把wrapper对象赋值给request中的request属性,供后续使用
		ReflectionUtils.setField(this.requestField, request, wrapper);
		//若请求已经被包装过,则将wrapper对象赋值给包装过的request中的request属性,供后续使用
        if (request instanceof ServletRequestWrapper) {
    
    
			ReflectionUtils.setField(this.servletRequestField, request, wrapper);
		}
	}
	else {
    
    
		wrapper = new FormBodyRequestWrapper(request);
		ctx.setRequest(wrapper);
	}
	if (wrapper != null) {
    
    
		ctx.getZuulRequestHeaders().put("content-type", wrapper.getContentType());
	}
	return null;
}

PreDecorationFilter: Mainly responsible for determining the routing of client requests and additional request headers sent to downstream services.

@Override
public boolean shouldFilter() {
    
    
	RequestContext ctx = RequestContext.getCurrentContext();
	return !ctx.containsKey(FORWARD_TO_KEY) //还未经过决定请求转发的过滤器
			&& !ctx.containsKey(SERVICE_ID_KEY); //还未经过决定serviceId的过滤器
}
@Override
public Object run() {
    
    
	RequestContext ctx = RequestContext.getCurrentContext();
	final String requestURI = this.urlPathHelper
			.getPathWithinApplication(ctx.getRequest());
	if (insecurePath(requestURI)) {
    
    
		throw new InsecureRequestPathException(requestURI);
	}
    //根据URI以及配置的属性获取路由
	Route route = this.routeLocator.getMatchingRoute(requestURI);
	if (route != null) {
    
    
		String location = route.getLocation();
		if (location != null) {
    
    
			ctx.put(REQUEST_URI_KEY, route.getPath());
			ctx.put(PROXY_KEY, route.getId());
			if (!route.isCustomSensitiveHeaders()) {
    
    
                //将需要过滤的敏感头信息放入RequestContext
				this.proxyRequestHelper.addIgnoredHeaders(
						this.properties.getSensitiveHeaders().toArray(new String[0]));
			}
			else {
    
    
				this.proxyRequestHelper.addIgnoredHeaders(
						route.getSensitiveHeaders().toArray(new String[0]));
			}
				if (route.getRetryable() != null) {
    
    
				ctx.put(RETRYABLE_KEY, route.getRetryable());
			}
            //网关配置为url
			if (location.startsWith(HTTP_SCHEME + ":")
					|| location.startsWith(HTTPS_SCHEME + ":")) {
    
    
		//设置下游服务的地址,供后续SimpleHostRoutingFilter(以httpClient的方式请求下游服务)使用
                ctx.setRouteHost(getUrl(location));
				ctx.addOriginResponseHeader(SERVICE_HEADER, location);
			}
            //网关配置的url以forward:开头,表明请求转发。
			else if (location.startsWith(FORWARD_LOCATION_PREFIX)) {
    
    
		        //去除forward标识字符,重新设置下游服务url放入RequestContext,
                //供后续SendForwardFilter过滤器使用        
                ctx.set(FORWARD_TO_KEY,
						StringUtils.cleanPath(
								location.substring(FORWARD_LOCATION_PREFIX.length())											+ route.getPath()));
					ctx.setRouteHost(null);
					return null;
			}
			else {
    
    
				//设置serviceId,供后续RibbonRoutingFilter使用
                //由注册中心以及ribbon负载均衡决定最终下游服务地址
				ctx.set(SERVICE_ID_KEY, location);
				ctx.setRouteHost(null);
				ctx.addOriginResponseHeader(SERVICE_ID_HEADER, location);
			}
            //配置文件是否设置加入代理头
            //X-Forwarded-Host:请求的主机列表,用逗号隔开,越靠后越接近服务器
            //X-Forwarded-Port:请求的端口列表,用逗号隔开,越靠后越接近服务器
            //Forwarded-Proto:请求的协议列表,用逗号隔开,越靠后越接近服务器
            //X-Forwarded-Prefix:请求网关的前缀
            //X-Forwarded-For:请求的ip列表,用逗号隔开,越靠后越接近服务器
			if (this.properties.isAddProxyHeaders()) {
    
    
				addProxyHeaders(ctx, route);
				String xforwardedfor = ctx.getRequest()
						.getHeader(X_FORWARDED_FOR_HEADER);
				String remoteAddr = ctx.getRequest().getRemoteAddr();
				if (xforwardedfor == null) {
    
    
					xforwardedfor = remoteAddr;
				}
				else if (!xforwardedfor.contains(remoteAddr)) {
    
     // Prevent duplicates
					xforwardedfor += ", " + remoteAddr;
				}
				ctx.addZuulRequestHeader(X_FORWARDED_FOR_HEADER, xforwardedfor);
			}
			if (this.properties.isAddHostHeader()) {
    
    
				ctx.addZuulRequestHeader(HttpHeaders.HOST,
					toHostHeader(ctx.getRequest()));
			}
		}
	}
	else {
    
    
		log.warn("No route found for uri: " + requestURI);
		String forwardURI = getForwardUri(requestURI);
		ctx.set(FORWARD_TO_KEY, forwardURI);
	}
	return null;
}

route type

RibbonRoutingFilter: The downstream service address is determined by Ribbon load balancing and the request is made, and the downstream service response result is stored in the RequestContext

@Override
public boolean shouldFilter() {
    
    
	RequestContext ctx = RequestContext.getCurrentContext();
    //以serviceId的方式配置路由时执行该过滤器
	return (ctx.getRouteHost() == null && ctx.get(SERVICE_ID_KEY) != null
			&& ctx.sendZuulResponse());
}
@Override
public Object run() {
    
    
	RequestContext context = RequestContext.getCurrentContext();
	this.helper.addIgnoredHeaders();
	try {
    
    
        //根据请求头、请求参数、请求体、请求方法、serviceId、loadBalancerKey等构成
        //ribbon上下文
		RibbonCommandContext commandContext = buildCommandContext(context);
        //创建command并且进行远程调用
		ClientHttpResponse response = forward(commandContext);
		//将下游服务响应结果存放在RequestContext,供后post过滤器SendResponseFilter使用。
        setResponse(response);
		return response;
	}catch (ZuulException ex) {
    
    
		throw new ZuulRuntimeException(ex);
	}catch (Exception ex) {
    
    
		throw new ZuulRuntimeException(ex);
	}
}

SimpleHostRoutingFilter: Call downstream services in a simple Apache HttpClient way

@Override
public boolean shouldFilter() {
    
    
   //路由配置的是URL时执行该过滤器
   return RequestContext.getCurrentContext().getRouteHost() != null
         && RequestContext.getCurrentContext().sendZuulResponse();
}

@Override
public Object run() {
    
    
   RequestContext context = RequestContext.getCurrentContext();
   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 (getContentLength(request) < 0) {
    
    
      context.setChunkedRequestBody();
   }

   String uri = this.helper.buildZuulRequestURI(request);
   this.helper.addIgnoredHeaders();

   try {
    
    
   	  //远程调用下游服务
      CloseableHttpResponse response = forward(this.httpClient, verb, uri, request,
            headers, params, requestEntity);
      //保存下游服务响应结果
      setResponse(response);
   }
   catch (Exception ex) {
    
    
      throw new ZuulRuntimeException(handleException(ex));
   }
   return null;
}

post type

SendResponseFilter: Take and assemble the response information from the RequestContext, and respond to the browser.

@Override
public boolean shouldFilter() {
    
    
	RequestContext context = RequestContext.getCurrentContext();
    //不发生异常,下游服务有响应并且响应信息没被清空时执行
	return context.getThrowable() == null
			&& (!context.getZuulResponseHeaders().isEmpty()
					|| context.getResponseDataStream() != null
					|| context.getResponseBody() != null);
}
@Override
public Object run() {
    
    
	try {
    
    
        //组装下游服务的响应头和浏览器响应头
		addResponseHeaders();
        //响应给浏览器
		writeResponse();
	}
	catch (Exception ex) {
    
    
		ReflectionUtils.rethrowRuntimeException(ex);
	}
	return null;
}

```


Guess you like

Origin blog.csdn.net/qq_42600094/article/details/130639697