I have a SpringMVC web service for uploading files which looks like this:
#RequestMapping(value="/upload.json", method = RequestMethod.POST)
public #ResponseBody Map<String, Object> upload(MultipartHttpServletRequest request) {
// upload the file
}
and everything is dandy. But if one of the consumers posts a non-multipart form, then i get this exception
java.lang.IllegalStateException: Current request is not of type [org.springframework.web.multipart.MultipartHttpServletRequest]
Which makes sense.. however I dont want my end users to see 500 servlet exceptions. I want a friendly error message.
I just tried this (to be like a catchall for other POSTs):
#RequestMapping(value="/upload.json", method = RequestMethod.POST)
public #ResponseBody Map<String, Object> upload2(){
// return friendly msg
}
but I get this error:
java.lang.IllegalStateException: Ambiguous handler methods mapped for HTTP path '/upload.json'
Is there any way to safely handle both multipart and non-multipart POST requests? in one method, or 2 different methods i dont care.
Check if the request is a multipart yourself:
#RequestMapping(value="/upload.json", method = RequestMethod.POST)
public #ResponseBody Map<String, Object> upload(HttpServletRequest request) {
if (request instanceof MultipartHttpServletRequest) {
// process the uploaded file
}
else {
// other logic
}
}
Related
I'm working with Facebook messenger app (chatbot) and I want to see what GET request I'm receiving from it. I'm using Spring Framework to start http server and ngrok to make it visible for facebook.
Facebook sending webhooks to me and i receive them, but i don't understand how to extract data from this request. Here what i get when I try HttpRequest to receive GET request. ngrok screenshot (error 500).
When I tried without HttpRequest, i had response 200 (ok).
What do i need to put to parameters of my find method to see GET request data?
My code:
#RestController
public class botAnswer {
#RequestMapping(method= RequestMethod.GET)
public String find(HttpRequest request) {
System.out.println(request.getURI());
String aaa = "222";
return aaa;
}
}
I guess HttpRequest will not help you here. For simplicity, just change HttpRequest to HttpServletRequest. You can access all query string parameters from it using request.getParameter("..."). Something like the following should work:
#RequestMapping(value = "/", method = RequestMethod.GET)
public String handleMyGetRequest(HttpServletRequest request) {
// Reading the value of one specific parameter ...
String value = request.getParameter("myParam");
// or all parameters
Map<String, String[]> params = request.getParameterMap();
...
}
This blog post shows how to use the #RequestParam annotation as an alternative to reading the parameters from HttpServletRequest directly.
I want to override the whitelabel error page. So as an example I have done this simple class:
#RestController
public class MyCustomErrorController implements ErrorController {
private static final String PATH = "/error";
#RequestMapping(value = PATH)
public String error() {
return "This is the error page";
}
#Override
public String getErrorPath() {
return PATH;
}
}
I have taken my example from here:
https://gist.github.com/jonikarppinen/662c38fb57a23de61c8b
According to that gist, it actually has a comment like this:
// Appropriate HTTP response code (e.g. 404 or 500) is automatically set by Spring.
// Here we just define response body.
However that's not what I'm seeing. For instance if I hit to a URL that I know that it should respond me a 500 status code (intentional NullPointerException), then that's what I should see, but when I hit to that URL I get a 200 response back with my error message ("This is the error page")
If I don't use this custom controller, then it shows me a 500 error page with the stacktrace on it, which is the default behavior. I have seen an old issue opened in 2014 here:
https://github.com/spring-projects/spring-boot/issues/684 that someone mentioning the same problem, however their solution is to show explicitly 500 responses, which does not really pass through the HTTP response code.
Just for the record, I actually put a breakpoint to org.apache.catalina.connector.Response.sendError() method. When this custom error controller does not exist, I can clearly see that sendError() method is being called with a status 500. However if I were to add HttpServletResponse argument to my error() method I do not see that the instance has 500 status code set.
Explanation
In the website example you provided, the HttpStatus is retrieved from the injected HttpServletResponse.
So the following:
Appropriate HTTP response code (e.g. 404 or 500) is automatically set by Spring.
means
Spring sets it on the HttpServletResponse that he gets injected into his method as
argument.
He then has to retrieve the status and set it on his model ErrorJson.
Solution
To follow your example, you could change your method to this:
#RequestMapping(value = ERROR_MAPPING)
public ResponseEntity<String> error(HttpServletResponse response) {
return new ResponseEntity<String>("This is the error page",
HttpStatus.valueOf(response.getStatus()));
}
I used ResponseEntity<String> instead of defining a custom object (a.k.a. ErrorJson).
As I believe you know, alternatively to using the HttpServletResponse's status, you could just set yours with HttpStatus.
Here you are simply returning a message from one method, which is not an error as per SpringBoot.
Following method will help you to return http status code as you want :
#RequestMapping(value = PATH)
public ResponseEntity<Map<String, Object>> error() {
Map<String, Object> map = new HashMap<>();
String statusMessage = "This is the error page";
String statusCode = HttpStatus.BAD_REQUEST.value();
map.put(STATUS_CODE, statusCode);
map.put(STATUS_MESSAGE, statusMessage);
return ResponseEntity.badRequest().body(map);
}
I have a controller mapping, where I pass 2 request params instead of 1. But when done like that Spring is not throwing any exception rather it is ignoring the additional request params.
For eg:
#RequestMapping(value="/test", method = RequestMethod.GET)
public ModelAndView eGiftActivation(#RequestParam("value") String value)
When I hit my app using /test.do?value=abcd it is working fine. But when I pass additional params like /test.do?value=abcd&extra=unwanted also it's working fine.
In this case I want Spring to restrict the second URL where additional params are passed.
How can I achieve this?
You could check it manually, like this:
#RequestMapping("/test")
public ModelAndView eGiftActivation(HttpServletRequest request) {
Map<String, String[]> params = request.getParameterMap();
if (params.size() != 1 || !params.containsKey("value")) {
throw new RuntimeException("Extra parameters are present"); // or do redirect
}
...
}
I don't think it's possible (For Spring to prevent the request to flow to any controller's method). The reason being that:
Your controller handles request based on the URI path like, /app/hello/{name} rather than the request parameters
Request parameters are there to give extra set of meta-info for the request rather than endpoint specification of request.
But, if you wanted to restrict the URI path as such, you can use regex and you can avoid. I'm afraid it's not feasible and even the requirement for that never arose.
Programmatical Way:
Having said that, you can take HttpServletRequest for parameters and loop through the parameters to check for extra ones:
#RequestMapping(value="/test", method = RequestMethod.GET)
public Object eGiftActivation(#RequestParam("value") String value, HttpServletRequest request){
//check the request.getParameterMap() and throw custom exception if you need and handle using Exception handler or throw invalid request
return new ResponseEntity<String>(HttpStatus.SC_BAD_REQUEST);
}
I prefer handling these kind of validations (if required, what ever may be the reason) inside the Filter generically so that the requests will not even reach the Controller methods.
Please find the required code to handle inside the Filter as below (logic is almost similar to Slava).
#Component
public class InvalidParamsRequestFilter extends OncePerRequestFilter {
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
Map<String, String[]> params = request.getParameterMap();
if (request.getRequestURI().contains("/test") && (params.size() != 1 || !params.containsKey("value"))) {
//Here, Send back the Error Response OR Redirect to Error Page
} else {
filterChain.doFilter(request, response);
}
}
}
Say that my spring controller function receives a large amount of data.
I want to return 200 OK, given that the data is structured right, and after that I want to perform the processing, which might take a while.
To my understanding the only way to send response is by return command. But I don't want to end the function on response send.
Are there other ways to send response to client at the middle of the function?
Creating a new thread run is obvious but other languages (JS) let you handle it more elegantly.
#RequestMapping(value = Connectors.CONNECTOR_HEARTBEAT, method = RequestMethod.POST)
public ResponseEntity<String> doSomething(#RequestBody List<Message> messages) {
HttpStatus code = (messages!=null && !messages.isEmpty()) ? HttpStatus.OK
: HttpStatus.NOT_FOUND;
return new ResponseEntity<String>(res, code);
// how do I add code here??
}
You can of course do processing after sending the response. The more general way would be to use the afterCompletion method of a HandlerInterceptor. By construction, it will be executed after the response have been sent to client, but it forces you to split you logic in 2 components the before part in controller, and the after part in the interceptor.
The alternative way is to forget Spring MVC machinery and manually commit the response in the controller:
#RequestMapping(value = Connectors.CONNECTOR_HEARTBEAT, method = RequestMethod.POST)
public void doSomething(#RequestBody List<Message> messages, HttpServletResponse response) {
int code = (messages!=null && !messages.isEmpty()) ? HttpServletResponse.SC_OK
: HttpServletResponse.SC_NOT_FOUND;
if (code != HttpServletResponse.SC_OK) {
response.sendError(code, res);
return;
}
java.io.PrintWriter wr = response.getWriter();
response.setStatus(code);
wr.print(res);
wr.flush();
wr.close();
// Now it it time to do the long processing
...
}
Note the void return code to notify Spring that the response have been committed in the controller.
As a side advantage, the processing still occurs in the same thread, so you have full access to session scoped attributes or any other thread local variables used by Spring MVC or Spring Security...
You can use #Async
#RequestMapping(value = Connectors.CONNECTOR_HEARTBEAT, method =
RequestMethod.POST)
public ResponseEntity<String> doSomething(#RequestBody List<Message>
messages) {
do();
HttpStatus code = (messages!=null && !messages.isEmpty()) ? HttpStatus.OK
: HttpStatus.NOT_FOUND;
return new ResponseEntity<String>(res, code);
}
#Async
void do(){
//your code
}
this work in java 8
I guess you mau use the async mechanism of spring
Async methods have been introduced in servlet 3.0 and Spring offers some support to them
Basically... you make a request; the request is handled by the server and then, in background, a new thread manages the requesta data
Here a useful link (at least i hope :) ) http://spring.io/blog/2012/05/10/spring-mvc-3-2-preview-making-a-controller-method-asynchronous/
You should use the HandlerInterceptor. But the code get a little bit more complex than expected. So, here's a code suggestion to make it simpler by putting the whole solution in a single class:
#RequestMapping(value = Connectors.CONNECTOR_HEARTBEAT, method = RequestMethod.POST)
public ResponseEntity<String> doSomething(#RequestBody List<Message> messages) {
HttpStatus code = (messages!=null && !messages.isEmpty()) ? HttpStatus.OK
: HttpStatus.NOT_FOUND;
result.set(res); // Save the object to be used after response
return new ResponseEntity<String>(res, code);
}
private static final ThreadLocal<String> result = new ThreadLocal<String>();
#Bean
public HandlerInterceptor interceptor() {
return new HandlerInterceptor() {
#Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
// Get the saved object and clean for the next request
String res = result.get();
result.set(null);
// TODO Your code to be executed after response.
}
};
}
I have this piece of code:
#RequestMapping(value = "/test.json", method = RequestMethod.GET)
#ResponseStatus(HttpStatus.OK)
public #ResponseBody Object[] generateFile(#RequestParam String tipo) {
Object[] variaveis = Variavel.getListVariavelByTipo(tipo);
return variaveis;
}
As far as I know it should take a request to test.json?tipo=H and return the JSON representation of Variavel[], however when I make such request I get:
HTTP Status 406 -
type Status report
message
descriptionThe resource identified by this request is only capable of generating responses with characteristics not acceptable according to the request "accept" headers ()
By using the following function I can get the expected json:
#RequestMapping(value = "/teste.json")
public void testeJson(Model model, #RequestParam String tipo) {
model.addAttribute("data", Variavel.getListVariavelByTipo("H"));
}
What I'm doing wrong?
#RequestBody/#ResponseBody annotations don't use normal view resolvers, they use their own HttpMessageConverters. In order to use these annotations, you should configure these converters in AnnotationMethodHandlerAdapter, as described in the reference (you probably need MappingJacksonHttpMessageConverter).