Managing multiple routes in Spring Cloud Gateway - spring

I know Spring Cloud Gateway has multiple ways to configure routes:
using a Java-based DSL (eg: using RouteLocatorBuilder) and/or
property based configuration.
The offical Spring Cloud Gateway docs use properties to manage routes.
My questions are:
It is simple to configure routes for 2-3 microservices in a single file, but how do enterprise applications with so many microservices manage routes efficiently?
What is the recommend way to configure routes?
If using Java DSL, is it a good practice to use multiple Beans with the same return type. something like:
#Bean
RouteLocator bookRouteLocator(RouteLocatorBuilder builder) {
return builder.routes()
.route("book_route", r -> r.method(HttpMethod.GET)
.and().path("/api/book/**")
.uri("lb://book-service"))
.build();
}
#Bean
RouteLocator chapterRouteLocator(RouteLocatorBuilder builder) {
return builder.routes()
.route("chapter_route", r -> r.method(HttpMethod.GET)
.and().path("/api/chapter/**")
.uri("lb://chapter-service"))
.build();
}

This is a decently old post but this is something I recently ran into and wanted to share a solution.
Exposing multiple beans will not work - those two RouteLocator instances will collide with each other causing an error or you can allow spring to override the other bean - either way, its not going to work for you.
Instead, expose a Router that references other classes that expose functions that create your routes. Here is an example in Kotlin
Routes.kt
#Configuration
class Routes(
private val purchaseRoute: PurchaseRoute
) {
#Bean
fun router(routeLocatorBuilder: RouteLocatorBuilder): RouteLocator {
return routeLocatorBuilder.routes()
.route("purchase", purchaseRoute.route())
.build()
}
}
Router.kt interface all sub-routes will implement
interface Router {
fun route(): Function<PredicateSpec, Buildable<Route>>
}
PurchaseRoute.kt
#Configuration
class PurchaseRoute() : Router {
override fun route(): Function<PredicateSpec, Buildable<Route>> =
Function {
it.path("/api/purchase/**")
it.filters {
stripPrefix(1)
dedupeResponseHeader("Access-Control-Allow-Origin", "RETAIN_FIRST")
}
it.uri(routeConfigProperties.uri)
}
}
Then just create as many route classes as you like. The separation into multiple files dramatically cleaned up a gateway service I maintain.

Related

What is the recommended way to communicate between IntergrationFlow and services?

I'm looking into spring integration, more specifically the Java DSL.
The DSL is presented by the IntegrationFlows factory for the IntegrationFlowBuilder. This produces the IntegrationFlow component, which should be registered as a Spring bean (by using the #Bean annotation). The builder pattern is used to express arbitrarily complex structures as a hierarchy of methods that can accept lambdas as arguments.
In my mind then, I define a configuration class with a #Bean.
#Bean
fun ftpInboundIntegrationFlow(ftpSf: DefaultFtpSessionFactory?): IntegrationFlow {
val ftpSpecification = Ftp
.inboundStreamingAdapter(template())
.patternFilter("*.csv")
.remoteDirectory(fromFolder)
return IntegrationFlows
.from(ftpSpecification) { pc: SourcePollingChannelAdapterSpec ->
pc.poller { pm: PollerFactory ->
pm.cron(cronSyncExpression)
}
}
.transform(StreamTransformer())
.channel(doSomeBusinessLogic)
.get()
}
But I am rather confused how to, in my more specific service layer, handle this message.
I could, and it seems to work, create another #Bean in the service class.
#Bean
fun handleDoSomeBusinessLogicFlow(): IntegrationFlow {
return IntegrationFlows.from("doSomeBusinessLogic")
.handle { customers: List<SomeDomainModel>, headers: MessageHeaders ->
//Some repository and service logic
}
.get()
I tried looking at examples (https://github.com/spring-projects/spring-integration-samples/tree/833f55d662fb17edda6bcb000d952a3d00aa1dd8). But almost all of them are just creating all the logic in the same place. It feels strange to define a #Bean in the #Service class.
You are right: you can just have a business method in your service and use it from the .handle() instead of channel. You also can just use a #ServiceActivator on that method if you’d prefer loosely coupled distribution via channels. There is also an IntegrationFlowAdapter to have a service incapsulation, but still gain from flow definition.

How to use Protobuf and JSON API simultaneously on SpringBoot?

My project only had Protobuf APIs, but now it has to provide JSON APIs, too.
protobufHttpMessageConverter was applied to all controllers by configuration.
#Configuration
class APIConfig : WebMvcConfigurer {
#Bean
fun protobufHttpMessageConverter(): ProtobufHttpMessageConverter {
val converter = ProtobufHttpMessageConverter()
converter.defaultCharset = StandardCharsets.UTF_8
return converter
}
}
But now it should be applied to some controllers that provide Protobuf APIs, and should not be applied to other controllers that provide JSON APIs.
Does SpringBoot have that kind of flexibility?

Disable automatic registration of WebFilter

In a spring-boot 2.4 application, I have two SecurityWebFilterChains. For only one of them, I want to add some WebFilters via addFilterBefore().
#Configuration
#EnableWebFluxSecurity
class WebSecurityConfig {
#Bean
fun filter1(service: Service): WebFilter = Filter1(service)
#Bean
fun filter2(component: Component): WebFilter = Filter2(component)
#Bean
#Order(1)
fun apiSecurityConfiguration(
http: ServerHttpSecurity,
filter1: WebFilter,
filter2: WebFilter
): SecurityWebFilterChain = http
.securityMatcher(pathMatchers("/path/**"))
.addFilterBefore(filter1, SecurityWebFiltersOrder.AUTHENTICATION)
.addFilterAt(filter2, SecurityWebFiltersOrder.AUTHENTICATION)
.build()
#Bean
#Order(2)
fun actuatorSecurityConfiguration(
http: ServerHttpSecurity,
reactiveAuthenticationManager: ReactiveAuthenticationManager
): SecurityWebFilterChain = http
.securityMatcher(pathMatchers("/manage/**"))
.authenticationManager(reactiveAuthenticationManager)
.httpBasic { }
.build()
}
However, as those WebFilters are created as beans, they are registered automatically and are applied to all requests, seemingly outside the security chain.
For servlet filters, it is possible to disable this registration with a FilterRegistrationBean (see spring-boot documentation).
Is there a similar way for reactive WebFilters, or do I have to add additional URL filtering into those filters?
To find a solution, we first have to dig in a little deeper into how spring works and its internals.
All beans of type WebFilter are automatically added to the main web handling filter chain.
See spring boot documentation on the topic:
WebFilter beans found in the application context will be automatically used to filter each exchange.
So that happens even if you want them to be applied only to a specific spring-security filter chain.
(IMHO, it is a bit of a flaw of spring-security to re-use the Filter or WebFilter interfaces and not have something security-specific with the same signature.)
In code, the relevant part is in spring-web's WebHttpHandlerBuilder
public static WebHttpHandlerBuilder applicationContext(ApplicationContext context) {
// ...
List<WebFilter> webFilters = context
.getBeanProvider(WebFilter.class)
.orderedStream()
.collect(Collectors.toList());
builder.filters(filters -> filters.addAll(webFilters));
// ...
}
Which in turn is called in a spring-boot's HttpHandlerAutoConfiguration to create the main HttpHandler.
#Bean
public HttpHandler httpHandler(ObjectProvider<WebFluxProperties> propsProvider) {
HttpHandler httpHandler = WebHttpHandlerBuilder.applicationContext(this.applicationContext).build();
// ...
return httpHandler;
}
To prevent those filters to be applied to all exchanges, it might be possible to simply not create them as beans and create them manually, as suggested in a comment above. Then the BeanProvider will not find them and not add them to the HttpHandler. However, you leave IoC-country and lose autoconfiguration for the filters. Not ideal when those filters become more complex or when you have a lot of them.
So instead my solution is to manually configure a HttpHandler for my application, which does not add my security-specific filters to the global filter chain.
To make this work, I first declare a marker interface for my filters.
interface NonGlobalFilter
class MySecurityFilter : WebFilter, NonGlobalFilter {
// ...
}
Then, a configuration class is required where the custom HttpHandler is created. Conveniently, WebHttpHandlerBuilder has a method to manipulate its filter list with a Consumer.
This will prevent spring-boot to use its own HttpHandler from HttpHandlerAutoConfiguration because it is annotated with #ConditionalOnMissingBean(HttpHandler.class).
#Configuration
class WebHttpHandlerConfiguration(
private val applicationContext: ApplicationContext
) {
#Bean
fun httpHandler() = WebHttpHandlerBuilder
.applicationContext(applicationContext)
.filters {
it.removeIf {
webFilter -> webFilter is NonGlobalFilter
}
}
.build()
}
And that's it! As always, spring provides a lot of useful defaults out of the box, but when it gets in your way, there will be a means to adjust it as necessary.

How to configure in Spring Boot 2 (w/ WebFlux) two ports for HTTP and HTTPS?

Can anybody tell me how 2 ports (for HTTP and HTTPS) can be configured when using Spring Boot 2 and WebFlux? Any hint is appreciated!
This isn't directly supported by Spring Boot 2 yet.
But, you may be able to get it to work in a couple of ways.
By default, Spring Boot WebFlux uses Netty. If you are already configured for ssl, then Spring Boot will start up and open port 8443 (or whatever you have configured).
Then, to add 8080, you can do:
#Autowired
HttpHandler httpHandler;
WebServer http;
#PostConstruct
public void start() {
ReactiveWebServerFactory factory = new NettyReactiveWebServerFactory(8080);
this.http = factory.getWebServer(this.httpHandler);
this.http.start();
}
#PreDestroy
public void stop() {
this.http.stop();
}
Which is a bit clunky since your https configuration is in one spot (application.yml) and your http configuration is in Java config, but I have tested this myself with a toy application. Not sure how robust of a solution it is, though.
Another option that may work is to try the equivalent of other suggestions, but use the reactive version of the class, which is TomcatReactiveWebServerFactory. I'm not aware of any way to provide more than one connector for it, but you could possibly override its getWebServer method:
#Bean
TomcatReactiveWebServerFactory twoPorts() {
return new TomcatReactiveWebServerFactory(8443) {
#Override
public WebServer getWebServer(HttpHandler httpHandler) {
// .. copy lines from parent class
// .. and add your own Connector, similar to how tutorials describe for servlet-based support
}
}
}
Also, a bit messy, and I have not tried that approach myself.
Of course, keep track of the ticket so you know when Spring Boot 2 provides official support.
Follow the instructions listed in the link provided by jojo_berlin (here's the link). Instead of using his EmbeddedTomcatConfiguration class though, use this below
#Configuration
public class TomcatConfig {
#Value("${server.http.port}")
private int httpPort;
#Bean
public ConfigurableServletWebServerFactory webServerFactory() {
TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory();
Connector connector = new Connector(TomcatServletWebServerFactory.DEFAULT_PROTOCOL);
connector.setPort(httpPort);
factory.addAdditionalTomcatConnectors(connector);
return factory;
}
}
Actually you can define a second connector as described here . So you can define a https connector as your default and an additional HTTP Connector

Simple Reverse Proxy with Spring Boot and Netflix Zuul

I'm looking to implement a simple reverse proxy with Spring Boot that is:
Easy to add routes
Ability to add custom authentication on a per route basis
Add additional headers as needed
I've looked at the facilities provided by the #EnableZuulProxy annotation but it seems too heavyweight as I don't have a desire to use Eureka, Ribbon, or Hystrix. However, #EnableZuulServer is a bit light on configuration.
Would anyone be able to provide an example of what I'm after? Is Netflix Zuul the right choice for this or is there another library I should be looking at?
Thanks!
Simple Reverse Proxy Server
It's easy to set up a simple proxy reverse using Spring Boot without Ribbon, Eureka, or Hystrix.
Simply annotate your main application class with #EnableZuulProxy and set the following property in your configuration:
ribbon.eureka.enabled=false
Then define your routes in your configuration like such:
zuul.routes.<route_name>.path=<route_path>
zuul.routes.<route_name>.url=http://<url_to_host>/
where <route_name> is an arbitrary name for your route and <route_path> is a path using Ant-style path matching.
So a concrete example would be something like this
zuul.routes.userservice.path=users/**
zuul.routes.userservice.url=http://localhost:9999/
Custom Filters
You can also implement your custom authentication and any additional headers by extending and implementing the ZuulFilter class and adding it as an #Bean to your #Configuration class.
So another concrete example:
public class MyFilter extends ZuulFilter {
#Override
public String filterType() {
// can be pre, route, post, and error
return "pre";
}
#Override
public int filterOrder() {
return 0;
}
#Override
public boolean shouldFilter() {
return true;
}
#Override
public Object run() {
// RequestContext is shared by all ZuulFilters
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
// add custom headers
ctx.addZuulRequestHeader("x-custom-header", "foobar");
// additional custom logic goes here
// return isn't used in current impl, null is fine
return null;
}
}
and then
#Configuration
public class GatewayApplication {
#Bean
public MyFilter myFilter() {
return new myFilter();
}
}
Zuul is a good choice. Am not sure about other alternatives but, we've started building Zuul filters (Pre/Post and Route) that could intercept the request and do all pre/post processing and route based upon your need. It is not mandatory to use the whole bunch of Eureka, Ribbon and Hysterix along with Zuul.

Resources