Spring MVC controller is to be used as a proxy for another service to serving possibly large files.
To avoid storing the whole WebClient response from another service in memory at any given time I wanted to use reactive properties of Spring 5 + Reactor project and stream its response as it's consumed by the browser.
The current code looks like
import org.springframework.stereotype.Controller
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.reactive.function.client.ClientResponse
import org.springframework.web.reactive.function.client.WebClient
import reactor.core.publisher.Mono
#Controller
class ReportController {
#GetMapping("/reports")
fun getReport(): Mono<ClientResponse> {
val webCient = WebClient.create()
return webCient
.get()
.uri("http://localhost:3333/file")
.exchange()
}
}
And it causes
java.lang.IllegalStateException: Could not resolve view with name 'reports'.
at org.springframework.web.reactive.result.view.ViewResolutionResultHandler.lambda$resolveViews$3(ViewResolutionResultHandler.java:277) ~[spring-webflux-5.0.6.RELEASE.jar:5.0.6.RELEASE]
How to configure the controller to handle Mono<ClientResponse>?
If the response is of type Mono will the request be backpressured? Is there a need to convert it to bytes chunks and make it Flow?
Spring documentation says a lot of benefits of backpressure but doesn't answer the question above, nor gives any warning about Mono response. Is it still reactive?
Related
html file not returning in the Rest API and return the file name only like 'index' for the following code when used #RestController and working fine only for #Controller.
Here I am using spring boot REST application and index.html with bootstrap
If you know REST web services then you must know the fundamental difference between a REST API and a web application i.e. the response from a web application is generally view (HTML + CSS) because they are intended for human viewers.
REST API just returns data in form of JSON or XML because most of the REST clients are programs. This difference is also obvious in the #Controller and #RestController annotation.
#RestController = #Controller + #ResponseBody
When you use #Controller annotation then it corresponds to MVC workflow in Spring Boot and it renders the view. The key difference is that you do not need to use #ResponseBody on each and every handler method once you annotate the class with #RestController.
#ResponseBody is a Spring annotation which binds a method return value to the web response body. It is not interpreted as a view name. It uses HTTP Message converters to convert the return value to HTTP response body, based on the content-type in the request HTTP header. The content type can be JSON or XML.
It is the expected behaviour of #RestController. The main difference between #RestController and #Controller is #RestController will by-pass the view resolution but #Controller will not.
Technically , it is due to the presence of the #ResponseBody of the #RestController. #ResponseBody will enable RequestResponseBodyMethodProcessor to process the result return from the #RestController method and it will mark that the request has been fully handled within the controller method and by-pass the view resolution process (see this).
I have an existing spring-mvc application with different #RestController. Now I want to add a Mono<String> endpoint, and log the request timestamp with url path, as well as the response timestamp.
But how? I cannot simply #EnableWebFlux as I would have to disable spring-mvc therefor.
How could I register a filter explicit for the webflux endpoint that catches on invocation, and right before the response is written back async?
#RestController
public class FluxService {
#PostMapping
public Mono<String> post() {
return webClient.post().uri(uri).bodyValue(payload).retrieve().bodyToMono(String.class);
}
}
I want enhanced logging on the #PostMapping endpoint, not on the webClient!
MVC is a blocking/servlet based api, mixing it with webflux isn't going to work. They use different filters, security etc as MVC assumes a lot of ThreadLocal stuff. (Can I use SpringMvc and webflux together?)
If you really want to mix them like this you could use .doOnNext() once you get your mono and call your logger there.
I am learning spring MVC and have wrote following code. I read some articles about SOAP and REST but in the beginner level controller code I have written I am not able to distinguish whether SOAP or REST is used. My controller code is as follows:
import java.util.ArrayList;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import com.model.Customer;
#Controller
public class SelectController {
#RequestMapping("/")
public String display(){
System.out.println("Inside controller");
return "demo";
}
#RequestMapping("/index")
public String indexpage(HttpServletRequest req, Model m){
String name = req.getParameter("name");
String pass = req.getParameter("pass");
String random = req.getParameter("abc");
System.out.println("Index page"+name+pass+random);
Customer cust = new Customer();
cust.setUsername(name);
cust.setPassword(pass);
System.out.println("Index page"+name+pass);
m.addAttribute("customer", cust);
return "hello";
}
The Controller that you have written is
neither REST nor SOAP.
Its a MVC Controller.
In your case, your returning "hello" string at the end of controller method, which in-turn gets translated/mapped to a page(hello.jsp or hello.html based on the configuration) and returns the same. So, at the end, what you get is Page rendered in a beautiful way with all the necessary Stylings and scripts applied.
In contrast, REST and SOAP doesn't work in that way. Its main purpose is for transferring the data alone(Yet you can send HTML page also)
Writing a REST Service is almost similar to what you have currently and is fairly easy. If you use Springboot then all you have to do is just replace the #Controller annotation with #RestController and return Customer object directly. In REST Controller you wont have HttpServletRequest & Model arguments.
But writing a SOAP service is another topic which may seem entirely different.
There are tons of examples and tutorials scattered around the web, which you can find easily on these topics.
References :
Controller vs RestController in Spring
Difference between Controller & RestController in Spring
SOAP vs REST
Hope this gives some high level idea of what those three are.
I have a question regarding Spring Webflux. I wanted to create a reactive endpoint that consumes content type text/event-stream. Not produce but consume. One of our services needs to send a lot of small objects to another one and we thought that streaming it this way might be a good solution.
#PostMapping(value = "/consumeStream", consumes = MediaType.TEXT_EVENT_STREAM_VALUE)
public Mono<Void> serve(#RequestBody Flux<String> data) {
return data.doOnNext(s -> System.out.println("MessageReceived")).then();
}
I am trying to use Spring WebClient to establish a connection to the endpoint and stream data to it. For example using code:
WebClient.builder().baseUrl("http://localhost:8080")
.clientConnector(new ReactorClientHttpConnector())
.build()
.post()
.uri("/test/serve")
.contentType(MediaType.TEXT_EVENT_STREAM)
.body(BodyInserters.fromPublisher(flux, String.class))
.exchange()
.block();
The flux is a stream that produces a single value every 1 sec.
The problem I have is that the WebClient fully reads the publisher and then sends the data as a whole and not streams it one by one.
Is there anything I can do to do this using this client or any other ? I do not want to go the websockets way.
SSE standard does not allow POST. There is no way to specify method even in browser API https://www.w3.org/TR/eventsource/
Server Side Events as name states are designed for delivering events from the server to the client.
I'm developing some reactive microservices using Spring Boot 2 and Spring 5 with WebFlux reactive starter.
I'm facing the following problem: I want to handle all HTTP Statuses that I receive from calling another REST Services and throws an exception when I receive some bad HTTP Status. For example, when I call an endpoint and I receive an 404 HTTP Status, I want to throw an exception and that exception to be handled in some ExceptionHandler class, just like the way it was in Spring 4 with #ControllerAdvice.
What is the right way to do this? Hope to receive some good suggestions.
This can be addressed in two independent parts.
How to convert HTTP 404 responses received by WebClient into custom exceptions
When using WebClient, you can receive HTTP 404 responses from remote services. By default, all 4xx and 5xx client responses will be turned into WebClientResponseException. So you can directly handle those exceptions in your WebFlux app.
If you'd like to turn only 404 responses into custom exceptions, you can do the following:
WebClient webClient = //...
webClient.get().uri("/persons/1")
.retrieve()
.onStatus(httpStatus -> HttpStatus.NOT_FOUND.equals(httpStatus),
clientResponse -> Mono.error(new MyCustomException()))
.bodyToMono(...);
This is obviously done on a per client call basis.
You can achieve the same in a more reusable way with an ExchangeFilterFunction that you can set once and for all on a WebClient instance like this:
WebClient.builder().filter(myExchangeFilterFunction)...
How to handle custom exceptions in WebFlux apps
With Spring WebFlux with annotations, you can handle exceptions with methods annotated with #ExceptionHandler (see Spring Framework reference documentation).
Note: using a WebExceptionHandler is possible, but it's quite low level as you'll have no high-level support there: you'll need to manually write the response with buffers without any support for serialization.
I think what you are looking for is WebFluxResponseStatusExceptionHandler the check this for reference.
In the WebHandler API, a WebExceptionHandler can be used to to handle
exceptions from the chain of WebFilter's and the target WebHandler.
When using the WebFlux Config, registering a WebExceptionHandler is as
simple as declaring it as a Spring bean, and optionally expressing
precedence via #Order on the bean declaration or by implementing
Ordered.
This example may help, have not tried it myself.
#Component
#Order(-2)
class RestWebExceptionHandler implements WebExceptionHandler{
#Override
public Mono<Void> handle(ServerWebExchange exchange, Throwable ex) {
if (ex instanceof PostNotFoundException) {
exchange.getResponse().setStatusCode(HttpStatus.NOT_FOUND);
// marks the response as complete and forbids writing to it
return exchange.getResponse().setComplete();
}
return Mono.error(ex);
}
}
class PostNotFoundException extends RuntimeException {
PostNotFoundException(String id) {
super("Post:" + id + " is not found.");
}
}