【微服务】服务网关 Zuul 工作原理源码解析

烈酒焚心 提交于 2020-08-04 17:58:40

zuul 服务网关工作原理

1. zuul 基础概念

1.1 zuul 自动化配置:ZuulServerAutoConfiguration

// Zuul 空指处理器,处理所有非默认/zuul路径请求
@Bean
public ZuulController zuulController() {
    return new ZuulController();
}

// Zuul 请求映射器: 绑定请求路径与处理器关系(Springmvc 标准组件)
@Bean
public ZuulHandlerMapping zuulHandlerMapping(RouteLocator routes,
        ZuulController zuulController) {
    ZuulHandlerMapping mapping = new ZuulHandlerMapping(routes, zuulController);
    mapping.setErrorController(this.errorController);
    mapping.setCorsConfigurations(getCorsConfigurations());
    return mapping;
}

// 注入ZuulServlet,负责zuul业务实际处理, 默认注入
@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;
}

// zuul请求过滤器,默认false不注入
@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;
}

1.2 ZuulController Zuul控制器,负责处理非默认/zuul/** 请求

ZuulController 继承 ServletWrappingController, springmvc定义Controller的另外1种方式,内部包装servlet,业务处理全部交给ZuulServlet处理

public class ZuulController extends ServletWrappingController {

	public ZuulController() {
		setServletClass(ZuulServlet.class);
		setServletName("zuul");
		setSupportedMethods((String[]) null); // Allow all
	}

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

}

1.3 ZuulHandlerMapping zuul请求映射处理器

public class ZuulHandlerMapping extends AbstractUrlHandlerMapping {

	private final RouteLocator routeLocator;

	private final ZuulController zuul;

	private ErrorController errorController;

	private PathMatcher pathMatcher = new AntPathMatcher();

	private volatile boolean dirty = true;
    
    // ZuulHandlerMapping构造器
    // RouteLocator:路由定位器,包含zuul网关可处理的路径信息
    // ZuulController: zuul控制器,否则实际业务处理
	public ZuulHandlerMapping(RouteLocator routeLocator, ZuulController zuul) {
		this.routeLocator = routeLocator;
		this.zuul = zuul;
		setOrder(-200);
	}

    // CORS 跨域处理,若已配置则补充PreFlightHandler拦截器多一层跨域处理
	@Override
	protected HandlerExecutionChain getCorsHandlerExecutionChain(
			HttpServletRequest request, HandlerExecutionChain chain,
			CorsConfiguration config) {
		if (config == null) {
			// Allow CORS requests to go to the backend
			return chain;
		}
		return super.getCorsHandlerExecutionChain(request, chain, config);
	}

    // 错误处理器,springboot默认自带ErrorController,可重写设置
	public void setErrorController(ErrorController errorController) {
		this.errorController = errorController;
	}

    // 路由信息是否是脏数据,如果是且路由定位器是RefreshableRouteLocator,重新刷新路由信息(例如:应用重启:ZuulRefreshListener)
	public void setDirty(boolean dirty) {
		this.dirty = dirty;
		if (this.routeLocator instanceof RefreshableRouteLocator) {
			((RefreshableRouteLocator) this.routeLocator).refresh();
		}
	}

    // 根据请求路径去匹配处理器
	@Override
	protected Object lookupHandler(String urlPath, HttpServletRequest request)
			throws Exception {
		if (this.errorController != null
				&& urlPath.equals(this.errorController.getErrorPath())) {
			return null;
		}
		if (isIgnoredPath(urlPath, this.routeLocator.getIgnoredPaths())) {
			return null;
		}
		RequestContext ctx = RequestContext.getCurrentContext();
		if (ctx.containsKey("forward.to")) {
			return null;
		}
        // 若路由配置还是脏数据,则同步重新注册路由及处理器
		if (this.dirty) {
			synchronized (this) {
				if (this.dirty) {
                    // 注册处理器
					registerHandlers();
					this.dirty = false;
				}
			}
		}
		return super.lookupHandler(urlPath, request);
	}

	private boolean isIgnoredPath(String urlPath, Collection<String> ignored) {
		if (ignored != null) {
			for (String ignoredPath : ignored) {
				if (this.pathMatcher.match(ignoredPath, urlPath)) {
					return true;
				}
			}
		}
		return false;
	}

    // 注册路由及相应处理器
	private void registerHandlers() {
		Collection<Route> routes = this.routeLocator.getRoutes();
		if (routes.isEmpty()) {
			this.logger.warn("No routes found from RouteLocator");
		}
		else {
			for (Route route : routes) {
                // 注册处理器: 这里所有路由路径(非 /zuul/** 格式), 请求都交给ZuulController处理
				registerHandler(route.getFullPath(), this.zuul);
			}
		}
	}

}

1.4 ZuulServlet zuul业务处理逻辑

ZuulServlet:真正业务处理逻辑

业务场景:

  1. zuul springmvc 原生控制器业务处理:ZuulController, 默认适用于所有非 /zuul/** 请求
  2. J2EE Servlet纯粹Servlet请求处理,不依赖spring也可运行, 适用于所有 /zuul/** 请求,可以绕过springmvc处理逻辑,处理文件传输等数据量较大的场景可提升性能

DispatcherServlet与ZuulServlet优先级顺序:Servlet loadOnStartup数值越小优先级越高,即ZuulServlet优于DispatcherServlet匹配请求处理

  1. AbstractDispatcherServletInitializer.registerDispatcherServlet: registration.setLoadOnStartup(1);
  2. org.springframework.boot.web.servlet.ServletRegistrationBean.loadOnStartup: -1

ZuulServlet: 使用路径

  • org.springframework.cloud.netflix.zuul.filters.ZuulProperties.servletPath: 默认: /zuul

2. Zuul 工作流程

2.1 ZuulServlet 负责Zuul业务处理流程

public class ZuulServlet extends HttpServlet {

    private static final long serialVersionUID = -3374242278843351500L;
    private ZuulRunner zuulRunner;


    @Override
    public void init(ServletConfig config) throws ServletException {
        super.init(config);
        String bufferReqsStr = config.getInitParameter("buffer-requests");
        boolean bufferReqs = bufferReqsStr != null && bufferReqsStr.equals("true") ? true : false;
        zuulRunner = new ZuulRunner(bufferReqs);
    }

    @Override
    public void service(javax.servlet.ServletRequest servletRequest, javax.servlet.ServletResponse servletResponse) throws ServletException, IOException {
        try {
            // 将请求和响应对象放入RequestContext(请求处理线程工作环境,ThreadLocal保存,线程独立)
            init((HttpServletRequest) servletRequest, (HttpServletResponse) servletResponse);
            RequestContext context = RequestContext.getCurrentContext();
            context.setZuulEngineRan();

            try {
                // 前置路由处理:对应Zuul Pre类型过滤器
                preRoute();
            } catch (ZuulException e) {
                // 异常处理,对应Zuul Error类型过滤器
                error(e);

                // 后置处理:对饮Zuul Post类型过滤器
                postRoute();
                return;
            }
            try {
                // 请求路由,解析请求路径,转发到真实服务处理,对应Zuul Route类型过滤器
                route();
            } catch (ZuulException e) {
                error(e);
                postRoute();
                return;
            }
            try {
                postRoute();
            } catch (ZuulException e) {
                error(e);
                return;
            }
        // 总结:postRoute 请求后置处总是会执行,可做一些日志监控
        } catch (Throwable e) {
            error(new ZuulException(e, 500, "UNHANDLED_EXCEPTION_" + e.getClass().getName()));
        } finally {
            // 清理当前请求处理线程环境信息
            RequestContext.getCurrentContext().unset();
        }
    }

    /**
     * executes "post" ZuulFilters
     *
     * @throws ZuulException
     */
    void postRoute() throws ZuulException {
        zuulRunner.postRoute();
    }

    /**
     * executes "route" filters
     *
     * @throws ZuulException
     */
    void route() throws ZuulException {
        zuulRunner.route();
    }

    /**
     * executes "pre" filters
     *
     * @throws ZuulException
     */
    void preRoute() throws ZuulException {
        zuulRunner.preRoute();
    }

    /**
     * initializes request
     *
     * @param servletRequest
     * @param servletResponse
     */
    void init(HttpServletRequest servletRequest, HttpServletResponse servletResponse) {
        zuulRunner.init(servletRequest, servletResponse);
    }

    /**
     * sets error context info and executes "error" filters
     *
     * @param e
     */
    void error(ZuulException e) {
        RequestContext.getCurrentContext().setThrowable(e);
        zuulRunner.error();
    }
}

2.2 Zuul 过滤器

2.2.1 Zuul 过滤器 IZuulFilter

Zuul过滤器类型:

  • Pre: 前置过滤器,请求达到Zuul网关,但未进行请求分发,一般用于接口鉴权、请求参数日志打印等
  • route: 路由过滤器,进行实际的路由分发,将解析请求路径转发到真实服务
  • post: 后置处理器,zuul请求分发且响应或请求路由出现异常后调用,一般用于响应日志打印等
  • error: 错误处理器,zuul请求处理过程出现异常捕获,自定义处理

Zuul生命周期:

public interface IZuulFilter {
    /**
     * 判断过滤器是否可处理对应请求
     */
    boolean shouldFilter();

    /**
     * shouldFilter 返回true,表示过滤器可以处理该请求,run 负责实际业务处理逻辑
     */
    Object run() throws ZuulException;

}

2.2.2 过滤器列表维护

过滤器缓存:com.netflix.zuul.FilterLoader.filterRegistry

配置:org.springframework.cloud.netflix.zuul.ZuulServerAutoConfiguration.ZuulFilterConfiguration

@Configuration(proxyBeanMethods = false)
protected static class ZuulFilterConfiguration {

    @Autowired
    private Map<String, ZuulFilter> filters;

    @Bean
    public ZuulFilterInitializer zuulFilterInitializer(CounterFactory counterFactory,
            TracerFactory tracerFactory) {
        // FilterLoader、FilterRegistry 实例化
        FilterLoader filterLoader = FilterLoader.getInstance();
        FilterRegistry filterRegistry = FilterRegistry.instance();

        // 构建ZuulFilterInitializer对象
        return new ZuulFilterInitializer(this.filters, counterFactory, tracerFactory,
                filterLoader, filterRegistry);
    }

}

ZuulFilterInitializer 源码

public class ZuulFilterInitializer {

	private static final Log log = LogFactory.getLog(ZuulFilterInitializer.class);

	private final Map<String, ZuulFilter> filters;

	private final CounterFactory counterFactory;

	private final TracerFactory tracerFactory;

	private final FilterLoader filterLoader;

	private final FilterRegistry filterRegistry;

	public ZuulFilterInitializer(Map<String, ZuulFilter> filters,
			CounterFactory counterFactory, TracerFactory tracerFactory,
			FilterLoader filterLoader, FilterRegistry filterRegistry) {
		this.filters = filters;
		this.counterFactory = counterFactory;
		this.tracerFactory = tracerFactory;
		this.filterLoader = filterLoader;
		this.filterRegistry = filterRegistry;
	}

    /**
     * Bean构造完成后后置处理
     */
	@PostConstruct
	public void contextInitialized() {
		log.info("Starting filter initializer");

		TracerFactory.initialize(tracerFactory);
		CounterFactory.initialize(counterFactory);
        // 将Zuul过滤器列表维护到filterRegistry对象,key为过滤器Bean名称
		for (Map.Entry<String, ZuulFilter> entry : this.filters.entrySet()) {
			filterRegistry.put(entry.getKey(), entry.getValue());
		}
	}

	@PreDestroy
	public void contextDestroyed() {
		log.info("Stopping filter initializer");
		for (Map.Entry<String, ZuulFilter> entry : this.filters.entrySet()) {
			filterRegistry.remove(entry.getKey());
		}
		clearLoaderCache();

		TracerFactory.initialize(null);
		CounterFactory.initialize(null);
	}

	private void clearLoaderCache() {
		Field field = ReflectionUtils.findField(FilterLoader.class, "hashFiltersByType");
		ReflectionUtils.makeAccessible(field);
		@SuppressWarnings("rawtypes")
		Map cache = (Map) ReflectionUtils.getField(field, filterLoader);
		cache.clear();
	}

}

Zuul 默认提供断点:FiltersEndpoint,可包含到actuator进行暴露访问filters: management.endpoints.web.exposure.include=info, health, filters

2.2.3 Zuul 过滤器流程处理

源码:com.netflix.zuul.FilterProcessor.runFilters

 public Object runFilters(String sType) throws Throwable {
        if (RequestContext.getCurrentContext().debugRouting()) {
            Debug.addRoutingDebug("Invoking {" + sType + "} type filters");
        }
        boolean bResult = false;
        // 缓存获取ZuulFilter 过滤器列表
        List<ZuulFilter> list = FilterLoader.getInstance().getFiltersByType(sType);
        if (list != null) {
            // 相同类型的过滤器同步轮询调用
            for (int i = 0; i < list.size(); i++) {
                ZuulFilter zuulFilter = list.get(i);
                // 过滤器单独业务处理:先根据shouldFilter判断过滤器是否适用当前请求 ---> run() 过滤器适用则执行具体逻辑
                Object result = processZuulFilter(zuulFilter);
                if (result != null && result instanceof Boolean) {
                    bResult |= ((Boolean) result);
                }
            }
        }
        return bResult;
    }

2.2.4 PreDecorationFilter 决定路由类型

PreDecorationFilter: Pre类型过滤器,最前起作用

public class PreDecorationFilter extends ZuulFilter {

	private static final Log log = LogFactory.getLog(PreDecorationFilter.class);

	/**
	 * @deprecated use {@link FilterConstants#PRE_DECORATION_FILTER_ORDER}
	 */
	@Deprecated
	public static final int FILTER_ORDER = PRE_DECORATION_FILTER_ORDER;

	/**
	 * A double slash pattern.
	 */
	public static final Pattern DOUBLE_SLASH = Pattern.compile("//");

	private RouteLocator routeLocator;

	private String dispatcherServletPath;

	private ZuulProperties properties;

	private UrlPathHelper urlPathHelper = new UrlPathHelper();

	private ProxyRequestHelper proxyRequestHelper;

	public PreDecorationFilter(RouteLocator routeLocator, String dispatcherServletPath,
			ZuulProperties properties, ProxyRequestHelper proxyRequestHelper) {
		this.routeLocator = routeLocator;
		this.properties = properties;
		this.urlPathHelper.setRemoveSemicolonContent(properties.isRemoveSemicolonContent());
		this.urlPathHelper.setUrlDecode(properties.isDecodeUrl());
		this.dispatcherServletPath = dispatcherServletPath;
		this.proxyRequestHelper = proxyRequestHelper;
	}

	@Override
	public int filterOrder() {
		return PRE_DECORATION_FILTER_ORDER;
	}

	@Override
	public String filterType() {
		return PRE_TYPE;
	}

    /**
     * 判断是否适应当前请求
     * 1. 若网关自身不提供对外业务接口,相当于1个请求只在网关与外部系统路由1次,则FORWARD_TO_KEY、SERVICE_ID_KEY为空则直接适用
     * 2. 若网关自身对外提供服务,最终会路由到自身,相当于网关第1次网关 --> 网关业务接口,网关充当双重角色,这种场景第2次访问网关相当于只访问业务接口,过滤器不生效
     */
	@Override
	public boolean shouldFilter() {
		RequestContext ctx = RequestContext.getCurrentContext();
		return !ctx.containsKey(FORWARD_TO_KEY) // a filter has already forwarded
				&& !ctx.containsKey(SERVICE_ID_KEY); // a filter has already determined
		// serviceId
	}

	@Override
	public Object run() {
		RequestContext ctx = RequestContext.getCurrentContext();
		final String requestURI = this.urlPathHelper.getPathWithinApplication(ctx.getRequest());

        // 获取请求路由信息
		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());
                
                // 自定义敏感头处理:如果开启则RequestContext添加属性,对应请求头信息会被忽略:ignoredHeaders,默认:"Cookie", "Set-Cookie", "Authorization"
                // 注意:很多系统需要使用Authorization鉴权,就需要自定义且重写敏感头信息,否则目标服务拿不到Authorization,导致鉴权失败
				if (!route.isCustomSensitiveHeaders()) {
					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());
				}

                // location 以http或https开发头,则对应SimpleHostRoutingFilter
				if (location.startsWith(HTTP_SCHEME + ":")
						|| location.startsWith(HTTPS_SCHEME + ":")) {
					ctx.setRouteHost(getUrl(location));
					ctx.addOriginResponseHeader(SERVICE_HEADER, location);
				}
                // location 以forward:开头,则对应SendForwardFilter
				else if (location.startsWith(FORWARD_LOCATION_PREFIX)) {
					ctx.set(FORWARD_TO_KEY,
							StringUtils.cleanPath(
									location.substring(FORWARD_LOCATION_PREFIX.length())
											+ route.getPath()));
					ctx.setRouteHost(null);
					return null;
				}
                // 其他:则是服务名访问,若以服务名称访问,则location默认serviceID,详细参考:DiscoveryClientRouteLocator.locateRoutes
				else {
					// set serviceId for use in filters.route.RibbonRequest
					ctx.set(SERVICE_ID_KEY, location);
					ctx.setRouteHost(null);
					ctx.addOriginResponseHeader(SERVICE_ID_HEADER, location);
				}
                // Http代理相关处理
				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;
	}

	/* for testing */ String getForwardUri(String requestURI) {
		// default fallback servlet is DispatcherServlet
		String fallbackPrefix = this.dispatcherServletPath;

		String fallBackUri = requestURI;
		if (RequestUtils.isZuulServletRequest()) {
			// remove the Zuul servletPath from the requestUri
			log.debug("zuulServletPath=" + this.properties.getServletPath());
			fallBackUri = fallBackUri.replaceFirst(this.properties.getServletPath(), "");
			log.debug("Replaced Zuul servlet path:" + fallBackUri);
		}
		else if (this.dispatcherServletPath != null) {
			// remove the DispatcherServlet servletPath from the requestUri
			log.debug("dispatcherServletPath=" + this.dispatcherServletPath);
			fallBackUri = fallBackUri.replaceFirst(this.dispatcherServletPath, "");
			log.debug("Replaced DispatcherServlet servlet path:" + fallBackUri);
		}
		if (!fallBackUri.startsWith("/")) {
			fallBackUri = "/" + fallBackUri;
		}

		String forwardURI = (fallbackPrefix == null) ? fallBackUri
				: fallbackPrefix + fallBackUri;
		forwardURI = DOUBLE_SLASH.matcher(forwardURI).replaceAll("/");
		return forwardURI;
	}

	private void addProxyHeaders(RequestContext ctx, Route route) {
		HttpServletRequest request = ctx.getRequest();
		String host = toHostHeader(request);
		String port = String.valueOf(request.getServerPort());
		String proto = request.getScheme();
		if (hasHeader(request, X_FORWARDED_HOST_HEADER)) {
			host = request.getHeader(X_FORWARDED_HOST_HEADER) + "," + host;
		}
		if (!hasHeader(request, X_FORWARDED_PORT_HEADER)) {
			if (hasHeader(request, X_FORWARDED_PROTO_HEADER)) {
				StringBuilder builder = new StringBuilder();
				for (String previous : StringUtils.commaDelimitedListToStringArray(
						request.getHeader(X_FORWARDED_PROTO_HEADER))) {
					if (builder.length() > 0) {
						builder.append(",");
					}
					builder.append(
							HTTPS_SCHEME.equals(previous) ? HTTPS_PORT : HTTP_PORT);
				}
				builder.append(",").append(port);
				port = builder.toString();
			}
		}
		else {
			port = request.getHeader(X_FORWARDED_PORT_HEADER) + "," + port;
		}
		if (hasHeader(request, X_FORWARDED_PROTO_HEADER)) {
			proto = request.getHeader(X_FORWARDED_PROTO_HEADER) + "," + proto;
		}
		ctx.addZuulRequestHeader(X_FORWARDED_HOST_HEADER, host);
		ctx.addZuulRequestHeader(X_FORWARDED_PORT_HEADER, port);
		ctx.addZuulRequestHeader(X_FORWARDED_PROTO_HEADER, proto);
		addProxyPrefix(ctx, route);
	}

	private boolean hasHeader(HttpServletRequest request, String name) {
		return StringUtils.hasLength(request.getHeader(name));
	}

	private void addProxyPrefix(RequestContext ctx, Route route) {
		String forwardedPrefix = ctx.getRequest().getHeader(X_FORWARDED_PREFIX_HEADER);
		String contextPath = ctx.getRequest().getContextPath();
		String prefix = StringUtils.hasLength(forwardedPrefix) ? forwardedPrefix
				: (StringUtils.hasLength(contextPath) ? contextPath : null);
		if (StringUtils.hasText(route.getPrefix())) {
			StringBuilder newPrefixBuilder = new StringBuilder();
			if (prefix != null) {
				if (prefix.endsWith("/") && route.getPrefix().startsWith("/")) {
					newPrefixBuilder.append(prefix, 0, prefix.length() - 1);
				}
				else {
					newPrefixBuilder.append(prefix);
				}
			}
			newPrefixBuilder.append(route.getPrefix());
			prefix = newPrefixBuilder.toString();
		}
		if (prefix != null) {
			ctx.addZuulRequestHeader(X_FORWARDED_PREFIX_HEADER, prefix);
		}
	}

	private String toHostHeader(HttpServletRequest request) {
		int port = request.getServerPort();
		if ((port == HTTP_PORT && HTTP_SCHEME.equals(request.getScheme()))
				|| (port == HTTPS_PORT && HTTPS_SCHEME.equals(request.getScheme()))) {
			return request.getServerName();
		}
		else {
			return request.getServerName() + ":" + port;
		}
	}

	private URL getUrl(String target) {
		try {
			return new URL(target);
		}
		catch (MalformedURLException ex) {
			throw new IllegalStateException("Target URL is malformed", ex);
		}
	}

}

2.2.5 route 类型过滤器处理

默认Zuul提供的3种Route类型处理器:

  • SimpleHostRoutingFilter:接收请求主机名与目标服务直接名绑定,然后通过CloseableHttpClient客户端直接访问获取响应,然后响应给调用方
  • SendForwardFilter: 请求转发
  • RibbonRoutingFilter: ribbon路由,对应服务名路径访问场景,底层由Ribbon负责实际路由处理,不过Zuul内部继承Hystrix断路器功能,网关层面可做服务熔断、降级处理

2.2.6 error 错误过滤器

默认:SendErrorFilter

@Override
public Object run() {
    try {
        RequestContext ctx = RequestContext.getCurrentContext();
        ExceptionHolder exception = findZuulException(ctx.getThrowable());
        HttpServletRequest request = ctx.getRequest();

        request.setAttribute("javax.servlet.error.status_code",
                exception.getStatusCode());

        log.warn("Error during filtering", exception.getThrowable());
        request.setAttribute("javax.servlet.error.exception",
                exception.getThrowable());

        if (StringUtils.hasText(exception.getErrorCause())) {
            request.setAttribute("javax.servlet.error.message",
                    exception.getErrorCause());
        }
        // 请求转发:${error.path:/error}, 默认转发到 springboot 自带 BasicErrorController,可自定义实现ErrorController自定义异常处理
        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;
}

2.2.7 post 后置处理器

zuul后置过滤器:

  • SendResponseFilter: 默认,补充一些响应信息,例如响应数据大小、数据编码、压缩格式等
  • LocationRewriteFilter: 请求处理完后重定向

3. Zuul扩展

3.1 解决DiscoveryClientRouteLocator前缀路径匹配问题

遗留问题:Zuul无法处理项目有 server.servlet.context-path 前缀路径场景

问题场景:

1. 服务不添加前缀:
http://localhost:8089/springcloud-gateway-client/index:
zuul转义: http://localhost:8080/index(可直接访问:DiscoveryClientRouteLocator.locateRoutes,直接new ZuulRoute(key, serviceId), stripPrefix默认true)


2. 服务添加前缀:例如:server.servlet.context-path=/${spring.application.name}
问题:DiscoveryClientRouteLocator默认直接去掉前缀,无法访问目标服务

解决方案:重写DiscoveryClientRouteLocator,自定义stripPrefix标识

public class EurekaRouteLocator extends DiscoveryClientRouteLocator {

    private final DiscoveryClient discovery;

    private final ZuulProperties properties;

    private boolean stripPrefix = true;

    public EurekaRouteLocator(String servletPath, DiscoveryClient discovery, ZuulProperties properties,
                              ServiceRouteMapper serviceRouteMapper, ServiceInstance localServiceInstance) {
        this(servletPath, discovery, properties, serviceRouteMapper, localServiceInstance, true);
    }

    public EurekaRouteLocator(String servletPath, DiscoveryClient discovery, ZuulProperties properties,
                              ServiceRouteMapper serviceRouteMapper, ServiceInstance localServiceInstance, boolean stripPrefix) {
        super(servletPath, discovery, properties, serviceRouteMapper, localServiceInstance);
        this.discovery = discovery;
        this.properties = properties;
        this.stripPrefix = stripPrefix;
    }

    /**
     * 重新修正服务注册中心路由配置
     *
     * @return
     */
    @Override
    protected LinkedHashMap<String, ZuulProperties.ZuulRoute> locateRoutes() {
        LinkedHashMap<String, ZuulProperties.ZuulRoute> routesMap = new LinkedHashMap<>(super.locateRoutes());
        for (Map.Entry<String, ZuulProperties.ZuulRoute> zuulRouteEntry : routesMap.entrySet()) {
            if (!StringUtils.isEmpty(zuulRouteEntry.getValue().getServiceId())) {
                zuulRouteEntry.getValue().setStripPrefix(this.stripPrefix);
            }
        }
        return routesMap;
    }
}
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!