Skip to content

zuul simple webapp

Charles Chan edited this page Feb 17, 2015 · 16 revisions

Running zuul-simple-webapp

zuul-simple-webapp is an example web application that shows a few simple use cases of zuul-core.

The best way to understand it is to load it into your environment, run it, modify it, then run it again to notice the changes.

On a Mac or unix-like environment, cd into the zuul-simple-webapp directory and run this command:

../gradlew jettyRun

After compilation you will see a message like this:

> Building > :zuul-simple-webapp:jettyRun > Running at http://localhost:8080//

By default zuul-simple-webapp uses apache.org as the origin. You can verify this by accessing http://localhost:8080 in a browser.

Examining zuul-simple-webapp

In build.gradle we set the zuul.filter.root system property to src/main/filters in order to make it work.

zuul-simple-webapp/src/main/groovy/filters is a standard layout for Zuul Filters, containing pre, route, and post directories for each primary Filter type, respectively.

web.xml declarations

web.xml configures a few things:

  • StartServer as a ServletContextListener that initializes the app.
  • ZuulServlet is a servlet that matches all requests. It performs the core Zuul Filter flow of executing pre, routing, and post Filters.
  • ContextLifecycleFilter is a servlet filter matching all requests. It cleans up the RequestContext after each request, ensuring isolation.
<listener>
    <listener-class>com.netflix.zuul.StartServer</listener-class>
</listener>

<servlet>
    <servlet-name>Zuul</servlet-name>
    <servlet-class>com.netflix.zuul.http.ZuulServlet</servlet-class>
</servlet>

<servlet-mapping>
    <servlet-name>Zuul</servlet-name>
    <url-pattern>/*</url-pattern>
</servlet-mapping>

<filter>
    <filter-name>ContextLifecycleFilter</filter-name>
    <filter-class>com.netflix.zuul.context.ContextLifecycleFilter</filter-class>
</filter>
<filter-mapping>
    <filter-name>ContextLifecycleFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

StartServer initialization

StartServer first sets the FilterLoader's compiler as a GroovyCompiler instance.

FilterLoader.getInstance().setCompiler(new GroovyCompiler());

The compiler can be set to any implementation of DynamicCodeCompiler that is capable of converting Files or named code snippets into Class instances.

We then initialize the FilterFileManager to look for Groovy files on the filesystem in the specified pre, routing, and post directories every 5 seconds.

final String scriptRoot = System.getProperty("zuul.filter.root");
try {
    FilterFileManager.setFilenameFilter(new GroovyFileFilter());
    FilterFileManager.init(5,
            scriptRoot + "/pre",
            scriptRoot + "/route",
            scriptRoot + "/post");
} catch (Exception e) {
    throw new RuntimeException(e);
}

zuul-simple-webapp Filter Execution Flow

1. Debug executes with type "pre" and filterOrder 1

shouldFilter() evaluates to true since the DynamicBooleanProperty routingDebug defaults to true. If routingDebug was changed to default to false it could be enabled by passing the HTTP parameter "d=true".

boolean shouldFilter() {
    if ("true".equals(RequestContext.getCurrentContext().getRequest().getParameter(debugParameter.get()))) return true;
    return routingDebug.get();
}

run() enables Zuul route and request debugging:

Object run() {
    RequestContext ctx = RequestContext.getCurrentContext()
    ctx.setDebugRouting(true)
    ctx.setDebugRequest(true)
    return null;
}

2. PreDecoration executes with type "pre" and filterOrder 5

shouldFilter() is always true:

boolean shouldFilter() {
    return true;
}

run() sets the origin host (via RequestContext.setRouteHost() and adds a Cache-Control header to pass along (via RequestContext.addZuulRequestHeader()

Object run() {
    RequestContext ctx = RequestContext.getCurrentContext()

    // sets origin
    ctx.setRouteHost(new URL("http://apache.org/"));

    // sets custom header to send to the origin
    ctx.addOriginRequestHeader("cache-control", "max-age=3600");
}

3. DebugRequest executes with type "pre" and filterOrder 10000

shouldFilter() evaluates to true (since [Debug.debugRequest()](http://netflix.github.io/zuul/javadoc/zuul-core/com/netflix/zuul/context/Debug.html#debugRequest(\)) was set to true by [DebugFilter][]:

boolean shouldFilter() {
    return Debug.debugRequest()
}

run() adds request debug information via [Debug.addRequestDebug()](http://netflix.github.io/zuul/javadoc/zuul-core/com/netflix/zuul/context/Debug.html#addRequestDebug(java.lang.String\))

Object run() {
    HttpServletRequest req = RequestContext.currentContext.request as HttpServletRequest

    Debug.addRequestDebug("REQUEST:: " + req.getScheme() + " " + req.getRemoteAddr() + ":" + req.getRemotePort())
    Debug.addRequestDebug("REQUEST:: > " + req.getMethod() + " " + req.getRequestURI() + " " + req.getProtocol())
    
   // and so on
}

4. javaPreFilter executes with type "pre" and filterOrder 50000

This filter doesn't do anything of value - it serves as an example of a pure Java filter that can even be registered as an anonymous inner class.

5. SimpleHostRequest executes with type "routing" and filterOrder 100

shouldFilter evaluates to true since the routeHost was set by [PreDecorationFilter][] and sendZuulResponse defaults to true:

boolean shouldFilter() {
    return RequestContext.currentContext.getRouteHost() != null && RequestContext.currentContext.sendZuulResponse()
}

6. SendResponse executes with type "post" and filterOrder 1000

shouldFilter evaluates to true since zuulResponseHeaders, responseDataStream and responseBody were set by SimpleHostRequest.

boolean shouldFilter() {
    return !RequestContext.currentContext.getZuulResponseHeaders().isEmpty() ||
            RequestContext.currentContext.getResponseDataStream() != null ||
            RequestContext.currentContext.responseBody != null
}

run copies the origin response into Zuul's response - gruesome details omitted for brevity:

Object run() {
    addResponseHeaders()
    writeResponse()
}

7. Stats executes with type "post" and filterOrder 2000

shouldFilter is hardcoded to true as we always want to log the stats if they exist.

run logs the routing and request stats to stout:

Object run() {
    dumpRoutingDebug()
    dumpRequestDebug()
}

8. javaPostFilter executes with type "post" and filterOrder 50000

This filter doesn't do anything of value - it serves as an example of a pure Java filter that can even be registered as an anonymous inner class.