Zuul, official website:
Zuul is the front door for all requests from devices and web sites to the backend of the Netflix streaming application. As an edge service application, Zuul is built to enable dynamic routing, monitoring, resiliency and security.
Zuul acts as the Api gateway front-end controller, the entry point for all requests, with features such as dynamic routing, monitoring, resilience (limiting traffic) and security.
This article is based on the 1.x version
Request process:
How does that work?
Let’s start with a simple example of a test
Create registry Eureka-Server,Api gateway service service-zuul, and service provider service-hi respectively. Service-zuul will register with eureka-server when it is started. We request service-zuul service and service-Zuul will forward the request to service-Hi, and service-Hi will output the result.
Service-zuul configuration is as follows:
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/
server:
port: 8769
spring:
application:
name: service-zuul
zuul:
routes:
api-a:
path: /api-a/**
serviceId: service-ribbon
api-b:
path: /api-b/**
serviceId: service-hi
Copy the code
Service-zuul adds MyFilter as test:
@Component
public class MyFilter extends ZuulFilter {
private static Logger log = LoggerFactory.getLogger(MyFilter.class);
@Override
public String filterType() {
return "pre";
}
@Override
public int filterOrder() {
return 0;
}
@Override
public boolean shouldFilter() {
return true;
}
@Override
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
log.info(String.format("%s >>> %s", request.getMethod(), request.getRequestURL().toString()));
Object accessToken = request.getParameter("token");
if(accessToken == null) {
log.warn("token is empty");
ctx.setSendZuulResponse(false);
ctx.setResponseStatusCode(401);
try {
ctx.getResponse().getWriter().write("token is empty");
}catch (Exception e){}
return null;
}
log.info("ok");
returnnull; }}Copy the code
The Controller of service-hi is as follows:
@SpringBootApplication
@EnableEurekaClient
@EnableDiscoveryClient
@RestController
public class ServiceHiApplication {
public static void main(String[] args) {
SpringApplication.run( ServiceHiApplication.class, args );
}
@RequestMapping("/hi")
public String home(@RequestParam(value = "name", defaultValue = "xxx") String name) {
return "hi " + name"; }}Copy the code
Start request test:
The browser open http://localhost:8769/api-b/hi? name=x&token=”998″
Break the point, step by step.
Springboot integrates Zuul and automatically loads the following configuration classes at startup:
@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 // FIXME @Import(ServerPropertiesAutoConfiguration.class) public class ZuulServerAutoConfiguration {/ / omitted code... @bean public ZuulHandlerMapping ZuulHandlerMapping (RouteLocator routes) { ZuulHandlerMapping mapping = new ZuulHandlerMapping(routes, zuulController()); mapping.setErrorController(this.errorController);returnmapping; }... @Configuration protected static class ZuulFilterConfiguration { @Autowired private Map<String, ZuulFilter> filters; @bean public ZuulFilterInitializer ZuulFilterInitializer (CounterFactory CounterFactory, TracerFactory tracerFactory) { FilterLoader filterLoader = FilterLoader.getInstance(); //FilterRegistry singletons are used to hold filters, including requests that are also wrapped as a filter FilterRegistry FilterRegistry = filterregistry.instance ();returnnew ZuulFilterInitializer(this.filters, counterFactory, tracerFactory, filterLoader, filterRegistry); }}... }Copy the code
/**
* Initializes various Zuul components including {@link ZuulFilter}.
*
* @author Spencer Gibb
*
*/
public class ZuulFilterInitializer {
private static final Log log = LogFactory.getLog(ZuulFilterInitializer.class);
private final Map<String, ZuulFilter> filters;
private final CounterFactory counterFactory;
private final TracerFactory tracerFactory;
private final FilterLoader filterLoader;
private final FilterRegistry filterRegistry;
public ZuulFilterInitializer(Map<String, ZuulFilter> filters,
CounterFactory counterFactory,
TracerFactory tracerFactory,
FilterLoader filterLoader,
FilterRegistry filterRegistry) {
this.filters = filters;
this.counterFactory = counterFactory;
this.tracerFactory = tracerFactory;
this.filterLoader = filterLoader;
this.filterRegistry = filterRegistry;
}
@PostConstruct
public void contextInitialized() {
log.info("Starting filter initializer");
TracerFactory.initialize(tracerFactory);
CounterFactory.initialize(counterFactory);
for(Map.Entry<String, ZuulFilter> entry : This.filters.entryset ()) {// Save the operation filterregistry.put (entry.getkey (), entry.getValue()); }}... }Copy the code
public class FilterRegistry {
private static final FilterRegistry INSTANCE = new FilterRegistry();
public static final FilterRegistry instance() {
return INSTANCE;
}
private final ConcurrentHashMap<String, ZuulFilter> filters = new ConcurrentHashMap<String, ZuulFilter>();
private FilterRegistry() {
}
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() {
returnthis.filters.size(); } / /"Route"Public Collection<ZuulFilter>getAllFilters() {
returnthis.filters.values(); }}Copy the code
ZuulServerAutoConfiguration generates ZuulHandlerMapping corresponding Bean, DispatcherServlet initialization, call initHandlerMappings loaded into handlerMappings.
DispatcherServlet class
/** List of HandlerMappings used by this servlet */ private List<HandlerMapping> handlerMappings; . /** * Initialize the HandlerMappings used by this class. * <p>If no HandlerMapping beans are definedin the BeanFactory for this namespace,
* we default to BeanNameUrlHandlerMapping.
*/
private void initHandlerMappings(ApplicationContext context) {
this.handlerMappings = null;
if (this.detectAllHandlerMappings) {
// Find all HandlerMappings in the ApplicationContext, including ancestor contexts.
Map<String, HandlerMapping> matchingBeans =
BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true.false);
if(! matchingBeans.isEmpty()) { this.handlerMappings = new ArrayList<HandlerMapping>(matchingBeans.values()); // We keep HandlerMappingsinsorted order. AnnotationAwareOrderComparator.sort(this.handlerMappings); }}else {
try {
HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
this.handlerMappings = Collections.singletonList(hm);
}
catch (NoSuchBeanDefinitionException ex) {
// #Ignore, we'll add a default HandlerMapping later.
}
}
// Ensure we have at least one HandlerMapping, by registering
// a default HandlerMapping if no other mappings are found.
if (this.handlerMappings == null) {
this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
if (logger.isDebugEnabled()) {
logger.debug("No HandlerMappings found in servlet '" + getServletName() + "': using default"); }}}Copy the code
HandlerAdapters similarly.
According to SpringMVC, requests are distributed by a DispatcherServlet.
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception { HttpServletRequest processedRequest = request; HandlerExecutionChain mappedHandler = null; boolean multipartRequestParsed =false; WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request); try { ModelAndView mv = null; Exception dispatchException = null; try { processedRequest = checkMultipart(request); multipartRequestParsed = (processedRequest ! = request); // Determine handlerforMappedHandler = getHandler(processedRequest);if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
}
// Determine handler adapter forHandlerAdapter ha = getHandlerAdapter(mappedHandler.gethandler ()); // Process last-modified header,if supported by the handler.
String method = request.getMethod();
boolean isGet = "GET".equals(method);
if (isGet || "HEAD".equals(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (logger.isDebugEnabled()) {
logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);
}
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return; }}if(! mappedHandler.applyPreHandle(processedRequest, response)) {return; } // Actually invoke the handler. //3. Call mv = ha.handle(processedRequest, response, mappedHandler.gethandler ());if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
applyDefaultViewName(processedRequest, mv);
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
catch (Exception ex) {
dispatchException = ex;
}
catch (Throwable err) {
// Thrown from handler methods As well,
// making them available for @ExceptionHandler methods and other scenarios.
dispatchException = new NestedServletException("Handler dispatch failed", err);
}
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
catch (Exception ex) {
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
}
catch (Throwable err) {
triggerAfterCompletion(processedRequest, response, mappedHandler,
new NestedServletException("Handler processing failed", err));
}
finally {
if (asyncManager.isConcurrentHandlingStarted()) {
// Instead of postHandle and afterCompletion
if (mappedHandler != null) {
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
}
else {
// Clean up any resources used by a multipart request.
if(multipartRequestParsed) { cleanupMultipart(processedRequest); }}}}Copy the code
/**
* Return the HandlerExecutionChain for this request.
* <p>Tries all handler mappings in order.
* @param request current HTTP request
* @return the HandlerExecutionChain, or {@code null} if no handler could be found
*/
@Nullable
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
if(this.handlerMappings ! = null) {for (HandlerMapping hm : this.handlerMappings) {
if (logger.isTraceEnabled()) {
logger.trace(
"Testing handler map [" + hm + "] in DispatcherServlet with name '" + getServletName() + "'"); } //hm is ZuulHandlerMapping HandlerExecutionChain Handler = hm.getHandler(request);if(handler ! = null) {returnhandler; }}}return null;
}
Copy the code
Take a look at the data structure of the mappedHandler and HandlerAdapter runtime in the doDispatch method
public class ZuulController extends ServletWrappingController {
public ZuulController() {
setServletClass(ZuulServlet.class);
setServletName("zuul");
setSupportedMethods((String[]) null); // Allow all
}
@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
returnsuper.handleRequestInternal(request, response); } finally { // @see com.netflix.zuul.context.ContextLifecycleFilter.doFilter RequestContext.getCurrentContext().unset(); }}}Copy the code
Moving on to the ZuulServlet highlight method:
/**
* Core Zuul servlet which intializes and orchestrates zuulFilter execution
*
* @author Mikey Cohen
* Date: 12/23/11
* Time: 10:44 AM
*/
public class ZuulServlet extends HttpServlet {
...
@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 whichrequests will not have the same data attached RequestContext context = RequestContext.getCurrentContext(); context.setZuulEngineRan(); Try {// 1. PreRoute () is executed preRoute() because MyFilter is pre; } catch (ZuulException e) { error(e); postRoute();return; } try {//2. Perform the actual service forwarding, encapsulating it as a filter advantage? route(); } catch (ZuulException e) { error(e); postRoute();return; } try {//3. PostRoute (); } catch (ZuulException e) { error(e);return;
}
} catch (Throwable e) {
error(new ZuulException(e, 500, "UNHANDLED_EXCEPTION_"+ e.getClass().getName())); } finally { RequestContext.getCurrentContext().unset(); }}... }Copy the code
1. Pre-filter
There are five default filters, MyFilter is a custom filter
INFO 84524 --- [io-8769-exec-10] com.forezp.servicezuul.filter.MyFilter : GET >>> http://localhost:8769/api-b/hi
Copy the code
Process:
PreRoute () for ZuulServlet encapsulates preRoute() for ZuulRunner class
/**
* executes "pre" filters
*
* @throws ZuulException
*/
void preRoute() throws ZuulException {
zuulRunner.preRoute();
}
Copy the code
ZuulRunner class
/** * This class initializes servlet requests and responses into the RequestContext and wraps the FilterProcessor calls * to preRoute(), route(), postRoute(), And error() methods // This class initializes servlet requests and responses into the request context singleton and wraps a series of calling methods * * @author for FilterProcessor [email protected] * @version 1.0 */ public class ZuulRunner {... /** * 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(); }... }Copy the code
The FilterProcessor class: runFilters() -> Traverse the Filters -> execute processZuulFilter one by one, and finally execute the runFilter of ZuulServlet and return the ZuulFilterResult object
/**
* This the the core class to execute filters.
*/
public class FilterProcessor {
...
/**
* runs all "pre" filters. These filters are run before routing to the orgin.
*
* @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 "route" filters. These filters route calls to an origin.
*
* @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 filters of the filterType sType/ Use this method within filters to run custom filters bytype
*
* @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; / / FilterLoader getInstance () to obtain the singleton, find out corresponding filters List < ZuulFilter > List = FilterLoader. GetInstance (). GetFiltersByType (sType);if(list ! = null) {for(int i = 0; i < list.size(); i++) { ZuulFilter zuulFilter = list.get(i); RibbonRoutingFilter Object result = processZuulFilter(zuulFilter);if(result ! = null && result instanceof Boolean) { bResult |= ((Boolean) result); }}}returnbResult; }... /** * 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. * * @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();
}
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);
}
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; }}}Copy the code
ZuulServlet’s runFilter() method
/** * runFilter checks ! isFilterDisabled() and shouldFilter(). The run() method is invokedif both are true.
*
* @return the return from ZuulFilterResult
*/
public ZuulFilterResult runFilter() {
ZuulFilterResult zr = new ZuulFilterResult();
if(! isFilterDisabled()) {if (shouldFilter()) {
Tracer t = TracerFactory.instance().startMicroTracer("ZUUL::"+ this.getClass().getSimpleName()); // Run () 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;
}
Copy the code
At this point, preRoute filters are done.
2. Request forwarding process:
Start with the Route() method of ZuulServlet, the same steps except that the filter type is “Route” and the filter is RibbonRoutingFilter. The other two are skipped and configurable
@Override
public Object run() {
RequestContext context = RequestContext.getCurrentContext();
this.helper.addIgnoredHeaders();
try {
RibbonCommandContext commandContext = buildCommandContext(context); // HTTP request forwarding ClientHttpResponse response = forward(commandContext);
setResponse(response);
returnresponse; } catch (ZuulException ex) { throw new ZuulRuntimeException(ex); } catch (Exception ex) { throw new ZuulRuntimeException(ex); }}Copy the code
protected ClientHttpResponse forward(RibbonCommandContext context) throws Exception {
Map<String, Object> info = this.helper.debug(context.getMethod(),
context.getUri(), context.getHeaders(), context.getParams(),
context.getRequestEntity());
RibbonCommand command = this.ribbonCommandFactory.create(context);
try {
ClientHttpResponse response = command.execute();
this.helper.appendDebug(info, response.getRawStatusCode(), response.getHeaders());
return response;
}
catch (HystrixRuntimeException ex) {
returnhandleException(info, ex); }}Copy the code
/**
* Used for synchronous execution of command.
*
* @return R
* Result of {@link #run()} execution or a fallback from {@link #getFallback()} if the command fails for any reason.
* @throws HystrixRuntimeException
* if a failure occurs and a fallback cannot be retrieved
* @throws HystrixBadRequestException
* if invalid arguments or state were used representing a user failure, not a system failure
* @throws IllegalStateException
* if invoked more than once
*/
public R execute() {
try {
returnqueue().get(); } catch (Exception e) { throw Exceptions.sneakyThrow(decomposeException(e)); }}Copy the code
3. PostRoute System filter
@Override
public Object run() {
try {
addResponseHeaders();
writeResponse();
}
catch (Exception ex) {
ReflectionUtils.rethrowRuntimeException(ex);
}
return null;
}
Copy the code
The request ends with: hi x
Summary: Zuul, service request processing is divided into three stages: Pre, Route and post. The request forwarding is performed in the Route stage through the RibbonRoutingFilter class.