Zuul网关原理以及源码解析

一、基本概念

1.1 Zuul网关过滤器类型

pre:优先执行的过滤器,其中自动配置中配置的PreDecorationFilter,主要负责决定该请求的路由以及发送给下游服务的额外请求头。

route:在pre过滤器后执行的过滤器。当zuul网关中为某一路由名称配置的是serviceId时,由ribbonRoutingFilter采用ribbion的方式决定请求下游服务地址并且进行远程调用,并将响应结果写入的RequestContext中;当zuul网关中为某一路由名称配置的是url时,由SimpleHostRoutingFilter采用apache httpClient的方式,将路由配置的URL与请求URI拼接作为下游服务请求URL并进行远程调用,将响应结果写入的RequestContext中。

error:pre、route过滤器中任何步骤出错都会执行error过滤器。

post:在pre、route、error类型的过滤器执行后执行。自动配置中的SendResponseFilter过滤器将RequestContext中存放的远程调用返回的响应头、响应体等信息组成响应信息响应给客户端。

1.2 Zuul网关过滤器执行优先级

从类型来看:pre–>route–>(error)—>post

从指定的order来看:oder越小,越优先执行

1.3 Zuul网关过滤器执行条件

Zuul网关过滤器执行条件由父类ZuulFilter中的方法shouldFilter返回值决定,true表示执行,false表示不执行。

二、Zuul网关原理

2.1 Zuul网关架构图

在这里插入图片描述

三、源码解读

以spring-cloud-netflix-zuul-2.2.9.RELEASE为基础解读Zuul网关源码

3.1 与Zuul网关相关的SpringBoot自动配置

spring-cloud-netflix-zuul-2.2.9.RELEASE包中的spring.factories文件:

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

ZuulServerAutoConfiguration配置类注入的比较重要的Bean:

CompositeRouteLocator:主要负责根据path以及路由配置决定路由。

ZuulController:网关接收客户端请求的统一Controller

ZuulServlet:执行网关逻辑的入口

FormBodyWrapperFilter:解析客户端表单数据,并为下游服务重新编码。

SendResponseFilter:当网关没有发生异常时,从RequestContext取出下游服务的响应信息,并将它们组装成返回给浏览器的响应信息并响应。

SendErrorFilter:当网关发生异常时,从RequestContext取出异常信息组装成返回给浏览器的响应信息并响应。

ZuulProxyAutoConfiguration配置类注入的比较重要的Bean:

PreDecorationFilter:主要负责决定客户端请求的路由以及发送给下游服务的额外请求头。

RibbonRoutingFilter:当zuul网关中为某一路由名称配置的是serviceId时,由ribbonRoutingFilter采用ribbion的方式决定请求下游服务地址并且进行远程调用,并将响应结果写入的RequestContext中。

配置示例:

zuul:
  routes:
    goods:
      path: /goods/**
      serviceId: goods

SimpleHostRoutingFilter:当zuul网关中为某一路由名称配置的是url时,由SimpleHostRoutingFilter采用apache httpClient的方式,将路由配置的URL与请求URI拼接作为下游服务请求URL并进行远程调用,将响应结果写入的RequestContext中。

zuul:
  routes:
    goods:
      path: /goods/**
      url: http://localhost:8081

例如:请求到网关的URL为localhost:8090/goods/hello,则网关请求下游服务的URL为:http://localhost:8081/hello。

3.2 Zuul网关执行逻辑

3.2.1 由ZuulController统一接收浏览器请求。

ZuulController#handleRequest()

@Override
public ModelAndView handleRequest(HttpServletRequest request,
		HttpServletResponse response) throws Exception {
	try {
		// We don't care about the other features of the base class, just want to			    // handle the request
		return super.handleRequestInternal(request, response);
	}finally {
	    // @see com.netflix.zuul.context.ContextLifecycleFilter.doFilter
		RequestContext.getCurrentContext().unset();
	}
}

handleRequestInternal方法:

protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception {
        Assert.state(this.servletInstance != null, "No Servlet instance");
        this.servletInstance.service(request, response);
        return null;
}

3.2.2由ZuulServlet中的service方法执行逻辑:

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

ZuulServlet中的service中首先调用init方法,将浏览器的request和response存放到RequestContext。RequestContext继承ConcurrentHashMap并且存放在threadLocal,作用域在一次请求中。调用完init方法,执行preRoute方法。

FilterProcessor#preRoute():

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

preRoute方法执行类型为“pre”的过滤器。

FilterProcessor#runFilte():

public Object runFilters(String sType) throws Throwable {
        if (RequestContext.getCurrentContext().debugRouting()) {
            Debug.addRoutingDebug("Invoking {" + sType + "} type filters");
        }

        boolean bResult = false;
        List<ZuulFilter> list = FilterLoader.getInstance().getFiltersByType(sType);
        if (list != null) {
            for(int i = 0; i < list.size(); ++i) {
                ZuulFilter zuulFilter = (ZuulFilter)list.get(i);
                Object result = this.processZuulFilter(zuulFilter);
                if (result != null && result instanceof Boolean) {
                    bResult |= (Boolean)result;
                }
            }
        }

        return bResult;
}

在根据过滤器类型名称获取过滤器方法getFiltersByType中获取对应的过滤器并调用Collections.sort(list)方法对过滤器排序。

public List<ZuulFilter> getFiltersByType(String filterType) {
    List<ZuulFilter> list = (List)this.hashFiltersByType.get(filterType);
    if (list != null) {
        return list;
    } else {
        List<ZuulFilter> list = new ArrayList();
        Collection<ZuulFilter> filters = this.filterRegistry.getAllFilters();
        Iterator iterator = filters.iterator();

        while(iterator.hasNext()) {
            ZuulFilter filter = (ZuulFilter)iterator.next();
            if (filter.filterType().equals(filterType)) {
                list.add(filter);
            }
        }

        Collections.sort(list);
        this.hashFiltersByType.putIfAbsent(filterType, list);
        return list;
    }
}

ZuulFilter#compareTo方法表明默认根据order从小到大排序,即在后续的对过滤器进行for循环执行时,order越小越优先执行。

public int compareTo(ZuulFilter filter) {
        return Integer.compare(this.filterOrder(), filter.filterOrder());
}

for循环为每个过滤器执行processZuulFilter方法,processZuulFilter方法中调用runFilter方法,如果过滤器中的shouldFilter返回true时执行ZuulFilter#run方法

public ZuulFilterResult runFilter() {
        ZuulFilterResult zr = new ZuulFilterResult();
        if (!this.isFilterDisabled()) {
            if (this.shouldFilter()) {
                //省略代码..
                try {
                    Object res = this.run();
                    //省略代码..
                } catch (Throwable var7) {
                    //省略代码..
                } finally {
                    //省略代码..
                }
            } else {
                zr = new ZuulFilterResult(ExecutionStatus.SKIPPED);
            }
        }

        return zr;
    }

执行完preRoute()方法,若出现异常,则依次执行类型为error、post的过滤器,逻辑与pre过滤器一致,区别在于过滤器类型不一样;否则依次执行route()、postRoute()方法。

3.3 主要过滤器执行逻辑

详情解释见代码注释

3.3.1 pre类型

SevletDetectionFilter:决定由ZuulServlet还是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:表单数据解析过滤器

@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:主要负责决定客户端请求的路由以及发送给下游服务的额外请求头。

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

3.3.2 route类型

RibbonRoutingFilter:由Ribbon负载均衡决定下游服务地址并且进行请求,将下游服务响应结果存入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:以简单Apache HttpClient的方式调用下游服务

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

SendForwardFilter:当网关配置的URL中含有forward标识时,执行的请求转发过滤器

@Override
public boolean shouldFilter() {
	RequestContext ctx = RequestContext.getCurrentContext();
    //需要请求转发时但还未执行请求转发的情况下执行该过滤器
	return ctx.containsKey(FORWARD_TO_KEY)
			&& !ctx.getBoolean(SEND_FORWARD_FILTER_RAN, false);
}
@Override
public Object run() {
	try {
		RequestContext ctx = RequestContext.getCurrentContext();
		String path = (String) ctx.get(FORWARD_TO_KEY);
		RequestDispatcher dispatcher = ctx.getRequest().getRequestDispatcher(path);
		if (dispatcher != null) {
            //表示请求转发了
			ctx.set(SEND_FORWARD_FILTER_RAN, true);
			if (!ctx.getResponse().isCommitted()) {
                //请求转发
				dispatcher.forward(ctx.getRequest(), ctx.getResponse());
                //将response清空,这样后续的响应客户端的过滤器SendResponseFilter就不会执行了
				ctx.getResponse().flushBuffer();
			}
		}
	}
	catch (Exception ex) {
		ReflectionUtils.rethrowRuntimeException(ex);
	}
	return null;
}

3.3.3 post类型

SendResponseFilter:将响应信息从RequestContext中取出并组装,响应给浏览器。

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

参考地址:

1.Zuul网关官网文档

2.关于x-forward-for的解释

3.请求转发和重定向后面的代码是否执行

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值