I've been experiencing a strange problem using the ResourceHttpMessageConverter in the latest Spring 3.2.4 version. I have an annotated controller that returns a Resource, in specific a UrlResource. This UrlResource is nothing more than a Request to another remote server that serves a pdf file. Usually the pdf is a small file (less than 1MB) but under some circumstances is larger. In case the file is large the client that contacts to my Controller can't download the file resulting in a connection closed error. The code I am using is the following
#Controller
#PreAuthorize(value = "isAuthenticated()")
public class TestController {
#ResponseBody
#RequestMapping(value="/report/", method = RequestMethod.GET,
produces = "application/pdf")
public Resource getReport() {
//Ignore the getResource method, it is not the problem
//this method returns an object of type UrlResource
return this.getResource();
}
}
Although there is a workaround using the StreamUtils class to copy the InputStream from the UrlResource to the OutputStream of the HttpServletResponse, I wanted to know for sure if there is anything else I could do to avoid that, and rely on the Spring MessageConverter infrastructure rather than reimplementing the same logic in my controller. Is there any spring developer around that can point me in the right direction if it is possible, or if this is a bug let me know so I can report it. Thanks!
Related
I wonder why spring boot inject same response object to my controller method parameter for different request, i use it like follow:
#Controller
#Slf4j
#Profile("default")
#RequestMapping("/test")
public class TestController {
#RequestMapping("/test")
#ResponseBody
public void getDeviceImage(#RequestParam("serialNumber") String serialNumber, HttpServletResponse response) {
return balabala;
}
}
I add a breakpoint before return command, and i find that response object's address is same for different request.
I want to write some thing to response.getOutputStream(), and i found there exists previous buffered data.
HttpServletResponse can be used if you need to add some extra meta information like cookies etc. By default even if you don't specify HttpServletResponse in the arguments, in typical MVC, model is added to the newly constructed response before propagating to the view.
If you just need to return some response back, say a model or entity or a simple JSON, you don't have to manually mess the HttpServletResponse. Unless you want to dig through cookies or headers etc.,. In your code, if you don't need to care about this, you might probably not need it.
As per the API doc for HttpServletResponse:
The servlet container creates an HttpServletResponse object and passes
it as an argument to the servlet's service methods (doGet, doPost,
etc).
What you see is probably the default configurations that Spring sets up.
With #ResponseBody, the return type is directly written back to response.
https://docs.spring.io/spring-framework/docs/3.2.x/spring-framework-reference/html/mvc.html#mvc-ann-responsebody
Finally, i find Response will been reused all the time, and Response's recycle method will been invoked for each request(org.apache.catalina.connector.Response#recycle).
But by default facade&outputStream&writer will not been cleaned, so i make system property "org.apache.catalina.connector.RECYCLE_FACADES" to be "true", after that issue disappears.
In my spring controller, I have 2 rest api methods. example: getUser, getRole
One client is accessing it by like "/api/v1".
Now I want to update one of the methods. i.e., getRole. So the new method/version will be "/api/v2".
But no change in methods of v1. i.e., "/api/v1".
How to handle the rest methods with both versions in the same project ?
I mean, getUser rest API should support both "/api/v1" and "/api/v2".
And getRole rest API should support both versions but different functionality (example: database changed, logic changed).
In simple words,
1. getUser will have 1 method which supports both versions.
2. getRole will have 2 methods for each versions.
Please help me here.
If you want to do use separate method for both version you can by defining different value in #RequestMapping
method 1
#RequestMapping(
value = "baseurl/v1/role",
method = RequestMethod.GET,
produces = MediaType.APPLICATION_JSON_VALUE)
and
#RequestMapping(
value = "baseurl/v2/role",
method = RequestMethod.GET,
produces = MediaType.APPLICATION_JSON_VALUE)
but if you want to handle it in same method you can using
method 2: use #PathVariable
#RequestMapping(
value = "baseurl/{version}/role",
method = RequestMethod.GET,
produces = MediaType.APPLICATION_JSON_VALUE)
public returnType methodName(#PathVariable("version") String version){
// code to check version
}
method 3: use #RequestHeader
#RequestMapping(
value = "baseurl/role",
method = RequestMethod.GET,
produces = MediaType.APPLICATION_JSON_VALUE)
public returnType methodName(#RequestHeader("version") String version){
// code to check version
}
But as you said you have different version of api's prefer to manage them in separate controller using #RequestMapping at class level
method 4:
#RestController
#RequestMapping("/v1")
public class anyController {
}
and
#RestController
#RequestMapping("/v2")
public class anyController {
}
It really depends on your use case and what all is changing. There are technically many different paths you can take, but I will describe two that sound like they would work for you.
Create path param for v1 and add conditional that checks to see if the path param is v2 and call different method.
Create new path for api/v2, add changes functionality and call v1 endpoint method.
This really gets into specifics and should probably be evaluated on which way you should take it, depending on how the existing code is implemented.
I would do the following approach
Define a version at the application level (Start with /v1) -- this version is changed only when you want to make bigger changes to your whole API and is usually stable.
Resources (roles, users) etc. should be versioned additionally using content negotiation via the headers
e.g.
GET /v1/users/503deb67-ff6e-4d1c-a225-408b910b7252/ HTTP/1.1
Accept: application/vnd.yourorg.users-v1+json
Here the client uses content negotiation for the specific resource she is interested in via the HTTP header
See https://www.mashery.com/blog/ultimate-solution-versioning-rest-apis-content-negotiation and http://blog.ploeh.dk/2015/06/22/rest-implies-content-negotiation/ for more details
There are many ways, but most likely you want to keep the code as decoupled as possible. This makes keeping the versions in separate classes a good option:
V1Controller.java:
#RestController
#RequestMapping("/api/v1")
public class V1Controller{
// version 1 api
}
V2Controller.java :
#RestController
#RequestMapping("/api/v2")
public class V2Controller{
// version 2 api
}
But for a even higher degree of decoupling you could have two different webapps deployed on the same Tomcat or Jetty server - version 1 under the context path "/api/v1" and version 2 on the context path "/api/v2". The webapps would simply be builds of different versions of the same code (or builds of different branches - if you're using git).
Hi I need to pass a json object in POST request of the SPRING DATA REST. Is it possible to pass directly and make it process with save(iterable) with any Jackson script or we have to use a Controller with #RequestBodyand process the Iterableand save it using repository function??
Now I am doing,
#RequestMapping(value = "batchInsert", method = RequestMethod.POST)
#ResponseBody
public String batchInsert(#RequestBody List<Test> test){
testRepo.save(test);
return "loaded";
}
and implements Serilizable in DAO objectand my doubt whether there is any default format to pass whole json without using any controller as CRUD operates normally. Please help me find solution. Am new to springs and I am unable to use the same url to get request in spring-data-rest API, if I use batchInsertin controller and in rest api. Fortunate to use different api calls now for inserting and searching purpose. Thanks in advance.
Have you tried specifying the consumable media type?
#RequestMapping(value = "batchInsert", method = RequestMethod.POST, consumes="application/json")
It's quite common to pass JSON objects to Spring controllers, so it should work...
Posted in spring forum with no response.
I have the following code snippet (from here), which is part of my pet project.
#Controller
#RequestMapping("/browse")
public class MediaBrowser {
...
#RequestMapping("/**")
public final ModelAndView listContents(final HttpServletRequest request) {
String folder = (String) request.getAttribute(
HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE);
...
}
I access the following url:
http://localhost:8080/myapp/browse
In spring 3.0.6.RELEASE, I got the folder variable as null, which is the expected value.
In spring 3.1.RC1, the folder variable is /browse.
Is this a bug or has something changed in spring-3.1?
As skaffman said, you probably shouldn't use PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE. Take a look at How to match a Spring #RequestMapping having a #pathVariable containing "/"? for an example of using AntPathMatcher to accomplish what you are trying
This looks very much like an internal implementation detail of the framework, one that you should not be relying on.
The javadoc for PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE says:
Note: This attribute is not required to be supported by all HandlerMapping implementations. URL-based HandlerMappings will typically support it, but handlers should not necessarily expect this request attribute to be present in all scenarios.
I wouldn't be surprised if the behaviour changed slightly between 3.0 and 3.1.
I have been trying to get exception handling working in my simple Spring 3 based ReST web services. Based on everything I have seen, there is a bug that prevents this from working automatically with the #ResponseBody and #ExceptionHandler annotations
https://jira.springsource.org/browse/SPR-6902
So given that it isn't supported until Spring 3.1 or 3.0.6, what is the current best method for doing exception handling? I have seen numerous posts but haven't found a clear answer that has worked for me. An ideal solution would be one that automatically provides support for both xml and json
Do I have to manually define the entire marshalling setup? Won't this remove the need for the annotations that make using Spring 3 rest support worth it?
Seems in order to manually define marshalling (i.e. Jaxb2Marshaller) I need to add a new dependency on spring-ws which is a bit of a pain
Is it easier to just define a 'Response' object that all my methods return and wrap all functions in try/catch blocks?
You can redirect on error and then return something in #ResponseBody:
#ExceptionHandler(Exception.class)
public ModelAndView handleMyException(Exception exception) {
return new ModelAndView("redirect:errorMessage?error="+exception.getMessage());
}
#RequestMapping(value="/errorMessage", method=RequestMethod.GET)
#Responsebody
public String handleMyExceptionOnRedirect(#RequestParameter("error") String error) {
return error;
}
Little ugly, but this is just work around till the fix will be available.
This is a good workaround, but with one addition. The #ExceptionHandler(Exception.class)
should be #ExceptionHandler(MyException.class, YourException.class) as you can get into a loop using the general Exception class.
You can then test for (ex instanceof Myexception) to determine the message to display if need be.