Dynamic Gateway is a Gateway pattern implementation that dynamically routes incoming requests to endpoints exposed by other applications connected to the same service discovery server. As services become available, Dynamic Gateway automatically fetches their API documentation (if it's accessible) and builds a Route for each* exposed endpoint providing users with a single point of entry. By the same token, if a service goes down, this Gateway will remove any Route associated with that service at the next refresh cycle. Such changes don't require a restart of this Gateway which dynamically updates its API in runtime
Route. This Gateway implementation largely relies on Spring Framework and Spring Cloud Gateway in particular. One of its key abstractions is a so-called
Routethat represents a mapping between a request and its final destination. Essentially, aRouteis a collection of predicates applied on a request. Once an aggregate predicate of aRouteis satisfied by some request, thatRoute's filters get applied to it – potentially mutating or replacing it with another request – which is then (typically) redirected elsewhereThough technically there's no one-to-one relationship between a
Routeand an endpoint (aRoutecan have a filter that changes the request path to different values based on some condition, for example), in this application eachRouteis associated with exactly one endpoint.Whenever the word "route" is used in this README, it refers to an instance of
Route
* It's possible to filter out discovered endpoints preventing them from becoming part of this Gateway's API, see EndpointSieve
Here is an overview of properties specific to this application
-
gateway.versionPrefix– a prefix that will be appended to an endpoint's path when building a route. It is assumed by this application that such a prefix would specify the API version hence the name. For example, if it's set to/api/v1and some discovered service has endpointGET /example, a route matchingGET /api/v1/examplewill be built. Once it happens, this Gateway upon receiving a requestGET /api/v1/examplewill route it to the service'sGET /example. Defaults to an empty string -
gateway.publicPatterns– a list of Ant-style path patterns that specifies endpoints not requiring an authenticated user. Defaults to a list of/{random UUID}which in effect doesn't match any request path -
gateway.ignoredPatterns– a list of Ant path patterns which specifies endpoints that shouldn't be mapped to this Gateway's routes. For example, if a service exposes endpointsGET /exampleandGET /error, and the list of ignored patterns includes/error/**, only one Route will be built, the one that routes toGET /example. Defaults to an empty unmodifiable list -
gateway.ignoredPrefixes– a list of endpoint prefixes that should be ignored when building routes. For example, if the list includes/auth, and the endpointGET /auth/examplewas discovered, the routeGET <versionPrefix>/examplewill be built (notGET <versionPrefix>/auth/example). Each item on the list must be a single path segment prepended by a forward slash. Prefixes that include more than one segment, for example/segment-one/segment-two, are not guaranteed to be taken into account at all. Defaults to an empty unmodifiable list -
gateway.servers– a list of this Gateway's servers. This property is mainly for Swagger UI's dropdown menu. Defaults to a list ofhttp://localhost:{server-port} -
gateway.timeout– timeoutDurationused by Dynamic Gateway's circuit breaker. If a service doesn't respond in the specified period of time, Dynamic Gateway will return a default fallback message. Defaults to five seconds
The properties are encapsulated by the GatewayMeta class
Here's the easiest way to launch this application
- Clone it
git clone https://github.com/NadChel/dynamic-gateway
- Locate to the project root
cd dynamic-gateway
- Execute the
docker-composefile
docker-compose up
The docker-compose file comprises images of the following applications:
- Dynamic Gateway
- an Eureka server (Docker Hub)
- Token Service, a no-frills authentication server (Docker Hub)
- a Postgres server, Token Service's data store (Docker Hub)
- Hello World Service, a simple REST service (Docker Hub)
You may choose to run only some of those services by explicitly passing them as arguments. For example, if you want to run only Dynamic Gateway and the Eureka server, you may execute the following command:
docker-compose up gateway eureka
If you want Dynamic Gateway to pick up endpoints of your own service, it should meet these two requirements (unless you choose to reconfigure Dynamic Gateway):
- It should provide its Open API at
GET /v3/api-docs - It should be connected to the same Eureka server as this Gateway
All services referenced in the docker-compose file, including Dynamic Gateway, expose their API via Swagger UI at /swagger-ui
To have a better understanding of this application, it's important to know its fundamental concepts
-
DiscoverableApplication– application that could be discovered via a service discovery mechanism such as Netflix Eureka. Implementation:EurekaDiscoverableApplicationwhich is a simplistic wrapper aroundcom.netflix.discovery.shared.Application -
DocumentedApplication– application that publishes its API documentation. It holds a reference to the associatedEurekaDiscoverableApplicationobject. Implementation:SwaggerApplication -
DocumentedEndpoint– endpoint exposed by aDocumentedApplication, that is described in its API documentation. It holds a reference to theDocumentedApplicationthat declares it. Implementation:SwaggerEndpoint -
EndpointDetails– actual endpoint information such as the method and the request path. Implementation:SwaggerEndpointDetails
Here's a high-level overview of the typical flow of this application
-
ApplicationCollectorfinds aDiscoverableApplicationand applies itsApplicationSieves on it (which are in effectPredicate<DiscoverableApplication>). If the application passes through,ApplicationCollectorcollects it -
EndpointCollectortries to fetch API documentation of the application collected byApplicationCollector. If it succeeds, it wraps the application and the doc in aDocumentedApplicationinstance, gets itsDocumentedEndpoints, appliesEndpointSieves on each of them, and collects all retained ones -
DynamicRouteLocatorcreates a route from each endpoint collected byEndpointCollectorby applying itsEndpointRouteProcessors on a freshRoutebuilder before callingbuild()on it. The job of aEndpointRouteProcessoris to take a route builder along with aDocumentedEndpointand return a potentially mutated builder back to the caller. For example, anEndpointRouteProcessormay get the endpoint's HTTP method and then add a predicate matching that method to the builder before returning it
RouteLocator, which
DynamicRouteLocatorimplements, is another key abstraction of Spring Cloud Gateway. It's a functional interface that returns aFlux<Route>ongetRoutes()invocations. An application may have multiple registeredRouteLocators, though Dynamic Gateway has only one
Here's what happens when a DiscoverableApplication goes down. In the case of EurekaDiscoverableApplication it means an application that was once in a Eureka client cache is no longer there
ApplicationCollectorremoves the application from its collectionEndpoingCollectorremoves all endpoints owned by the lost application from its collectionDynamicRouteLocatorremoves all routes whose URI's hosts match the lost app's name. For instance, ifSOME-APPis lost, all routes with URIlb://SOME-APPwill be evicted (assuming the lost app was an instance ofEurekaDiscoverableApplicationwhich useslb://schemes)
Out-of-the-box implementations of these three key types – ApplicationCollector, EndpointCollector, and DynamicRouteLocator – relay ApplicationEvents to communicate that information between each other
This gateway assumes the existence of an external authentication server and, as is, only extracts user claims contained in the request's JSON Web Token without authenticating them. It validates the token by checking its signing key
The two most important types involved in this process are AuthenticationExtractionWebFilter and AuthenticationExtractor. AuthenticationExtractionWebFilter is a WebFilter that extracts authentication claims and puts them in the ReactiveSecurityContextHolder. The filter delegates the extraction to the injected AuthenticationExtractor. By default, it's an instance of CompositeAuthenticationExtractor that has a collection of other AuthenticationExtractors and delegates the actual extraction to the first of them that may extract claims from a given exchange
If you chose to use your own JWTs instead of the ones issued by Token Service, make sure the tokens are signed with a key this gateway expects
You may also create your own AuthenticationExtractor implementation and register it in the Spring context. For example, if you want to add support for other authentication schemes, other than bearer, you may implement AuthorizationHeaderAuthenticationExtractor and override the tryExtractAuthentication(AuthorizationHeader) and isSupportedAuthorizationHeader(AuthorizationHeader) methods. AuthorizationHeader is a simple data class that encapsulates the header's scheme and credentials and exposes convenient accessors for those properties
See the Javadocs for more information