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

编程

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;

}

}

以上是 【微服务】服务网关Zuul工作原理源码解析 的全部内容, 来源链接: utcz.com/z/518885.html

回到顶部