Spring Boot + Jersey type filter - Bad request 400 for service Consumes MULTIPART_FORM_DATA - spring-boot

I'm using Spring boot v1.5.10 + Jersey v2.25.1, configured jersey as filter to access static folder files. I'm getting HTTP response 400 Bad Request for a service consuming MULTIPART_FORM_DATA.
Props to configure Jersey as filter.
spring.jersey.type=filter
If I remove above property i.e., using Jersey as Servlet, the service is working, but I'm not able to access static folder.
Here is the controller,
#POST
#Path("/save")
#Consumes(MediaType.MULTIPART_FORM_DATA)
#Produces(MediaType.APPLICATION_JSON)
public ResponseBean save(
#FormDataParam("fileToUpload") InputStream file,
#FormDataParam("fileToUpload") FormDataContentDisposition fileDisposition,
#FormDataParam("fromData") FormDataDto data) {
// stuff
}
EDIT:
GitHub link https://github.com/sundarabalajijk/boot-jersey
When you start the app, spring.jersey.type=filter
http://localhost:8080/ (works)
http://localhost:8080/hello.html (works)
http://localhost:8080/save (not working) - used postman.
When spring.jersey.type=servlet
http://localhost:8080/ (works)
http://localhost:8080/hello.html (not working)
http://localhost:8080/save (works)

After some research and finding related issues1, it seems that Spring's HiddenHttpMethodFilter reads the input stream, which leaves it empty for any other filters further down the filter chain. This is why we are getting a Bad Request in the Jersey filter; because the entity stream is empty. Here is the note from the Javadoc
NOTE: This filter needs to run after multipart processing in case of a multipart POST request, due to its inherent need for checking a POST body parameter.
So what we need to do is configure the Jersey filter to be called before this Spring filter2. Based on the Spring Boot docs, there is a property we can use to easily configure the order of this filter.
spring.jersey.filter.order
Doing a Github search in the Spring Boot repo for the HiddenHttpMethodFilter, we can see the subclass that is used OrderedHiddenHttpMethodFilter, where the order is set to -10000. So we want to set the order of our Jersey filter to less than that (higher precedence). So we can set the following value
spring.jersey.filter.order=-100000
If you test it now, it should now work.
One more thing we need to fix is the order of the Spring RequestContextFilter. This is originally configured to be ordered to be called right before the Jersey filter. When we set the order configuration above for the Jersey filter, the RequestContextFilter stays where it was originally at. So we need to change this. We can do this just by adding a bean to override the original one and set the order.
#Bean
public RequestContextFilter requestContextFilter() {
OrderedRequestContextFilter filter = new OrderedRequestContextFilter();
filter.setOrder(-100001);
return filter;
}
Now if we check the logs on startup, we should see the filer ordering we want.
Mapping filter: 'characterEncodingFilter' to: [/*]
Mapping filter: 'requestContextFilter' to: [/*]
Mapping filter: 'jerseyFilter' to urls: [/*]
Mapping filter: 'hiddenHttpMethodFilter' to: [/*]
Mapping filter: 'httpPutFormContentFilter' to: [/*]
Aside
The reason you need to configure Jersey as a filter in your case is because of the static content. If you don't configure the root path for the Jersey app, then it defaults to /*, which will hog up all requests, included those to the static content. So Jersey will throw 404 errors when requests are made for the static content. We configure Jersey as a filter and tell it to forward request that it cannot find.
If we were to just configure the root path for Jersey, then we would not need to worry about this problem with the static content, and we would be able to just leave Jersey configured as a servlet by default.
To change the base path for the Jersey app, we can either add the #ApplicatuonPath annotation to our ResourceConfig or we can use the property spring.jersey.application-path
#Component
#ApplicationPath("/api")
public class JerseyConfig extends ResourceConfig {
...
}
or in your application.properties
spring.jersey.application-path=/api
See also
File upload along with other object in Jersey restful web service. This has some information on how you can accept JSON as a body part in the multipart request.
Footnotes
1. Some issues to look at [ 1, 2 ]
2. See Change order of RequestContextFilter in the filter chain

I guess the accepted answer is not up to date anymore. With Spring Boot 2 I did not have such problems. There was one issue though. I had a custom filter which was calling getParameterMap on the request object. Apparently getParameterMap implicitly reads the input stream of the request which then gets closed afterwards so that no other code can read the request body anymore.
Strangely with the setting spring.jersey.type=servlet I could get the request body even though getParameterMap was called beforehand, so I guess the implementation of ServletRequest is different depending on what you have configured for spring.jersey.type.

Related

Spring Boot + Jersey + view controller not working together [duplicate]

I am getting a HTTP 404 error when trying to serve index.html ( located under main/resources/static) from a spring boot app. However if I remove the Jersey based JAX-RS class from the project, then http://localhost:8080/index.html works fine.
The following is main class
#SpringBootApplication
public class BootWebApplication {
public static void main(String[] args) {
SpringApplication.run(BootWebApplication.class, args);
}
}
I am not sure if I am missing something here.
Thanks
The problem is the default setting of the Jersey servlet path, which defaults to /*. This hogs up all the requests, including request to the default servlet for static content. So the request is going to Jersey looking for the static content, and when it can't find the resource within the Jersey application, it will send out a 404.
You have a couple options around this:
Configure Jerse runtime as a filter (instead of as a servlet by default). See this post for how you can do that. Also with this option, you need to configure one of the ServletProperties to forward the 404s to the servlet container. You can use the property that configures Jersey to forward all request which results in a Jersey resource not being found, or the property that allows you to configure a regex pattern for requests to foward.
You can simply change the Jersey servlet pattern to something else other than the default. The easiest way to do that is to annotate your ResourceConfig subclass with #ApplicationPath("/root-path"). Or you can configure it in your application.properties - spring.jersey.applicationPath.

Spring StandardServletMultipartResolver

I was wondering where is located the code that automatically create a temporary file when you send a multipart request using StandardServletMultipartResolver?
Can i disable that behavior? I want to decide how its going to be stored and where. I don't want spring to do it for me.
I'm considering creating my own resolver but I cant find information on how to disable spring default behavior.
To quote from API docs StandardServletMultipartResolver does not support temporary file configuration on resolver level rather it is to be done on servlet registration level -
In order to use Servlet 3.0 based multipart parsing, you need to mark the affected servlet with a "multipart-config" section in web.xml, or with a MultipartConfigElement in programmatic servlet registration, or (in case of a custom servlet class) possibly with a MultipartConfig annotation on your servlet class.
Configuration settings such as maximum sizes or storage locations need to be applied at that servlet registration level; Servlet 3.0 does not allow for them to be set at the MultipartResolver level.
So either you can configure it on servlet or switch to CommonsMultipartResolver which has the support to set the temp directory out-of-the-box as it inherits it from CommonsFileUploadSupport.setUploadTempDir (see respective docs here and here)

Spring boot - Serving static content

I have issues configuring Spring boot 1.2.0.M1 to serve static content.
As soon as I add a #RestController component in my application, the static content is not displayed and I get the whitelabel error page instead. My resources are in the src/main/resources/static folder.
I followed the instructions at http://spring.io/blog/2013/12/19/serving-static-web-content-with-spring-boot and managed to have them displayed by setting the ResourceHandlerRegistry priority to -1. But i guess it is not the standard and right way to do it. It seems that the handler of the REST resources takes priority over the resource handler registry. Is there a possibility to configure the handler for rest resource to be used for a sub-context only like /api ?
Update:
I have put the DispatcherServlet in debug and I have better understanding on why this is happening but still not sure what would be the best way to deal with it.
I have noticed that the following handlers are registered in the Dispatch servlet in the respective order by default :
SimpleUrlHandlerMapping -> favico
RequestMappingHandlerMapping -> annotated #RestController methods registered
SimpleUrlHanderMapping -> / (home page handling ?)
BeanNameUrlHandlerMapping
SimpleUrlHandlerMapping -> handles resources configured in the resources registry
WebMVCConfigurationSupport handler
When I perform a POST on the REST resource 2. handles it.
When I perform a GET on a static resource 2. throws a HttpRequestMethodNotSupportedException because it cannot find the resource (in its handleNoMatch method).
If i change the priorities using ResourceHandlerRegistry#setOrder(Ordered.HIGHEST_PRECEDENCE); 5. is placed before 2. but in this case i cannot perform a POST /user/api intended for my REST resource, it is not matched as a resource (i have configured a /** pattern for the resource handler).
If i compare to what node.js/express does for instance is that you configure routes for your controllers and if none matches the request, the request is handled by the handler for static resources or templates.
Do you know how if it is possible through annotation to have the 2 (i.e RequestMappingHandlerMapping ) not throw an exception but just pass the request to the next handler in the chain in case of no-match ?
I'd like avoiding having a specific context (/static for static resources).
Update 2:
Actually it was just a misconfiguration of my annotated REST controller #RestController
I configured the path in the value attribute of the annotation that is not meant for that but to store the controller's name
I forgot to add the #RequestMapping therefore the handler RequestMappingHandlerMapping was enabled for any url path and the get request did not match any annotated methods, therefore it returned an error.

Jersey 2: What replaces FEATURE_FILTER_FORWARD_ON_404?

What is the Jersey 2 equivalent of ServletContainer.FEATURE_FILTER_FORWARD_ON_404 from Jersey 1?
ServletContainer.FEATURE_FILTER_FORWARD_ON_404 is defined as:
If true and a 404 response with no entity body is returned from either the runtime or the application then the runtime forwards the request to the next filter in the filter chain
Please explain why you are downvoting. I can't improve the question/answer if you don't provide an explanation of what is wrong.
The properties you should be using are ServletProperties.FILTER_FORWARD_ON_404 and ServletProperties.FILTER_STATIC_CONTENT_REGEX.
ServletProperties.FILTER_FORWARD_ON_404 is defined as:
If set to true and a 404 response with no entity body is returned from either the runtime or the application then the runtime forwards the request to the next filter in the filter chain.
ServletProperties.FILTER_STATIC_CONTENT_REGEX is defined as:
If set the regular expression is used to match an incoming servlet path URI to some web page content such as static resources or JSPs to be handled by the underlying servlet engine.
#gili is correct. For a complete running example of the jersey 2.x config check out my simple toy project on github:
https://github.com/depsypher/flapjack
Essentially you'll have to run the Jersey ServletContainer as a filter and provide the jersey.config.servlet.filter.forwardOn404 property as an init param.
Here's an example of the setup using Spring Boot; the web.xml equivalent should be pretty obvious:
FilterRegistrationBean filter = new FilterRegistrationBean(new ServletContainer());
filter.addInitParameter("jersey.config.servlet.filter.forwardOn404", "true");

JAX-RS Jersey - Howto force a Response ContentType? Overwrite content negotiation

Jersey identifies requests by looking at the accept header. I have a request which accepts only text/* - How can i force the response to be for example application/json?
#POST
#Path("/create")
#Produces(MediaType.APPLICATION_JSON)
public MyResponseObject create() {
return new MyResponseObject();
}
If a request is directed to create which only accepts text/* jersey will return a 500. Is there a way to workaround this issue? (I can't change the requests accept header).
Jersey also supports this via ResourceConfig property PROPERTY_MEDIA_TYPE_MAPPINGS that you could configure in your web.xml or programatically via Jersey APIs as shown below:
DefaultResourceConfig rc = new DefaultResourceConfig(MyResource.class);
rc.getMediaTypeMappings().put("json", MediaType.APPLICATION_JSON_TYPE);
rc.getMediaTypeMappings().put("xml", MediaType.APPLICATION_XML_TYPE);
SimpleServerFactory.create("http://localhost:9090", rc);
You can force content type negotiation by suffixing either .json or .xml to your URL.
I solved this by using a servlet filter:
http://www.zienit.nl/blog/2010/01/rest/control-jax-rs-content-negotiation-with-filters

Resources