Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

TestRestTemplate PUT and POST requests not working when adding Netty as a dependency #7240

Closed
eseidinger opened this issue Oct 27, 2016 · 7 comments
Labels
status: feedback-provided Feedback has been provided

Comments

@eseidinger
Copy link

When adding Netty as Dependency (compile 'io.netty:netty-all:4.1.6.Final') to my Spring Boot project my tests using TestRestTemplate.postForObject(...) and TestRestTemplate.put(...) aren't working anymore. I get the following error:

Failed to read HTTP message:... HttpMessageNotReadableException: Required request body is missing

Using TestRestTemplate.getForObject(...) works fine. RestTemplate.postForObject(...) and RestTemplate.put(...) are also working fine so I use those as a workaround for the moment.

I am using Spring Boot version 1.4.1.RELEASE

@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged label Oct 27, 2016
@wilkinsona
Copy link
Member

@eseidinger Can you please provide a small sample that reproduces the problem?

@wilkinsona wilkinsona added status: waiting-for-feedback We need additional information before we can continue and removed status: waiting-for-triage An issue we've not yet triaged labels Oct 28, 2016
@eseidinger
Copy link
Author

eseidinger commented Oct 28, 2016

@wilkinsona I created a little project on GitHub to isolate the problem. It's just one RestController with one request handler method and two test methods, one using RestTemplate and one using TestRestTemplate.
See https://github.com/eseidinger/spring-boot-netty-problem
It's actually not Netty alone causing the error. Adding the spring-boot-starter-data-cassandra dependency I get the following error running the test.

java.lang.NoClassDefFoundError: io/netty/handler/codec/http/FullHttpRequest

You can see that by removing the comments in the build.gradle file from the line
compile 'org.springframework.boot:spring-boot-starter-data-cassandra'

That's why I added the Netty dependency to my project in the first place.
When adding Netty as dependency
compile 'io.netty:netty-all:4.1.6.Final'
to resolve the error above, TestRestTemplate.postForObject(...) fails with the error message

Failed to read HTTP message: org.springframework.http.converter.HttpMessageNotReadableException: Required request body is missing: ...

So it looks like the error is caused by spring-boot-starter-data-cassandra

@spring-projects-issues spring-projects-issues added status: feedback-provided Feedback has been provided and removed status: waiting-for-feedback We need additional information before we can continue labels Oct 28, 2016
@wilkinsona
Copy link
Member

Thanks for the sample.

It appears to be a problem with Spring Framework's Netty4ClientHttpRequestFactory. When Netty's on the classpath, TestRestTemplate will automatically use Netty4ClientHttpRequestFactory. If you set the request factory on RestTemplate it has the same problem.

@wilkinsona
Copy link
Member

Here's a couple of stripped down tests that don't use Spring Boot at all and illustrate the problem:

package com.example;

import static io.undertow.servlet.Servlets.defaultContainer;
import static io.undertow.servlet.Servlets.deployment;
import static io.undertow.servlet.Servlets.servlet;
import static org.assertj.core.api.Assertions.assertThat;

import java.io.IOException;
import java.nio.charset.Charset;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.springframework.http.client.Netty4ClientHttpRequestFactory;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.util.StreamUtils;
import org.springframework.web.client.RestTemplate;

import io.undertow.Undertow;
import io.undertow.server.HttpHandler;
import io.undertow.servlet.api.DeploymentInfo;
import io.undertow.servlet.api.DeploymentManager;

public class ClientHttpRequestFactoryTests {

    private Undertow server;

    @Before
    public void startUndertow() throws Exception {
        DeploymentInfo servletBuilder = deployment()
                .setClassLoader(getClass().getClassLoader())
                .setContextPath("/")
                .setDeploymentName("test.war")
                .addServlets(
                        servlet("test", TestServlet.class)
                                .addMapping("/*"));

        DeploymentManager manager = defaultContainer().addDeployment(servletBuilder);
        manager.deploy();

        HttpHandler servletHandler = manager.start();
        this.server = Undertow.builder()
                .addHttpListener(8080, "localhost")
                .setHandler(servletHandler)
                .build();
        this.server.start();
    }

    @After
    public void stopUndertow() {
        this.server.stop();
    }

    @Test
    public void simpleClient() throws Exception {
        new RestTemplate(new SimpleClientHttpRequestFactory())
                .postForEntity("http://localhost:8080", new DummyData(), Void.class);
        assertThat(TestServlet.body).contains("data");
    }

    @Test
    public void nettyClient() throws Exception {
        new RestTemplate(new Netty4ClientHttpRequestFactory())
                .postForEntity("http://localhost:8080", new DummyData(), Void.class);
        assertThat(TestServlet.body).contains("data");
    }

    @SuppressWarnings("serial")
    static class TestServlet extends HttpServlet {

        private static String body;

        @Override
        protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
            body = StreamUtils.copyToString(req.getInputStream(), Charset.forName("UTF-8"));
        }

    }

    static class DummyData {

        private String data;

        public String getData() {
            return data;
        }

        public void setData(String data) {
            this.data = data;
        }

    }

}

@eseidinger Unfortunately, I think this needs to be addressed by the Spring Framework team (or perhaps the Netty team). Can you please open an SPR Jira ticket (https://jira.spring.io/browse/SPR) in the first instance?

@eseidinger
Copy link
Author

eseidinger commented Oct 28, 2016

I wrote this before I saw that you closed this issue but maybe you find it interesting anyway...

@wilkinsona Thanks for your feedback.

The JavaDoc for TestRestTemplate says that it can be configured by providing a RestTemplateBuilder @Bean. So I added one to a static inner @TestConfiguration class and set the request factory to SimpleClientHttpRequestFactory. I injected the RestTemplateBuilder into the test class so I can also use it to build my normal RestTemplate.

This works just fine without the dependency on Netty. But when I add the dependencies BOTH tests fail. If the problem was the Netty4ClientHttpRequestFactory this shouldn't happen. Isn't that odd?

I updated the sample accordingly. Maybe you want to have a look.

@wilkinsona
Copy link
Member

wilkinsona commented Oct 28, 2016

Isn't that odd?

Each method on the builder returns a new instance so the code that you had was returning the builder in its default form. This meant that both tests were now be using the Netty client.

Your method that configures the builder should look like this:

@Bean
public RestTemplateBuilder restTemplateBuilder() {
    return new RestTemplateBuilder().requestFactory(SimpleClientHttpRequestFactory.class);
}

@eseidinger
Copy link
Author

@wilkinsona Thanks for your help. The workaround with the SimpleClientHttpRequestFactory is working now. I created an issue on JIRA:
https://jira.spring.io/browse/SPR-14860

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
status: feedback-provided Feedback has been provided
Projects
None yet
Development

No branches or pull requests

3 participants