How to modify response status and body after committed spring boot - spring-boot

I implemented a rate limiter with Filter.class. However, we encountered that we should not limit successful requests. So, I needed status code of the response. When I get status code in Filter chain, it always returns 200. That means request not processed. When I trigger chain.doFilter status is set but response is in the committed state means read-only. However, I need to return 429 response for the rate limit responses
I tried OncePerRequestFilter.class, lots of wrappers that I forget. I expect to set response body via response status

I came upon lots of answers. However it was like that.
So I removed Filter usage. I use ResponseBodyAdvice globally.
#ControllerAdvice
public class RequestLimitAdvice implements ResponseBodyAdvice<Object> {
private final LoadingCache<String, Integer> requestCountsPerClient;
private static final List<Integer> statusCodes = Arrays.asList(HttpStatus.BAD_REQUEST.value(), HttpStatus.UNAUTHORIZED.value(),
HttpStatus.FORBIDDEN.value(), HttpStatus.NOT_FOUND.value(), HttpStatus.METHOD_NOT_ALLOWED.value(),
HttpStatus.NOT_ACCEPTABLE.value(), HttpStatus.REQUEST_TIMEOUT.value(), HttpStatus.CONFLICT.value(),
HttpStatus.INTERNAL_SERVER_ERROR.value(), HttpStatus.NOT_IMPLEMENTED.value(), HttpStatus.SERVICE_UNAVAILABLE.value(),
HttpStatus.GATEWAY_TIMEOUT.value(), HttpStatus.HTTP_VERSION_NOT_SUPPORTED.value());
public RequestLimitAdvice() {
requestCountsPerClient = Caffeine.newBuilder().
expireAfterWrite(1, TimeUnit.MINUTES).build(key -> 0);
}
#Override
public boolean supports(#NotNull MethodParameter returnType, #NotNull Class<? extends HttpMessageConverter<?>> converterType) {
return true;
}
#Override
public Object beforeBodyWrite(Object body, #NotNull MethodParameter returnType, #NotNull MediaType selectedContentType,
#NotNull Class<? extends HttpMessageConverter<?>> selectedConverterType,
#NotNull ServerHttpRequest request, #NotNull ServerHttpResponse response) {
if (response instanceof ServletServerHttpResponse) {
ServletServerHttpResponse httpServletResponse = (ServletServerHttpResponse) (response);
if (statusCodes.contains(httpServletResponse.getServletResponse().getStatus())) {
String client = getClient(((ServletServerHttpRequest) request).getServletRequest());
if (isMaximumRequestsPerSecondExceeded(client)) {
response.setStatusCode(HttpStatus.TOO_MANY_REQUESTS);
return Response.notOk(Translator.toLocale("http.status.too_many_requests"), EErrorCode.TOO_MANY_REQUESTS).getError();
}
}
}
return body;
}
So in ResponseBodyAdvice, thanks to set of response status code and modify enable of response body. I could make what I wanted.
From the answers of
https://stackoverflow.com/a/54501013/16263216 (ResponseBodyAdvice usage)
https://stackoverflow.com/a/65015720/16263216 (String responses ClassCastException issue)

Related

Accessing ModelView attributes in Spring ResponseBodyAdvice

For all my RestControllers handlers, which all return a subtype of MyResponseType, I would like to write a Spring ResponseBodyAdvice which manipulates the response based on some #ModelAttribute. However, I don't know how to access the ModelView at this place.
#ControllerAdvice
public class MyGoldenAdvice implements ResponseBodyAdvice<MyResponseType> {
#Override
public boolean supports(final MethodParameter returnType, final Class<? extends HttpMessageConverter<?>> converterType) {
return true;
}
#Override
public RepresentationModel<?> beforeBodyWrite(
final MyResponseType body,
final MethodParameter returnType,
final MediaType selectedContentType,
final Class<? extends HttpMessageConverter<?>> selectedConverterType,
final ServerHttpRequest request,
final ServerHttpResponse response) {
// here I would like to access the ModelView
return body;
}
}
Unfortunately, since I have to implement the interface ResponseBodyAdvice, I cannot have #ModelAttribute arguments.
Of course, I could also use an interceptor. However, there the response is already written, thus I could only manipulate the JSON of my MyResponseType instance.
Any idea?
Although it is not a solution to the concrete question (Access zu #ModelAttributes) and violates the idea behind #ControllerAdvice, you can use #RequestAttributes for the same purpose. For this, the advice needs a dependency to current HttpServletRequest to store and get the attribute (the ServerHttpRequest argument of beforeBodyWrite does not provide request attributes).
public class MyGoldenAdvice implements ResponseBodyAdvice<MyResponseType> {
public final static String ATTRIBUTE_NAME="MyGoldenAdviceAttribute";
private final HttpServletRequest currentRequest;
// spring injects a proxy to the current request
#Autowired
public MyGoldenAdvice(HttpServletRequest currentRequest) {
this.currentRequest = currentRequest;
}
// in order that the advice method is triggered, we have to annotate it
// with #ModelAttribute, although we store the attribute in the request
#ModelAttribute
public MyModelAttributeType createModelAttribute() {
MyModelAttributeType attribute = MyModelAttributeType.build();
currentRequest.setAttribute(ATTRIBUTE_NAME, attribute);
return attribute;
}
#Override
public boolean supports(final MethodParameter returnType, final Class<? extends HttpMessageConverter<?>> converterType) {
return true;
}
#Override
public RepresentationModel<?> beforeBodyWrite(
final MyResponseType body,
final MethodParameter returnType,
final MediaType selectedContentType,
final Class<? extends HttpMessageConverter<?>> selectedConverterType,
final ServerHttpRequest request,
final ServerHttpResponse response) {
// get the request attribute
MyModelAttributeType attribute = (MyModelAttributeType) currentRequest.getAttribute(ATTRIBUTE_NAME);
// do something with the body
return body;
}
}
In some REST controller, you can use the request attribute as follows
#GetMapping
public handleGetRequest(#RequestAttribute(name=MyGoldenAdvice.ATTRIBUTE_NAME)
MyModelAttributeType attribute) {
// pass attribute around in services and so on...
}

How to get request's URI from WebRequest in Spring?

I am handling REST exceptions using #ControllerAdvice and ResponseEntityExceptionHandler in a spring Rest webservice. So far everything was working fine until I decided to add the URI path(for which exception has occurred) into the BAD_REQUEST response.
#ControllerAdvice
public class RestResponseEntityExceptionHandler extends ResponseEntityExceptionHandler {
#Override
protected ResponseEntity<Object> handleHttpMessageNotReadable(HttpMessageNotReadableException ex,
HttpHeaders headers, HttpStatus status, WebRequest request) {
logger.info(request.toString());
return handleExceptionInternal(ex, errorMessage(HttpStatus.BAD_REQUEST, ex, request), headers, HttpStatus.BAD_REQUEST, request);
}
private ApiError errorMessage(HttpStatus httpStatus, Exception ex, WebRequest request) {
final String message = ex.getMessage() == null ? ex.getClass().getName() : ex.getMessage();
final String developerMessage = ex.getCause() == null ? ex.toString() : ex.getCause().getMessage();
return new ApiError(httpStatus.value(), message, developerMessage, System.currentTimeMillis(), request.getDescription(false));
}
ApiError is just a Pojo class:
public class ApiError {
private Long timeStamp;
private int status;
private String message;
private String developerMessage;
private String path;
}
But WebRequest has not given any api to get the path for which the request failed. I tried:
request.toString() returns -> ServletWebRequest: uri=/signup;client=0:0:0:0:0:0:0:1
request.getDescription(false) returns -> uri=/signup
getDescription is pretty close to the requirement, but doesn't meet it. Is there any way to get only the uri part?
Found the solution. Casting WebRequest to ServletWebRequest solved the purpose.
((ServletWebRequest)request).getRequest().getRequestURI().toString()
returns the complete path - http://localhost:8080/signup
There are multiple solutions to this problem.
1) One can get request URI and client information from WebRequest using
webRequest.getDescription(true).
true will show user's information such as client id and false will just print URI.
2) Instead of WebRequest of Use HttpServletRequest directly in method definition as
#Override
protected ResponseEntity<Object> handleHttpMessageNotReadable(HttpMessageNotReadableException ex,
HttpHeaders headers, HttpStatus status, WebRequest request, HttpServletRequest httpRequest) {
logger.info(httpRequest.getRequestURI());
return handleExceptionInternal(ex, errorMessage(HttpStatus.BAD_REQUEST, ex, request), headers, HttpStatus.BAD_REQUEST, request);
}
Access the attribute of WebRequest object:
Object obj = webRequest.getAttribute("org.springframework.web.util.UrlPathHelper.PATH", 0)
String uri = String.valueOf(obj);
webRequest.getAttribute(String attributeName, int scope);
// scope can be either:
// 0: request
// 1: session
// valid attribute names can be fetched with call:
String[] attributeNames = webRequest.getAttributeNames(0); //scope is request
Valid attribute names are:
org.springframework.web.util.UrlPathHelper.PATH
org.springframework.web.context.request.async.WebAsyncManager.WEB_ASYNC_MANAGER
org.springframework.web.servlet.HandlerMapping.bestMatchingHandler
org.springframework.web.servlet.DispatcherServlet.CONTEXT
org.springframework.web.servlet.resource.ResourceUrlProvider
characterEncodingFilter.FILTERED
org.springframework.boot.web.servlet.error.DefaultErrorAttributes.ERROR
org.springframework.web.servlet.DispatcherServlet.THEME_SOURCE
org.springframework.web.servlet.DispatcherServlet.LOCALE_RESOLVER
formContentFilter.FILTERED
org.springframework.web.servlet.HandlerMapping.bestMatchingPattern
requestContextFilter.FILTERED
org.springframework.web.servlet.DispatcherServlet.OUTPUT_FLASH_MAP
org.springframework.web.servlet.HandlerMapping.pathWithinHandlerMapping
org.springframework.web.servlet.DispatcherServlet.FLASH_MAP_MANAGER
org.springframework.web.servlet.HandlerMapping.uriTemplateVariables
org.springframework.web.servlet.DispatcherServlet.THEME_RESOLVER
org.springframework.core.convert.ConversionService
ResponseEntityExceptionHandler explains A convenient base class for #ControllerAdvice classes that wish to provide centralized exception handling across all #RequestMapping methods through #ExceptionHandler methods. here
In Spring Boot 2.1.6, You can write as below:
RestExceptionHandler.java
#Order(Ordered.HIGHEST_PRECEDENCE)
#RestControllerAdvice
public class RestExceptionHandler extends ResponseEntityExceptionHandler {
private static final Logger logger = LoggerFactory.getLogger(RestExceptionHandler.class);
#ExceptionHandler(ResourceNotFoundException.class)
protected ResponseEntity<Object> handleEntityNotFound(ResourceNotFoundException ex, final HttpServletRequest httpServletRequest) {
ApiError apiError = new ApiError(HttpStatus.NOT_FOUND);
apiError.setMessage("Resource not found");
apiError.setDebugMessage(ex.getMessage());
apiError.setPath(httpServletRequest.getRequestURI());
return buildResponseEntity(apiError);
}
private ResponseEntity<Object> buildResponseEntity(ApiError apiError) {
return new ResponseEntity<>(apiError, apiError.getStatus());
}
#Override
protected ResponseEntity<Object> handleHttpRequestMethodNotSupported(HttpRequestMethodNotSupportedException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
ApiError apiError = new ApiError(HttpStatus.METHOD_NOT_ALLOWED);
apiError.setMessage(ex.getMessage());
apiError.setPath(((ServletWebRequest)request).getRequest().getRequestURI().toString());
logger.warn(ex.getMessage());
return buildResponseEntity(apiError);
}
}
Let's start by implementing a simple structure for sending errors:
ApiError.java
public class ApiError {
// 4xx and 5xx
private HttpStatus status;
#JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'")
private LocalDateTime timestamp;
// holds a user-friendly message about the error.
private String message;
// holds a system message describing the error in more detail.
#JsonInclude(value = Include.NON_EMPTY)
private String debugMessage;
// returns the part of this request's URL
private String path;
#JsonInclude(value = Include.NON_EMPTY)
private List<String> details=new ArrayList<>();
// setters & getters
}
ResourceNotFoundException.java
public class ResourceNotFoundException extends RuntimeException {
private static final long serialVersionUID = 1L;
public ResourceNotFoundException() {
super();
}
public ResourceNotFoundException(String msg) {
super(msg);
}
I am using SpringBoot 2.5.3 and globalExceptionHandler.
short snipit. used "TheCoder" answer and went from there.
You do not have to use header, status, ... WebRequest as input args if you don't need them. This gives just the endpoint of the url and not hostname.
#ExceptionHandler(value = NotFound.class)
ResponseEntity<...> httpNotFoundException(NotFound exc, HttpServletRequest req ) {
//use req.getRequestURI();
}
#ExceptionHandler(value = HttpClientErrorException.class)
ResponseEntity<...> httpClientException(HttpClientErrorException exc, HttpServletRequest req ) {
exc.getRawStatusCode() //to get status code
//I am using this to check for 404 and handling here with other stuff instead of using NotFound.class above.
// Use req.getRequestURI();
}
You could use request.getDescription(false).

Spring Boot equivalent to JAX-RS Interceptors

I would like to wrap the response of my #RestController method into different object structure before Jackson starts to serialize the response to JSON. Let's say I work with the following Spring controller.
#RestController
#RequestMapping("/api/susu")
public class SusuController {
#RequestMapping(path = "/{id}", method = RequestMethod.GET)
public Susu hello(String id) {
Susu susu = new Susu();
susu.setDate(LocalDate.now());
susu.setName("Peter Pan");
return susu;
}
}
In JEE7 I used JAX-RS Interceptor the get access to the Susu instance and wrap it.
#Provider
#Priority(1)
public class JsonStructureInterceptor implements WriterInterceptor {
private final JsonResponseBuilder jsonResponseBuilder = new JsonResponseBuilder();
#Override
public void aroundWriteTo(WriterInterceptorContext context) throws IOException, WebApplicationException {
Susu s = (Susu) context.getEntity(); // read the JAX-RS response entity
JsonObject jsonObjectWithStructure = jsonResponseBuilder.toResponse(s); // wrap it
// add it back into the JAX-RS context
context.setEntity(jsonObjectWithStructure);
context.proceed();
}
}
When using Spring Boot what is the preferred way to to something equivalent without using JAX-RS features?
Update 1: Using a HandlerInterceptorAdapter
I added the following HandlerInterceptorAdapter to my application context and the postHandle method gets called. Everything works fine so far but I cannot figure out how to get the Susu instance and how to pass the wrapped instance over for further processing.
#Component
public class SusuHandlerInterceptor extends HandlerInterceptorAdapter {
#Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
Susu s = ; // how to get access to my Susu instance?
Wrapper w = new Wrapper(s);
// how to pass Wrapper instance on?
}
}
Update 2: Implementing a ResponseBodyAdvice
I found another approach that allows me to access the return value of my controller action. The problem here is that I cannot change the type of the return value. It seems it is not possible to wrap Susu instance in a Wrapper instance.
#ControllerAdvice
public class JsonFilter implements ResponseBodyAdvice<SusuController.Susu> {
#Override
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
return true;
}
#Override
public SusuController.Susu beforeBodyWrite(SusuController.Susu body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
return body;
}
}
Implementing a ResponseBodyAdvice lets you modify the object before it's converted.
If the return-type should be modified, one has to omit the Generic-types:
#ControllerAdvice
class JsonModifyingAdvice implements ResponseBodyAdvice {
#Override
public boolean supports(MethodParameter returnType, Class converterType) {
return true;
}
#Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType,
Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
return new WrappedResponse(body);
}
}

How to get the #RequestBody in an #ExceptionHandler (Spring REST)

I am using Spring Boot 1.4.1 which includes spring-web-4.3.3. I have a class annotated with #ControllerAdvice and methods annotated with #ExceptionHandler to handle exceptions thrown by the service code. When handling these exceptions, I would like to log the #RequestBody that was part of the request for PUT and POST operations so I can see the request body that caused the problem which in my case is crucial for diagnosis.
Per Spring Docs the method signature for #ExceptionHandler methods can include various things including the HttpServletRequest. The request body can normally be obtained from here via getInputStream() or getReader(), but if my controller methods parse the request body like "#RequestBody Foo fooBody" as all of mine do, the HttpServletRequest's input stream or reader is already closed by the time my exception handler method is called. Essentially the request body has already been read by Spring, similar to the issue described here. It is a common problem working with servlets that the request body can only be read once.
Unfortunately #RequestBody is not one of the options available for the exception handler method, if it were then I could use that.
I can add an InputStream to the exception handler method, but that ends up being the same thing as the HttpServletRequest's InputStream and so has the same issue.
I also tried getting the current request with ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest() which is another trick for getting the current request, but this ends up being the same HttpServletRequest that Spring passes into the exception handler method and so has the same problem.
I have read about a few solutions like this and this that involve inserting a custom request wrapper in the filter chain that will read the contents of the request and cache them so they can be read more than once. I don't like this solution because I don't want to interrupt the entire filter/request/response chain (and potentially introduce performance or stability problems) just to implement logging, and if I have any large requests such as uploaded documents (which I do), I don't want to cache that in memory. Besides, Spring probably has the #RequestBody cached somewhere already if I could only find it.
Incidentally many solutions recommend using the ContentCachingRequestWrapper Spring class but in my experience this does not work. Aside from not being documented, looking at its source code it looks like it only caches the parameters, but not the request body. Trying to get the request body from this class always results in an empty string.
So I am looking for any other options that I may have missed. thanks for reading.
Accepted answer creates a new POJO to pass things around, but the same behaviour can be achieved without creating additional objects by reusing the http request.
Example code for Controller mapping:
public ResponseEntity savePerson(#RequestBody Person person, WebRequest webRequest) {
webRequest.setAttribute("person", person, RequestAttributes.SCOPE_REQUEST);
And later in the ExceptionHandler class / method you can use:
#ExceptionHandler(Exception.class)
public ResponseEntity exceptionHandling(WebRequest request,Exception thrown) {
Person person = (Person) request.getAttribute("person", RequestAttributes.SCOPE_REQUEST);
You can reference the request body object to a request-scoped bean. And then inject that request-scoped bean in your exception handler to retrieve the request body (or other request-context beans that you wish to reference).
// #Component
// #Scope("request")
#ManagedBean
#RequestScope
public class RequestContext {
// fields, getters, and setters for request-scoped beans
}
#RestController
#RequestMapping("/api/v1/persons")
public class PersonController {
#Inject
private RequestContext requestContext;
#Inject
private PersonService personService;
#PostMapping
public Person savePerson(#RequestBody Person person) throws PersonServiceException {
requestContext.setRequestBody(person);
return personService.save(person);
}
}
#ControllerAdvice
public class ExceptionMapper {
#Inject
private RequestContext requestContext;
#ExceptionHandler(PersonServiceException.class)
protected ResponseEntity<?> onPersonServiceException(PersonServiceException exception) {
Object requestBody = requestContext.getRequestBody();
// ...
return responseEntity;
}
}
You should be able to get the content of the request body by using the RequestBodyAdvice interface. If you implement this on a class annotated with #ControllerAdvice it should be picked up automatically.
To get other request information like the HTTP method and query params I'm using an interceptor. I'm capturing all this request info for error reporting in a ThreadLocal variable which I clear on the afterCompletion hook in that same interceptor.
The class below implements this and can be used in your ExceptionHandler to get all request information:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdvice;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.Map;
#ControllerAdvice
public class RequestInfo extends HandlerInterceptorAdapter implements RequestBodyAdvice {
private static final Logger logger = LoggerFactory.getLogger(RequestInfo.class);
private static final ThreadLocal<RequestInfo> requestInfoThreadLocal = new ThreadLocal<>();
private String method;
private String body;
private String queryString;
private String ip;
private String user;
private String referrer;
private String url;
public static RequestInfo get() {
RequestInfo requestInfo = requestInfoThreadLocal.get();
if (requestInfo == null) {
requestInfo = new RequestInfo();
requestInfoThreadLocal.set(requestInfo);
}
return requestInfo;
}
public Map<String,String> asMap() {
Map<String,String> map = new HashMap<>();
map.put("method", this.method);
map.put("url", this.url);
map.put("queryParams", this.queryString);
map.put("body", this.body);
map.put("ip", this.ip);
map.put("referrer", this.referrer);
map.put("user", this.user);
return map;
}
private void setInfoFromRequest(HttpServletRequest request) {
this.method = request.getMethod();
this.queryString = request.getQueryString();
this.ip = request.getRemoteAddr();
this.referrer = request.getRemoteHost();
this.url = request.getRequestURI();
if (request.getUserPrincipal() != null) {
this.user = request.getUserPrincipal().getName();
}
}
public void setBody(String body) {
this.body = body;
}
private static void setInfoFrom(HttpServletRequest request) {
RequestInfo requestInfo = requestInfoThreadLocal.get();
if (requestInfo == null) {
requestInfo = new RequestInfo();
}
requestInfo.setInfoFromRequest(request);
requestInfoThreadLocal.set(requestInfo);
}
private static void clear() {
requestInfoThreadLocal.remove();
}
private static void setBodyInThreadLocal(String body) {
RequestInfo requestInfo = get();
requestInfo.setBody(body);
setRequestInfo(requestInfo);
}
private static void setRequestInfo(RequestInfo requestInfo) {
requestInfoThreadLocal.set(requestInfo);
}
// Implementation of HandlerInterceptorAdapter to capture the request info (except body) and be able to add it to the report in case of an error
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
RequestInfo.setInfoFrom(request);
return true;
}
#Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception exception) {
RequestInfo.clear();
}
// Implementation of RequestBodyAdvice to capture the request body and be able to add it to the report in case of an error
#Override
public boolean supports(MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
return true;
}
#Override
public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
return inputMessage;
}
#Override
public Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
RequestInfo.setBodyInThreadLocal(body.toString());
return body;
}
#Override
public Object handleEmptyBody(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
return body;
}
}
Just an enhancement to quintencls answer
I got request body and can use it anywhere inside exception handler class.
#ControllerAdvice
public class CustomErrorHandler extends ResponseEntityExceptionHandler implements RequestBodyAdvice {
...
private Object reqBody;
...
#ExceptionHandler(NoSuchElementException.class)
public ResponseEntity<Object> handleNoSuchElementException(final NoSuchElementException ex,
final WebRequest request) {
System.out.println("===================================" + reqBody);
return handleNotFoundException(ex, request);
}
...
#Override
public boolean supports(MethodParameter methodParameter, Type targetType,
Class<? extends HttpMessageConverter<?>> converterType) {
return true;
}
#Override
public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType,
Class<? extends HttpMessageConverter<?>> converterType) throws IOException {
return inputMessage;
}
#Override
public Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType,
Class<? extends HttpMessageConverter<?>> converterType) {
// capture request body here to use in our controller advice class
this.reqBody = body;
return body;
}
#Override
public Object handleEmptyBody(Object body, HttpInputMessage inputMessage, MethodParameter parameter,
Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
return body;
}
}
Here is a solution in Kotlin syntax that I used for some fields validation control.
I needed to enhance the default handleMethodArgumentNotValid(...) method from the #RestControllerAdvice, to systematically log a field that was embedded in that same request body object.
override fun handleMethodArgumentNotValid(e: MethodArgumentNotValidException, headers: HttpHeaders, status: HttpStatus, request: WebRequest): ResponseEntity<Any> {
val error = e.bindingResult.fieldErrors.first()
val requestBody = try {
val field = error.javaClass.getDeclaredField("violation").apply { trySetAccessible() }
((field[error] as ConstraintViolationImpl<Any>).rootBean as MyRequestBodyObject)
} catch (ex : Exception) {
//do some failsafe here
}
}
See here: https://stackoverflow.com/a/61813076/1036433 - for a clean way to have access to the HttpServerRequest.

How to make json response Unified format in spring?

I want all json response is
{
"status":"ok"
"data":"..."
}
I only care #ResponseBody function return value;Don't need wrap any object to do it;
example:
#ResponseBody
public String test(){
return "Hello,World"
}
I want get
{
"status":"ok"
"data":"Hello,World"
}
ResponseBodyAdvicecan do it.you can implement the interface to handle ReqeustBody. Here is the api.
In the documents.
Allows customizing the response after the execution of an #ResponseBody or a ResponseEntity controller method but before the body is written with an HttpMessageConverter.
Implementations may be registered directly with RequestMappingHandlerAdapter and ExceptionHandlerExceptionResolver or more likely annotated with #ControllerAdvice in which case they will be auto-detected by both.
here is a simple code.
#ControllerAdvice
public customerResponseBody implements ResponseBodyAdvice{
#Override
public boolean supports (MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType){
return true;
}
#Override
public Object beforeBodyWrite (Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response){
body = new ResponseTemplate<Object>("001",body);
return body;
}
}
You must return a Object instead of String for example :
public class CustomResponse {
private String status;
private String data;
// Getters & Setters
}
#ResponseBody
public CustomResponse test(){
CustomResponse response = new CustomResponse();
response.setStatus("OK");
response.setData("Hello,World");
return response;
}
I thought it is not a good solution but you can format string
#RequestMapping(value="testData")
public #ResponseBody String testData(){
String sdata="ok";
String value ="Hello,World";
return "{\"status\" :\""+sdata+"\",\"data \":\""+value+"\"}";
}
I found this solve problem.
rewrite default RequestResponseBodyMethodProcessor can wrap any object to return value

Resources