一、基本概念
1.1 Zuul网关过滤器类型
pre:优先执行的过滤器,其中自动配置中配置的PreDecorationFilter,主要负责决定该请求的路由以及发送给下游服务的额外请求头。
route:在pre过滤器后执行的过滤器。当zuul网关中为某一路由名称配置的是serviceId时,由ribbonRoutingFilter采用ribbion的方式决定请求下游服务地址并且进行远程调用,并将响应结果写入的RequestContext中;当zuul网关中为某一路由名称配置的是url时,由SimpleHostRoutingFilter采用apache httpClient的方式,将路由配置的URL与请求URI拼接作为下游服务请求URL并进行远程调用,将响应结果写入的RequestContext中。
error:pre、route过滤器中任何步骤出错都会执行error过滤器。
post:在pre、route、error类型的过滤器执行后执行。自动配置中的SendResponseFilter过滤器将RequestContext中存放的远程调用返回的响应头、响应体等信息组成响应信息响应给客户端。
1.2 Zuul网关过滤器执行优先级
从类型来看:pre–>route–>(error)—>post
从指定的order来看:oder越小,越优先执行
1.3 Zuul网关过滤器执行条件
Zuul网关过滤器执行条件由父类ZuulFilter中的方法shouldFilter返回值决定,true表示执行,false表示不执行。
二、Zuul网关原理
2.1 Zuul网关架构图
三、源码解读
以spring-cloud-netflix-zuul-2.2.9.RELEASE为基础解读Zuul网关源码
3.1 与Zuul网关相关的SpringBoot自动配置
spring-cloud-netflix-zuul-2.2.9.RELEASE包中的spring.factories文件:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.netflix.zuul.ZuulServerAutoConfiguration,\
org.springframework.cloud.netflix.zuul.ZuulProxyAutoConfiguration
ZuulServerAutoConfiguration配置类注入的比较重要的Bean:
CompositeRouteLocator:主要负责根据path以及路由配置决定路由。
ZuulController:网关接收客户端请求的统一Controller
ZuulServlet:执行网关逻辑的入口
FormBodyWrapperFilter:解析客户端表单数据,并为下游服务重新编码。
SendResponseFilter:当网关没有发生异常时,从RequestContext取出下游服务的响应信息,并将它们组装成返回给浏览器的响应信息并响应。
SendErrorFilter:当网关发生异常时,从RequestContext取出异常信息组装成返回给浏览器的响应信息并响应。
ZuulProxyAutoConfiguration配置类注入的比较重要的Bean:
PreDecorationFilter:主要负责决定客户端请求的路由以及发送给下游服务的额外请求头。
RibbonRoutingFilter:当zuul网关中为某一路由名称配置的是serviceId时,由ribbonRoutingFilter采用ribbion的方式决定请求下游服务地址并且进行远程调用,并将响应结果写入的RequestContext中。
配置示例:
zuul:
routes:
goods:
path: /goods/**
serviceId: goods
SimpleHostRoutingFilter:当zuul网关中为某一路由名称配置的是url时,由SimpleHostRoutingFilter采用apache httpClient的方式,将路由配置的URL与请求URI拼接作为下游服务请求URL并进行远程调用,将响应结果写入的RequestContext中。
zuul:
routes:
goods:
path: /goods/**
url: http://localhost:8081
例如:请求到网关的URL为localhost:8090/goods/hello,则网关请求下游服务的URL为:http://localhost:8081/hello。
3.2 Zuul网关执行逻辑
3.2.1 由ZuulController统一接收浏览器请求。
ZuulController#handleRequest()
@Override
public ModelAndView handleRequest(HttpServletRequest request,
HttpServletResponse response) throws Exception {
try {
// We don't care about the other features of the base class, just want to // handle the request
return super.handleRequestInternal(request, response);
}finally {
// @see com.netflix.zuul.context.ContextLifecycleFilter.doFilter
RequestContext.getCurrentContext().unset();
}
}
handleRequestInternal方法:
protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception {
Assert.state(this.servletInstance != null, "No Servlet instance");
this.servletInstance.service(request, response);
return null;
}
3.2.2由ZuulServlet中的service方法执行逻辑:
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
try {
this.init((HttpServletRequest)servletRequest, (HttpServletResponse)servletResponse);
RequestContext context = RequestContext.getCurrentContext();
context.setZuulEngineRan();
try {
this.preRoute();
} catch (ZuulException var13) {
this.error(var13);
this.postRoute();
return;
}
try {
this.route();
} catch (ZuulException var12) {
this.error(var12);
this.postRoute();
return;
}
try {
this.postRoute();
} catch (ZuulException var11) {
this.error(var11);
}
} catch (Throwable var14) {
this.error(new ZuulException(var14, 500, "UNHANDLED_EXCEPTION_" + var14.getClass().getName()));
} finally {
RequestContext.getCurrentContext().unset();
}
}
ZuulServlet中的service中首先调用init方法,将浏览器的request和response存放到RequestContext。RequestContext继承ConcurrentHashMap并且存放在threadLocal,作用域在一次请求中。调用完init方法,执行preRoute方法。
FilterProcessor#preRoute():
public void preRoute() throws ZuulException {
try {
this.runFilters("pre");
} catch (ZuulException var2) {
throw var2;
} catch (Throwable var3) {
throw new ZuulException(var3, 500, "UNCAUGHT_EXCEPTION_IN_PRE_FILTER_" + var3.getClass().getName());
}
}
preRoute方法执行类型为“pre”的过滤器。
FilterProcessor#runFilte():
public Object runFilters(String sType) throws Throwable {
if (RequestContext.getCurrentContext().debugRouting()) {
Debug.addRoutingDebug("Invoking {" + sType + "} type filters");
}
boolean bResult = false;
List<ZuulFilter> list = FilterLoader.getInstance().getFiltersByType(sType);
if (list != null) {
for(int i = 0; i < list.size(); ++i) {
ZuulFilter zuulFilter = (ZuulFilter)list.get(i);
Object result = this.processZuulFilter(zuulFilter);
if (result != null && result instanceof Boolean) {
bResult |= (Boolean)result;
}
}
}
return bResult;
}
在根据过滤器类型名称获取过滤器方法getFiltersByType中获取对应的过滤器并调用Collections.sort(list)方法对过滤器排序。
public List<ZuulFilter> getFiltersByType(String filterType) {
List<ZuulFilter> list = (List)this.hashFiltersByType.get(filterType);
if (list != null) {
return list;
} else {
List<ZuulFilter> list = new ArrayList();
Collection<ZuulFilter> filters = this.filterRegistry.getAllFilters();
Iterator iterator = filters.iterator();
while(iterator.hasNext()) {
ZuulFilter filter = (ZuulFilter)iterator.next();
if (filter.filterType().equals(filterType)) {
list.add(filter);
}
}
Collections.sort(list);
this.hashFiltersByType.putIfAbsent(filterType, list);
return list;
}
}
ZuulFilter#compareTo方法表明默认根据order从小到大排序,即在后续的对过滤器进行for循环执行时,order越小越优先执行。
public int compareTo(ZuulFilter filter) {
return Integer.compare(this.filterOrder(), filter.filterOrder());
}
for循环为每个过滤器执行processZuulFilter方法,processZuulFilter方法中调用runFilter方法,如果过滤器中的shouldFilter返回true时执行ZuulFilter#run方法
public ZuulFilterResult runFilter() {
ZuulFilterResult zr = new ZuulFilterResult();
if (!this.isFilterDisabled()) {
if (this.shouldFilter()) {
//省略代码..
try {
Object res = this.run();
//省略代码..
} catch (Throwable var7) {
//省略代码..
} finally {
//省略代码..
}
} else {
zr = new ZuulFilterResult(ExecutionStatus.SKIPPED);
}
}
return zr;
}
执行完preRoute()方法,若出现异常,则依次执行类型为error、post的过滤器,逻辑与pre过滤器一致,区别在于过滤器类型不一样;否则依次执行route()、postRoute()方法。
3.3 主要过滤器执行逻辑
详情解释见代码注释
3.3.1 pre类型
SevletDetectionFilter:决定由ZuulServlet还是DispatchServlet执行请求。
@Override
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
if (!(request instanceof HttpServletRequestWrapper)
&& isDispatcherServletRequest(request)) {
//由DispatchServlet执行请求时,HttpServletRequest没有被包装过并且Attributes中包含
//DispatchServlet上下文参数
ctx.set(IS_DISPATCHER_SERVLET_REQUEST_KEY, true);
}
else {
//由ZuulServlet执行请求
ctx.set(IS_DISPATCHER_SERVLET_REQUEST_KEY, false);
}
return null;
}
FormBodyWrapperFilter:表单数据解析过滤器
@Override
public boolean shouldFilter() {
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
String contentType = request.getContentType();
//GET 请求不执行
if (contentType == null) {
return false;
}
//仅处理表单数据和在DispatchServlet中的Multipart数据。
try {
MediaType mediaType = MediaType.valueOf(contentType);
return MediaType.APPLICATION_FORM_URLENCODED.includes(mediaType)
|| (isDispatcherServletRequest(request)
&& MediaType.MULTIPART_FORM_DATA.includes(mediaType));
}
catch (InvalidMediaTypeException ex) {
return false;
}
}
@Override
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
FormBodyRequestWrapper wrapper = null;
if (request instanceof HttpServletRequestWrapper) {
HttpServletRequest wrapped = (HttpServletRequest) ReflectionUtils
.getField(this.requestField, request);
//FormBodyRequestWrapper其实就是将request中的表单数据提取出来
wrapper = new FormBodyRequestWrapper(wrapped);
//把wrapper对象赋值给request中的request属性,供后续使用
ReflectionUtils.setField(this.requestField, request, wrapper);
//若请求已经被包装过,则将wrapper对象赋值给包装过的request中的request属性,供后续使用
if (request instanceof ServletRequestWrapper) {
ReflectionUtils.setField(this.servletRequestField, request, wrapper);
}
}
else {
wrapper = new FormBodyRequestWrapper(request);
ctx.setRequest(wrapper);
}
if (wrapper != null) {
ctx.getZuulRequestHeaders().put("content-type", wrapper.getContentType());
}
return null;
}
PreDecorationFilter:主要负责决定客户端请求的路由以及发送给下游服务的额外请求头。
@Override
public boolean shouldFilter() {
RequestContext ctx = RequestContext.getCurrentContext();
return !ctx.containsKey(FORWARD_TO_KEY) //还未经过决定请求转发的过滤器
&& !ctx.containsKey(SERVICE_ID_KEY); //还未经过决定serviceId的过滤器
}
@Override
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
final String requestURI = this.urlPathHelper
.getPathWithinApplication(ctx.getRequest());
if (insecurePath(requestURI)) {
throw new InsecureRequestPathException(requestURI);
}
//根据URI以及配置的属性获取路由
Route route = this.routeLocator.getMatchingRoute(requestURI);
if (route != null) {
String location = route.getLocation();
if (location != null) {
ctx.put(REQUEST_URI_KEY, route.getPath());
ctx.put(PROXY_KEY, route.getId());
if (!route.isCustomSensitiveHeaders()) {
//将需要过滤的敏感头信息放入RequestContext
this.proxyRequestHelper.addIgnoredHeaders(
this.properties.getSensitiveHeaders().toArray(new String[0]));
}
else {
this.proxyRequestHelper.addIgnoredHeaders(
route.getSensitiveHeaders().toArray(new String[0]));
}
if (route.getRetryable() != null) {
ctx.put(RETRYABLE_KEY, route.getRetryable());
}
//网关配置为url
if (location.startsWith(HTTP_SCHEME + ":")
|| location.startsWith(HTTPS_SCHEME + ":")) {
//设置下游服务的地址,供后续SimpleHostRoutingFilter(以httpClient的方式请求下游服务)使用
ctx.setRouteHost(getUrl(location));
ctx.addOriginResponseHeader(SERVICE_HEADER, location);
}
//网关配置的url以forward:开头,表明请求转发。
else if (location.startsWith(FORWARD_LOCATION_PREFIX)) {
//去除forward标识字符,重新设置下游服务url放入RequestContext,
//供后续SendForwardFilter过滤器使用
ctx.set(FORWARD_TO_KEY,
StringUtils.cleanPath(
location.substring(FORWARD_LOCATION_PREFIX.length()) + route.getPath()));
ctx.setRouteHost(null);
return null;
}
else {
//设置serviceId,供后续RibbonRoutingFilter使用
//由注册中心以及ribbon负载均衡决定最终下游服务地址
ctx.set(SERVICE_ID_KEY, location);
ctx.setRouteHost(null);
ctx.addOriginResponseHeader(SERVICE_ID_HEADER, location);
}
//配置文件是否设置加入代理头
//X-Forwarded-Host:请求的主机列表,用逗号隔开,越靠后越接近服务器
//X-Forwarded-Port:请求的端口列表,用逗号隔开,越靠后越接近服务器
//Forwarded-Proto:请求的协议列表,用逗号隔开,越靠后越接近服务器
//X-Forwarded-Prefix:请求网关的前缀
//X-Forwarded-For:请求的ip列表,用逗号隔开,越靠后越接近服务器
if (this.properties.isAddProxyHeaders()) {
addProxyHeaders(ctx, route);
String xforwardedfor = ctx.getRequest()
.getHeader(X_FORWARDED_FOR_HEADER);
String remoteAddr = ctx.getRequest().getRemoteAddr();
if (xforwardedfor == null) {
xforwardedfor = remoteAddr;
}
else if (!xforwardedfor.contains(remoteAddr)) { // Prevent duplicates
xforwardedfor += ", " + remoteAddr;
}
ctx.addZuulRequestHeader(X_FORWARDED_FOR_HEADER, xforwardedfor);
}
if (this.properties.isAddHostHeader()) {
ctx.addZuulRequestHeader(HttpHeaders.HOST,
toHostHeader(ctx.getRequest()));
}
}
}
else {
log.warn("No route found for uri: " + requestURI);
String forwardURI = getForwardUri(requestURI);
ctx.set(FORWARD_TO_KEY, forwardURI);
}
return null;
}
3.3.2 route类型
RibbonRoutingFilter:由Ribbon负载均衡决定下游服务地址并且进行请求,将下游服务响应结果存入RequestContext
@Override
public boolean shouldFilter() {
RequestContext ctx = RequestContext.getCurrentContext();
//以serviceId的方式配置路由时执行该过滤器
return (ctx.getRouteHost() == null && ctx.get(SERVICE_ID_KEY) != null
&& ctx.sendZuulResponse());
}
@Override
public Object run() {
RequestContext context = RequestContext.getCurrentContext();
this.helper.addIgnoredHeaders();
try {
//根据请求头、请求参数、请求体、请求方法、serviceId、loadBalancerKey等构成
//ribbon上下文
RibbonCommandContext commandContext = buildCommandContext(context);
//创建command并且进行远程调用
ClientHttpResponse response = forward(commandContext);
//将下游服务响应结果存放在RequestContext,供后post过滤器SendResponseFilter使用。
setResponse(response);
return response;
}catch (ZuulException ex) {
throw new ZuulRuntimeException(ex);
}catch (Exception ex) {
throw new ZuulRuntimeException(ex);
}
}
SimpleHostRoutingFilter:以简单Apache HttpClient的方式调用下游服务
@Override
public boolean shouldFilter() {
//路由配置的是URL时执行该过滤器
return RequestContext.getCurrentContext().getRouteHost() != null
&& RequestContext.getCurrentContext().sendZuulResponse();
}
@Override
public Object run() {
RequestContext context = RequestContext.getCurrentContext();
HttpServletRequest request = context.getRequest();
//组装请求头,将忽略的请求头过滤
MultiValueMap<String, String> headers = this.helper
.buildZuulRequestHeaders(request);
//组装请求参数
MultiValueMap<String, String> params = this.helper
.buildZuulRequestQueryParams(request);
//组装请求方法
String verb = getVerb(request);
InputStream requestEntity = getRequestBody(request);
if (getContentLength(request) < 0) {
context.setChunkedRequestBody();
}
String uri = this.helper.buildZuulRequestURI(request);
this.helper.addIgnoredHeaders();
try {
//远程调用下游服务
CloseableHttpResponse response = forward(this.httpClient, verb, uri, request,
headers, params, requestEntity);
//保存下游服务响应结果
setResponse(response);
}
catch (Exception ex) {
throw new ZuulRuntimeException(handleException(ex));
}
return null;
}
SendForwardFilter:当网关配置的URL中含有forward标识时,执行的请求转发过滤器
@Override
public boolean shouldFilter() {
RequestContext ctx = RequestContext.getCurrentContext();
//需要请求转发时但还未执行请求转发的情况下执行该过滤器
return ctx.containsKey(FORWARD_TO_KEY)
&& !ctx.getBoolean(SEND_FORWARD_FILTER_RAN, false);
}
@Override
public Object run() {
try {
RequestContext ctx = RequestContext.getCurrentContext();
String path = (String) ctx.get(FORWARD_TO_KEY);
RequestDispatcher dispatcher = ctx.getRequest().getRequestDispatcher(path);
if (dispatcher != null) {
//表示请求转发了
ctx.set(SEND_FORWARD_FILTER_RAN, true);
if (!ctx.getResponse().isCommitted()) {
//请求转发
dispatcher.forward(ctx.getRequest(), ctx.getResponse());
//将response清空,这样后续的响应客户端的过滤器SendResponseFilter就不会执行了
ctx.getResponse().flushBuffer();
}
}
}
catch (Exception ex) {
ReflectionUtils.rethrowRuntimeException(ex);
}
return null;
}
3.3.3 post类型
SendResponseFilter:将响应信息从RequestContext中取出并组装,响应给浏览器。
@Override
public boolean shouldFilter() {
RequestContext context = RequestContext.getCurrentContext();
//不发生异常,下游服务有响应并且响应信息没被清空时执行
return context.getThrowable() == null
&& (!context.getZuulResponseHeaders().isEmpty()
|| context.getResponseDataStream() != null
|| context.getResponseBody() != null);
}
@Override
public Object run() {
try {
//组装下游服务的响应头和浏览器响应头
addResponseHeaders();
//响应给浏览器
writeResponse();
}
catch (Exception ex) {
ReflectionUtils.rethrowRuntimeException(ex);
}
return null;
}
参考地址: