Different actuator (management server) port changes HTTP response - spring-boot

I am having a hard time in Chaos Monkey For Spring Boot regarding error responses when a user POSTs an invalid (like {"level": -2}update via REST to our actuator endpoint where one can update options of the behavior of CMSB (only positive levels are allowed). In the first image, I set the management.server.port to 8888 and the app port to 8080. When posting a new property to the CMSB REST API I am getting the following response (which is not what we would have expected):
And in case I leave the management port at the same port the same as the app I am getting the following response:
For both cases we would have expected the same error response (the second one). So we're asking us (at CMSB) whether this is an intended behavior of spring boot and if not, what our options are to get around writing our own error response handler in case the management port is different from the app port.
Please note that this is not about the intended behavior of chaos monkey for spring boot but rather about whether this is a spring boot bug or not. In both cases we would like to have a detailed error response so a user knows what's wrong. Under the hood we are using the #Validated annotation in combination with something like this to validate inputs:
#Data
#NoArgsConstructor
#Validated
#JsonInclude(JsonInclude.Include.NON_NULL)
public class AssaultPropertiesUpdate {
#Nullable
#Min(value = 1)
#Max(value = 10000)
private Integer level;
On a side note: in both cases the error message in the logs is correct. But only in the second case is this error message
WARN 4477 --- [nio-8080-exec-1] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.web.bind.MethodArgumentNotValidException: Validation failed for argument [0] in public org.springframework.http.ResponseEntity<?> de.codecentric.spring.boot.chaos.monkey.endpoints.ChaosMonkeyRestEndpoint.updateAssaultProperties(de.codecentric.spring.boot.chaos.monkey.endpoints.AssaultPropertiesUpdate): [Field error in object 'assaultPropertiesUpdate' on field 'level': rejected value [-2]; codes [Min.assaultPropertiesUpdate.level,Min.level,Min.java.lang.Integer,Min]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [assaultPropertiesUpdate.level,level]; arguments []; default message [level],1]; default message [must be greater than or equal to 1]] ]
used as the response payload.
Minimal example project: https://github.com/fletchgqc/mediator
Start the project with mvn spring-boot:run. and then do a POST against http://localhost:8080/actuator/chaosmonkey/assaults with the the payload: {"level": -2}. Correct error response should be shown (like in image 2).
Then stop the project, to https://github.com/fletchgqc/mediator/blob/master/src/main/resources/application.properties add management.server.port=8888 and start the app again. Do a POST against http://localhost:8888/actuator/chaosmonkey/assaults with the same payload as before. The wrong error message should appear (like in image 1).

Looks like the spring team fixed it here: https://github.com/spring-projects/spring-boot/issues/21036

Related

Spring-Boot removes message from ErrorAttributes in response

When throwing an exception from a spring-boot controller, the message in the server response is empty - but only if I don't run locally. That last part is what's confusing me the most. I mean, it would make perfect sense to be able to have spring-boot remove parts from the error response. Like the stacktrace for example, noone wants to send that out except when debugging.
But when I run the application locally, I get the full error response, message, stacktrace and all (even when not running in debug mode, which I first suspected might be the reason for this). A typical error might look something like this:
{
"timestamp": "2020-10-14T09:46:35.784+00:00",
"status": 400,
"error": "Bad Request",
"trace": "webcam.yellow.service.controller.error.BadRequestException: New password must be different from old password! at /*REDACTED*/",
"message": "New password must be different from old password!",
"path": "/users/9"
}
But when I produce the same error on a deployed server, all I get is this:
{
"timestamp": "2020-10-14T09:29:57.720+00:00",
"status": 400,
"error": "Bad Request",
"message": "",
"path": "/users/9"
}
I don't mind the stacktrace being removed at all (in fact I want it to be removed), but I would really like to receive that error message.
One thought I had was that it might be related to cross-origin access, but I get the same behaviour when producing the error through swagger instead of our frontend, and swagger is same-origin.
I would fully expect such behaviour to be configurable in spring-boot, that would be convenient. Trouble is, I'm not configuring it. I compared the configuration properties of the running server to my local ones and I don't see any property that might be responsible for that. Nor can I find any if I google it. According to all the tutorials I find, this should work just fine. Which it kind of does, except not on the running servers.
Does anybody know what in spring-boot is causing this behaviour and how to configure it? Using spring-boot 2.3.3 by the way.
Additional information:
After some fooling around, I managed to reproduce the problem locally. I get the shortened error response if I build the application, and then run it from the command line directly with java -jar. Running gradle bootRun results in the server returning the full error message.
I've tried to return my own error response through a ControllerAdvice:
#ControllerAdvice
class BadRequestHandler : ResponseEntityExceptionHandler() {
#ExceptionHandler(value = [BadRequestException::class])
protected fun handleBadRequest(ex: BadRequestException, request: WebRequest): ResponseEntity<Any> {
val message = ex.message
return ResponseEntity(message, HttpHeaders(), HttpStatus.BAD_REQUEST)
}
}
This was just intended to be a quick test to see if I could change the server response. Turns out I can't, the client still gets the same response, although the handler is executed. So whatever takes the information out of the request must come further down the chain.
Does anybody have any idea what's happening here??
This is intended behavior since Spring Boot 2.3 as explained here
Setting server.error.include-message=always in the application.properties resolves this issue.
The response can be configured by injecting a custom ErrorController, like for example this one:
#Controller
class ExampleErrorController(private val errorAttributes: ErrorAttributes) : ErrorController {
private val mapper = ObjectMapper()
#RequestMapping("/error")
#ResponseBody
fun handleError(request: HttpServletRequest): String {
val webRequest = ServletWebRequest(request)
val error = errorAttributes.getError(ServletWebRequest(request))
// if it's not a 500, include the error message in the response. If it's a 500, better not...
val errorAttributeOptions = if (error !is HttpServerErrorException.InternalServerError) {
ErrorAttributeOptions.defaults().including(ErrorAttributeOptions.Include.MESSAGE)
} else ErrorAttributeOptions.defaults()
val errorDetails = errorAttributes.getErrorAttributes(
webRequest, errorAttributeOptions)
return mapper.writeValueAsString(errorDetails)
}
override fun getErrorPath(): String = "/error"
}
Note that this takes ErrorAttributeOptions.defaults() as a baseline, then configures what goes in. It appears that this default object is the one used by the default ErrorController spring boot provides, and it is in fact this object that is different depending on whether I run this from gradle/intelij directly or build it into a jar. Why I couldn't find out, but I verified and confirmed the behaviour. I assume it is intended, albeit not widely documented.
Once I learned this, I wondered why it wasn't possible to just configure the default Options object globally for an application rather than providing an entire controller, which in many instances would be sufficient, but it does not look like that's possible at this point.

How to log 415 errors in Spring

My service is currently experiencing 415 errors, but I'm not getting any logs to find out what errors they are.
Will I be able to add logging in some kind of filter so that I can know what's going on?
#RequiresHttps
#RequestMapping(value = "/test", method = RequestMethod.POST)
public ResponseEntity<String> doAction(#NonNull #RequestBody CustomRequest[] requests) {
It looks like the 415 is happening inside spring mvc engine and it doesn't event reach your controller so that you can't really place the logs in our code (in doAction method for example).
Try to enable tomcat embedded access logs and you'll see the file with all the requests and return status. These are disabled by default so you should add the following into application.properties or yaml:
server.tomcat.accesslog.enabled=true
There are some configurations / customizations you can do with that, you can read about them in this tutorial for example

Why are details not being returned for spring health indicator?

I am currently using spring boot 2.1.4 and jackson 2.9.8
When hitting the health endpoint the details are not displayed.
{
status: "UP"
}
I have added the following property to my configuration.
management.endpoint.health.show-details=always
When I look at the environment management.endpoint.health.show-details it is set.
When I monitor org.springframework.boot.actuate.health.HealthWebEndpointResponseMapper.HealthWebEndpointResponseMapper(HealthStatusHttpMapper, ShowDetails, Set<String>) it is showing that ALWAYS is in fact being used, and details is not being stripped out.
However, when it gets to com.fasterxml.jackson.databind.introspect.POJOPropertiesCollector it doesn't add details, because the method getDetails has not been annotated in org.springframework.boot.actuate.health.Health like it has with getStatus (being #JsonWrapped)
How do I get details to be serialized by Jackson, so they can be returned via the health endpoint?
EDIT: This appears to be getting caused by a custom ObjectMapper that is being injected into the spring environment at startup which sets both AUTO_DETECT_GETTERS and AUTO_DETECT_IS_GETTERS to disabled. While this may of worked in spring 4, it no longer appears to work in spring 5.

HTTP : What is the difference between SC_NOT_FOUND and NOT_FOUND

I am using spring for my project and a question arises in my mind that what is the basic difference between these two HttpStatus
return ResponseEntity.status(HttpStatus.SC_NOT_FOUND).body("Email address not found");
return ResponseEntity.status(HttpStatus.NOT_FOUND).body("Token Expired");
The first one is from the apache servlet API for status codes from Interface HttpServletResponse found here
SC_NOT_FOUND - Status code (404) indicating that the requested
resource is not available.
The second one is from spring framework http status codes constants from here
NOT_FOUND 404 Not Found.
For spring Framework (& spring boot) the second one is used widely.
There is no difference, it is same status code for HTTP from different libraries.

How to use Spring Hatoas ControllerLinkBuilder for a Thymeleaf templated scheduled email

I'm using Spring Hateoas in a Boot app to avoid manual creation of links in the view. It works great in Thymeleaf views, it works when a controller calls a service to send an email that is also rendered by Thymeleaf.
The code to create link is pretty standard
this.readLink = linkTo(methodOn(PostController.class)
.readPost(eventId, postId))
.withRel("ReadPost");
But for a #Scheduled service generated email, it fails like this
015-08-23 22:28:40.886 ERROR 1180 --- [pool-2-thread-1] o.s.s.s.TaskUtils$LoggingErrorHandler : Unexpected error occurred in scheduled task.
java.lang.IllegalStateException: Could not find current request via RequestContextHolder. Is this being called from a Spring MVC handler?
at org.springframework.util.Assert.state(Assert.java:392) ~[spring-core-4.2.0.RELEASE.jar:4.2.0.RELEASE]
at org.springframework.hateoas.mvc.ControllerLinkBuilder.getCurrentRequest(ControllerLinkBuilder.java:242) ~[spring-hateoas-0.18.0.RELEASE.jar:na]
at org.springframework.hateoas.mvc.ControllerLinkBuilder.getBuilder(ControllerLinkBuilder.java:189) ~[spring-hateoas-0.18.0.RELEASE.jar:na]
at org.springframework.hateoas.mvc.ControllerLinkBuilderFactory.linkTo(ControllerLinkBuilderFactory.java:121) ~[spring-hateoas-0.18.0.RELEASE.jar:na]
Is there anything I can do to get around the lack of an HttpServletRequest due to the code running as a #Scheduled job?
ControllerLinkBuilder currently can only be used from within a request as only that allows it to create a fully qualified link using server and port information from it.
Within an #Scheduled-invoked method that information is not available. If you provide more information on what you're actually creating in that very method, I can suggest workarounds.

Resources