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
Related
For my use case I need management.metrics.web.client.request.autotime.enabled: true. However there is one client name I need filter out from these metrics. For example, everything except metrics below should be allowed. Here, the differentiating factor is the clientName
http_client_requests_seconds_count{clientName="someClientName",method="GET",outcome="CLIENT_ERROR",status="404",uri="client.com",} 1.0
http_client_requests_seconds_sum{clientName="someClientName",method="GET",outcome="SUCCESS",status="200",uri="client.com",} 1.0
Is this possible?
It is possible with the a meter filter which gives the possibility to deny or accept a specific metric based on the tags.
Simple example how to use in a Spring Boot #Configuration class:
#Bean
public MeterRegistryCustomizer<MeterRegistry> metricsRegistryConfig() {
return registry -> registry.config()
.meterFilter(MeterFilter.deny(id -> {
var clientName = id.getTag("clientName");
return clientName != null && "someClientName".equals(clientName);
}));
}
This will deny all metrics using the given tag. It is also possible to further refine the filter using the meter name, e.g if (id.getName().startsWith("http.client.requests"))
I am new to Spring. I was trying to make a sample application for spring webflux in functional way.
Why can't our handler function pass Flux. is there any way to make router function accept it as it is said that router function accept a subtype of serverResponse.
Show Handler code
public Mono<ServerResponse> getShowList(ServerRequest request){
Flux<Show> showList = showRepository.findAll();
Flux<ShowVo> showVoList= showList.map(s -> {
return new ShowVo(s.getId(), s.getTitle());
});
return ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(showVoList, ShowVo.class); }
Here i am passing the Mono <ServerResponse> but I want to it as Flux <ServerResponse> to the Router function
Router function code
#Bean
public RouterFunction<ServerResponse> routeShow(ShowHandler showHandler){
return RouterFunctions.route(RequestPredicates.GET("/shows").and(RequestPredicates.accept(MediaType.APPLICATION_JSON)), showHandler::getShowList)
}
}
So is there any way to do it, I have gone through different articles. All I can find is Mono but if I use annotation based webflux I can pass flux.
Why the webhandler doesn't accept a stream to the server, and you can only return a stream is because of the HTTP protocol specification.
If you wish to both stream data to the server, and stream data to the client (full duplex) you need to use websockets with webflux.
you can read all about it in the webflux documentation:
HTTP versus Websockets
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();
so I've started playing with rsocket and spring boot 2.2 to see if I can use it in my projects, but I'm facing a bit of troubles.
Normally, with spring messaging I define a listener method like the following:
#MessageMapping("addGeolocation")
public Mono<Boolean> addGeolocation(#Header("metadata") MmeMetadata metadata, #Payload String geolocation) { ... }
My understanding is that with rsocket I should be able to use the same logic, but when I'm defining the client I couldn't find an easy way to set message headers.
Currently I'm stuck with this:
boolean outcome = rSocketRequester.route("addGeolocation").metadata(...?).data(geolocationWKT).block();
is the metadata a replacement for headers? that method signature seems a little too generic to be used like headers. If I put an Map in it will spring be able to decode headers out of it?
Thank you,
Fernando
Please see this question: RSocket Metadata - custom object.
I used it as a starting point for my solution.
The term 'header' actually means some custom metadata. So, in order to get the correct value you need to configure metadataExtractorRegistry. For Spring Boot do it this way (code in kotlin):
val CUSTOM_MIMETYPE = MimeType.valueOf("<some custom mime type>")
val CUSTOM_HEADER = "<the name of a header>"
...
#Bean
fun rSocketStrategiesCustomizer(): RSocketStrategiesCustomizer {
return RSocketStrategiesCustomizer { strategies: RSocketStrategies.Builder ->
strategies.metadataExtractorRegistry {
it.metadataToExtract(CUSTOM_MIMETYPE, String::class.java, CUSTOM_HEADER)
}
}
}
The type of the data object can be any, not necessarily a String. There is default String endcoder/decoder, so I didn't provide one in the code. For your own type you can provide one of existing encoders/decoders (Json for example) or create your own:
#Bean
fun rSocketStrategiesCustomizer(): RSocketStrategiesCustomizer {
return RSocketStrategiesCustomizer { strategies: RSocketStrategies.Builder ->
strategies.metadataExtractorRegistry {
it.metadataToExtract(CUSTOM_MIMETYPE, YourType::class.java, CUSTOM_HEADER)
}.decoder(Jackson2JsonDecoder())
.encoder(Jackson2JsonEncoder())
}
}
After you've configured registry as above, use the header name defined in the registry in your controller:
#MessageMapping("addGeolocation")
public Mono<Boolean> addGeolocation(#Header(CUSTOM_HEADER) String metadata, #Payload String geolocation) { ... }
And in order to send that header, use next code:
boolean outcome = rSocketRequester.route("addGeolocation")
.metadata("value", CUSTOM_MIMETYPE)
.data(geolocationWKT)
.block();
Hope this helps
Instead of a bag of name-value pairs (i.e. headers), RSocket uses metadata which can be in any format (i.e. MIME type) and it can be composite metadata with multiple types of metadata each formatted differently. So you can have one section with routing metadata, another with security, yet another with tracing, and so on.
To achieve something similar to headers, you can send name-value pairs as JSON-formatted metadata. Now on the server side, you'll need to provide a hint to Spring for how to extract a Map (of headers) from the metadata of incoming requests. To do that you can configure a MetadataExtractor and that's described in this section of the docs. Once that's configured, the extracted Map becomes the headers of the message and can be accessed from #MessageMapping methods as usual (via MessageHeaders, #Header, etc).
I have integration flow and I want to write the entity in the flow to my Gemfire cache between the steps but I am not figuring out how to do it.
#Bean
public IntegrationFlow myFlow(CacheEntityDAL cacheDAL,Transformer t,
MyFilter f) {
return IntegrationFlows.from("inChannel")
.transform(t) //returns the entity
//I want to put to my cacheDAL.put(entity) here
.filter(f)
.channel("outChannel")
.get();
}
Thanks
To write to the Gemfire cache, you need to use a CacheWritingMessageHandler from the Spring Integration Gemfire support.
However since this one is one-way, it is just for writing, there is no straight forward way to insert it in the middle of the flow. On the other you just would like to store and proceed downstream with the same payload. For this purpose I suggest to use a PublishSubscribeChannel with two subscribers: a mentioned CacheWritingMessageHandler and then rest of the flow. Something like this:
return IntegrationFlows.from("inChannel")
.transform(t) //returns the entity
.publishSubscribeChannel(c -> c
.subscribe(sf -> sf
.handle(new CacheWritingMessageHandler(gemfireRegion()))
.filter(f)
.channel("outChannel")
.get();
So, this way you send to the Gemfire and move to the main flow, where the next filter()
is going to be as a second subscriber which won't work until a success of the first one for Gemfire.