Given: I have a service which produces a Flowable<T>. This Flowable<T> can be empty.
I have a controller, which looks similar to this:
#Controller("/api}")
class ApiController constructor( private val myService: MyService) {
#Get("/")
#Produces(MediaType.APPLICATION_JSON)
fun getSomething(): Flowable<T> {
return myService.get()
}
}
What I want to achieve: when the flowable is empty -> throw a HttpStatusException(404).
Otherwise return the flowable with the data inside.
What I already tried
I tried different combinations of the following RxJava Operators:
doOnError
onErrorResumeNext
onErrorReturn
switchIfEmpty
...
What I experienced
None of the options produced a 404 in the Browser/Postman.
A couple of options are just doing "nothing". Which means, that the page is not loading in the browser.
Other options are creating "OK" (200) responses with empty bodies.
And some are creating a CompositeException...
Does someone have a hint for me?
Update: as suggested:
#Controller("/api")
class HelloController {
#Get("/")
#Produces(MediaType.APPLICATION_JSON)
fun get(): Flowable<String> {
return Flowable.empty<String>()
.switchIfEmpty {
it.onError(HttpStatusException(HttpStatus.NOT_FOUND,""))
}
}
}
This produces the following, when I call it with firefox:
HttpResponseStatus: 200
HttpContent: [
Yes, the closing bracet is missing!
A possible Solution is, to use Maybe instead of Flowable.
#Controller("/api")
class HelloController {
#Get("/")
#Produces(MediaType.APPLICATION_JSON)
fun get(): Maybe<String> {
return Flowable.empty<String>()
.toList()
.flatMapMaybe { x ->
if (x.size == 0)
Maybe.empty<String>()
else
Maybe.just(x)
}
}
}
}
It is not the best solution, but a working one.
I don't know Micronaut, but I think this may be what you want:
Flowable.empty<Int>()
.switchIfEmpty { it.onError(Exception()) } // or HttpStatusException(404) in your case
.subscribe({
println(it)
}, {
it.printStackTrace()
})
The Flowable is empty, and what you get downstream is the Exception created inside switchIfEmpty. Note that you have to call it.onError inside switchIfEmpty.
Related
I hope someone can help me or at least point me to the right direction.
I'm only getting familiar with reactive programming, so this concepts are pretty new to me.
Let's assume that I have this RestController:
#RestController
#RequiredArgsConstructor
#RequestMapping("/states")
public class StateController {
private final StateService stateService;
#GetMapping(value = "/stream/{ids}", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<State> getConfiguredStatesStream(#PathVariable List<Long> ids) {
return stateService.getStatesByIds(oddsIds);
}
}
And a Service:
#Service
#RequiredArgsConstructor
public class StateService {
...
public Flux<State> getStatesByIds(List<Long> ids) {
return Flux.fromIterable(ids)
.flatMap(id -> calculateStateById(id)); // calculateStateById(id) returns Mono<State>
}
}
When I make a request to an endpoint with a lot of ids, I was expecting the stream to work like this:
In flatMap, when State is calculated and returned, it is immediately sent through stream.
Instead, I can see that it first calculates all states and then returns them all in one bulk.
How do I sent a State back the moment it is calculated (reactive way)?
Have the following implementation of webclient :
public <T> WebClient.ResponseSpec sendRequest(HttpMethod method, String contentType, T body, String baseUrl, String path) {
try {
WebClient webClient = WebClient.builder().baseUrl(baseUrl).filter(logRequest()).build();
WebClient.ResponseSpec responseSpec = webClient.method(method)
.uri(path)
.header(HttpHeaders.CONTENT_TYPE, contentType)
.body(BodyInserters.fromObject(body))
.retrieve();
return responseSpec;
} catch (Exception e) {
throw new WebClientProcessingException("Exception when trying to execute request", e);
}
}
// This method returns filter function which will log request data
private static ExchangeFilterFunction logRequest() {
return ExchangeFilterFunction.ofRequestProcessor(clientRequest -> {
LOGGER.info("Request: {} {} {}", clientRequest.method(), clientRequest.url(), clientRequest.body());
clientRequest.headers().forEach((name, values) -> values.forEach(value -> LOGGER.info("{}={}", name, value)));
return Mono.just(clientRequest);
});
}
Also have the following code , which is creating user object and command which contains user object , then calling webclient to send an request
#Autowired
private BaseWebClient baseWebClient;
#Override
public void saveOrUpdateUser() {
UserPayload userPayload = new UserPayload();
userPayload.setUserId(111L);
userPayload.setCreatedAt(ZonedDateTime.now(DateTimeProps.systemTimeZone));
UserCommand userCommand = new UserCommand();
userCommand.setUser(userPayload);
baseWebClient.sendRequest(HttpMethod.POST, "application/json",
Stream.of(userCommand).collect(Collectors.toList()),
"http://localhost:8080",
"/users").onStatus(HttpStatus::isError, clientResponse -> {
throw new WebClientUserSaveOrUpdateeFailedException("Exception when trying to update user state")
.bodyToMono(String.class);
});
}
User payload :
#Data
#NoArgsConstructor
#AllArgsConstructor
public class UserPayload {
Long userId;
ZonedDateTime createdAt;
}
User command :
#Data
#NoArgsConstructor
#AllArgsConstructor
public class UserCommand {
#JsonProperty("user")
UserPayload user;
}
Json which is waiting for my other app (whom I am sending a request) :
[
{ "user":
{
"userId": 1,
"createdAt": "2019-05-16T08:24:46.412Z"
}
}
]
Using : Spring boot 2 , Lombok (for getter/setter) , gradle
When I'm trying to send a request nothing happens. No exception even.
I tried with very simple case as well the same issue.
One more note, is it possible to log body? I mean somehow see final json
I guess I am missing something general.
In Reactor, nothing happens until you subscribe. retrive() does not actually start the request. As you can see in the example, you should use one of the method to convert the ResponseSpec to a Publisher and then eventually subscribe to that publisher.
Depending on how you're using this method, you might be able to let Spring subscribe to the publisher instead. WebFlux supports reactive types in the model which means you can directly return a Mono from your RestController methods, for example.
i'm using both a WebFilter and a WebExceptionHandler.
The WebFilter should add a new header only if the ExceptionHandler didn't set it.
However, the WebFilter is added to the ServerWebExchange before the ExceptionHandler is executed by the WebHttpHandler, so it cannot tell whether the ExceptionHandler was triggered or not.
#Component
#Order(-2)
class MyErrorWebExceptionHandler(g: MyErrorAttributes, applicationContext: ApplicationContext, serverCodecConfigurer: ServerCodecConfigurer)
: AbstractErrorWebExceptionHandler(g, ResourceProperties(), applicationContext) {
init {
super.setMessageWriters(serverCodecConfigurer.writers)
super.setMessageReaders(serverCodecConfigurer.readers)
}
#Override
override fun getRoutingFunction(errorAttributes: ErrorAttributes): RouterFunction<ServerResponse> {
return RouterFunctions.route(RequestPredicates.all(), HandlerFunction<ServerResponse> { renderErrorResponse(it) })
}
private fun renderErrorResponse(request: ServerRequest): Mono<ServerResponse> {
val errorPropertiesMap = getErrorAttributes(request, false)
return ServerResponse.status(HttpStatus.OK)
.contentType(MediaType.APPLICATION_JSON_UTF8)
.headers { x ->
x.set(c_ApplicationStatus, errorPropertiesMap[c_ApplicationStatus].toString())
}.build()
}
#Component
class ServerResponseHeaderWebFilter : WebFilter {
override fun filter(exchange: ServerWebExchange, chain: WebFilterChain): Mono<Void> {
exchange.response.headers.set(c_ApplicationStatus, HttpStatus.OK.value().toString())
return chain.filter(exchange)
}
}
We can quickly model the execution order in this case, with something like:
WebFilter
|- setting the application status header to OK
|- calling chain.filter
|- finding the handler and calling it
|- in case of an error, the exception handler is called
|- after chain.filter
Once the filter chain is done with the exchange, the response has been committed and processed, so there's no way to change the response headers at that point. With this code sample, the exception handler, if executed, will override whatever header the web filter has set.
So technically, the answer to your question is that there's no way to modify the response once the handler chain has taken care of it. This is the expected behavior of Spring WebFlux, by design.
But it sounds like we need to take a step back and talk about what you're trying to achieve.
Are you trying to check whether your exception handler is being called at all while developing a feature?
Are you trying to adapt the error handling mechanism in Spring Boot in some way that's not possible right now?
If you'd like to talk about what you're trying to achieve, please ask another question so that SO users can benefit from this question.
eventually i found that the header set in the filter can be seen in the request.exchange() object. it must be removed there for the new header set to replace it.
private fun renderErrorResponse(request: ServerRequest): Mono<ServerResponse> {
val errorPropertiesMap = getErrorAttributes(request, false)
request.exchange().response.headers.remove(c_ApplicationStatus)
return ServerResponse.status(HttpStatus.OK)
.contentType(MediaType.APPLICATION_JSON_UTF8)
.headers { x -> x.set(c_ApplicationStatus, value) }
.build()
}
I am new to both Spring and the REST API overall but I am now trying to make one. So from my controller, I want to return a list of files that another method returns in another class. The code probably says more then I can explain:
#RequestMapping("/backup")
public #ResponseBody List<FileInfo> backupFiles() {
return //Here i want to return the list of files
}
This is in my restController the "method" i want to return a list of FileInfo and today it already exists a method that does this that looks like this
private List<String> listBackupFiles() {
List<FileInfo> files = util.listBackupFilesInLocalDir(localStorage);
fileNameToSize = files.stream()
.collect(toMap(f -> f.name, f -> f.size));
return files.stream()
.map(f -> f.name)
.collect(toList());
}
So basically, I want to when someone goes to /backup I want the above method to trigger and return the list of files to my restController that then returns it to the requester. I don't know if this is even possible or if there is a better way to do this. I take any tips on how to tackle this problem.
The controller would be:
// #RestController = #Controller + #ResponseBody
#RestController
public class BackupController {
#Autowired
private BackupService backupService;
// you don't need #ResponseBody as you use #RestController
#RequestMapping("/backup")
public List<FileInfo> backupFiles() {
return backupService.listBackupFiles()
}
}
The service interface would be:
public interface BackupService {
public List<FileInfo> listBackupFiles();
}
The service implementation would be:
#Service
public class BackupServiceImpl implements BackupService {
public List<FileInfo> listBackupFiles() {
// localStorage come from
// maybe Util has it as static method, else inject it
return util.listBackupFilesInLocalDir(localStorage);
}
}
Hope was helpful :)
I've been doing some research using spring-webflux and I like to understand what should be the right way to handle errors using Router Functions.
I've created an small project to test a couple of scenarios, and I like to get feedback about it, and see what other people is doing.
So far what I doing is.
Giving the following routing function:
#Component
public class HelloRouter {
#Bean
RouterFunction<?> helloRouterFunction() {
HelloHandler handler = new HelloHandler();
ErrorHandler error = new ErrorHandler();
return nest(path("/hello"),
nest(accept(APPLICATION_JSON),
route(GET("/"), handler::defaultHello)
.andRoute(POST("/"), handler::postHello)
.andRoute(GET("/{name}"), handler::getHello)
)).andOther(route(RequestPredicates.all(), error::notFound));
}
}
I've do this on my handler
class HelloHandler {
private ErrorHandler error;
private static final String DEFAULT_VALUE = "world";
HelloHandler() {
error = new ErrorHandler();
}
private Mono<ServerResponse> getResponse(String value) {
if (value.equals("")) {
return Mono.error(new InvalidParametersException("bad parameters"));
}
return ServerResponse.ok().body(Mono.just(new HelloResponse(value)), HelloResponse.class);
}
Mono<ServerResponse> defaultHello(ServerRequest request) {
return getResponse(DEFAULT_VALUE);
}
Mono<ServerResponse> getHello(ServerRequest request) {
return getResponse(request.pathVariable("name"));
}
Mono<ServerResponse> postHello(ServerRequest request) {
return request.bodyToMono(HelloRequest.class).flatMap(helloRequest -> getResponse(helloRequest.getName()))
.onErrorResume(error::badRequest);
}
}
Them my error handler do:
class ErrorHandler {
private static Logger logger = LoggerFactory.getLogger(ErrorHandler.class);
private static BiFunction<HttpStatus,String,Mono<ServerResponse>> response =
(status,value)-> ServerResponse.status(status).body(Mono.just(new ErrorResponse(value)),
ErrorResponse.class);
Mono<ServerResponse> notFound(ServerRequest request){
return response.apply(HttpStatus.NOT_FOUND, "not found");
}
Mono<ServerResponse> badRequest(Throwable error){
logger.error("error raised", error);
return response.apply(HttpStatus.BAD_REQUEST, error.getMessage());
}
}
Here is the full sample repo:
https://github.com/LearningByExample/reactive-ms-example
Spring 5 provides a WebHandler, and in the JavaDoc, there's the line:
Use HttpWebHandlerAdapter to adapt a WebHandler to an HttpHandler. The WebHttpHandlerBuilder provides a convenient way to do that while also optionally configuring one or more filters and/or exception handlers.
Currently, the official documentation suggests that we should wrap the router function into an HttpHandler before booting up any server:
HttpHandler httpHandler = RouterFunctions.toHttpHandler(routerFunction);
With the help of WebHttpHandlerBuilder, we can configure custom exception handlers:
HttpHandler httpHandler = WebHttpHandlerBuilder.webHandler(toHttpHandler(routerFunction))
.prependExceptionHandler((serverWebExchange, exception) -> {
/* custom handling goes here */
return null;
}).build();
If you think, router functions are not the right place to handle exceptions, you throw HTTP Exceptions, that will result in the correct HTTP Error codes.
For Spring-Boot (also webflux) this is:
import org.springframework.http.HttpStatus;
import org.springframework.web.server.ResponseStatusException;
.
.
.
new ResponseStatusException(HttpStatus.NOT_FOUND, "Collection not found");})
spring securities AccessDeniedException will be handled correctly, too (403/401 response codes).
If you have a microservice, and want to use REST for it, this can be a good option, since those http exceptions are quite close to business logic, and should be placed near the business logic in this case. And since in a microservice you shouldn't have to much businesslogic and exceptions, it shouldn't clutter your code, too... (but of course, it all depends).
Why not do it the old fashioned way by throwing exceptions from handler functions and implementing your own WebExceptionHandler to catch 'em all:
#Component
class ExceptionHandler : WebExceptionHandler {
override fun handle(exchange: ServerWebExchange?, ex: Throwable?): Mono<Void> {
/* Handle different exceptions here */
when(ex!!) {
is NoSuchElementException -> exchange!!.response.statusCode = HttpStatus.NOT_FOUND
is Exception -> exchange!!.response.statusCode = HttpStatus.INTERNAL_SERVER_ERROR
}
/* Do common thing like logging etc... */
return Mono.empty()
}
}
Above example is in Kotlin, since I just copy pasted it from a project I´m currently working on, and since the original question was not tagged for java anyway.
You can write a Global exception handler with custom response data and response code as follows. The code is in Kotlin. But you can convert it to java easily:
#Component
#Order(-2)
class GlobalWebExceptionHandler(
private val objectMapper: ObjectMapper
) : ErrorWebExceptionHandler {
override fun handle(exchange: ServerWebExchange, ex: Throwable): Mono<Void> {
val response = when (ex) {
// buildIOExceptionMessage should build relevant exception message as a serialisable object
is IOException -> buildIOExceptionMessage(ex)
else -> buildExceptionMessage(ex)
}
// Or you can also set them inside while conditions
exchange.response.headers.contentType = MediaType.APPLICATION_PROBLEM_JSON
exchange.response.statusCode = HttpStatus.valueOf(response.status)
val bytes = objectMapper.writeValueAsBytes(response)
val buffer = exchange.response.bufferFactory().wrap(bytes)
return exchange.response.writeWith(Mono.just(buffer))
}
}
A quick way to map your exceptions to http response status is to throw org.springframework.web.server.ResponseStatusException / or create your own subclasses...
Full control over http response status + spring will add a response body with the option to add a reason.
In Kotlin it could look as simple as
#Component
class MyHandler(private val myRepository: MyRepository) {
fun getById(req: ServerRequest) = req.pathVariable("id").toMono()
.map { id -> uuidFromString(id) } // throws ResponseStatusException
.flatMap { id -> noteRepository.findById(id) }
.flatMap { entity -> ok().json().body(entity.toMono()) }
.switchIfEmpty(notFound().build()) // produces 404 if not found
}
fun uuidFromString(id: String?) = try { UUID.fromString(id) } catch (e: Throwable) { throw BadRequestStatusException(e.localizedMessage) }
class BadRequestStatusException(reason: String) : ResponseStatusException(HttpStatus.BAD_REQUEST, reason)
Response Body:
{
"timestamp": 1529138182607,
"path": "/api/notes/f7b.491bc-5c86-4fe6-9ad7-111",
"status": 400,
"error": "Bad Request",
"message": "For input string: \"f7b.491bc\""
}
What I am currently doing is simply providing a bean my WebExceptionHandler :
#Bean
#Order(0)
public WebExceptionHandler responseStatusExceptionHandler() {
return new MyWebExceptionHandler();
}
The advantage than creating the HttpHandler myself is that I have a better integration with WebFluxConfigurer if I provide my own ServerCodecConfigurer for example or using SpringSecurity