Zuul 2 — Sample Filter
Zuul is a gateway that provides an entry point to an ecosystem of microservices. Learn how to write a custom Zuul 2 filter.
Join the DZone community and get the full member experience.
Join For FreeZuul 2 is finally open-source. I first heard of Zuul 2 during the Spring One 2016 talk by Mikey Cohen which is available here — it is good to finally be able to play with it.
To quickly touch on the purpose of a Gateway like Zuul 2, Gateways provide an entry point to an ecosystem of microservices. Since all the customer requests are routed through the Gateway, it can control aspects of routing, request, and response flowing through it:
- Routing based on different criteria - URI patterns, headers, etc.
- Monitors service health
- Load balancing and throttling requests to origin servers
- Security
- Canary testing
My objective in this post is simple — to write a Zuul2 filter that can remove a path prefix and send a request to a downstream service and back.
Zuul2 filters are the mechanism by which Zuul is customized. Say if a client sends a request to /passthrough/someapi call, then I want the Zuul 2 layer to forward the request to a downstream service using /someapi uri. Zuul2 filters are typically packaged up as groovy files and are dynamically loaded(and potentially refreshed) and applied. My sample here will be a little different though, my filters are coded in Java and I had to bypass the loading mechanism built into Zuul.
It may be easier simply to follow the code, which is available in my GitHub repository here; it is packaged with a set of samples which provide a similar functionality. The code is based on the Zuul 2 samples available here.
This is how my filter looks:
import com.netflix.zuul.context.SessionContext;
import com.netflix.zuul.filters.http.HttpInboundSyncFilter;
import com.netflix.zuul.message.http.HttpRequestMessage;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class StripPrefixFilter extends HttpInboundSyncFilter {
private final List<String> prefixPatterns;
public StripPrefixFilter(List<String> prefixPatterns) {
this.prefixPatterns = prefixPatterns;
}
@Override
public HttpRequestMessage apply(HttpRequestMessage input) {
SessionContext context = input.getContext();
String path = input.getPath();
String[] parts = path.split("/");
if (parts.length > 0) {
String targetPath = Arrays.stream(parts)
.skip(1).collect(Collectors.joining("/"));
context.set("overrideURI", targetPath);
}
return input;
}
@Override
public int filterOrder() {
return 501;
}
@Override
public boolean shouldFilter(HttpRequestMessage msg) {
for (String target: prefixPatterns) {
if (msg.getPath().matches(target)) {
return true;
}
}
return false;
}
}
It extends "HttpInboundSyncFilter," filters which handle the request inbound to origin servers. As you can imagine there is a "HttpOutboundSyncFilter" which intercept calls outbound from the origin servers. There is a "HttpInboundFilter" and "HttpOutboundFilter" counterpart to these "sync" filters, they return RxJavaObservable type.
There is a magic string "overrideUri" in my filter implementation. If you are curious about how I found that to be the override URI, it is by scanning through the Zuul2 codebase. There is likely a lot of filters used internally at Netflix which haven't been released for general consumption yet.
With this filter in place, I have bypassed the dynamic groovy scripts loading feature of Zuul2 by explicitly registering my custom filter using this component:
import com.netflix.zuul.filters.FilterRegistry;
import com.netflix.zuul.filters.ZuulFilter;
import javax.annotation.PostConstruct;
import javax.inject.Inject;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
public class FiltersRegisteringService {
private final List<ZuulFilter> filters;
private final FilterRegistry filterRegistry;
@Inject
public FiltersRegisteringService(FilterRegistry filterRegistry, Set<ZuulFilter> filters) {
this.filters = new ArrayList<>(filters);
this.filterRegistry = filterRegistry;
}
public List<ZuulFilter> getFilters() {
return filters;
}
@PostConstruct
public void initialize() {
for (ZuulFilter filter: filters) {
this.filterRegistry.put(filter.filterName(), filter);
}
}
}
I had to make a few more minor tweaks to get this entire set-up with my custom filter bootstrapped, these can be followed in the GitHub repo.
Once the Zuul2 sample with this custom filter is started up, the behavior is that any request to /passthrough/messages is routed to a downstream system after the prefix "/passthrough" is stipped out. The instructions to start-up the Zuul 2 app is part of the README of the repo.
This concludes a quick intro to writing a custom Zuul2 filter, I hope this gives just enough of a feel to evaluate Zuul 2.
Published at DZone with permission of Biju Kunjummen, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments