ZuulFilter定義
通過繼承ZuulFilter
我們可以定義一個新的過濾器,如下
public class IpAddressFilter extends ZuulFilter {
@Autowired
private IGatewayService iGatewayService;
@Override
public String filterType() {
// pre類型的過濾器
return PRE_TYPE;
}
@Override
public int filterOrder() {
// 排序
return 1;
}
@Override
public boolean shouldFilter() {
return true;
}
@Override
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
String ip = ctx.getRequest().getRemoteAddr();
Set<String> blackList = ConcurrentCache.getBlackSet();
Set<String> whiteList = ConcurrentCache.getWhiteSet();
blackList.removeAll(whiteList);
// 在黑名單中禁用
if (StringUtils.isNotBlank(ip)&& blackList.contains(ip)) {
ctx.setSendZuulResponse(false);
ctx.setResponseBody("Suspected flooding attack, IP blocked");
ctx.setResponseStatusCode(HttpStatus.FORBIDDEN.value());
ctx.addZuulResponseHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_UTF8_VALUE);
return null;
}
return null;
}
}
ZuulFilter
中實現了compareTo()
方法,根據它的值決定同類型的filter的執行順序。compareTo()
方法如下:
public int compareTo(ZuulFilter filter) {
return Integer.compare(this.filterOrder(), filter.filterOrder());
}
注冊ZuulFilter到spring容器中
ZuulFilter
可以通過@Component
,也可以通過@Bean
實例化來納入spring的生命周期中。
@Configuration
public class FilterConfig {
@Bean
public IpAddressFilter addIpAddressFilter() {
return new IpAddressFilter();
}
}
ZuulServerAutoConfiguration
中自動裝配了filter
,被spring實例化出來的所有的ZuulFilter
都會被自動裝配到Map中。
@Configuration
protected static class ZuulFilterConfiguration {
// 根據類型,自動裝配ZuulFilter到Map對象中
@Autowired
private Map<String, ZuulFilter> filters;
@Bean
public ZuulFilterInitializer zuulFilterInitializer(
CounterFactory counterFactory, TracerFactory tracerFactory) {
FilterLoader filterLoader = FilterLoader.getInstance();
// 單例模式
FilterRegistry filterRegistry = FilterRegistry.instance();
return new ZuulFilterInitializer(this.filters, counterFactory, tracerFactory, filterLoader, filterRegistry);
}
}
上面的代碼會調用ZuulFilterInitializer
的構造方法。
ZuulFilterInitializer
中的contextInitialized()
開啟了@PostConstruct
注解,在構造方法完成時,容器會調用contextInitialized()
方法(注意:ZuulFilterInitializer對象要由spring管理才會調用到@PostConstruct),將所有的filter保存到filterRegistry
中,filterRegistry
是一個單例對象。
說明:PostConstruct 注釋用於在依賴關系注入完成之后需要執行的方法上
contextInitialized()
方法如下:
@PostConstruct
public void contextInitialized() {
log.info("Starting filter initializer");
TracerFactory.initialize(tracerFactory);
CounterFactory.initialize(counterFactory);
for (Map.Entry<String, ZuulFilter> entry : this.filters.entrySet()) {
// 保存filter
filterRegistry.put(entry.getKey(), entry.getValue());
}
}
自定義路由轉發規則
ZuulProxyAutoConfiguration
類中注冊了RouteLocator
的bean
,@Bean
會按照類型,自動注入RouteLocator的實現類。
@Bean
public PreDecorationFilter preDecorationFilter(RouteLocator routeLocator,
ProxyRequestHelper proxyRequestHelper) {
return new PreDecorationFilter(routeLocator, this.server.getServletPrefix(),
this.zuulProperties, proxyRequestHelper);
}
RouteLocator
實例化
@Configuration
public class AppConfig{
//.....省略....
@Bean(value = "discoveryRouteLocator")
public DiscoveryClientRouteLocator discoveryClientRouteLocator(ServerProperties server, DiscoveryClient discovery, ZuulProperties properties,ServiceInstance localInstance) {
return new CustomRouteLocator(server.getServletPath(), discovery,properties,localInstance);
}
}
CustomRouteLocator
實現自定義路由的功能,類如下。
public class CustomRouteLocator extends DiscoveryClientRouteLocator {
// ....省略....
@Override
// 重寫
public Route getMatchingRoute(String path) {
// ....省略....
//可以從數據庫中讀取路由規則,並進行處理
}
// 重寫
@Override
protected LinkedHashMap<String, ZuulRoute> locateRoutes() {
// ....省略....
}
}
Servlet初始化
為什么通過訪問網關可以自動跳轉到zuul中,其實是通過servlet的實現的,該servlet對根路徑/
進行過濾。下面說明servlet的初始化內容。
ZuulServerAutoConfiguration
類中定義了ZuulController
@Bean
public ZuulController zuulController() {
return new ZuulController();
}
ZuulController
繼承了ServletWrappingController
類
public class ZuulController extends ServletWrappingController {
public ZuulController() {
// 設置類為ZuulServlet
setServletClass(ZuulServlet.class);
setServletName("zuul");
setSupportedMethods((String[]) null);
}
@Override
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
try {
return super.handleRequestInternal(request, response);
}
finally {
RequestContext.getCurrentContext().unset();
}
}
}
ServletWrappingController
對ZuulServlet
進行實例化
@Override
public void afterPropertiesSet() throws Exception {
if (this.servletClass == null) {
throw new IllegalArgumentException("'servletClass' is required");
}
if (this.servletName == null) {
this.servletName = this.beanName;
}
// 實例化
this.servletInstance = this.servletClass.newInstance();
// 調用servlet的init方法
this.servletInstance.init(new DelegatingServletConfig());
}
當訪問一個url的時候,服務請求會跳轉到ZuulController
中,執行handleRequest()
方法。
@Override
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
try {
// 調用父類的handleRequestInternal方法
return super.handleRequestInternal(request, response);
}
finally {
RequestContext.getCurrentContext().unset();
}
}
handleRequestInternal()
方法如下:
protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response)
throws Exception {
this.servletInstance.service(request, response);
return null;
}
servletInstance
即ZuulServlet
的實例,上面的方法最終調用ZuulServlet
中的service()
方法。
service()
方法如下:
@Override
public void service(javax.servlet.ServletRequest servletRequest, javax.servlet.ServletResponse servletResponse) throws ServletException, IOException {
try {
init((HttpServletRequest) servletRequest, (HttpServletResponse) servletResponse);
RequestContext context = RequestContext.getCurrentContext();
context.setZuulEngineRan();
try {
// pre過濾器
preRoute();
} catch (ZuulException e) {
error(e);
postRoute();
return;
}
try {
// route過濾器
route();
} catch (ZuulException e) {
error(e);
postRoute();
return;
}
try {
// post過濾器
postRoute();
} catch (ZuulException e) {
error(e);
return;
}
} catch (Throwable e) {
error(new ZuulException(e, 500, "UNHANDLED_EXCEPTION_" + e.getClass().getName()));
} finally {
RequestContext.getCurrentContext().unset();
}
}
根據上面的源碼我們知道,一個請求到來的時候,就要經歷preRoute、route、postRoute幾個階段,用官方的圖來說明
網關請求執行的過程
根據第上面的內容,我們知道,當通過網關對服務進行請求的時候,要經歷preRoute,route、postRoute階段,這里以以preRoute()
方法為例,對路由的處理過程進行說明。
preRoute()
方法如下:
void preRoute() throws ZuulException {
zuulRunner.preRoute();
}
ZuulRunner
中的preRoute()
方法如下:
public void preRoute() throws ZuulException {
FilterProcessor.getInstance().preRoute();
}
FilterProcessor
是一個單例模式,FilterProcessor
中的preRoute()
方法如下:
public void preRoute() throws ZuulException {
try {
// 運行pre過濾器
runFilters("pre");
} catch (ZuulException e) {
throw e;
} catch (Throwable e) {
throw new ZuulException(e, 500, "UNCAUGHT_EXCEPTION_IN_PRE_FILTER_" + e.getClass().getName());
}
}
執行過濾器,runFilters()
方法如下:
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;
}
根據類型獲取過濾器列表,getFiltersByType()
方法如下:
public List<ZuulFilter> getFiltersByType(String filterType) {
List<ZuulFilter> list = hashFiltersByType.get(filterType);
if (list != null) return list;
list = new ArrayList<ZuulFilter>();
// 獲取所有的過濾器
Collection<ZuulFilter> filters = filterRegistry.getAllFilters();
for (Iterator<ZuulFilter> iterator = filters.iterator(); iterator.hasNext(); ) {
ZuulFilter filter = iterator.next();
// 取得filterType的類型列表
if (filter.filterType().equals(filterType)) {
list.add(filter);
}
}
// 對filter進行排序
Collections.sort(list); // sort by priority
// 保存列表
hashFiltersByType.putIfAbsent(filterType, list);
return list;
}
FilterRegistry
類是一個單例模式,getAllFilters()
方法如下
public class FilterRegistry {
private static final FilterRegistry INSTANCE = new FilterRegistry();
// ....省略....
public Collection<ZuulFilter> getAllFilters() {
return this.filters.values();
}
}
過濾器的處理方法processZuulFilter()
如下:
public Object processZuulFilter(ZuulFilter filter) throws ZuulException {
RequestContext ctx = RequestContext.getCurrentContext();
boolean bDebug = ctx.debugRouting();
final String metricPrefix = "zuul.filter-";
long execTime = 0;
String filterName = "";
try {
long ltime = System.currentTimeMillis();
filterName = filter.getClass().getSimpleName();
// ....省略....
// 運行filter
ZuulFilterResult result = filter.runFilter();
ExecutionStatus s = result.getStatus();
execTime = System.currentTimeMillis() - ltime;
// .....省略....
usageNotifier.notify(filter, s);
return o;
} catch (Throwable e) {
// .....省略.....
}
}
runFilter()
方法如下,:
public ZuulFilterResult runFilter() {
ZuulFilterResult zr = new ZuulFilterResult();
if (!isFilterDisabled()) {
// 判斷過濾器是否需要執行
if (shouldFilter()) {
Tracer t = TracerFactory.instance().startMicroTracer("ZUUL::" + this.getClass().getSimpleName());
try {
// 調用filter的run方法。
Object res = run();
zr = new ZuulFilterResult(res, ExecutionStatus.SUCCESS);
} catch (Throwable e) {
// ....省略....
} finally {
t.stopAndLog();
}
} else {
zr = new ZuulFilterResult(ExecutionStatus.SKIPPED);
}
}
return zr;
}
最終調用到各ZuulFilter
中的run()
方法。
路由查找
pre
類型的PreDecorationFilter
過濾器,用來進行路由規則的匹配
如下:
執行后,上下文內容中的內容如下,加入了requestURI
訪問服務
根據下圖可以知道,真正訪問服務的是route階段。如下:
對於正常的服務,比如:/xxx/service_name
是通過RibbonRoutingFilter
實現對服務的負載均衡訪問,它的run()
方法如下:
public Object run() {
RequestContext context = RequestContext.getCurrentContext();
this.helper.addIgnoredHeaders();
try {
RibbonCommandContext commandContext = buildCommandContext(context);
ClientHttpResponse response = forward(commandContext);
setResponse(response);
return response;
}
catch (ZuulException ex) {
throw new ZuulRuntimeException(ex);
}
catch (Exception ex) {
throw new ZuulRuntimeException(ex);
}
}
如果是固定的url鏈接,如:http://www.abc.com/xxx/service_name
這種,則是通過SendForwardFilter
過濾器實現轉發。它的run()
方法如下:
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()) {
// url轉發
dispatcher.forward(ctx.getRequest(), ctx.getResponse());
ctx.getResponse().flushBuffer();
}
}
}
catch (Exception ex) {
ReflectionUtils.rethrowRuntimeException(ex);
}
return null;
}