Spring Boot REST controller not returning HTML string - spring-boot

I have a Spring Boot app that has one controller that serves mostly RESTful endpoints, but it has 1 endpoint that actually needs to return HTML.
#RestController
#RequestMapping("v1/data/accounts")
public class AccountResource {
// Half a dozen endpoints that are all pure data, RESTful APIs
#GetMapping("/confirmRegistration")
public void confirmRegistration(#RequestParam(value = "vt") String token) {
// Some logic goes here
System.out.println("This should work!");
return ResponseEntity.ok('<HTML><body>Hey you did a good job!.</body></HTML>')
}
}
When this runs, no errors/exceptions get thrown at all, and in fact I see the "This should work!" log message in my app logs. However from both a browser and a curl command, the HTTP response is empty. Any idea what I need to change in the ResponEntity builder to get the server returning a hand-crafted HTML string?

Add this to your #RequestMapping or #GetMapping
produces = MediaType.TEXT_HTML_VALUE
Spring defaults to application\json. If you need any other type, you need to specify it.

Related

How to initialize Jackson on Spring Boot start to have fast 1st request?

Problem
I have a simple Spring Boot app with a basic RestController (full code available here). It consumes JSON and uses Jackson to convert request from JSON and response to JSON.
#RestController("/")
#RequestMapping(consumes = MediaType.APPLICATION_JSON_VALUE)
public class SomeController {
#Autowired
private SomeService someService;
#PostMapping
public ResponseEntity<SomeResponseDto> post(#RequestBody #Valid SomeRequestDto someRequestDto) {
final SomeResponseDto responseDto = new SomeResponseDto();
responseDto.setMessage(someRequestDto.getInputMessage());
responseDto.setUuid(someService.getUuid());
return ResponseEntity.ok(responseDto);
}
After start-up, the 1st request is about 10-times slower than any sub-sequent request. I debugged and profiled the app and it seems that on first request a Jackson JSON parser is getting initialized somewhere in AbstractMessageConverterMethodArgumentResolver.readWithMessageConverters and AbstractJackson2HttpMessageConverter.
In sub-sequent requests, it seems to get re-used.
Question
How do I initialize Jackson JSON parsing during start-up so that also 1st request is fast?
I know how to trigger a method after Spring started. In PreloadComponent I added as an example how to do a REST request against the controller.
#Component
public class PreloadComponent implements ApplicationListener<ApplicationReadyEvent> {
private final Logger logger = LoggerFactory.getLogger(PreloadComponent.class);
#Autowired
private Environment environment;
#Autowired
private WebClient.Builder webClientBuilder;
#Override
public void onApplicationEvent(ApplicationReadyEvent event) {
// uncomment following line to directly send a REST request on app start-up
// sendRestRequest();
}
private void sendRestRequest() {
final String serverPort = environment.getProperty("local.server.port");
final String baseUrl = "http://localhost:" + serverPort;
final String warmUpEndpoint = baseUrl + "/warmup";
logger.info("Sending REST request to force initialization of Jackson...");
final SomeResponseDto response = webClientBuilder.build().post()
.uri(warmUpEndpoint)
.header(CONTENT_TYPE, APPLICATION_JSON_VALUE)
.body(Mono.just(createSampleMessage()), SomeRequestDto.class)
.retrieve()
.bodyToMono(SomeResponseDto.class)
.timeout(Duration.ofSeconds(5))
.block();
logger.info("...done, response received: " + response.toString());
}
private SomeRequestDto createSampleMessage() {
final SomeRequestDto someRequestDto = new SomeRequestDto();
someRequestDto.setInputMessage("our input message");
return someRequestDto;
}
}
This only works in this toy example. In reality, I have many REST endpoints with complex DTOs and I would need to add a "warm-up" endpoint next to each "real" endpoint as I can't call my real endpoints.
What I already tried?
I added a second endpoint with a different DTO and called it in my PreloadComponent. This doesn't solve the problem. I assume that an Jackson / whatever instance is created for each type.
I autowired ObjectMapper into my PreloadComponent and parsed JSON to my DTO. Again, this doesn't solve the issue.
Full source available at: https://github.com/steinsag/warm-me-up
It turns out that Jackson validation is the problem. I added the JVM option
-verbose:class
to see when classes get loaded. I noticed that on 1st request, there are many Jackson validation classes getting loaded.
To confirm my assumption, I re-worked my example and added another independent warm-up controller with a distinct DTO.
This DTO uses all Java validation annotations also present like in the real DTO, e.g. #NotNull, #Min, etc. In addition, it also has a custom enum to also have validation of sub-types.
During start-up, I now do a REST request to this warm-up endpoint, which doesn't need to contain any business logic.
After start-up, my 1st request is now only 2-3 times slower than any sub-sequent requests. This is is acceptable. Before, the 1st request was 20-40 times slower.
I also evaluated if really a REST request is needed or if it is sufficient to just do JSON parsing or validation of a DTO (see PreloadComponent). This reduces runtime of 1st request a bit, but it is still 5-15 times slower than with proper warm-up. So I guess a REST request is needed to also load other classes in Spring Dispatcher, etc.
I updated my example at: https://github.com/steinsag/warm-me-up
I believe, that a lot of classes will be lazy-loaded. If first call performance is important, then I think warming up by calling each endpoint is the way to go.
Why do you say, that you cannot call the endpoints? If you have a database and you don't want to change the data, wrap everything in a transaction and roll it back after the warm up calls.
I haven't seen any other method to solve this, which doesn't necessarily mean, that it doesn't exist ;)

Quarkus RestClient get response from redirect

I'm using Quarkus Rest Client to perform a GET request to an external API service. This service, however, does not directly return the resource (XML) I need to receive, but it performs a redirect to another API service which returns the resource.
When I try to navigate to the path which asks the API service for the resource (i.e. localhost:8080/hello) I get redirected to the page with the resource instead of receiving and processing it.
Putting a breakpoint after the request, shows that the part of the code after the request is never reached.
Here is the code of the endpoint:
#Path("/hello")
public class GreetingResource {
#Inject
#RestClient
MyService myService;
#GET
#Produces(MediaType.TEXT_PLAIN)
public String hello() {
myService.performGet();
return "you are here"; // breakpoint here, it is never reached
}
}
And here is the code of MyService:
#Path("/..")
#RegisterRestClient(configKey="key")
public interface MyService {
#GET
#Path("/..")
#Produces(MediaType.TEXT_XML)
String performGet(#QueryParam("c") String c, #QueryParam("d") String d);
}
I have tried to add the configuration key/mp-rest/followRedirects=true, but I still get the same problem. Also, with a path without redirects, everything works fine.
Using the native HttpURLConnection also works fine, but, since I am using Quarkus, I would like to use its features instead.

How Do I configure Spring View Resolver to Ignore a Specific Url and Not Look for Its .jsp?

I am working on a web application using Spring MVC architecture. I have a controller method that will be called by an ajax post(). The request mapper in the controller has a ".html" (meant for some cleanup task)for which the Spring Internal view resolver is trying to find a matching .JSP file and throws a 404 Not Found error. I donot want to create a .JSP which is not useful in my case. I need some help to determine if there is any setting in Spring Context xml to let the view resolver ignore this url and not to look for its .JSP file.
#RequestMapping(value = "/clearSession.html")
public void unloadDAOSession(HttpServletRequest request) {...}
the InternalViewResolver is looking for clearSession.jsp which throws a 404 Resource Not found. I dont want to create a JSP which is of no use.
Are there any application Context settings in the view resolver to ignore this ".html" file and not to look for its ".jsp"?
Any help is appreciated.
Even though the return type is void it only means that the view name will be resolved based on the URL as you have seen.
One way to avoid view resoluion is to annotate the response with #ResponseBody or
bypass the view resolver by tweaking your return type to something like
#RequestMapping(value = "/clearSession.html")
public ResponseEntity<String> unloadDAOSession(HttpServletRequest request) {
...
return new ResponseEntity<String>("OK",HttpStatus.NO_CONTENT);
}
This way instead of forwarding to a view, your just adding a header and an empty body to the response stream
In case of use of ajax and traditional controller, the best approach is write the your controller for answare to the page rendering as you had done adn then write a rest end-point layer for answare to ajax request.
I just give you some piece of code for clearing what I want say:
Controller layer:
#RequestMapping(value = "/yourPage")
public void yourPage(Model mode) {...}
Rest layer:
#RequestMapping(value = "/yourAjax")
public ResponseEntity<String> yourAjax(#RequstBody YoutDTOClass postBody) {
...
return ResponseEntity.created(...).build();
}
class YoutDTOClass{
// your properties
....
}
with this schema you separate the service layer for your ajax request and the classic web layer for serving the your page.
Just an hint don't use a pattern like /yourPage.html but you should prefare a pattern like /youtPage.This balically for banefit of the view resolver abstraction of Spring, you should let to springMVC to resolve the actual view.
Even for the ajax you should use the accept header for ask the correct rappresentation of your resource and the ResponseEntity should be used for return a 201 Http status for say to your browser that the your resource was created. remember that a better approach should prefere the use of http method in REST world and then for delete a session you should be prefare delte method for delete a session and put http method for update the sate of the session.
I hope that this can help you

Spring boot and checking HTTP header presence

I send along every REST call my custom header, which is (for instance) an authorization token. This token remains the same, as I do not need high security in this case. Can I use some simple way how to check every request coming to RestController whether it has this token among headers?
I can see a few ways:
Coding a #ModelAttribute in a #ControllerAdvice class, like this
#ControllerAdvice
public class HeaderFetcher {
#ModelAttribute
public void fetchHeader(#RequestHeader Optional<String> myHeader, Model model) {
if header is present
model.addAttribute("myHeader", the header);
else
throw exception
}
}
Haven't tried this, though.
Using a filter
Using AoP

Spring MVC image or character generic controller

I would like to implement a generic controller with one or two methods that react to any GET request. I am trying to simplify this to the point where I can return byte (image etc.) or character based (XML, CSS) without having to map each content type and put a RequestMapping in for each.
The app must be abel to handle any request with any content type.
My dispatcher is currently set to handle all requests via /.
The couple of attempts I have made so far throw ambigious handler errors, or the mapping doesn;t work to the point where text is sent back as byte[] or the other way around.
Has anyone made anything like this work ?
Regards,
Andy
You can have a controller like so
#Controller
public class YourController {
#RequestMapping("/*")
public String doLogic(HttpServletRequest request, HttpServletResponse response) throws Exception {
OutputStream out = response.getOutputStream();
out.write(/* some bytes, eg. from an image*/); // write the response yourself
return null; // this is telling spring that this method handled the response itself
}
}
The controller is mapped to every url and every http method. Spring has a set of accepted return types for its handler methods. With String, if you return null, Spring assumes you've handled the response yourself.
As #NilsH commented, you might be better off using a simple servlet for this.

Resources