Spring boot actuator breaks #AutoConfigureMockRestServiceServer - spring

I have a class which builds multiple RestTemplates using RestTemplateBuilder:
private RestTemplate build(RestTemplateBuilder restTemplateBuilder) {
return restTemplateBuilder
.rootUri("http://localhost:8080/rest")
.build();
}
For my test setup I use #AutoConfigureMockRestServiceServer and mock responses using MockServerRestTemplateCustomizer:
mockServerRestTemplateCustomizer.getServer()
.expect(ExpectedCount.times(2),
requestToUriTemplate("/some/path/{withParameters}", "withParameters"))
.andRespond(withSuccess());
My test passes when I uncomment the spring-boot-actuator dependency in my pom and fails in the other scenario with the following message.
Expected: /some/path/parameter
Actual: http://localhost:8080/rest/pos/some/path/withParameters
I noticed by debugging through MockServerRestTemplateCustomizer that spring-boot-actuator applies a "DelegateHttpClientInterceptor" for supporting their built in metrics for rest templates. However this creates a problem with the following code which I found in RootUriRequestExpectationManager:
public static RequestExpectationManager forRestTemplate(RestTemplate restTemplate,
RequestExpectationManager expectationManager) {
Assert.notNull(restTemplate, "RestTemplate must not be null");
UriTemplateHandler templateHandler = restTemplate.getUriTemplateHandler();
if (templateHandler instanceof RootUriTemplateHandler) {
return new RootUriRequestExpectationManager(((RootUriTemplateHandler) templateHandler).getRootUri(),
expectationManager);
}
return expectationManager;
}
Because as mentioned above spring-boot-actuator registers a "DelegateHttpClientInterceptor" which leads to the above code not recognizing the RootUriTemplateHandler and therefore not matching the request using requestToUriTemplate.
What am I missing here to get this working?

As Andy Wilkinson pointed out, this seems to be a bug in Spring boot. I created an issue with a sample project.

Related

traceId is lost in a coroutine?

I am upgrading an application with Kotlin, Webflux to Spring Boot 3.
Now I noticed that our logs are lacking traceIds.
My suspicion is that this is due to coroutines, since I observed that the logs of the controller contain a traceId and in the logs of the service it is not included. The controller method as well as the service method both have the suspend keyword.
#PostMapping("/upload-file")
#Observed
suspend fun uploadFile(
serverWebExchange: ServerWebExchange,
): ResponseEntity<MessageResponse> {
logger.debug("uploadFile")
importService.handle(getMultipartDataPart("file", serverWebExchange))
return ResponseEntity(MessageResponse("Success."), OK)
}
#Service
class ImportService() {
suspend fun handle(fileAsByteArray: ByteArray) {
log.debug("Import file.")
...
}
I followed the documentation and added depdencies
implementation("org.springframework.boot:spring-boot-starter-actuator")
implementation("io.micrometer:micrometer-tracing-bridge-brave")
Bean
#Bean
fun observedAspect(observationRegistry: ObservationRegistry?): ObservedAspect? {
return ObservedAspect(observationRegistry)
}
and Annotation on the controller method
#GetMapping("/keys/{key}")
#Observed
suspend fun isKeyInKeyStore(
Another interesting observation I made is that I don't observe the problem on my local machine, but only on our dev K8s cluster. Could a different JVM (OpenJDK vs AWS Corretto) cause this?
I am quite sure that the context is lost, but not sure how to deal with it in the best way since I am pretty new to coroutines and Webflux.

Mockito MockedStatic when() "Cannot resolve method"

I am trying to use Mockito MockedStatic to mock a static method.
I am using mockito-core and mockito-inline version 3.6.0 with Spring Boot and maven.
I can't manage to make the mock work, I have a "Cannot resolve method post" on the Unirest::post that you can see in the code below:
#Test
public void test() {
try (MockedStatic<Unirest> mock = Mockito.mockStatic(Unirest.class)) {
mock.when(Unirest::post).thenReturn(new HttpRequestWithBody(HttpMethod.POST, "url"));
}
}
The Unirest class comes from the unirest-java package.
Did someone encounter this issue already and have a solution?
The method Unirest.post(String url) takes an argument and hence you can't refer to it using Unirest::post.
You can use the following:
#Test
void testRequest() {
try (MockedStatic<Unirest> mockedStatic = Mockito.mockStatic(Unirest.class)) {
mockedStatic.when(() -> Unirest.post(ArgumentMatchers.anyString())).thenReturn(...);
someService.doRequest();
}
}
But keep in mind that you have to mock now the whole Unirest usage and every method call on it as the mock returns null by default.
If you want to test your HTTP clients take a look at WireMock or the MockWebServer from OkHttp. This way you test your clients with real HTTP communication and can test also corner cases like slow responses or 5xx HTTP codes.

Spring data rest base path is loosing it's #RepositoryRestResource links after running an unkown period of time

I have a very strange behavior since I updated to Spring Boot v2.2.6-RELEASE.
My spring data rest base path (/api/v1) is loosing it's #RepositoryRestResource links. Custom links are still available.
After a server restart I got:
After an unkown period of time (2-3 days) I got:
My base path it customized this way:
#Bean
public RepresentationModelProcessor<RepositoryLinksResource> globalLinkProcessor() {
// do not replace with lambda!!!
return new RepresentationModelProcessor<RepositoryLinksResource>() {
#Override
public RepositoryLinksResource process(final RepositoryLinksResource repositoryLinksResource) {
repositoryLinksResource.add(linkHelper.newLinkFromMethodInvocation(
WebMvcLinkBuilder.methodOn(FileProcessorController.class).status(), "fileProcessor"));
repositoryLinksResource.add(linkHelper.newLinkFromMethodInvocation(
WebMvcLinkBuilder.methodOn(CurrentUserController.class).whoAmI(), "whoAmI"));
repositoryLinksResource.add(linkHelper.newLinkFromMethodInvocation(
WebMvcLinkBuilder.methodOn(UserController.class).listOperations(), "users"));
repositoryLinksResource.add(linkHelper.newLinkFromMethodInvocation(
WebMvcLinkBuilder.methodOn(StatisticController.class).listOperations(), "statistics"));
return repositoryLinksResource;
}
};
}
There is NO exception in any log output. When I debug from my local machine everything is fine. I am not getting any hands on it. Can anyone help here?
Thanks for reading,
Christian
An update to Spring Boot v2.3.0-RELEASE solved the problem. (until now)

Spring sleuth Baggage key not getting propagated

I've a filter (OncePerRequestFilter) which basically intercepts incoming request and logs traceId, spanId etc. which works well,
this filter lies in a common module which is included in other projects to avoid including spring sleuth dependency in all of my micro-services, the reason why I've created it as a library because any changes to library will be common to all modules.
Now I've to add a new propagation key which need to be propagated to all services via http headers like trace and spanId for that I've extracted current span from HttpTracing and added a baggage key to it (as shown below)
Span span = httpTracing.tracing().tracer().currentSpan();
String corelationId =
StringUtils.isEmpty(request.getHeader(CORELATION_ID))
? "n/a"
: request.getHeader(CORELATION_ID);
ExtraFieldPropagation.set(CUSTOM_TRACE_ID_MDC_KEY_NAME, corelationId);
span.annotate("baggage_set");
span.tag(CUSTOM_TRACE_ID_MDC_KEY_NAME, corelationId);
I've added propagation-keys and whitelisted-mdc-keys to my application.yml (with my library) file like below
spring:
sleuth:
propagation-keys:
- x-corelationId
log:
slf4j:
whitelisted-mdc-keys:
- x-corelationId
After making this change in filter the corelationId is not available when I make a http call to another service with same app, basically keys are not getting propagated.
In your library you can implement ApplicationEnvironmentPreparedEvent listener and add the configuration you need there
Ex:
#Component
public class CustomApplicationListener implements ApplicationListener<ApplicationEvent> {
private static final Logger log = LoggerFactory.getLogger(LagortaApplicationListener.class);
public void onApplicationEvent(ApplicationEvent event) {
if (event instanceof ApplicationEnvironmentPreparedEvent) {
log.debug("Custom ApplicationEnvironmentPreparedEvent Listener");
ApplicationEnvironmentPreparedEvent envEvent = (ApplicationEnvironmentPreparedEvent) event;
ConfigurableEnvironment env = envEvent.getEnvironment();
Properties props = new Properties();
props.put("spring.sleuth.propagation-keys", "x-corelationId");
props.put("log.slf4j.whitelisted-mdc-keys:", "x-corelationId");
env.getPropertySources().addFirst(new PropertiesPropertySource("custom", props));
}
}
}
Then in your microservice you will register this custom listener
public static void main(String[] args) {
ConfigurableApplicationContext context = new SpringApplicationBuilder(MyApplication.class)
.listeners(new CustomApplicationListener()).run();
}
I've gone through documentation and seems like I need to add spring.sleuth.propagation-keys and whitelist them by using spring.sleuth.log.slf4j.whitelisted-mdc-keys
Yes you need to do this
is there another way to add these properties in common module so that I do not need to include them in each and every micro services.
Yes, you can use Spring Cloud Config server and a properties file called application.yml / application.properties that would set those properties for all microservices
The answer from Mahmoud works great when you want register the whitelisted-mdc-keys programatically.
An extra tip when you need these properties also in a test, then you can find the anwser in this post: How to register a ApplicationEnvironmentPreparedEvent in Spring Test

Understanding difference between Custom Handler and SpringBootApiGatewayRequestHandler

I'm new to Spring Cloud Function and came across it as one of best solution for developing FaaS based solution. I am specifically writing application for AWS Lambda Service which is back-end of API Gateway. I ran into very interesting problem with My test application and it is around Handler. My test application works well with Custom Handler written as -
public class UserProfileHandler extends SpringBootRequestHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> {
}
which works well when configured as Handler in the AWS Lambda. Then I came across org.springframework.cloud.function.adapter.aws.SpringBootApiGatewayRequestHandler which is available in Spring Cloud Function dependency, so I wanted to get rid of UserProfileHandler hence I changed Handler configuration in AWS Lambda to org.springframework.cloud.function.adapter.aws.SpringBootApiGatewayRequestHandler instead of ...UserProfileHandler and now lambda fails with following error message. Has anyone run into this problem?
{
"errorMessage": "java.util.Optional cannot be cast to com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent",
"errorType": "java.lang.ClassCastException",
"stackTrace": [
"com.transformco.hs.css.userprofile.function.UserProfileFunction.apply(UserProfileFunction.java:16)",
"org.springframework.cloud.function.context.catalog.BeanFactoryAwareFunctionRegistry$FunctionInvocationWrapper.invokeFunction(BeanFactoryAwareFunctionRegistry.java:499)",
"org.springframework.cloud.function.context.catalog.BeanFactoryAwareFunctionRegistry$FunctionInvocationWrapper.lambda$doApply$1(BeanFactoryAwareFunctionRegistry.java:543)",
"reactor.core.publisher.FluxMapFuseable$MapFuseableSubscriber.onNext(FluxMapFuseable.java:107)",
"reactor.core.publisher.FluxMapFuseable$MapFuseableSubscriber.onNext(FluxMapFuseable.java:121)",
"reactor.core.publisher.FluxJust$WeakScalarSubscription.request(FluxJust.java:99)",
"reactor.core.publisher.FluxMapFuseable$MapFuseableSubscriber.request(FluxMapFuseable.java:162)",
"reactor.core.publisher.FluxMapFuseable$MapFuseableSubscriber.request(FluxMapFuseable.java:162)",
"reactor.core.publisher.BlockingIterable$SubscriberIterator.onSubscribe(BlockingIterable.java:218)",
"reactor.core.publisher.FluxMapFuseable$MapFuseableSubscriber.onSubscribe(FluxMapFuseable.java:90)",
"reactor.core.publisher.FluxMapFuseable$MapFuseableSubscriber.onSubscribe(FluxMapFuseable.java:90)",
"reactor.core.publisher.FluxJust.subscribe(FluxJust.java:70)",
"reactor.core.publisher.InternalFluxOperator.subscribe(InternalFluxOperator.java:53)",
"reactor.core.publisher.BlockingIterable.iterator(BlockingIterable.java:80)",
"org.springframework.cloud.function.adapter.aws.SpringBootRequestHandler.result(SpringBootRequestHandler.java:59)",
"org.springframework.cloud.function.adapter.aws.SpringBootRequestHandler.handleRequest(SpringBootRequestHandler.java:52)",
"org.springframework.cloud.function.adapter.aws.SpringBootApiGatewayRequestHandler.handleRequest(SpringBootApiGatewayRequestHandler.java:140)",
"org.springframework.cloud.function.adapter.aws.SpringBootApiGatewayRequestHandler.handleRequest(SpringBootApiGatewayRequestHandler.java:43)"
]
}
Ganesh, I believe you have already raise the issue in Github of SCF. So as I stated there, we have recently did several enhancements, polished the sample and modified documentation by adding a Getting Started guide.
That said, with new generic request handler you no longer need to provide implementation of AWS request handler including SpringBootApiGatewayRequestHandler.
Simply write your boot application to contain function bean
#SpringBootApplication
public class FunctionConfiguration {
public static void main(String[] args) {
SpringApplication.run(FunctionConfiguration.class, args);
}
#Bean
public Function<String, String> uppercase() {
return value -> value.toUpperCase();
}
}
. . . and specify org.springframework.cloud.function.adapter.aws.FunctionInvoker as a handler in AWS dashboard. We'll do the rest for you.

Resources