入门实例
项目依赖
org.springframework.cloud spring-cloud-starter-netflix-zuul org.springframework.cloud spring-cloud-starter-netflix-eureka-client
驱动网关工作
@[email protected](scanBasePackages ="com.spring.cloud.zuul.filter")public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); }}
在网关应用入口处使用@EnableZuulProxy注解驱动Zuul网关工作。
@EnableZuulProxy注解
@EnableCircuitBreaker//驱动Hystrix熔断器工作@Target({ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)@Import({ZuulProxyMarkerConfiguration.class})public @interface EnableZuulProxy {}
之所以使用@EnableZuulProxy驱动Zuul网关工作后,需要驱动Hystrix熔断器开启工作,因为服务网关可以在高并发场景下请求流量过大时保护服务网关,使服务网关应用不至于瘫痪。默认情况下,Zuul会使用信号量隔离的方式使用Hystrix熔断器,并非使用线程池的方式进行隔离。
网关配置
application.properties
# Zuul应用名称spring.application.name=zuulDemo#Zuul应用端口server.port=8040#将服务网关应用注册至服务注册中心,并从服务注册中心获取服务实例列表(包含服务实例名称,服务实例IP,服务实例端口号)eureka.client.service-url.defaultZone=http://server1:8701/eureka/,http://server2:8702/eureka/#配置通过path方式进行服务路由zuul.routes.user-router.path=/user/**#配置通过url方式进行服务路由zuul.routes.user-router.url=http://localhost:8100/#配置服务路由service-idzuul.router.user-router.service-id=user-service-provider
上述配置文件中,zuul.routes.*用来配置路由信息,本身是一个Map对象,后续的配置是为了配置路由信息。zuul.routes.user-router.path用来配置用户微服务匹配路径;zuul.routes.user-router.url用来配置用户微服务实际路径;如果用户微服务存在多个服务实例,通过配置微服务实例Id的方式进行路由配置(zuul.router.user-router.service-id=服务实例Id)。
Zuul原理——过滤器
Zuul的本质是一套Servlet的API。其中ZuulServlet是核心Servlet,接收各类服务请求。此外,Zuul还提供了ZuulServletFilter拦截器,可以拦截各类请求。为了更加方便和快捷的增加和删除拦截逻辑,在ZuulServlet和ZuulServletFilter基础上,定义了自己的过滤器ZuulFilter。
过滤器设计
ZuulFilter是一个抽象类,实现了接口IZuulFilter。Netflix Zuul提供了许多ZuulFilter实现类。在Spring Cloud Zuul中实现的过滤器必须包含4个特征:过滤类型、执行顺序、执行条件、具体操作。在ZuulFilter抽象类中对4个过滤器特性进行了定义:
public abstract class ZuulFilter implements IZuulFilter, Comparable { public abstract String filterType(); public abstract int filterOrder(); boolean shouldFilter(); Object run() throws ZuulException;}
方法的含义和功能总结如下:
方法名 | 描述 |
filterType | 返回过滤器类型字符串(pre,routing,post,error) |
filterOrder | 返回该过滤器执行顺序,数值越小执行顺序越靠前 |
shouldFilter | 返回布尔值决定该过滤器是否需要执行 |
run | 过滤器执行的具体逻辑,可在该方法中实现自定义的业务逻辑,返回null则进行后续的正常逻辑 |
执行原理
在ZuulFilter抽象类中,定义的4个方法均没有参数,如何执行过滤器相关操作?
过滤器拦截的均为Http请求,在Zuul中提供了以下核心类来完成过滤器相关工作。
核心类 | 描述 |
ZuulServlet | 处理http请求入口,用于处理服务网关Http请求 |
ZuulRunner | 用于对不同类型的过滤器进行串联 |
FilterProcessor | 执行具体的过滤器相关操作 |
RequestContext | 请求上下文,用于获取请求参数和设置响应信息 |
RequestContext
每一个新的http请求都是由一个独立的线程进行处理(tomcat线程),本次请求的所有参数均在一个独立的线程中进行处理,故将每次的请求上下文存储于ThreadLocal(线程本地变量,每次处理过程中存储RequestContex实例副本,避免参数误操作)中。在initialValue方法中,每个Http请求只能实例化自己的请求上下文(RequestContext)实例。
public class RequestContext extends ConcurrentHashMap { private static final Logger LOG = LoggerFactory.getLogger(RequestContext.class); protected static Class extends RequestContext> contextClass = RequestContext.class; private static RequestContext testContext = null; protected static final ThreadLocal extends RequestContext> threadLocal = new ThreadLocal() { protected RequestContext initialValue() { try { return (RequestContext)RequestContext.contextClass.newInstance(); } catch (Throwable var2) { throw new RuntimeException(var2); } } }; public static RequestContext getCurrentContext() { if (testContext != null) { return testContext; } else { RequestContext context = (RequestContext)threadLocal.get(); return context; } } public boolean getBoolean(String key) { return this.getBoolean(key, false); } public boolean getZuulEngineRan() { return this.getBoolean("zuulEngineRan"); } public void setZuulEngineRan() { this.put("zuulEngineRan", true); } public HttpServletRequest getRequest() { return (HttpServletRequest)this.get("request"); } public void setRequest(HttpServletRequest request) { this.put("request", request); } public HttpServletResponse getResponse() { return (HttpServletResponse)this.get("response"); } public void setResponse(HttpServletResponse response) { this.set("response", response); } public void unset() { threadLocal.remove(); }}
方法 | 描述 |
getCurrentContext | 获取当前请求上下文(RequestContext) |
get(set)Request | 获取(设置)请求信息 |
get(set)Response | 获取(设置)响应信息 |
get(set)ZuulEngineRan | 设置Zuul引擎(ZuulRunner) |
get(set)Boolean | 获取(设置)Boolean值 |
unset | 从ThreadLocal中移除当前请求上下文副本 |
ZuulServlet
package com.netflix.zuul.http;public class ZuulServlet extends HttpServlet { private static final long serialVersionUID = -3374242278843351500L; private ZuulRunner zuulRunner; public ZuulServlet() { } public void init(ServletConfig config) throws ServletException { super.init(config); //buffer-requests:初始化Zuul引擎(ZuulRunner)必要参数(默认为false) String bufferReqsStr = config.getInitParameter("buffer-requests"); boolean bufferReqs = bufferReqsStr != null && bufferReqsStr.equals("true"); this.zuulRunner = new ZuulRunner(bufferReqs); } public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException { try { //初始化Zuul引擎(ZuulRunner) this.init((HttpServletRequest)servletRequest, (HttpServletResponse)servletResponse); RequestContext context = RequestContext.getCurrentContext(); //设置buffer-requests参数为true 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(); } } void postRoute() throws ZuulException { this.zuulRunner.postRoute(); } void route() throws ZuulException { this.zuulRunner.route(); } void preRoute() throws ZuulException { this.zuulRunner.preRoute(); } void init(HttpServletRequest servletRequest, HttpServletResponse servletResponse) { this.zuulRunner.init(servletRequest, servletResponse); } void error(ZuulException e) { RequestContext.getCurrentContext().setThrowable(e); this.zuulRunner.error(); }}
方法 | 描述 |
init(ServletConfig) | 初始化Zuul执行引擎(ZuulRunner) |
init(HttpServletRequst,HttpServletResponse) | 设置Zuul执行引擎(ZuulRunner)请求(request)响应(response)信息和参数 |
service() | 执行过滤器相关操作 |
preRoute() | 使用Zuul执行引擎执行前置过滤器(过滤器类型为pre) |
route() | 使用Zuul执行引擎执行路由过滤器(过滤器类型为routing) |
postRoute() | 使用Zuul执行引擎执行Post过滤器(过滤器类型为post) |
error() | 使用Zuul执行引擎执行异常过滤器(过滤器类型为error) |
通过service方法,我们可以获知Zuul过滤器执行顺序如下:
- 前置(pre)、路由(route)、post类型过滤器执行过程中均未发生异常,执行顺序为pre->route->post,error类型不执行
- 前置(pre)类型过滤器执行过程中发生异常,执行顺序为pre->error->post
- 路由(route)类型过滤器执行过程中发生异常,执行顺序为pre->route->error->post
- post类型过滤器执行过程中发生异常,执行顺序为pre->route->post->error
service方法执行的过程中一旦发生异常,将抛出的Throwable实例设置到当前执行请求上下文的Throwable属性内。执行过程中无论是否有异常发生,从线程本地变量中移除请求上下文(ReqeustContext)副本。
ZuulRunner
public class ZuulRunner { private boolean bufferRequests; public ZuulRunner() { this.bufferRequests = true; } public ZuulRunner(boolean bufferRequests) { this.bufferRequests = bufferRequests; } public void init(HttpServletRequest servletRequest, HttpServletResponse servletResponse) { RequestContext ctx = RequestContext.getCurrentContext(); if (this.bufferRequests) { ctx.setRequest(new HttpServletRequestWrapper(servletRequest)); } else { ctx.setRequest(servletRequest); } ctx.setResponse(new HttpServletResponseWrapper(servletResponse)); } public void postRoute() throws ZuulException { FilterProcessor.getInstance().postRoute(); } public void route() throws ZuulException { FilterProcessor.getInstance().route(); } public void preRoute() throws ZuulException { FilterProcessor.getInstance().preRoute(); } public void error() { FilterProcessor.getInstance().error(); }}
public class HttpServletResponseWrapper extends javax.servlet.http.HttpServletResponseWrapper { private int status = 0; public HttpServletResponseWrapper(HttpServletResponse response) { super(response); } public void setStatus(int sc) { this.status = sc; super.setStatus(sc); } public void setStatus(int sc, String sm) { this.status = sc; super.setStatus(sc, sm); } public int getStatus() { return this.status; }}
ZuulRunner作为Zuul执行引擎,初始化时将请求上下文(ReqeustContext)中的响应属性设置为HttpServletResponseWrapper实例,该实例主要通过status属性设置http响应状态码。如果参数bufferRequests为true,将请求上下文中的请求属性设置为HttpServletRequestWrapper实例,HttpServletRequestWrapper类主要用于把请求的表单参数和请求体缓存(设置)在对应的属性中,无特殊要求传递bufferRequests默认值(false)。
方法 | 描述 |
init | 初始化Zuul执行引擎 |
postRoute | 使用FilterProcessor实例执行post类型过滤器 |
route | 使用FilterProcessor实例执行路由(类型为route)过滤器 |
preRoute | 使用FilterProcessor实例执行前置(类型为pre)过滤器 |
error | 使用FilterProcessor实例执行错误(类型为error)过滤器 |
FilterProcessor
public class FilterProcessor { static FilterProcessor INSTANCE = new FilterProcessor(); protected static final Logger logger = LoggerFactory.getLogger(FilterProcessor.class); private FilterUsageNotifier usageNotifier = new FilterProcessor.BasicFilterUsageNotifier(); public FilterProcessor() { } public static FilterProcessor getInstance() { return INSTANCE; } public static void setProcessor(FilterProcessor processor) { INSTANCE = processor; } public void setFilterUsageNotifier(FilterUsageNotifier notifier) { this.usageNotifier = notifier; } public void postRoute() throws ZuulException { try { this.runFilters("post"); } catch (ZuulException var2) { throw var2; } catch (Throwable var3) { throw new ZuulException(var3, 500, "UNCAUGHT_EXCEPTION_IN_POST_FILTER_" + var3.getClass().getName()); } } public void error() { try { this.runFilters("error"); } catch (Throwable var2) { logger.error(var2.getMessage(), var2); } } public void route() throws ZuulException { try { this.runFilters("route"); } catch (ZuulException var2) { throw var2; } catch (Throwable var3) { throw new ZuulException(var3, 500, "UNCAUGHT_EXCEPTION_IN_ROUTE_FILTER_" + var3.getClass().getName()); } } 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()); } } public Object runFilters(String sType) throws Throwable { if (RequestContext.getCurrentContext().debugRouting()) { Debug.addRoutingDebug("Invoking {" + sType + "} type filters"); } boolean bResult = false; List 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; } 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; } } }}
在FilterProcessor类中,提供属性usageNotifier(类型为FilterUsageNotifier接口),默认实现类为FilterProcessor静态内部类BasicFilterUsageNotifier,该类实现接口FilterUsageNotifier的notify方法(方法参数为ZuulFilter和ExecutionStatus枚举),ExecutionStatus(处理状态)枚举中主要提供如下可选值
public enum ExecutionStatus { SUCCESS(1), SKIPPED(-1), DISABLED(-2), FAILED(-3); private int status; private ExecutionStatus(int status) { this.status = status; }}
可选值 | 描述 |
SUCCESS(1) | 该过滤器处理成功 |
SKIPPED(-1) | 该过滤器跳过处理 |
DISABLED(-2) | 该过滤器为禁用状态 |
FAILED(-3) | 该过滤器处理失败 |
在FilterProcesser类中,主要通过runFilter方法(参数为过滤器类型字符串)和processZuulFilter(参数为ZuulFilter对象)对过滤器进行执行并返回执行结果。runFilter方法中返回值为布尔类型(过滤器执行成功或失败),该返回值无实际意义,执行成功或失败都通过请求上下文(ReqeustContext).addFilterExecutionSummary(参数分别为过滤器名称,执行状态枚举值,执行时间)对过滤器执行结果进行记录。
方法 | 描述 |
postRoute | 执行post类型过滤器(前缀为post) |
route | 执行路由类型过滤器(前缀为route) |
preRoute | 执行前置类型过滤器(前缀为pre) |
error | 执行错误类型过滤器(前缀为error) |
runFilter | 通过参数获取需要执行的过滤器类型,返回执行结果 |
processZuulFilter | 对具体的过滤器执行操作,并将执行结果设置在请求上下文(RequestContext)中 |
自动装配的过滤器
过滤器类型 | 过滤器名 | 过滤器描述 | 顺序 |
pre | ServletDetectionFilter | 标记处理Servlet的类型 | -3 |
Servlet30WrapperFilter | 包装HttpServletRequest请求 | -2 | |
FromBodyWrapperFilter | 包装请求体 | -1 | |
DebugFilter | 标记调试状态 | 1 | |
PreDecorationFilter | 处理请求上下文供后续使用 | 5 | |
route | RibbonRoutingFilter | serviceId请求转发 | 10 |
SimpleHostRoutingFilter | url请求转发 | 100 | |
SendForwardFilter | forward请求转发 | 500 | |
post | SendErrorFilter | 处理有错误的请求响应 | |
SendResponseFilter | 处理正常处理的请求响应 | 1000 |
开发过滤器
在本例中,我们将通过过滤器对http请求过程中的用户名、密码、token参数信息进行校验,校验成功通过过滤器为响应信息设置新的响应头(response-header)信息。
用户名校验过滤器(UserNameFilter)
import com.netflix.zuul.ZuulFilter;import com.netflix.zuul.context.RequestContext;import com.netflix.zuul.exception.ZuulException;import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants;import org.springframework.cloud.netflix.zuul.util.ZuulRuntimeException;import org.springframework.http.HttpStatus;import org.springframework.stereotype.Component;import javax.servlet.http.HttpServletRequest;/** * 根据用户名进行校验 */@Componentpublic class UserNameFilter extends ZuulFilter { @Override //设置过滤器类型 public String filterType() { return FilterConstants.PRE_TYPE; } //用户名校验最先执行 @Override public int filterOrder() { return FilterConstants.SERVLET_DETECTION_FILTER_ORDER-3; } @Override public boolean shouldFilter() { return true; } //执行具体的校验逻辑 @Override public Object run() throws ZuulException { RequestContext requestContext=RequestContext.getCurrentContext(); HttpServletRequest request=requestContext.getRequest(); if(null!=request.getParameter("userName")) { requestContext.setSendZuulResponse(true); requestContext.setResponseStatusCode(200); requestContext.set("isSuccess",true); } else{ requestContext.setSendZuulResponse(false); throw new ZuulRuntimeException(new ZuulException(this.filterType()+":"+this.getClass().getSimpleName(), HttpStatus.UNAUTHORIZED.value(),"用户名不能为空")); } return null; }}
用户密码校验(PassWordFilter)
import com.netflix.zuul.ZuulFilter;import com.netflix.zuul.context.RequestContext;import com.netflix.zuul.exception.ZuulException;import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants;import org.springframework.cloud.netflix.zuul.util.ZuulRuntimeException;import org.springframework.http.HttpStatus;import org.springframework.stereotype.Component;import javax.servlet.http.HttpServletRequest;@Componentpublic class PassWordFilter extends ZuulFilter { //设置过滤器类型 @Override public String filterType() { return FilterConstants.PRE_TYPE; } //设置过滤器执行顺序 @Override public int filterOrder() { return FilterConstants.SERVLET_DETECTION_FILTER_ORDER-2; } @Override public boolean shouldFilter() { return true; } //执行具体的校验逻辑 @Override public Object run() throws ZuulException { RequestContext requestContext=RequestContext.getCurrentContext(); HttpServletRequest request=requestContext.getRequest(); if(null!=request.getParameter("passWord")) { requestContext.setSendZuulResponse(true); requestContext.setResponseStatusCode(200); requestContext.set("isSuccess",true); } else { requestContext.setSendZuulResponse(false); throw new ZuulRuntimeException(new ZuulException(this.filterType()+":"+this.getClass().getSimpleName(), HttpStatus.UNAUTHORIZED.value(),"密码不能为空")); } return null; }}
Token校验器(AccessTokenFilter)
import com.netflix.zuul.ZuulFilter;import com.netflix.zuul.context.RequestContext;import com.netflix.zuul.exception.ZuulException;import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants;import org.springframework.cloud.netflix.zuul.util.ZuulRuntimeException;import org.springframework.http.HttpStatus;import org.springframework.stereotype.Component;import javax.servlet.http.HttpServletRequest;@Componentpublic class AccessTokenFilter extends ZuulFilter { @Override public String filterType() { return FilterConstants.PRE_TYPE; } @Override public int filterOrder() { return FilterConstants.SERVLET_DETECTION_FILTER_ORDER-1; } @Override public boolean shouldFilter() { return true; } @Override public Object run() throws ZuulException { RequestContext requestContext=RequestContext.getCurrentContext(); HttpServletRequest request=requestContext.getRequest(); if(null!=request.getParameter("accessToken")) { requestContext.setResponseStatusCode(200); requestContext.setSendZuulResponse(true); requestContext.set("isSuccess",true); } else{ requestContext.setSendZuulResponse(false); throw new ZuulRuntimeException(new ZuulException(this.filterType()+":"+this.getClass().getSimpleName(), HttpStatus.UNAUTHORIZED.value(),"token不能为空")); } return null; }}
设置响应头过滤器(ResponseHeaderFilter)
import com.netflix.zuul.ZuulFilter;import com.netflix.zuul.context.RequestContext;import com.netflix.zuul.exception.ZuulException;import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants;import org.springframework.stereotype.Component;import javax.servlet.http.HttpServletResponse;@Componentpublic class ResponseHeaderFilter extends ZuulFilter { @Override public String filterType() { return FilterConstants.POST_TYPE; } @Override public int filterOrder() { return FilterConstants.SEND_RESPONSE_FILTER_ORDER-1; } @Override public boolean shouldFilter() { return true; } @Override public Object run() throws ZuulException { RequestContext requestContext=RequestContext.getCurrentContext(); HttpServletResponse response=requestContext.getResponse(); response.addHeader("response-header","response-header"); return null; }}
用户名、密码、token信息参数均在前置类型过滤器,在ServletDetectionFilter前执行校验操作,校验通过后交由Zuul内置过滤器在前置类型过滤器进行处理。前置类型过滤器处理完成,通过路由类型过滤器路由到post过滤器,设置响应头过滤器为post类型过滤器,在正常处理响应请求(SendResponseFilter)前执行。
路由配置规则
默认路由访问规则
http://服务网关应用名称:服务网关应用端口/服务提供者实例名称
自定义访问URL
zuul.routes.服务实例ID=URL
如zuul.routes.user-service=/user/**就会将user-service微服务映射到/user/**访问路径。
忽略指定微服务
zuul.ignored-services=服务提供者实例名称
通过上述配置,可以配置需要忽略的服务实例,多个服务实例间用逗号(,)间隔
忽略所有微服务,只路由指定微服务
zuul.ignored-services=*zuul.routes.user-service=/user/**
通过上述配置,可以配置忽略所有服务实例,只路由到user-service实例
定义服务实例ID和对应路径
zuul.routes.user-router.service-id=user-service-providerzuul.routes.user-router.path=/user/**
通过上述配置,同时指定用户微服务实例ID为user-service-provider,对应路径为/user/**,user-router只是给路由一个有意义的名称,可以任意起名。
同时指定path和URL
zuul.routes.user-route.url=http://localhost:8100/user-servicezuul.routes.user-route.path=/user/**
通过上述配置,可以将访问地址为/user/**的路由到用户服务实例(localhost:8100为服务注册中心地址,user-service为服务提供者配置的服务实例名称)。
使用上述配置方式,不会使HystrixCommand执行,也不会使Ribbon负载均衡多个URL,破坏了Zuul的服务熔断和负载均衡特性。
同时指定path和URL并且不破坏服务熔断(Hystrix)负载均衡(Ribbon)特性
zuul.routes.user-route.service-id=user-service-providerzuul.routes.user-route.path=/user/**ribbon.eureka.enabled=false #为Ribbon禁用Eurekauser-service-provider.ribbon.listOfServers=localhost:8100,localhost:8101
通过上述配置,即可以指定path和URL,又保证Zuul的服务熔断和负载均衡特性可用。配置负载均衡的服务实例名须与服务实例ID(service-id)配置内容一致。
路由前缀
zuul.prefix=/apizuul.routes.user-router=/user/**#zuul.routes.user-router.strip-prefix=true#zuul.strip-prefix=false
通过上述配置,路由转发至用户微服务实例时去掉/api前缀;如果需要路由转发时带上路由前缀,通过zuul.routes.user-service-provider.strip-prefix=true进行指定路由移除代理前置配置即可(user-router只是为服务路由起了一个有意义的名字,可以任意起名),也可以通过zuul.strip-prefix=false对所有服务路由。
忽略某些路径
在前面的路由配置规则中,提到了如何忽略微服务,有时我们还需要更细粒度的路由控制,例如想让zuul路由到用户微服务实例,又想保护用户微服务的用户角色配置相关服务不被访问,可以进行如下配置:
zuul.ignoredPatterns:/**/RoleConfig/**zuul.routes.user-service-provider=/user/**
通过上述配置,可以讲用户微服务路由到/userRoleConfig/**)。
本地转发
在Zuul实现的API网关路由功能中,还支持forward形式的服务端转发配置。通过配置path和url相关信息就能够实现本地转发。
zuul.routes.user-route.url=http://localhost:8100/zuul.routes.user-route.path=/user/**zuul.routes.order-route.url=forward:/order-servicezuul.routes.user-route.path=/order/**
上述配置中,/user/**请求被转发到http://localhost:8100/,/order/**请求被转发到/order-service进行处理。在配置本地转发后,需要在服务提供者应用中提供请求路径为/order-service对应的接口进行服务调用处理,否则会因为本地转发无法找到对应处理接口而返回404错误。