Spring Cloud Gateway: map path /a to path /b - java

I'm starting a new Spring Boot project using Spring Cloud Gateway: I need to implement a proxy application for an existing REST API.
The proxy application will implement new features (endpoints) while forwarding to the "old" application all the requests sent to existing endoints.
(Then I'll gradually move also the existing endpoints to the new application, following an approach similar to Strangler Pattern)
But I also need to rewrite the path of several existing endpoints, something like:
return routeLocatorBuilder.routes()
.route(p -> p
.path("/new-endopint")
.map("/old-endpoint") // <= is there something like 'map' method?
.uri("http://old-app-url")).build();
Is this possible? Is there some way to map an endpoint to another?

In cloud gateway there is a org.springframework.cloud.gateway.route.RouteDefinition that can map an incoming request to an upstream by applying FilterDefinition and PredicateDefinition.
You can see how this works by having a look at org.springframework.cloud.gateway.discovery.DiscoveryClientRouteDefinitionLocator.
So a simple RouteDefinitionLocator e.g. InMemoryRouteDefinitionRepository could solve your use case.
If you want to stay with the high level api, then org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder.RouteSpec#predicateBuilder (org.springframework.cloud.gateway.route.builder.GatewayFilterSpec#rewritePath ...) looks promising.
return routeLocatorBuilder.routes()
.routes()
.route(
p ->
p.path("/new-endpoint/**")
.filters(
spec ->
spec.rewritePath(
"/new-endpoint/(?<segment>.*)", "/old-endpoint/${segment}"))
.uri("http://old-app-url"))
.build();

Related

Where is the Spring Actuator Controller endpoint and can I call it programmatically with jvm call?

I want to find the actual java class that serves the Spring Actuator endpoint (/actuator).
It's similar to this question in a way, but that person wanted to call it via a network HTTP call. Ideally, I can call it within the JVM to save on the cost of setting up an HTTP connection.
The reason for this is because we have 2 metrics frameworks in our system. We have a legacy metrics framework built on OpenCensus and we migrated to Spring Actuator (Prometheus metrics based on Micrometer). I think the Spring one is better but I didn't realize how much my company built infrastructure around the old one. For example, we leverage internal libraries that use OpenCensus. Infra team is depending on Opencensus-based metrics from our app. So the idea is to try to merge and report both sets of metrics.
I want to create my own metrics endpoint that pulls in data from Opencensus's endpoint and Actuator's endpoint. I could make an HTTP call to each, but I'd rather call them within the JVM to save on resources and reduce latency.
Or perhaps I'm thinking about it wrong. Should I simply be using MeterRegistry.forEachMeter() in my endpoint?
In any case, I thought if I found the Spring Actuator endpoint, I can see an example of how they're doing it and mimic the implementation even if I don't call it directly.
Bonus: I'll need to track down the Opencensus handler that serves its endpoint too and will probably make another post for that, but if you know the answer to that as well, please share!
I figured it out and posting this for anyone else interested.
The key finding: The MeterRegistry that is #Autowired is actually a PrometheusMeterRegistry if you enable the prometheus metrics.
Once you cast it into a PrometheusMeterRegistry, you can call its .scrape() method to return the exact same metrics printout you would when you hit the http endpoint.
I also need to get the same info from OpenCensus and I found a way to do that too.
Here's the snippet of code for getting metrics from both frameworks
Enumeration<MetricFamilySamples> openCensusSamples = CollectorRegistry.defaultRegistry.filteredMetricFamilySamples(ImmutableSet.of());
StringWriter writer = new StringWriter();
TextFormat.write004(writer, openCensusSamples);
String openCensusMetrics = writer.toString();
PrometheusMeterRegistry registry = (PrometheusMeterRegistry) meterRegistry;
String micrometerMetrics = registry.scrape();
return openCensusMetrics.concat(micrometerMetrics);
I found out another interesting way of doing this.
The other answer I gave but it has one issue. It contains duplicate results. When I looked into it, I realized that both OpenCensus and Micrometer were reporting the same result.
Turns out that the PrometheusScrapeEndpoint implementation uses the same CollectorRegistry that OpenCensus does so the both sets of metrics were being added to the same registry.
You just need to make sure to provide these beans
#PostConstruct
public void openCensusStats() {
PrometheusStatsCollector.createAndRegister();
}
#Bean
public CollectorRegistry collectorRegistry() {
return CollectorRegistry.defaultRegistry;
}

Keycloak annotation based Resource configuration

I'm recently working with microservices, developed as Spring Boot applications (v 2.2) and in my company we're using Keycloak as authorization server.
We chose it because we need complex policies, roles and groups, and we also need the User Managed Authorization (UMA) to share resources between users.
We configured Keycloak with a single realm and many clients (one client per microservice).
Now, I understand that I need to explicitly define Resources within Keycloak and this is fine, but the question is: do I really need to duplicate all of them in my microservice's property file?
All the documentation, examples and tutorials end up with the same thing, that is something like:
keycloak.policy-enforcer-config.enforcement-mode=PERMISSIVE
keycloak.policy-enforcer-config.paths[0].name=Car Resource
keycloak.policy-enforcer-config.paths[0].path=/cars/create
keycloak.policy-enforcer-config.paths[0].scopes[0]=car:create
keycloak.policy-enforcer-config.paths[1].path=/cars/{id}
keycloak.policy-enforcer-config.paths[1].methods[0].method=GET
keycloak.policy-enforcer-config.paths[1].methods[0].scopes[0]=car:view-detail
keycloak.policy-enforcer-config.paths[1].methods[1].method=DELETE
keycloak.policy-enforcer-config.paths[1].methods[1].scopes[0]=car:delete
(this second example fits better our case because it also uses different authorization scopes per http method).
In real life each microservice we're developing has dozens of endpoints and define them one by one seems to me a waste of time and a weakness in the code's robustness: we change an endpoint, we need to reconfigure it in both Keycloak and the application properties.
Is there a way to use some kind of annotation at Controller level? Something like the following pseudo-code:
#RestController
#RequestMapping("/foo")
public class MyController {
#GetMapping
#KeycloakPolicy(scope = "foo:view")
public ResponseEntity<String> foo() {
...
}
#PostMapping
#KeycloakPolicy(scope = "bar:create")
public ResponseEntity<String> bar() {
...
}
}
In the end, I developed my own project that provides auto-configuration capabilities to a spring-boot project that needs to work as a resource server.
The project is released under MIT2 license and it's available on my github:
keycloak-resource-autoconf

Spring webflux - Routing

I am using Spring boot 2.3.3.RELASE and using webflux. Using the below router config.
#Bean
public RouterFunction<ServerResponse> itemRoute() {
return RouterFunctions.route(POST("/api/v1/item").and(accept(APPLICATION_JSON)), itemHandler::createItem)
.andRoute(GET("/api/v1/item/{itemId}").and(accept(APPLICATION_JSON)), itemHandler::getItemById)
.andRoute(GET("/api/v1/item/list").and(accept(APPLICATION_JSON)), itemHandler::getItems);
}
When I hit /api/v1/item/1 ---> It works as expected.
But, hitting /api/v1/list also goes to getItemById instead of getItems. /api/v1/item/list also considered as /api/v1/item/{itemId} and list is coming as itemId.
Anything wrong with this?
Spring documentation for andRoute
Return a composed routing function that routes to the given handler function if this route does not match and the given request predicate applies.
The key word here is composed. It means that you can declare multiple routes that all together must match together for the route to trigger.
what you are looking for is probably just using the plain route builder function.
Taken example from the spring documentation:
RouterFunction<ServerResponse> route = route()
.GET("/person/{id}", accept(APPLICATION_JSON), handler::getPerson)
.GET("/person", accept(APPLICATION_JSON), handler::listPeople)
.POST("/person", handler::createPerson)
.add(otherRoute)
.build();
or you could use the path builder function is another option.
RouterFunction<ServerResponse> route = route()
.path("/api/person", builder -> builder
.POST( ...)
.GET( ... )
.GET( ... )
).build())
.build()
Webflux Router Function

Is there a component for logging messages with Spring Integration using Java DSL?

I'm using Spring Integration to develop my integration scenarios. When I have to write some logs to provide some information, I write this way:
#Bean
IntegrationFlow blacklist(BlacklistService service) {
return m -> m
.wireTap(f -> f.handle(t -> log.info("Adding email source address in blacklist...")))
.<MessageHandlingException, Blacklist>transform(p -> SourceBlacklist.of((Email) p.getFailedMessage().getHeaders().get(IntegrationConstants.MailSender.EMAIL)))
.wireTap(f -> f.handle(t -> log.info("Email source address added to blacklist.")))
.handle(service, "voidSave");
}
I'm using a wiretap with lambda and handle to log my messages. Is there a better way to write log with Spring Integration using Java DSL?
Thanks.
You always can just switch on the logging for the org.springframework.integration category.
From other side Spring Integration suggests logging in the integration flow as an adapter - <logging-channel-adapter>. So, what you need is just send message to the channel of that adapter. From the configuration perspective that looks like:
<wire-tap channel="logging" pattern="*"/>
<logging-channel-adapter id="logging"/>
The same we can configure with Java DSL, but we should rely on the target class - LoggingHandler:
#ServiceActivator(inputChannel = "logging")
#Bean
public loggingHandler() {
return new LoggingHandler();
}
...
.transform()
.wireTap("logging")
.handle();
Although I can see your point and we really could add something convenient to framework directly.
Feel free to raise a GH issue (https://github.com/spring-projects/spring-integration-java-dsl/issues) on the matter and we continue to discuss the feature there.
But right now your solution doesn't look bad, to be honest.
UPDATE
The request for the Framework on the matter: https://github.com/spring-projects/spring-integration-java-dsl/issues/70

Check Camel config at load-time?

We're using Spring Boot for an application, with Camel for routing. We split most routes with an endpoint "URI" String, mainly for testability's sake:
rest("/foo").post().to("direct:foo");
from("direct:foo").process(exchange -> {
exchange.getIn().setBody(service.doStuff());
exchange.getIn().setHeader(Exchange.CONTENT_TYPE, CONTENT_TYPE_JSON);
});
However if we mis-type one of the "direct:foo" strings, we don't get an error until the route is invoked. I'd like to be able to get the error earlier, as part of the application's startup process.
Obviously using static finals instead of String literals will keep us from mis-typing an individual endpoint value, but it won't help with the situation where one endpoint doesn't go anywhere, or we're using one for a from without sending anything to it.
Is there a way I can ask Camel to verify all routes have working to/from endpoints, once Spring has finished scanning the classpath and building beans?

Categories