I'm totally unable to get the Request payload/form in a JaxRS ContainerRequestFilter.
My setup :
here's my pom : (from Spring Initialzr)
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.3.0.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>demo</name>
<description>Demo project for Spring Boot</description>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jersey</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
Here's my Application class:
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
Here's my JerseyConfig:
@Configuration
public class JerseyConfig extends ResourceConfig {
public JerseyConfig() {
register(HelloController.class);
register(MyFilter.class);
}
}
Here's my HelloController:
@Component
@Path("/hello")
public class HelloController {
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
@POST
public String onHelloRequest(@FormParam("foo") String foo) {
System.out.println(foo);
return foo + "bar";
}
}
And here's the core part of the problem I have, the ContainerRequestFilter:
public class MyFilter implements ContainerRequestFilter {
@Override
public void filter(ContainerRequestContext ctx) throws IOException {
System.err.println("I'm in the filter");
// Solution #1 doesn't work
if (ctx instanceof ContainerRequest) {
ContainerRequest request = (ContainerRequest) ctx;
request.bufferEntity();
Form f = request.readEntity(Form.class);
System.err.println(f.asMap().toString());
}
// Solution #2 doesn't work either
InputStream inputStream = ctx.getEntityStream();
StringBuilder textBuilder = new StringBuilder();
try (Reader reader = new BufferedReader(new InputStreamReader
(inputStream, Charset.forName(StandardCharsets.UTF_8.name())))) {
int c = 0;
while ((c = reader.read()) != -1) {
textBuilder.append((char) c);
}
}
System.err.println(textBuilder.toString());
}
}
As you can see, this is a very lean SpringBoot example using Jersey. However, it looks like the InputStream from the ContainerRequestFilter has already been consumed. I tried it with a Javax ServletFilter and I have the same problem.
Is it possible that Spring-web consumes the InputStream before calling Jersey?
Please help me.
EDIT
To test it I used POSTMAN and sent:
POST http://localhost:8080/hello
Headers:
Content-type = application/x-www-form-urlencoded
Payload:
foo=bar
My Response was
barbar
My Console output was
I'm in the filter
{} // Solution #1 <- Should have been something like foo=bar
// Solution #2 <- an empty string
bar
EDIT2
I also have that message in the console, even without any custom Filter :
2015-11-21 20:14:05.438 WARN 4440 --- [nio-8080-exec-2] o.glassfish.jersey.servlet.WebComponent : A servlet request to the URI http://localhost:8080/hello contains form parameters in the request body but the request body has been consumed by the servlet or a servlet filter accessing the request parameters. Only resource methods using @FormParam will work as expected. Resource methods consuming the request body by other means will not work as expected.
It definitely looks like something else consumes the InputStream before passing it out to Jersey, leaving every Jax-RS Filters|Interceptors without state.
Here's the "solution"
In SpringBoot, if you add the starter-web module, it'll add spring:webmvc and this seems to be not compatible with JaxRS and Servlet Filters. If you use SpringBoot and Jersey, make sure the webmvc jar is not on the classpath.
The Spring stack will consume the Servlet's Request InputStream and every downward intercepter/filter in the pipeline will be left with nothing.
Here's a working snipet of code that fetch the Request's payload in a Servlet Filter
public MyFilter implements Filter
{
@Override
public final void doFilter(
final ServletRequest request,
final ServletResponse response,
final FilterChain chain)
throws IOException,
ServletException {
getRawFormPayload((HttpServletRequest) request);
chain.doFilter(request, response);
}
protected String getRawFormPayload(HttpServletRequest request) {
final int contentLength = request.getContentLength();
final StringBuilder payloadBuilder = new StringBuilder(contentLength);
try {
final InputStream inputStream = request.getInputStream();
final BufferedReader reader =
new BufferedReader(new InputStreamReader(inputStream, request.getCharacterEncoding()));
// Buffer should not be higher than the content-length of the request
reader.mark(contentLength + 1);
final char[] charBuffer = new char[contentLength + 1];
int bytesRead = -1;
while ((bytesRead = reader.read(charBuffer)) > 0) {
payloadBuilder.append(charBuffer, 0, bytesRead);
}
// Reset the buffer so the next Filter/Interceptor have unaltered InputStream
reader.reset();
} catch (final IOException e) {
this.LOG.error(e.getMessage(), e);
}
return payloadBuilder.toString();
}
}