Spring Webflux - Publish all HTTP requests to pubsub - spring

In my app I have one endpoint under /my-endpoint path which supports only post method. It accepts a body that must be compatible with my MyRequest class.
#Validated
data class MyRequest(
#get:JsonProperty("age", required = true)
#field:Size(min = 3, max = 128, message = "age must be between 3 and 128")
val age: String,
#get:JsonProperty("zip_code", required = true)
#field:Pattern(regexp = "\\d{2}-\\d{3}", message = "address.zip_code is invalid. It is expected to match pattern \"\\d{2}-\\d{3}\"")
val zipCode: String
)
And my controller looks like this
#PostMapping("/my-endpoint")
fun myEndpoint(
#Valid #RequestBody request: MyRequest,
): Mono<ResponseEntity<MyResponse>> {
return myService.processRequest(request)
.map { ResponseEntity.ok().body(it) }
}
Each time I receive some request to THIS particular endpoint (I have other endpoints but them should be ignored) - I'd like to publish a message to my pubsub consisting raw request body (as a string) - no matter whether the request body was valid or not.
How to intercept the request to be able to publish the message - still having the endpoint working ?

I think you could implement your own WebFilter. Filter the API path through exchange.getRequest().getPath() using simple if block and get the body through exchange.getRequest().getBody()
#Component
#RequiredArgsConstructor
#Slf4j
public class MyFilter implements WebFilter {
private final MyPublisher myPublisher;
#Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
if (pathMatches(exchange.getRequest().getPath()) {
return exchange.getRequest().getBody()
.map(dataBuffer -> {
final String requestBody = dataBuffer.toString(StandardCharsets.UTF_8));
this.myPublisher.publish(requestBody).subscribe();
return exchange;
}).then(chain.filter(exchange));
}
return chain.filter(exchange);
}
}

Related

Spring Boot WebFlux with RouterFunction log request and response body

I'm trying to log request and response body of every call received by my microservice which is using reactive WebFlux with routing function defined as bellow:
#Configuration
public class FluxRouter {
#Autowired
LoggingHandlerFilterFunction loggingHandlerFilterFunction;
#Bean
public RouterFunction<ServerResponse> routes(FluxHandler fluxHandler) {
return RouterFunctions
.route(POST("/post-flux").and(accept(MediaType.APPLICATION_JSON)), fluxHandler::testFlux)
.filter(loggingHandlerFilterFunction);
}
}
#Component
public class FluxHandler {
private static final Logger logger = LoggerFactory.getLogger(FluxHandler.class);
#LogExecutionTime(ActionType.APP_LOGIC)
public Mono<ServerResponse> testFlux(ServerRequest request) {
logger.info("FluxHandler.testFlux");
return request
.bodyToMono(TestBody.class)
.doOnNext(body -> logger.info("FluxHandler-2: "+ body.getName()))
.flatMap(testBody -> ServerResponse.ok()
.contentType(MediaType.APPLICATION_JSON)
.body(BodyInserters.fromValue("Hello, ")));
}
}
and a router filter defined like this:
#Component
public class LoggingHandlerFilterFunction implements HandlerFilterFunction<ServerResponse, ServerResponse> {
private static final Logger logger = LoggerFactory.getLogger(LoggingHandlerFilterFunction.class);
#Override
public Mono<ServerResponse> filter(ServerRequest request, HandlerFunction<ServerResponse> handlerFunction) {
logger.info("Inside LoggingHandlerFilterFunction");
return request.bodyToMono(Object.class)
.doOnNext(o -> logger.info("Request body: "+ o.toString()))
.flatMap(testBody -> handlerFunction.handle(request))
.doOnNext(serverResponse -> logger.info("Response body: "+ serverResponse.toString()));
}
}
Console logs here are:
Inside LoggingHandlerFilterFunction
LoggingHandlerFilterFunction-1: {name=Name, surname=Surname}
FluxHandler.testFlux
As you can see I'm able to log the request but after doing it the body inside the FluxHandler seems empty because this is not triggered .doOnNext(body -> logger.info("FluxHandler-2: "+ body.getName()) and also no response body is produced. Can someone show me how to fix this or is there any other way to log request/response body when using routes in webflux?

Websocket request not routed through gateway global filter

Problem
I have setup a global filter for all requests that adds user information to the headers. This filter works for all my http requests but doesnt apply them to my websocket requests.
Questions
Do websocket requests got through GlobalFilters?
If websocket requests do not go through GlobalFilters is there a way to specify a filter for websockets?
Are there any approaches that will allow me to inject user info into the websocket requests via spring-cloud-gateway?
Setup
Gateway configuration
gateway:
routes:
- id: example
uri: http://localhost:80
predicates:
- Path=/example/**
filters:
- RewritePath=/example/(?<path>.*), /$\{path}
GloablFilter
#Component
class CustomGlobalFilter(private val jwtDecoder: ReactiveJwtDecoder) : GlobalFilter {
private val logger = KotlinLogging.logger {}
/**
* Given exchange, extract Authorization header value and modify to retrieve JWT token.
*/
fun extractJwtToken(exchange: ServerWebExchange): String {
return (exchange.request.headers["Authorization"]
?: throw JwtExtractionException("Request does not contain Authorization header"))[0]
.replace("Bearer ", "")
}
/**
* Modify request headers to add `username`.
*/
fun modifyHeaders(exchange: ServerWebExchange): Mono<ServerWebExchange> {
return try {
val jwt = extractJwtToken(exchange)
jwtDecoder
.decode(jwt)
.map {
val username = it.claims["username"] as String
val modRequest = exchange
.request
.mutate()
.header("username", username)
.build()
exchange.mutate()
.request(modRequest)
.build()
}
} catch (e: JwtExtractionException) {
exchange.toMono() // fall back on default exchange
}
}
/**
* Filter all outgoing requests to modify headers.
*/
override fun filter(exchange: ServerWebExchange, chain: GatewayFilterChain): Mono<Void> {
return modifyHeaders(exchange)
.flatMap { chain.filter(it) }
}
}
I was able to fix the routing issue by setting the custom filter order before the WebsocketRoutingFilter
#Component
class CustomGlobalFilter(
private val authFilterUtil: AuthFilterUtil,
) : GlobalFilter, Ordered {
override fun filter(exchange: ServerWebExchange, chain: GatewayFilterChain): Mono<Void> {
return authFilterUtil
.modifyHeaders(exchange)
.flatMap { chain.filter(it) }
}
override fun getOrder(): Int {
return Ordered.HIGHEST_PRECEDENCE
}
}

Spring-webflux filter to fetch the request body

I need to fetch the entire request body in filter and convert it into String. Below is my code but nothing is getting printed on console.
#Component
public class WebFilter01 implements WebFilter {
#Override
public Mono<Void> filter(ServerWebExchange serverWebExchange,
WebFilterChain webFilterChain) {
Flux<DataBuffer> requestBody = serverWebExchange.getRequest().getBody();
Flux<String> decodedRequest = requestBody.map(databuffer -> {
return decodeDataBuffer(databuffer);
});
decodedRequest.doOnNext(s -> System.out.print(s));
return webFilterChain.filter(serverWebExchange);
}
protected String decodeDataBuffer(DataBuffer dataBuffer) {
Charset charset = StandardCharsets.UTF_8;
CharBuffer charBuffer = charset.decode(dataBuffer.asByteBuffer());
DataBufferUtils.release(dataBuffer);
String value = charBuffer.toString();
return value;
}
}
Nothing is getting printed on console because you did not subscribe to decodedRequest ,
as we know one of the Reactive aspect:
Nothing happens until you subscribe
But if you do that you will see printed body on console but your code will not work, because the next operators cannot read the body and you will get IllegalStateException(Only one connection receive subscriber allowed.)
So, how to resolve it?
Create your own wrapper for ServerWebExchange (please read about this here: How to log request and response bodies in Spring WebFlux)
Log bodies in HttpMessageDecoder. If you see, for instance, AbstractJackson2Decoder you will found code where Spring decode you buffer to object and can log it:
try {
Object value = reader.readValue(tokenBuffer.asParser(getObjectMapper()));
if (!Hints.isLoggingSuppressed(hints)) {
LogFormatUtils.traceDebug(logger, traceOn -> {
String formatted = LogFormatUtils.formatValue(value, !traceOn);
return Hints.getLogPrefix(hints) + "Decoded [" + formatted + "]";
});
}
return value;
}

How to log request and response bodies in Spring WebFlux

I want to have centralised logging for requests and responses in my REST API on Spring WebFlux with Kotlin. So far I've tried this approaches
#Bean
fun apiRouter() = router {
(accept(MediaType.APPLICATION_JSON) and "/api").nest {
"/user".nest {
GET("/", userHandler::listUsers)
POST("/{userId}", userHandler::updateUser)
}
}
}.filter { request, next ->
logger.info { "Processing request $request with body ${request.bodyToMono<String>()}" }
next.handle(request).doOnSuccess { logger.info { "Handling with response $it" } }
}
Here request method and path log successfully but the body is Mono, so how should I log it? Should it be the other way around and I have to subscribe on request body Mono and log it in the callback?
Another problem is that ServerResponse interface here doesn't have access to the response body. How can I get it here?
Another approach I've tried is using WebFilter
#Bean
fun loggingFilter(): WebFilter =
WebFilter { exchange, chain ->
val request = exchange.request
logger.info { "Processing request method=${request.method} path=${request.path.pathWithinApplication()} params=[${request.queryParams}] body=[${request.body}]" }
val result = chain.filter(exchange)
logger.info { "Handling with response ${exchange.response}" }
return#WebFilter result
}
Same problem here: request body is Flux and no response body.
Is there a way to access full request and response for logging from some filters? What don't I understand?
This is more or less similar to the situation in Spring MVC.
In Spring MVC, you can use a AbstractRequestLoggingFilter filter and ContentCachingRequestWrapper and/or ContentCachingResponseWrapper. Many tradeoffs here:
if you'd like to access servlet request attributes, you need to actually read and parse the request body
logging the request body means buffering the request body, which can use a significant amount of memory
if you'd like to access the response body, you need to wrap the response and buffer the response body as it's being written, for later retrieval
ContentCaching*Wrapper classes don't exist in WebFlux but you could create similar ones. But keep in mind other points here:
buffering data in memory somehow goes against the reactive stack, since we're trying there to be very efficient with the available resources
you should not tamper with the actual flow of data and flush more/less often than expected, otherwise you'd risk breaking streaming uses cases
at that level, you only have access to DataBuffer instances, which are (roughly) memory-efficient byte arrays. Those belong to buffer pools and are recycled for other exchanges. If those aren't properly retained/released, memory leaks are created (and buffering data for later consumption certainly fits that scenario)
again at that level, it's only bytes and you don't have access to any codec to parse the HTTP body. I'd forget about buffering the content if it's not human-readable in the first place
Other answers to your question:
yes, the WebFilter is probably the best approach
no, you shouldn't subscribe to the request body otherwise you'd consume data that the handler won't be able to read; you can flatMap on the request and buffer data in doOn operators
wrapping the response should give you access to the response body as it's being written; don't forget about memory leaks, though
I didn't find a good way to log request/response bodies, but if you are just interested in meta data then you can do it like follows.
import org.springframework.http.HttpHeaders
import org.springframework.http.HttpStatus
import org.springframework.http.server.reactive.ServerHttpResponse
import org.springframework.stereotype.Component
import org.springframework.web.server.ServerWebExchange
import org.springframework.web.server.WebFilter
import org.springframework.web.server.WebFilterChain
import reactor.core.publisher.Mono
#Component
class LoggingFilter(val requestLogger: RequestLogger, val requestIdFactory: RequestIdFactory) : WebFilter {
val logger = logger()
override fun filter(exchange: ServerWebExchange, chain: WebFilterChain): Mono<Void> {
logger.info(requestLogger.getRequestMessage(exchange))
val filter = chain.filter(exchange)
exchange.response.beforeCommit {
logger.info(requestLogger.getResponseMessage(exchange))
Mono.empty()
}
return filter
}
}
#Component
class RequestLogger {
fun getRequestMessage(exchange: ServerWebExchange): String {
val request = exchange.request
val method = request.method
val path = request.uri.path
val acceptableMediaTypes = request.headers.accept
val contentType = request.headers.contentType
return ">>> $method $path ${HttpHeaders.ACCEPT}: $acceptableMediaTypes ${HttpHeaders.CONTENT_TYPE}: $contentType"
}
fun getResponseMessage(exchange: ServerWebExchange): String {
val request = exchange.request
val response = exchange.response
val method = request.method
val path = request.uri.path
val statusCode = getStatus(response)
val contentType = response.headers.contentType
return "<<< $method $path HTTP${statusCode.value()} ${statusCode.reasonPhrase} ${HttpHeaders.CONTENT_TYPE}: $contentType"
}
private fun getStatus(response: ServerHttpResponse): HttpStatus =
try {
response.statusCode
} catch (ex: Exception) {
HttpStatus.CONTINUE
}
}
This is what I came up with for java.
public class RequestResponseLoggingFilter implements WebFilter {
#Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
ServerHttpRequest httpRequest = exchange.getRequest();
final String httpUrl = httpRequest.getURI().toString();
ServerHttpRequestDecorator loggingServerHttpRequestDecorator = new ServerHttpRequestDecorator(exchange.getRequest()) {
String requestBody = "";
#Override
public Flux<DataBuffer> getBody() {
return super.getBody().doOnNext(dataBuffer -> {
try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream()) {
Channels.newChannel(byteArrayOutputStream).write(dataBuffer.asByteBuffer().asReadOnlyBuffer());
requestBody = IOUtils.toString(byteArrayOutputStream.toByteArray(), "UTF-8");
commonLogger.info(LogMessage.builder()
.step(httpUrl)
.message("log incoming http request")
.stringPayload(requestBody)
.build());
} catch (IOException e) {
commonLogger.error(LogMessage.builder()
.step("log incoming request for " + httpUrl)
.message("fail to log incoming http request")
.errorType("IO exception")
.stringPayload(requestBody)
.build(), e);
}
});
}
};
ServerHttpResponseDecorator loggingServerHttpResponseDecorator = new ServerHttpResponseDecorator(exchange.getResponse()) {
String responseBody = "";
#Override
public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
Mono<DataBuffer> buffer = Mono.from(body);
return super.writeWith(buffer.doOnNext(dataBuffer -> {
try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream()) {
Channels.newChannel(byteArrayOutputStream).write(dataBuffer.asByteBuffer().asReadOnlyBuffer());
responseBody = IOUtils.toString(byteArrayOutputStream.toByteArray(), "UTF-8");
commonLogger.info(LogMessage.builder()
.step("log outgoing response for " + httpUrl)
.message("incoming http request")
.stringPayload(responseBody)
.build());
} catch (Exception e) {
commonLogger.error(LogMessage.builder()
.step("log outgoing response for " + httpUrl)
.message("fail to log http response")
.errorType("IO exception")
.stringPayload(responseBody)
.build(), e);
}
}));
}
};
return chain.filter(exchange.mutate().request(loggingServerHttpRequestDecorator).response(loggingServerHttpResponseDecorator).build());
}
}
You can actually enable DEBUG logging for Netty and Reactor-Netty related to see full picture of what's happening. You could play with the below and see what you want and don't. That was the best I could.
reactor.ipc.netty.channel.ChannelOperationsHandler: DEBUG
reactor.ipc.netty.http.server.HttpServer: DEBUG
reactor.ipc.netty.http.client: DEBUG
io.reactivex.netty.protocol.http.client: DEBUG
io.netty.handler: DEBUG
io.netty.handler.proxy.HttpProxyHandler: DEBUG
io.netty.handler.proxy.ProxyHandler: DEBUG
org.springframework.web.reactive.function.client: DEBUG
reactor.ipc.netty.channel: DEBUG
Since Spring Boot 2.2.x, Spring Webflux supports Kotlin coroutines. With coroutines, you can have the advantages of non-blocking calls without having to handle Mono and Flux wrapped objects. It adds extensions to ServerRequest and ServerResponse, adding methods like ServerRequest#awaitBody() and ServerResponse.BodyBuilder.bodyValueAndAwait(body: Any). So you could rewrite you code like this:
#Bean
fun apiRouter() = coRouter {
(accept(MediaType.APPLICATION_JSON) and "/api").nest {
"/user".nest {
/* the handler methods now use ServerRequest and ServerResponse directly
you just need to add suspend before your function declaration:
suspend fun listUsers(ServerRequest req, ServerResponse res) */
GET("/", userHandler::listUsers)
POST("/{userId}", userHandler::updateUser)
}
}
// this filter will be applied to all routes built by this coRouter
filter { request, next ->
// using non-blocking request.awayBody<T>()
logger.info("Processing $request with body ${request.awaitBody<String>()}")
val res = next(request)
logger.info("Handling with Content-Type ${res.headers().contentType} and status code ${res.rawStatusCode()}")
res
}
}
In order to create a WebFilter Bean with coRoutines, I think you can use this CoroutineWebFilter interface (I haven't tested it, I don't know if it works).
I am pretty new to Spring WebFlux, and I don't know how to do it in Kotlin, but should be the same as in Java using WebFilter:
public class PayloadLoggingWebFilter implements WebFilter {
public static final ByteArrayOutputStream EMPTY_BYTE_ARRAY_OUTPUT_STREAM = new ByteArrayOutputStream(0);
private final Logger logger;
private final boolean encodeBytes;
public PayloadLoggingWebFilter(Logger logger) {
this(logger, false);
}
public PayloadLoggingWebFilter(Logger logger, boolean encodeBytes) {
this.logger = logger;
this.encodeBytes = encodeBytes;
}
#Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
if (logger.isInfoEnabled()) {
return chain.filter(decorate(exchange));
} else {
return chain.filter(exchange);
}
}
private ServerWebExchange decorate(ServerWebExchange exchange) {
final ServerHttpRequest decorated = new ServerHttpRequestDecorator(exchange.getRequest()) {
#Override
public Flux<DataBuffer> getBody() {
if (logger.isDebugEnabled()) {
final ByteArrayOutputStream baos = new ByteArrayOutputStream();
return super.getBody().map(dataBuffer -> {
try {
Channels.newChannel(baos).write(dataBuffer.asByteBuffer().asReadOnlyBuffer());
} catch (IOException e) {
logger.error("Unable to log input request due to an error", e);
}
return dataBuffer;
}).doOnComplete(() -> flushLog(baos));
} else {
return super.getBody().doOnComplete(() -> flushLog(EMPTY_BYTE_ARRAY_OUTPUT_STREAM));
}
}
};
return new ServerWebExchangeDecorator(exchange) {
#Override
public ServerHttpRequest getRequest() {
return decorated;
}
private void flushLog(ByteArrayOutputStream baos) {
ServerHttpRequest request = super.getRequest();
if (logger.isInfoEnabled()) {
StringBuffer data = new StringBuffer();
data.append('[').append(request.getMethodValue())
.append("] '").append(String.valueOf(request.getURI()))
.append("' from ")
.append(
Optional.ofNullable(request.getRemoteAddress())
.map(addr -> addr.getHostString())
.orElse("null")
);
if (logger.isDebugEnabled()) {
data.append(" with payload [\n");
if (encodeBytes) {
data.append(new HexBinaryAdapter().marshal(baos.toByteArray()));
} else {
data.append(baos.toString());
}
data.append("\n]");
logger.debug(data.toString());
} else {
logger.info(data.toString());
}
}
}
};
}
}
Here some tests on this: github
I think this is what Brian Clozel (#brian-clozel) meant.
Here is the GitHub Repo with complete implementation to log both request and response body along with http headers for webflux/java based application...
What Brian said. In addition, logging request/response bodies don't make sense for reactive streaming. If you imagine the data flowing through a pipe as a stream, you don't have the full content at any time unless you buffer it, which defeats the whole point. For small request/response, you can get away with buffering, but then why use the reactive model (other than to impress your coworkers :-) )?
The only reason for logging request/response that I could conjure up is debugging, but with the reactive programming model, debugging method has to be modified too. Project Reactor doc has an excellent section on debugging that you can refer to: http://projectreactor.io/docs/core/snapshot/reference/#debugging
Assuming we are dealing with a simple JSON or XML response, if debug level for corresponding loggers is not sufficient for some reason, one can use string representation before transforming it to object:
Mono<Response> mono = WebClient.create()
.post()
.body(Mono.just(request), Request.class)
.retrieve()
.bodyToMono(String.class)
.doOnNext(this::sideEffectWithResponseAsString)
.map(this::transformToResponse);
the following are the side-effect and transformation methods:
private void sideEffectWithResponseAsString(String response) { ... }
private Response transformToResponse(String response) { /*use Jackson or JAXB*/ }
If your using controller instead of handler best way is aop with annotating you controller class with #Log annotation.And FYI this takes plain json object as request not mono.
#Target(AnnotationTarget.FUNCTION)
#Retention(AnnotationRetention.RUNTIME)
annotation class Log
#Aspect
#Component
class LogAspect {
companion object {
val log = KLogging().logger
}
#Around("#annotation(Log)")
#Throws(Throwable::class)
fun logAround(joinPoint: ProceedingJoinPoint): Any? {
val start = System.currentTimeMillis()
val result = joinPoint.proceed()
return if (result is Mono<*>) result.doOnSuccess(getConsumer(joinPoint, start)) else result
}
fun getConsumer(joinPoint: ProceedingJoinPoint, start: Long): Consumer<Any>? {
return Consumer {
var response = ""
if (Objects.nonNull(it)) response = it.toString()
log.info(
"Enter: {}.{}() with argument[s] = {}",
joinPoint.signature.declaringTypeName, joinPoint.signature.name,
joinPoint.args
)
log.info(
"Exit: {}.{}() had arguments = {}, with result = {}, Execution time = {} ms",
joinPoint.signature.declaringTypeName, joinPoint.signature.name,
joinPoint.args[0],
response, System.currentTimeMillis() - start
)
}
}
}
I think the appropriate thing to do here is to write the contents of each request to a file in an asynchronous manner (java.nio) and set up an interval that reads those request body files asynchrolusly and writes them to the log in a memory usage aware manner (atleast one file at a time but up too 100 mb at a time) and after logging them removes the files from disk.
Ivan Lymar's answer but in Kotlin:
import org.apache.commons.io.IOUtils
import org.reactivestreams.Publisher
import org.springframework.core.io.buffer.DataBuffer
import org.springframework.http.server.reactive.ServerHttpRequestDecorator
import org.springframework.http.server.reactive.ServerHttpResponseDecorator
import org.springframework.stereotype.Component
import org.springframework.web.server.ServerWebExchange
import org.springframework.web.server.WebFilter
import org.springframework.web.server.WebFilterChain
import reactor.core.publisher.Flux
import reactor.core.publisher.Mono
import java.io.ByteArrayOutputStream
import java.io.IOException
import java.nio.channels.Channels
#Component
class LoggingWebFilter : WebFilter {
override fun filter(exchange: ServerWebExchange, chain: WebFilterChain): Mono<Void> {
val httpRequest = exchange.request
val httpUrl = httpRequest.uri.toString()
val loggingServerHttpRequestDecorator: ServerHttpRequestDecorator =
object : ServerHttpRequestDecorator(exchange.request) {
var requestBody = ""
override fun getBody(): Flux<DataBuffer> {
return super.getBody().doOnNext { dataBuffer: DataBuffer ->
try {
ByteArrayOutputStream().use { byteArrayOutputStream ->
Channels.newChannel(byteArrayOutputStream)
.write(dataBuffer.asByteBuffer().asReadOnlyBuffer())
requestBody =
IOUtils.toString(
byteArrayOutputStream.toByteArray(),
"UTF-8"
)
log.info(
"Logging Request Filter: {} {}",
httpUrl,
requestBody
)
}
} catch (e: IOException) {
log.error(
"Logging Request Filter Error: {} {}",
httpUrl,
requestBody,
e
)
}
}
}
}
val loggingServerHttpResponseDecorator: ServerHttpResponseDecorator =
object : ServerHttpResponseDecorator(exchange.response) {
var responseBody = ""
override fun writeWith(body: Publisher<out DataBuffer>): Mono<Void> {
val buffer: Mono<DataBuffer> = Mono.from(body)
return super.writeWith(
buffer.doOnNext { dataBuffer: DataBuffer ->
try {
ByteArrayOutputStream().use { byteArrayOutputStream ->
Channels.newChannel(byteArrayOutputStream)
.write(
dataBuffer
.asByteBuffer()
.asReadOnlyBuffer()
)
responseBody = IOUtils.toString(
byteArrayOutputStream.toByteArray(),
"UTF-8"
)
log.info(
"Logging Response Filter: {} {}",
httpUrl,
responseBody
)
}
} catch (e: Exception) {
log.error(
"Logging Response Filter Error: {} {}",
httpUrl,
responseBody,
e
)
}
}
)
}
}
return chain.filter(
exchange.mutate().request(loggingServerHttpRequestDecorator)
.response(loggingServerHttpResponseDecorator)
.build()
)
}
}

Spring Boot Rest Controller how to return different HTTP status codes?

I am using Spring Boot for a simple REST API and would like to return a correct HTTP statuscode if something fails.
#RequestMapping(value="/rawdata/", method = RequestMethod.PUT)
#ResponseBody
#ResponseStatus( HttpStatus.OK )
public RestModel create(#RequestBody String data) {
// code ommitted..
// how do i return a correct status code if something fails?
}
Being new to Spring and Spring Boot, the basic question is how do i return different status codes when something is ok or fails?
There are several options you can use. Quite good way is to use exceptions and class for handling called #ControllerAdvice:
#ControllerAdvice
class GlobalControllerExceptionHandler {
#ResponseStatus(HttpStatus.CONFLICT) // 409
#ExceptionHandler(DataIntegrityViolationException.class)
public void handleConflict() {
// Nothing to do
}
}
Also you can pass HttpServletResponse to controller method and just set response code:
public RestModel create(#RequestBody String data, HttpServletResponse response) {
// response committed...
response.setStatus(HttpServletResponse.SC_ACCEPTED);
}
Please refer to the this great blog post for details: Exception Handling in Spring MVC
NOTE
In Spring MVC using #ResponseBody annotation is redundant - it's already included in #RestController annotation.
One of the way to do this is you can use ResponseEntity as a return object.
#RequestMapping(value="/rawdata/", method = RequestMethod.PUT)
public ResponseEntity<?> create(#RequestBody String data) {
if(everything_fine) {
return new ResponseEntity<>(RestModel, HttpStatus.OK);
} else {
return new ResponseEntity<>(null, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
A nice way is to use Spring's ResponseStatusException
Rather than returning a ResponseEntityor similar you simply throw the ResponseStatusException from the controller with an HttpStatus and cause, for example:
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Cause description here");
This results in a response to the client containing the HTTP status:
{
"timestamp": "2020-07-09T04:43:04.695+0000",
"status": 400,
"error": "Bad Request",
"message": "Cause description here",
"path": "/test-api/v1/search"
}
Note: HttpStatus provides many different status codes for your convenience.
In case you want to return a custom defined status code, you can use the ResponseEntity as here:
#RequestMapping(value="/rawdata/", method = RequestMethod.PUT)
public ResponseEntity<?> create(#RequestBody String data) {
int customHttpStatusValue = 499;
Foo foo = bar();
return ResponseEntity.status(customHttpStatusValue).body(foo);
}
The CustomHttpStatusValue could be any integer within or outside of standard HTTP Status Codes.
Try this code:
#RequestMapping(value = "/validate", method = RequestMethod.GET, produces = "application/json")
public ResponseEntity<ErrorBean> validateUser(#QueryParam("jsonInput") final String jsonInput) {
int numberHTTPDesired = 400;
ErrorBean responseBean = new ErrorBean();
responseBean.setError("ERROR");
responseBean.setMensaje("Error in validation!");
return new ResponseEntity<ErrorBean>(responseBean, HttpStatus.valueOf(numberHTTPDesired));
}
There are different ways to return status code,
1 : RestController class should extends BaseRest class, in BaseRest class we can handle exception and return expected error codes.
for example :
#RestController
#RequestMapping
class RestController extends BaseRest{
}
#ControllerAdvice
public class BaseRest {
#ExceptionHandler({Exception.class,...})
#ResponseStatus(value=HttpStatus.INTERNAL_SERVER_ERROR)
public ErrorModel genericError(HttpServletRequest request,
HttpServletResponse response, Exception exception) {
ErrorModel error = new ErrorModel();
resource.addError("error code", exception.getLocalizedMessage());
return error;
}
I think the easiest way is to make return type of your method as
ResponseEntity<WHATEVER YOU WANT TO RETURN>
and for sending any status code, just add return statement as
return ResponseEntity.status(HTTP STATUS).build();
For example, if you want to return a list of books,
public ResponseEntity<List<books>> getBooks(){
List<books> list = this.bookService.getAllBooks();
if(list.size() <= 0)
return ResponseEntity.status(HttpStatus.NOT_FOUND).build();
else
return ResponseEntity.of(Optional.of(list));
}

Resources