API网关服务对微服务框架的重要性:
1. 作为系统的统一入口,屏蔽系统的内部各个微服务的细节。
2. 与微服务治理框架结合,实现自动化的服务实例维护以及负载均衡的路由转发。
3. 可以实现接口权限校验与微服务业务逻辑解耦。
4. 通过服务网关中的过滤器,在各生命周期中去校验请求的内容,将原本在对外服务层的校验前移,保证了微服务的无状态性,同时降低了微服务的测试难度,让服务本身更集中关注业务逻辑的处理。
接下来实现一个会打印服务请求路径的网关:ApiGateway
要实现一个服务网关,使用Spring boot + Spring cloud Zuul来实现是一个非常方便的事情。只需要在普通的 Springboot 的启动类上增加 @enableZuulProxy ,然后在配置文件配上相关的参数即可。
下面是 ApiGateWay 项目的详细信息:
1. 项目目录结构:
├── build.gradle ├── gradle │ └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── settings.gradle └── src ├── main │ ├── java │ │ └── com │ │ └── simonton │ │ └── api │ │ ├── ApiGateway.java │ │ └── log │ │ └── LogPrintFilter.java │ └── resources │ └── application.yml └── test └── java
2. build.gradle内容:
buildscript { repositories { jcenter() } dependencies { classpath 'org.springframework.boot:spring-boot-gradle-plugin:1.5.6.RELEASE' } } apply plugin: 'java' apply plugin: 'org.springframework.boot' repositories { jcenter() } dependencies { compile 'org.springframework.boot:spring-boot-starter-web' compile 'org.springframework.cloud:spring-cloud-starter-zuul:1.4.2.RELEASE' }
3. 代码核心 ApiGateway 内容:
package com.simonton.api; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.zuul.EnableZuulProxy; import org.springframework.context.annotation.Bean; import com.simonton.api.log.LogPrintFilter; @EnableZuulProxy @SpringBootApplication public class ApiGateway { public static void main(String[] args) { SpringApplication.run(ApiGateway.class, args); } @Bean public LogPrintFilter logPrintFilter() { return new LogPrintFilter(); } }
4. 日志打印过滤器 内容:
/** * */ package com.simonton.api.log; import javax.servlet.http.HttpServletRequest; import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants; import com.netflix.zuul.ZuulFilter; import com.netflix.zuul.context.RequestContext; /** * 打印请求的服务 * * @author simonton * */ public class LogPrintFilter extends ZuulFilter { @Override public boolean shouldFilter() { return true; } @Override public Object run() { RequestContext contenxt = RequestContext.getCurrentContext(); HttpServletRequest request = contenxt.getRequest(); String url = request.getRequestURI(); System.out.println("@request url: " + url); return null; } @Override public String filterType() { return FilterConstants.PRE_TYPE; } @Override public int filterOrder() { return 0; } }
4. 配置文件 application.yml 内容:
server: port: 7070 zuul: routes: baidu: path: /demo/** url: http://localhost:9090/demo add-host-header: true
ps: 当做服务路由分发时,host 信息设置不正确时,容易出现服务请求302跳转, add-host-header=true可以解决这个问题。
接下来我们来分析下,为什么只要在 springboot 启动类上加上 @enableZuulProxy就能实现服务的分发及过滤?
我们先来看@enableZuulProxy这个实现:
@EnableCircuitBreaker @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Import(ZuulProxyMarkerConfiguration.class) public @interface EnableZuulProxy { }
从源码可以看出,@enableZuulProxy只是导入了配置类 ZuulProxyMarkerConfiguration。
那ZuulProxyMarkerConfiguration又做了什么呢?
@Configuration public class ZuulProxyMarkerConfiguration { @Bean public Marker zuulProxyMarkerBean() { return new Marker(); } class Marker { } }
ZuulProxyMarkerConfiguration它更简单,只是 new 了一个 内部类 Marker对象。事实上本人看到这里非常郁闷,这个Marker类是个什么鬼?后来一搜源码才发现,是@ConditionalOnBean 在搞鬼。
在 ZuulServerAutoConfiguration 配置类中,它的实例化依赖羽刚才ZuulProxyMarkerConfiguration的 Marker 类。下面是ZuulServerAutoConfiguration部分源码:
@Configuration @EnableConfigurationProperties({ ZuulProperties.class }) @ConditionalOnClass(ZuulServlet.class) @ConditionalOnBean(ZuulServerMarkerConfiguration.Marker.class) // Make sure to get the ServerProperties from the same place as a normal web app would @Import(ServerPropertiesAutoConfiguration.class) public class ZuulServerAutoConfiguration {
到此为止,我才看到了关键的类ZuulServerAutoConfiguration出场 ,该类中会把 ZuulController 注册到 spring mvc的处理逻辑中。
@Bean public ZuulController zuulController() { return new ZuulController(); } @Bean public ZuulHandlerMapping zuulHandlerMapping(RouteLocator routes) { ZuulHandlerMapping mapping = new ZuulHandlerMapping(routes, zuulController()); mapping.setErrorController(this.errorController); return mapping; }
ZuulHandlerMapping 继承自 AbstractUrlHandlerMapping,它将 Spring cloud zuul 的ZuulController关联进 Spring MVC, 可以处理 request 请求。
那 ZuulController 又做了什么呢? 它将收到的请求统一交给 ZuulServlet 的 service方法 处理。
ZuulServlet 核心代码:
@Override public void service(javax.servlet.ServletRequest servletRequest, javax.servlet.ServletResponse servletResponse) throws ServletException, IOException { try { init((HttpServletRequest) servletRequest, (HttpServletResponse) servletResponse); // Marks this request as having passed through the "Zuul engine", as opposed to servlets // explicitly bound in web.xml, for which requests will not have the same data attached RequestContext context = RequestContext.getCurrentContext(); context.setZuulEngineRan(); try { preRoute(); } catch (ZuulException e) { error(e); postRoute(); return; } try { route(); } catch (ZuulException e) { error(e); postRoute(); return; } try { postRoute(); } catch (ZuulException e) { error(e); return; } } catch (Throwable e) { error(new ZuulException(e, 500, "UNHANDLED_EXCEPTION_" + e.getClass().getName())); } finally { RequestContext.getCurrentContext().unset(); } }
从源码可知,对于 Zuul 定义的不同类型的过滤器: pre, post, route, err 就是在这里被相应的处理。在ZuulServlet类中又引入了 ZuulRunner, ZuulRunner 中又使用 FilterProcessor 类专门执行对应的过滤器逻辑:
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 = list.get(i); Object result = processZuulFilter(zuulFilter); if (result != null && result instanceof Boolean) { bResult |= ((Boolean) result); } } } return bResult; }
从源码可知,FilterProcessor 通过 FilterLoader 把同一类型的filter 捞出来,按顺序执行。这里的这个顺序可是有规则的,每个 ZuulFilter 可以通过设置 filterOrder 来改变执行顺序的,原因是 ZuulFilter 实现了 Comparable 接口:
public abstract class ZuulFilter implements IZuulFilter, Comparable<ZuulFilter> { public int compareTo(ZuulFilter filter) { return Integer.compare(this.filterOrder(), filter.filterOrder()); } public ZuulFilterResult runFilter() { ZuulFilterResult zr = new ZuulFilterResult(); if (!isFilterDisabled()) { if (shouldFilter()) { Tracer t = TracerFactory.instance().startMicroTracer("ZUUL::" + this.getClass().getSimpleName()); try { Object res = run(); zr = new ZuulFilterResult(res, ExecutionStatus.SUCCESS); } catch (Throwable e) { t.setName("ZUUL::" + this.getClass().getSimpleName() + " failed"); zr = new ZuulFilterResult(ExecutionStatus.FAILED); zr.setException(e); } finally { t.stopAndLog(); } } else { zr = new ZuulFilterResult(ExecutionStatus.SKIPPED); } } return zr; }
好了,到目前为止,我们已经看到了Spring cloud Zuul 是如何开始处理 request 请求,及怎么处理过滤请求的。