4

I'm totally unable to get the Request payload/form in a JaxRS ContainerRequestFilter.

My setup :

  • JDK 8
  • SpringBoot 1.3.0.RELEASE
  • Jersey 2.22.1 (from SpringBoot)

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.

Daniel Marcotte
  • 1,270
  • 3
  • 16
  • 27
  • https://java.net/jira/browse/JERSEY-3007 – Daniel Marcotte Nov 21 '15 at 17:13
  • https://jira.spring.io/browse/SPR-13715 – Daniel Marcotte Nov 21 '15 at 17:19
  • I can't reproduce the problem. Solution #1 works fine for me. Solution #2 shouldn't work, as the stream should already be consumed by the HttpServletRequest to create its request properties (only for form-urlencoded). Oddly I actually had to take out the @Component on the filter for the filter to even get hit. I don't know if you are experiencing that either or not. But when the filter _is_ hit, the your Solution #1 works fine for me – Paul Samsotha Nov 21 '15 at 20:06
  • You are right about the @Component, it's useless, actually the JerseyConfig registers it. Just edited it. You are saying that you send a POST on http://localhost:8080/hello with the payload being foo=bar and you should receive "barbar" as a response. The console should print "I'm in the filter" and {} <-- being the empty inputStream. – Daniel Marcotte Nov 22 '15 at 00:51
  • @peeskillet did you use the same pom I posted on the project and the same code example? Or did you hook a Jersey Server on a clean Servlet without Spring? – Daniel Marcotte Nov 22 '15 at 01:05
  • Not _exactly_ the same. Just used a boot-jersey app I already had laying around. But the test concept was the same. Only difference is I was using 1.2.7.RELEASE. I wonder if that is the difference. Oh and my project didn't have the starter-web – Paul Samsotha Nov 22 '15 at 01:29
  • Ok, the starter-web is the problem. For an unknown reason, it contains code that breaks the filters. Without it I'm able to print the form properly... – Daniel Marcotte Nov 22 '15 at 01:36
  • Using 1.2.7.RELEASE, the Filter just doesn't kick in... wow... – Daniel Marcotte Nov 22 '15 at 01:45
  • Hmm so I added the spring-web. And like you said, it doesn't work. One work around is to get the `Form` from a property `InternalServerProperties.FORM_DECODED_PROPERTY`. Found out about it [here](https://github.com/jersey/jersey/blob/master/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/WebComponent.java#L666). It's actually how Jersey gets the `@FormParam`s, as seen in [here](https://github.com/jersey/jersey/blob/master/core-server/src/main/java/org/glassfish/jersey/server/internal/inject/FormParamValueFactoryProvider.java#L170). – Paul Samsotha Nov 22 '15 at 02:05
  • So you can just to `Form form = (Form)cr.getProperty(ISP.F_D_P)` (abbreviated). Tested and works – Paul Samsotha Nov 22 '15 at 02:07
  • Nice, thank you. I'll still follow up on the Spring side since it looks like a bug. – Daniel Marcotte Nov 22 '15 at 04:54

1 Answers1

1

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();
    }
}
Daniel Marcotte
  • 1,270
  • 3
  • 16
  • 27