Zuul 是netflix开源的一个API Gateway 服务器,可提供动态路由,监视,弹性,安全性等,其核心是基于一些列的过滤器链实现的(源码地址以及维基介绍)Spring-Security也是基于过滤器链实现的,这点我们可以学习一下,不是有句话说:对于Web资源进行保护最好的方式是Filter,对于方法调用保护最好的方式是AOP。
这是直接在网上复制的对于Zuul的架构图:
Zuul的核心就是Filter,下面看一下IZuulFilter的代码,下面有关加载Filter的是架构图粉色部分的代码:
public interface IZuulFilter {
//是否开启这个Filter
boolean shouldFilter();
//Filter执行的核心方法
Object run() throws ZuulException;
}
下面看一下IZuulFilter的实现类,这个大家很熟悉自己写Filter需要继承的父类:
public abstract class ZuulFilter implements IZuulFilter, Comparable<ZuulFilter> {
private final AtomicReference<DynamicBooleanProperty> filterDisabledRef = new AtomicReference();
public ZuulFilter() {
}
public abstract String filterType();
//Filter排序用的
public abstract int filterOrder();
public boolean isStaticFilter() {
return true;
}
public String disablePropertyName() {
return "zuul." + this.getClass().getSimpleName() + "." + this.filterType() + ".disable";
}
//判断当前Filter是否禁用
public boolean isFilterDisabled() {
this.filterDisabledRef.compareAndSet((Object)null, DynamicPropertyFactory.getInstance().getBooleanProperty(this.disablePropertyName(), false));
return ((DynamicBooleanProperty)this.filterDisabledRef.get()).get();
}
//核心执行方法
public ZuulFilterResult runFilter() {
//ZuulFilter的执行结果,点进去看了跟平常Controller写的Result差不多
ZuulFilterResult zr = new ZuulFilterResult();
if (!this.isFilterDisabled()) { //上面说过判断是否禁用
if (this.shouldFilter()) { //这也是是否开启
//这个是来跟踪网关的执行的
Tracer t = TracerFactory.instance().startMicroTracer("ZUUL::" + this.getClass().getSimpleName());
try {
Object res = this.run(); //执行了核心Run方法
zr = new ZuulFilterResult(res, ExecutionStatus.SUCCESS);
} catch (Throwable var7) {
t.setName("ZUUL::" + this.getClass().getSimpleName() + " failed");
zr = new ZuulFilterResult(ExecutionStatus.FAILED);
zr.setException(var7);
} finally {
t.stopAndLog();
}
} else {
zr = new ZuulFilterResult(ExecutionStatus.SKIPPED);
}
}
return zr;
}
//filter比较,Filter也是责任链排序的,所以需要排哪个filter放前面,order小的先执行
public int compareTo(ZuulFilter filter) {
return Integer.compare(this.filterOrder(), filter.filterOrder());
}
}
按住Ctrl点击ZuulFilter跟踪ZuulFilter的执行像下面这样的,这是用的源码版本的,jar包版本是点不进去的
点进去看显示出来的这三个其他类,FilterInfo代码很简单其实就是FIlter的实体类;FilterVerifier是对FIlter进行校验,源码上是这样描述这个类的verifies that the given source code is compilable in Groovy, can be instanciated, and is a ZuulFilter type(校验Filter能否在Groovy中编译运行);FilterRegistry是Filter注册用的,大家如果看代码多了会条件反射的对某些单词敏感知道哪些单词的类是比较重要的。我们看一下FilterRegistry的源码:
//为了方便调整了下代码顺序
public class FilterRegistry {
//是不是很熟悉的饿汉式单例
private static final FilterRegistry INSTANCE = new FilterRegistry();
private FilterRegistry() {
}
public static final FilterRegistry instance() {
return INSTANCE;
}
//单例结束,定义了用来装Filter的,为了保证线程安全就用了ConcurrentHashMap,这里的Key比较特殊:文件绝对路径 + 文件名
private final ConcurrentHashMap<String, ZuulFilter> filters = new ConcurrentHashMap<String, ZuulFilter>();
public ZuulFilter remove(String key) {
return this.filters.remove(key);
}
public ZuulFilter get(String key) {
return this.filters.get(key);
}
public void put(String key, ZuulFilter filter) {
this.filters.putIfAbsent(key, filter);
}
public int size() {
return this.filters.size();
}
public Collection<ZuulFilter> getAllFilters() {
return this.filters.values();
}
}
同样点击FilterRegistry,看一下它在哪里被使用了,FilterLoader(This class is one of the core classes in Zuul. It compiles, loads from a File, and checks if source code changed)加载Filter用的,StartServer启动服务。首先看一下加载Filter的FilterLoader代码:
public class FilterLoader {
//又是个单例模式
final static FilterLoader INSTANCE = new FilterLoader();
private static final Logger LOG = LoggerFactory.getLogger(FilterLoader.class);
//四个线程安全的Map,看一下起名知道最后修改时间(Long时间戳)、代码、检验、类型
private final ConcurrentHashMap<String, Long> filterClassLastModified = new ConcurrentHashMap<String, Long>();
private final ConcurrentHashMap<String, String> filterClassCode = new ConcurrentHashMap<String, String>();
private final ConcurrentHashMap<String, String> filterCheck = new ConcurrentHashMap<String, String>();
private final ConcurrentHashMap<String, List<ZuulFilter>> hashFiltersByType = new ConcurrentHashMap<String, List<ZuulFilter>>();
//调用了上面的单例模式
private FilterRegistry filterRegistry = FilterRegistry.instance();
//GroovyCompiler动态编译用的
static DynamicCodeCompiler COMPILER;
//Filter工厂,使用的工厂模式
static FilterFactory FILTER_FACTORY = new DefaultFilterFactory();
/**
* Sets a Dynamic Code Compiler
*/
public void setCompiler(DynamicCodeCompiler compiler) {
COMPILER = compiler;
}
// overidden by tests
public void setFilterRegistry(FilterRegistry r) {
this.filterRegistry = r;
}
/**
* Sets a FilterFactory
*/
public void setFilterFactory(FilterFactory factory) {
FILTER_FACTORY = factory;
}
/**
* @return Singleton FilterLoader
*/
public static FilterLoader getInstance() {
return INSTANCE;
}
/**
* Given source and name will compile and store the filter if it detects that the filter code has changed or
* the filter doesn't exist. Otherwise it will return an instance of the requested ZuulFilter
* 通过ZuulFilter的类代码和Filter名称获取ZuulFilter实例
*/
public ZuulFilter getFilter(String sCode, String sName) throws Exception {
//首先进行校验是否加载过了,加载过了就去掉重新加载,因为加载的代码写到了if外面
if (filterCheck.get(sName) == null) {
filterCheck.putIfAbsent(sName, sName);
if (!sCode.equals(filterClassCode.get(sName))) {
LOG.info("reloading code " + sName);
filterRegistry.remove(sName);
}
}
//通过名称获取Filter,如果Filter不存在,通过工厂模式重新反射出一个来
ZuulFilter filter = filterRegistry.get(sName);
if (filter == null) {
Class clazz = COMPILER.compile(sCode, sName);
if (!Modifier.isAbstract(clazz.getModifiers())) {
filter = (ZuulFilter) FILTER_FACTORY.newInstance(clazz);
}
}
return filter;
}
/**
* @return the total number of Zuul filters
* 返回所有缓存的ZuulFilter实例的总数量
*/
public int filterInstanceMapSize() {
return filterRegistry.size();
}
/**
* From a file this will read the ZuulFilter source code, compile it, and add it to the list of current filters
* a true response means that it was successful.
* 通过File文件来加载Filter
*/
public boolean putFilter(File file) throws Exception {
//使用绝对路径+名称,上面对FilterRegistry里面Key清楚了
String sName = file.getAbsolutePath() + file.getName();
//同样如果存在了移除
if (filterClassLastModified.get(sName) != null && (file.lastModified() != filterClassLastModified.get(sName))) {
LOG.debug("reloading filter " + sName);
filterRegistry.remove(sName);
}
//直接获取,获取失败后同样使用工厂模式创建后放进去
ZuulFilter filter = filterRegistry.get(sName);
if (filter == null) {
Class clazz = COMPILER.compile(file);
if (!Modifier.isAbstract(clazz.getModifiers())) {
filter = (ZuulFilter) FILTER_FACTORY.newInstance(clazz);
List<ZuulFilter> list = hashFiltersByType.get(filter.filterType());
if (list != null) {
hashFiltersByType.remove(filter.filterType()); //rebuild this list
}
filterRegistry.put(file.getAbsolutePath() + file.getName(), filter);
filterClassLastModified.put(sName, file.lastModified());
return true;
}
}
return false;
}
/**
* Returns a list of filters by the filterType specified
* 通过Filter类型获取所有ZuulFilter,结果是个List就是
*/
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();
if (filter.filterType().equals(filterType)) {
list.add(filter);
}
}
//排序,就是通过FilterOrder属性排序的
Collections.sort(list); // sort by priority
hashFiltersByType.putIfAbsent(filterType, list);
return list;
}
...//删除了Test代码
}
再来看一下启动服务干啥了,启动了服务干了三个事:观察、加载文件、初始化JavaFIlter
@Override
public void contextInitialized(ServletContextEvent sce) {
logger.info("starting server");
//这个是观察用的
// mocks monitoring infrastructure as we don't need it for this simple app
MonitoringHelper.initMocks();
//初始化文件管理
// initializes groovy filesystem poller
initGroovyFilterManager();
//初始化JavaFilter
// initializes a few java filter examples
initJavaFilters();
}
Filter是从文件加载的,我们看到initGroovyFilterManager()方法代码里面用到了FilterFileManager 过滤器文件管理用的:
//代码也没什么解释的,就是用线程定时一直在后台找想要的代码加进来
public class FilterFileManager {
private static final Logger LOG = LoggerFactory.getLogger(FilterFileManager.class);
String[] aDirectories;
int pollingIntervalSeconds;
Thread poller;
boolean bRunning = true;
//Zuul中的默认实现是GroovyFileFilter,只接受.groovy后缀的文件
static FilenameFilter FILENAME_FILTER;
static FilterFileManager INSTANCE;
private FilterFileManager() {
}
public static void setFilenameFilter(FilenameFilter filter) {
FILENAME_FILTER = filter;
}
/**
* Initialized the GroovyFileManager.
*
* @param pollingIntervalSeconds the polling interval in Seconds
* @param directories Any number of paths to directories to be polled may be specified
* @throws IOException
* @throws IllegalAccessException
* @throws InstantiationException
*/
public static void init(int pollingIntervalSeconds, String... directories) throws Exception, IllegalAccessException, InstantiationException {
if (INSTANCE == null) INSTANCE = new FilterFileManager();
INSTANCE.aDirectories = directories;
INSTANCE.pollingIntervalSeconds = pollingIntervalSeconds;
INSTANCE.manageFiles();
INSTANCE.startPoller();
}
public static FilterFileManager getInstance() {
return INSTANCE;
}
/**
* Shuts down the poller
*/
public static void shutdown() {
INSTANCE.stopPoller();
}
void stopPoller() {
bRunning = false;
}
void startPoller() {
poller = new Thread("GroovyFilterFileManagerPoller") {
public void run() {
while (bRunning) {
try {
sleep(pollingIntervalSeconds * 1000);
manageFiles();
} catch (Exception e) {
e.printStackTrace();
}
}
}
};
poller.setDaemon(true);
poller.start();
}
/**
* Returns the directory File for a path. A Runtime Exception is thrown if the directory is in valid
*
* @param sPath
* @return a File representing the directory path
*/
public File getDirectory(String sPath) {
File directory = new File(sPath);
if (!directory.isDirectory()) {
URL resource = FilterFileManager.class.getClassLoader().getResource(sPath);
try {
directory = new File(resource.toURI());
} catch (Exception e) {
LOG.error("Error accessing directory in classloader. path=" + sPath, e);
}
if (!directory.isDirectory()) {
throw new RuntimeException(directory.getAbsolutePath() + " is not a valid directory");
}
}
return directory;
}
/**
* Returns a List<File> of all Files from all polled directories
*
* @return
*/
List<File> getFiles() {
List<File> list = new ArrayList<File>();
for (String sDirectory : aDirectories) {
if (sDirectory != null) {
File directory = getDirectory(sDirectory);
File[] aFiles = directory.listFiles(FILENAME_FILTER);
if (aFiles != null) {
list.addAll(Arrays.asList(aFiles));
}
}
}
return list;
}
/**
* puts files into the FilterLoader. The FilterLoader will only addd new or changed filters
*
* @param aFiles a List<File>
* @throws IOException
* @throws InstantiationException
* @throws IllegalAccessException
*/
void processGroovyFiles(List<File> aFiles) throws Exception, InstantiationException, IllegalAccessException {
for (File file : aFiles) {
FilterLoader.getInstance().putFilter(file);
}
}
void manageFiles() throws Exception, IllegalAccessException, InstantiationException {
List<File> aFiles = getFiles();
processGroovyFiles(aFiles);
}
}
关于加载Filter的代码已经分析的差不多了,下面再看一下具体的执行(架构图中浅蓝色部分的代码)按照顺序来:
ZuulServlet
public class ZuulServlet extends HttpServlet {
private static final long serialVersionUID = -3374242278843351500L;
private ZuulRunner zuulRunner;
//初始化调用了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 {
//
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();
//初始化RequestContext
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 {
//最后清空ThreadLocal避免内存泄漏
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();
}
}
直接看ZuulRunner吧,可以看到ZuulServlet把活都给了ZuulRunner:
//很简单的代码调用FilterProcessor执行
public class ZuulRunner {
private boolean bufferRequests;
/**
* Creates a new <code>ZuulRunner</code> instance.
*/
public ZuulRunner() {
this.bufferRequests = true;
}
/**
*
* @param bufferRequests - whether to wrap the ServletRequest in HttpServletRequestWrapper and buffer the body.
*/
public ZuulRunner(boolean bufferRequests) {
this.bufferRequests = bufferRequests;
}
/**
* sets HttpServlet request and HttpResponse
* 初始化RequestContext,RequestContext一会看
* @param servletRequest
* @param servletResponse
*/
public void init(HttpServletRequest servletRequest, HttpServletResponse servletResponse) {
RequestContext ctx = RequestContext.getCurrentContext();
if (bufferRequests) {
ctx.setRequest(new HttpServletRequestWrapper(servletRequest));
} else {
ctx.setRequest(servletRequest);
}
ctx.setResponse(new HttpServletResponseWrapper(servletResponse));
}
/**
* executes "post" filterType ZuulFilters
*
* @throws ZuulException
*/
public void postRoute() throws ZuulException {
FilterProcessor.getInstance().postRoute();
}
/**
* executes "route" filterType ZuulFilters
*
* @throws ZuulException
*/
public void route() throws ZuulException {
FilterProcessor.getInstance().route();
}
/**
* executes "pre" filterType ZuulFilters
*
* @throws ZuulException
*/
public void preRoute() throws ZuulException {
FilterProcessor.getInstance().preRoute();
}
/**
* executes "error" filterType ZuulFilters
*/
public void error() {
FilterProcessor.getInstance().error();
}
}
再看一下FilterProcessor,这次找到真正干活的了,看看它怎么干的:
public class FilterProcessor {
static FilterProcessor INSTANCE = new FilterProcessor();
protected static final Logger logger = LoggerFactory.getLogger(FilterProcessor.class);
private FilterUsageNotifier usageNotifier;
public FilterProcessor() {
usageNotifier = new BasicFilterUsageNotifier();
}
/**
* @return the singleton FilterProcessor
*/
public static FilterProcessor getInstance() {
return INSTANCE;
}
/**
* sets a singleton processor in case of a need to override default behavior
*
* @param processor
*/
public static void setProcessor(FilterProcessor processor) {
INSTANCE = processor;
}
/**
* Override the default filter usage notification impl.
*
* @param notifier
*/
public void setFilterUsageNotifier(FilterUsageNotifier notifier) {
this.usageNotifier = notifier;
}
/**
* runs "post" filters which are called after "route" filters. ZuulExceptions from ZuulFilters are thrown.
* Any other Throwables are caught and a ZuulException is thrown out with a 500 status code
* Filter之后
* @throws ZuulException
*/
public void postRoute() throws ZuulException {
try {
runFilters("post");
} catch (ZuulException e) {
throw e;
} catch (Throwable e) {
throw new ZuulException(e, 500, "UNCAUGHT_EXCEPTION_IN_POST_FILTER_" + e.getClass().getName());
}
}
/**
* runs all "error" filters. These are called only if an exception occurs. Exceptions from this are swallowed and logged so as not to bubble up.
* 运行错误
*/
public void error() {
try {
runFilters("error");
} catch (Throwable e) {
logger.error(e.getMessage(), e);
}
}
/**
* Runs all "route" filters. These filters route calls to an origin.
* 直接执行Filter
* @throws ZuulException if an exception occurs.
*/
public void route() throws ZuulException {
try {
runFilters("route");
} catch (ZuulException e) {
throw e;
} catch (Throwable e) {
throw new ZuulException(e, 500, "UNCAUGHT_EXCEPTION_IN_ROUTE_FILTER_" + e.getClass().getName());
}
}
/**
* runs all "pre" filters. These filters are run before routing to the orgin.
* Filter之前
* @throws ZuulException
*/
public void preRoute() throws ZuulException {
try {
runFilters("pre");
} catch (ZuulException e) {
throw e;
} catch (Throwable e) {
throw new ZuulException(e, 500, "UNCAUGHT_EXCEPTION_IN_PRE_FILTER_" + e.getClass().getName());
}
}
/**
* runs all filters of the filterType sType/ Use this method within filters to run custom filters by type
*
* @param sType the filterType.
* @return
* @throws Throwable throws up an arbitrary exception
*/
public Object runFilters(String sType) throws Throwable {
if (RequestContext.getCurrentContext().debugRouting()) {
Debug.addRoutingDebug("Invoking {" + sType + "} type filters");
}
boolean bResult = false;
//根据Type获取到所有该类型的Filter,上面也说了这个已经排好序了
List<ZuulFilter> list = FilterLoader.getInstance().getFiltersByType(sType);
if (list != null) {
for (int i = 0; i < list.size(); i++) {
//遍历开始执行每个Filter
ZuulFilter zuulFilter = list.get(i);
Object result = processZuulFilter(zuulFilter);
if (result != null && result instanceof Boolean) {
bResult |= ((Boolean) result);
}
}
}
return bResult;
}
/**
* Processes an individual ZuulFilter. This method adds Debug information. Any uncaught Thowables are caught by this method and converted to a ZuulException with a 500 status code.
* 执行单个Filter
* @param filter
* @return the return value for that filter
* @throws ZuulException
*/
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();
RequestContext copy = null;
Object o = null;
Throwable t = null;
if (bDebug) {
Debug.addRoutingDebug("Filter " + filter.filterType() + " " + filter.filterOrder() + " " + filterName);
copy = ctx.copy();
}
//开始执行Filter
ZuulFilterResult result = filter.runFilter();
ExecutionStatus s = result.getStatus(); //执行状态
execTime = System.currentTimeMillis() - ltime;
//执行时间,失败还是成功调用方法记录调用链中当前Filter的名称,执行结果状态和执行时间
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);
}
break;
default:
break;
}
if (t != null) throw t;
//计数器具体代码在下面内部类
usageNotifier.notify(filter, s);
return o;
} catch (Throwable e) {
if (bDebug) {
Debug.addRoutingDebug("Running Filter failed " + filterName + " type:" + filter.filterType() + " order:" + filter.filterOrder() + " " + e.getMessage());
}
usageNotifier.notify(filter, ExecutionStatus.FAILED);
if (e instanceof ZuulException) {
throw (ZuulException) e;
} else {
ZuulException ex = new ZuulException(e, "Filter threw Exception", 500, filter.filterType() + ":" + filterName);
ctx.addFilterExecutionSummary(filterName, ExecutionStatus.FAILED.name(), execTime);
throw ex;
}
}
}
/**
* Publishes a counter metric for each filter on each use.
*/
public static class BasicFilterUsageNotifier implements FilterUsageNotifier {
private static final String METRIC_PREFIX = "zuul.filter-";
@Override
public void notify(ZuulFilter filter, ExecutionStatus status) {
DynamicCounter.increment(METRIC_PREFIX + filter.getClass().getSimpleName(), "status", status.name(), "filtertype", filter.filterType());
}
}
}
最后看一下维持上下文的RequestContext:
//继承了并发类HashMap,代码很简历,易于理解。大家看一下就能自己明白
public class RequestContext extends ConcurrentHashMap<String, Object> {
private static final Logger LOG = LoggerFactory.getLogger(RequestContext.class);
protected static Class<? extends RequestContext> contextClass = RequestContext.class;
private static RequestContext testContext = null;
//同样采用了ThreadLocal,目前我所见到的Context大部分采用的ThreadLocal
protected static final ThreadLocal<? extends RequestContext> threadLocal = new ThreadLocal<RequestContext>() {
@Override
protected RequestContext initialValue() {
try {
return contextClass.newInstance();
} catch (Throwable e) {
throw new RuntimeException(e);
}
}
};
public RequestContext() {
super();
}
/**
* Override the default RequestContext
*
* @param clazz
*/
public static void setContextClass(Class<? extends RequestContext> clazz) {
contextClass = clazz;
}
/**
* Get the current RequestContext
*
* @return the current RequestContext
*/
public static RequestContext getCurrentContext() {
if (testContext != null) return testContext;
RequestContext context = threadLocal.get();
return context;
}
/**
* Convenience method to return a boolean value for a given key
*
* @param key
* @return true or false depending what was set. default is false
*/
public boolean getBoolean(String key) {
return getBoolean(key, false);
}
/**
* Convenience method to return a boolean value for a given key
*
* @param key
* @param defaultResponse
* @return true or false depending what was set. default defaultResponse
*/
public boolean getBoolean(String key, boolean defaultResponse) {
Boolean b = (Boolean) get(key);
if (b != null) {
return b.booleanValue();
}
return defaultResponse;
}
/**
* sets a key value to Boolen.TRUE
*
* @param key
*/
public void set(String key) {
put(key, Boolean.TRUE);
}
/**
* puts the key, value into the map. a null value will remove the key from the map
*
* @param key
* @param value
*/
public void set(String key, Object value) {
if (value != null) put(key, value);
else remove(key);
}
/**
* true if zuulEngineRan
*
* @return
*/
public boolean getZuulEngineRan() {
return getBoolean("zuulEngineRan");
}
/**
* sets zuulEngineRan to true
*/
public void setZuulEngineRan() {
put("zuulEngineRan", true);
}
/**
* @return the HttpServletRequest from the "request" key
*/
public HttpServletRequest getRequest() {
return (HttpServletRequest) get("request");
}
/**
* sets the HttpServletRequest into the "request" key
*
* @param request
*/
public void setRequest(HttpServletRequest request) {
put("request", request);
}
/**
* @return the HttpServletResponse from the "response" key
*/
public HttpServletResponse getResponse() {
return (HttpServletResponse) get("response");
}
/**
* sets the "response" key to the HttpServletResponse passed in
*
* @param response
*/
public void setResponse(HttpServletResponse response) {
set("response", response);
}
/**
* returns a set throwable
*
* @return a set throwable
*/
public Throwable getThrowable() {
return (Throwable) get("throwable");
}
/**
* sets a throwable
*
* @param th
*/
public void setThrowable(Throwable th) {
put("throwable", th);
}
/**
* sets debugRouting
*
* @param bDebug
*/
public void setDebugRouting(boolean bDebug) {
set("debugRouting", bDebug);
}
/**
* @return "debugRouting"
*/
public boolean debugRouting() {
return getBoolean("debugRouting");
}
/**
* sets "debugRequestHeadersOnly" to bHeadersOnly
*
* @param bHeadersOnly
*/
public void setDebugRequestHeadersOnly(boolean bHeadersOnly) {
set("debugRequestHeadersOnly", bHeadersOnly);
}
/**
* @return "debugRequestHeadersOnly"
*/
public boolean debugRequestHeadersOnly() {
return getBoolean("debugRequestHeadersOnly");
}
/**
* sets "debugRequest"
*
* @param bDebug
*/
public void setDebugRequest(boolean bDebug) {
set("debugRequest", bDebug);
}
/**
* gets debugRequest
*
* @return debugRequest
*/
public boolean debugRequest() {
return getBoolean("debugRequest");
}
/**
* removes "routeHost" key
*/
public void removeRouteHost() {
remove("routeHost");
}
/**
* sets routeHost
*
* @param routeHost a URL
*/
public void setRouteHost(URL routeHost) {
set("routeHost", routeHost);
}
/**
* @return "routeHost" URL
*/
public URL getRouteHost() {
return (URL) get("routeHost");
}
/**
* appends filter name and status to the filter execution history for the
* current request
*
* @param name filter name
* @param status execution status
* @param time execution time in milliseconds
*/
public void addFilterExecutionSummary(String name, String status, long time) {
StringBuilder sb = getFilterExecutionSummary();
if (sb.length() > 0) sb.append(", ");
sb.append(name).append('[').append(status).append(']').append('[').append(time).append("ms]");
}
/**
* @return String that represents the filter execution history for the current request
*/
public StringBuilder getFilterExecutionSummary() {
if (get("executedFilters") == null) {
putIfAbsent("executedFilters", new StringBuilder());
}
return (StringBuilder) get("executedFilters");
}
/**
* sets the "responseBody" value as a String. This is the response sent back to the client.
*
* @param body
*/
public void setResponseBody(String body) {
set("responseBody", body);
}
/**
* @return the String response body to be snt back to the requesting client
*/
public String getResponseBody() {
return (String) get("responseBody");
}
/**
* sets the InputStream of the response into the responseDataStream
*
* @param responseDataStream
*/
public void setResponseDataStream(InputStream responseDataStream) {
set("responseDataStream", responseDataStream);
}
/**
* sets the flag responseGZipped if the response is gzipped
*
* @param gzipped
*/
public void setResponseGZipped(boolean gzipped) {
put("responseGZipped", gzipped);
}
/**
* @return true if responseGZipped is true (the response is gzipped)
*/
public boolean getResponseGZipped() {
return getBoolean("responseGZipped", true);
}
/**
* @return the InputStream Response
*/
public InputStream getResponseDataStream() {
return (InputStream) get("responseDataStream");
}
/**
* If this value is true then the response should be sent to the client.
*
* @return
*/
public boolean sendZuulResponse() {
return getBoolean("sendZuulResponse", true);
}
/**
* sets the sendZuulResponse boolean
*
* @param bSend
*/
public void setSendZuulResponse(boolean bSend) {
set("sendZuulResponse", Boolean.valueOf(bSend));
}
/**
* returns the response status code. Default is 200
*
* @return
*/
public int getResponseStatusCode() {
return get("responseStatusCode") != null ? (Integer) get("responseStatusCode") : 500;
}
/**
* Use this instead of response.setStatusCode()
*
* @param nStatusCode
*/
public void setResponseStatusCode(int nStatusCode) {
getResponse().setStatus(nStatusCode);
set("responseStatusCode", nStatusCode);
}
/**
* add a header to be sent to the origin
*
* @param name
* @param value
*/
public void addZuulRequestHeader(String name, String value) {
getZuulRequestHeaders().put(name.toLowerCase(), value);
}
/**
* return the list of requestHeaders to be sent to the origin
*
* @return the list of requestHeaders to be sent to the origin
*/
public Map<String, String> getZuulRequestHeaders() {
if (get("zuulRequestHeaders") == null) {
HashMap<String, String> zuulRequestHeaders = new HashMap<String, String>();
putIfAbsent("zuulRequestHeaders", zuulRequestHeaders);
}
return (Map<String, String>) get("zuulRequestHeaders");
}
/**
* add a header to be sent to the response
*
* @param name
* @param value
*/
public void addZuulResponseHeader(String name, String value) {
getZuulResponseHeaders().add(new Pair<String, String>(name, value));
}
/**
* returns the current response header list
*
* @return a List<Pair<String, String>> of response headers
*/
public List<Pair<String, String>> getZuulResponseHeaders() {
if (get("zuulResponseHeaders") == null) {
List<Pair<String, String>> zuulRequestHeaders = new ArrayList<Pair<String, String>>();
putIfAbsent("zuulResponseHeaders", zuulRequestHeaders);
}
return (List<Pair<String, String>>) get("zuulResponseHeaders");
}
/**
* the Origin response headers
*
* @return the List<Pair<String, String>> of headers sent back from the origin
*/
public List<Pair<String, String>> getOriginResponseHeaders() {
if (get("originResponseHeaders") == null) {
List<Pair<String, String>> originResponseHeaders = new ArrayList<Pair<String, String>>();
putIfAbsent("originResponseHeaders", originResponseHeaders);
}
return (List<Pair<String, String>>) get("originResponseHeaders");
}
/**
* adds a header to the origin response headers
*
* @param name
* @param value
*/
public void addOriginResponseHeader(String name, String value) {
getOriginResponseHeaders().add(new Pair<String, String>(name, value));
}
/**
* returns the content-length of the origin response
*
* @return the content-length of the origin response
*/
public Long getOriginContentLength() {
return (Long) get("originContentLength");
}
/**
* sets the content-length from the origin response
*
* @param v
*/
public void setOriginContentLength(Long v) {
set("originContentLength", v);
}
/**
* sets the content-length from the origin response
*
* @param v parses the string into an int
*/
public void setOriginContentLength(String v) {
try {
final Long i = Long.valueOf(v);
set("originContentLength", i);
} catch (NumberFormatException e) {
LOG.warn("error parsing origin content length", e);
}
}
/**
* @return true if the request body is chunked
*/
public boolean isChunkedRequestBody() {
final Object v = get("chunkedRequestBody");
return (v != null) ? (Boolean) v : false;
}
/**
* sets chunkedRequestBody to true
*/
public void setChunkedRequestBody() {
this.set("chunkedRequestBody", Boolean.TRUE);
}
/**
* @return true is the client request can accept gzip encoding. Checks the "accept-encoding" header
*/
public boolean isGzipRequested() {
final String requestEncoding = this.getRequest().getHeader(ZuulHeaders.ACCEPT_ENCODING);
return requestEncoding != null && requestEncoding.toLowerCase().contains("gzip");
}
/**
* unsets the threadLocal context. Done at the end of the request.
*/
public void unset() {
threadLocal.remove();
}
/**
* Mkaes a copy of the RequestContext. This is used for debugging.
*
* @return
*/
public RequestContext copy() {
RequestContext copy = new RequestContext();
Iterator<String> it = keySet().iterator();
String key = it.next();
while (key != null) {
Object orig = get(key);
try {
Object copyValue = DeepCopy.copy(orig);
if (copyValue != null) {
copy.set(key, copyValue);
} else {
copy.set(key, orig);
}
} catch (NotSerializableException e) {
copy.set(key, orig);
}
if (it.hasNext()) {
key = it.next();
} else {
key = null;
}
}
return copy;
}
/**
* @return Map<String, List<String>> of the request Query Parameters
*/
public Map<String, List<String>> getRequestQueryParams() {
return (Map<String, List<String>>) get("requestQueryParams");
}
/**
* sets the request query params list
*
* @param qp Map<String, List<String>> qp
*/
public void setRequestQueryParams(Map<String, List<String>> qp) {
put("requestQueryParams", qp);
}
}
到这Zuul网关1.0的源码以及执行路线就看完了。
Zuul1.0采用的同步IO的方式被大家吐槽性能差,于是大家转入了SpringCloud gateway的怀抱,但是在新版的zuul2.0中Zuul做出了很大的修改,同样采用的Netty的方式进行通讯,大大的提高了性能,降低了延迟。下面是Zuul2.0官网给出的运行流程图:
下面是某个最受我们欢迎的大公司使用zuul的感言(时间过得好快,德玛西亚已经陪伴了我7年了)