Why my request entity InputStream is always empty in ContainerRequestFilter (Spring+Jersey)

Deadly 提交于 2019-12-10 17:37:38

问题


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.


回答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();
    }
}


来源:https://stackoverflow.com/questions/33846197/why-my-request-entity-inputstream-is-always-empty-in-containerrequestfilter-spr

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!