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
Route
that represents a mapping between a request and its final destination. Essentially, aRoute
is a collection of predicates applied on a request. Once an aggregate predicate of aRoute
is 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
Route
and an endpoint (aRoute
can have a filter that changes the request path to different values based on some condition, for example), in this application eachRoute
is 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/v1
and some discovered service has endpointGET /example
, a route matchingGET /api/v1/example
will be built. Once it happens, this Gateway upon receiving a requestGET /api/v1/example
will 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 /example
andGET /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/example
was discovered, the routeGET <versionPrefix>/example
will 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
– timeoutDuration
used 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-compose
file
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:EurekaDiscoverableApplication
which is a simplistic wrapper aroundcom.netflix.discovery.shared.Application
-
DocumentedApplication
– application that publishes its API documentation. It holds a reference to the associatedEurekaDiscoverableApplication
object. Implementation:SwaggerApplication
-
DocumentedEndpoint
– endpoint exposed by aDocumentedApplication
, that is described in its API documentation. It holds a reference to theDocumentedApplication
that 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
-
ApplicationCollector
finds aDiscoverableApplication
and applies itsApplicationSieve
s on it (which are in effectPredicate<DiscoverableApplication>
). If the application passes through,ApplicationCollector
collects it -
EndpointCollector
tries to fetch API documentation of the application collected byApplicationCollector
. If it succeeds, it wraps the application and the doc in aDocumentedApplication
instance, gets itsDocumentedEndpoint
s, appliesEndpointSieve
s on each of them, and collects all retained ones -
DynamicRouteLocator
creates a route from each endpoint collected byEndpointCollector
by applying itsEndpointRouteProcessor
s on a freshRoute
builder before callingbuild()
on it. The job of aEndpointRouteProcessor
is to take a route builder along with aDocumentedEndpoint
and return a potentially mutated builder back to the caller. For example, anEndpointRouteProcessor
may get the endpoint's HTTP method and then add a predicate matching that method to the builder before returning it
RouteLocator, which
DynamicRouteLocator
implements, is another key abstraction of Spring Cloud Gateway. It's a functional interface that returns aFlux<Route>
ongetRoutes()
invocations. An application may have multiple registeredRouteLocator
s, 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
ApplicationCollector
removes the application from its collectionEndpoingCollector
removes all endpoints owned by the lost application from its collectionDynamicRouteLocator
removes all routes whose URI's hosts match the lost app's name. For instance, ifSOME-APP
is lost, all routes with URIlb://SOME-APP
will be evicted (assuming the lost app was an instance ofEurekaDiscoverableApplication
which useslb://
schemes)
Out-of-the-box implementations of these three key types – ApplicationCollector
, EndpointCollector
, and DynamicRouteLocator
– relay ApplicationEvent
s 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 AuthenticationExtractor
s 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