Creating a Cloud Foundry Route Service with Spring Cloud Gateway

While traditional services (things like databases, message queues, search indexes) are widely used and marketed within the Cloud Foundry ecosystem, there are two other kinds of services that you might not be aware of. Volume services, which allow you to make a persistent mounted filesystem available to your application, and route services, which allow you to intercept all HTTP traffic that hits your applications.

Route Services

Route services can be used for many purposes, such as enhanced logging, rate limiting, and handling authentication/authorization without the application needing to be aware of it.

First, let’s take a moment to review how route services work. When a route service is bound to an application, the Gorouter will intercept all requests to the applications routes, append a few HTTP headers with metadata and a signature to protect the integrity of the request, and proxy the request to the route service URL. The route service is then able to do whatever processing it needs to do, and then either forward the request on to the original URL (or perhaps some modified, URL, or perhaps just return an error response). All of this can be reviewed in the route services documentation.

Simple Logging Service

In this post, we’ll learn how to build a simple route service using Spring Cloud Gateway, a new library from the Spring Cloud team for building API Gateways. It will be a very simple service, which will just log details about the incoming request, then forward it on to the original URL (somewhat similar to the Spring Boot example in the route service documentation).

Creating The Project

The first step, as always, is to create a new project from start.spring.io. You’ll want to make sure that you select the 2.x line of spring boot (2.0.0 RC1 at time of writing), and include the Gateway and Reactive Web dependencies. In this example I’ll be using Gradle out of personal preference, but Maven will work fine too.

The Route Predicate

Next, we need to set up a gateway route which will process route service requests. As mentioned in the Headers section of the route services documentation, there are 3 headers that will identify a route service request:

  • X-CF-Forwarded-Url contains the original request URL
  • X-CF-Proxy-Signature contains an encrypted token to validate the request
  • X-CF-Proxy-Metadata contains information to aid in decrypting and validating X-CF-Proxy-Signature

The Spring Cloud Gateway routing pattern is built around predicates. In this example, we have 3 very simple header based predicates. If a request meets all 3 of these predicates, then we will redirect it to http://google.com:80 (obviously we don’t actually want to do that, we will update the target url with a filter later). We can validate that this works so far with a ./gradlew bootRun and an http client.

$ http localhost:8080 X-CF-Forwarded-Url:whatever.com X-CF-Proxy-Metadata:some-metadata X-CF-Proxy-Signature:some-signature

As you can see, the route service detected the presence of the 3 headers, and forwarded the request to google as expected (then google responded with a 301).

Logging Filter

Next, let’s build a simple filter to log some information about the incoming request.

The call to chain.filter(exchange) at the end indicates that the filter is done processing and should move along to the next filter in the chain, or make the actual request if this is the end of the chain. Next we need to make sure that this filter is called when a route service request is made.

As you can see, we’re adding the logging filter within the filters method. f is GatewayFilterSpec, which contains convenience methods for commonly used filters, such as adding headers, redirecting, or enabling Hystrix.

If we test this again using an http client, we’ll see additional details in our logs:

$ http localhost:8080/some/path?myquery=something X-CF-Forwarded-Url:whatever.com X-CF-Proxy-Metadata:some-metadata X-Cf-Proxy-Signature:some-signature 
2018-02-10 16:25:39.655  INFO 30948 --- [ctor-http-nio-5] c.g.f.s.SimpleLoggingFilter              : Method:GET Host:localhost Path:/some/path QueryParams:{myquery=[something]}

Forwarding to the original URL

Finally, we need to make sure that the request makes it to the original URL, instead of google. We can accomplish this with another filter.

This gets the value of the X-CF-Forwarded-Url header and stores it in a special Spring Cloud Gateway attribute which determines where to send the final request. If something goes wrong, the gateway will stop processing the request and return a 500 error. Note that this is the general pattern you might use to deny access in other kinds of route services. If you need to deny access, you can just set the status code and stop processing the request.

Now to wire the new filter in to the route:

ROUTE_TO_URL_FILTER_ORDER is a constant that determines when the RouteToRequestUrlFilter filter runs. We need to make sure that our filter runs after it, so that it doesn’t overwrite our GATEWAY_REQUEST_URL_ATTR . Also, you may noticed that we switched from f.add to f.filter . This is because the current version of Spring Cloud Gateway doesn’t allow setting an order with the add method.

Once again, we can verify that this works with an http client:

$ http localhost:8080/some/path?myquery=something X-CF-Forwarded-Url:https://reddit.com X-CF-Proxy-Metadata:some-metadata X-Cf-Proxy-Signature:some-signature

Notice that we’re now routing to reddit instead of google, due to the value in the X-CF-Forwarded-Url header.

Deploying

At this point, the route service is ready to deploy. You should be able to build the app (./gradlew build for Gradle users), and follow the tutorial in the route services documentation.

Conclusion

Spring Cloud Gateway is still in its early days, but could be a useful option for java developers who want to be able to apply cross-cutting filters in front of their Cloud Foundry applications. If you’d like to see the final source code of this simple route service, the source code is here. Also, be sure to take a look at the documentation for Spring Cloud Gateway. It includes a ton of useful predicates and filters not mentioned in this tutorial.