Question
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)
Answer
After some research and finding related issues1, it seems that Spring's
[HiddenHttpMethodFilter
](https://docs.spring.io/spring-
framework/docs/current/javadoc-
api/org/springframework/web/filter/HiddenHttpMethodFilter.html) 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](https://docs.spring.io/spring- boot/docs/current/reference/htmlsingle/#boot-features-jersey), there is a property we can use to easily configure the order of this filter.
spring.jersey.filter.order
Doing a [Github search](https://github.com/spring-projects/spring-
boot/search?utf8=%E2%9C%93&q=HiddenHttpMethodFilter&type=) in the Spring Boot
repo for the HiddenHttpMethodFilter
, we can see the subclass that is used
[OrderedHiddenHttpMethodFilter](https://github.com/spring-projects/spring-
boot/blob/d3c34ee3d1bfd3db4a98678c524e145ef9bca51c/spring-boot-project/spring-
boot/src/main/java/org/springframework/boot/web/servlet/filter/OrderedHiddenHttpMethodFilter.java),
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](https://github.com/spring-projects/spring-
boot/issues/5676#issuecomment-209421669) ]
2. See Change order of RequestContextFilter in the filter
chain