【微服务】服务网关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:真正业务处理逻辑
业务场景:
- zuul springmvc 原生控制器业务处理:ZuulController, 默认适用于所有非 /zuul/** 请求
- J2EE Servlet纯粹Servlet请求处理,不依赖spring也可运行, 适用于所有 /zuul/** 请求,可以绕过springmvc处理逻辑,处理文件传输等数据量较大的场景可提升性能
DispatcherServlet与ZuulServlet优先级顺序:Servlet loadOnStartup数值越小优先级越高,即ZuulServlet优于DispatcherServlet匹配请求处理
- AbstractDispatcherServletInitializer.registerDispatcherServlet: registration.setLoadOnStartup(1);
- 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
@Overridepublic 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