使用 RestTemplate GET 请求抛出 400 Bad Request

Using RestTemplate GET request throws 400 Bad Request

当我从 JUnit 测试用例向 Rest 服务发送 GET 请求时收到 400 Bad Request。我的请求正文应该是 text/plain (Content-Type: text/plain),响应应该是类型 EmployeeResponseEntity。 当我使用 Postman 调用请求时我没有收到任何错误,但是我无法使用 Spring Web 客户端方法使其通过。

我有一个 POST REQUEST 的例子,其中几乎相似的测试实现通过了,不同之处在于,在请求正文中我传递了 Employee 的整个对象,而在测试中失败的是一个text/plain。 (最后添加)

我试图理解为什么它不起作用,但我想不通。 我也尝试用 getForObjectgetForEntity 更改交换方法,但它没有用。

完整 URL : http://localhost:8080/employee/find/lastName/

L.E.: IntelliJ 通过突出显示 .exchange 方法来帮助我,并告诉我该方法将以 400 响应。

控制器

@RequestMapping (value = "/find/lastName", headers="Content-Type=text/plain", method = RequestMethod.GET)
    public ResponseEntity<Employee> findEmployeeByLastName(@RequestBody String lastName) {
        return EmployeeService.getEmployeeByLastName(lastName);
    }

服务

public static ResponseEntity<Employee> getEmployeeByLastName(String lastName) {
    return employees
            .stream()
            .filter(employee -> employee.getLastName().equals(lastName))
            .findFirst()
            .map(employee -> new ResponseEntity<>(employee, HttpStatus.OK))
            .orElseThrow(() -> new ExmployeeResourceException("Not Found"));
}

JUnit 测试

@Test
public void ShouldReturnEmployeeByUsingLastName() {
    RestTemplate restTemplate = new RestTemplate();

    String employeeLastName = "Doe";
    HttpHeaders headers = new HttpHeaders();
    headers.setContentType(MediaType.TEXT_PLAIN);

    HttpEntity<String> request = new HttpEntity<>(employeeLastName, headers);
    ResponseEntity<Employee> responseEntity =
            restTemplate.exchange(
                    HOSTNAME + ENDPOINT + "find/lastName/",
                    HttpMethod.GET,
                    request,
                    Employee.class
            );
}

错误日志

22:12:28.619 [main] DEBUG org.springframework.web.client.RestTemplate - Writing [Doe] as "text/plain" using [org.springframework.http.converter.StringHttpMessageConverter@48075da3]
22:12:28.764 [main] DEBUG org.springframework.web.client.RestTemplate - GET request for "http://localhost:8080/employee/find/lastName/" resulted in 400 (null); invoking error handler
org.springframework.web.client.HttpClientErrorException: 400 null at org.springframework.web.client.DefaultResponseErrorHandler.handleError(DefaultResponseErrorHandler.java:63)
    at org.springframework.web.client.RestTemplate.handleResponse(RestTemplate.java:700)
    at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:653)
    at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:613)
    at org.springframework.web.client.RestTemplate.exchange(RestTemplate.java:531)
    at com.endava.rest.EmployeeTests.ShouldReturnEmployeeByUsingLastName(EmployeeTests.java:90)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:566)
    at org.junit.runners.model.FrameworkMethod.runReflectiveCall(FrameworkMethod.java:50)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
    at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:290)
    at org.junit.runners.ParentRunner.schedule(ParentRunner.java:71)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
    at org.junit.runners.ParentRunner.access[=13=]0(ParentRunner.java:58)
    at org.junit.runners.ParentRunner.evaluate(ParentRunner.java:268)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
    at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
    at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:69)
    at com.intellij.rt.junit.IdeaTestRunner$Repeater.execute(IdeaTestRunner.java:38)
    at com.intellij.rt.execution.junit.TestsRepeater.repeat(TestsRepeater.java:11)
    at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:35)
    at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:235)
    at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:54)

通过的测试示例

    @Test
    public void ShouldBeAbleToCreateAnEmployee() {
        RestTemplate restTemplate = new RestTemplate();

        Integer id = 3;
        String firstName = "Ionut";
        String lastName = "Popescu";

        Employee newEmployee = new Employee(id, firstName, lastName);
        HttpEntity<Employee> httpEntity = new HttpEntity<>(newEmployee);
        ResponseEntity<Employee> responseEntity =
                restTemplate.exchange(
                        HOSTNAME + ENDPOINT,
                        HttpMethod.POST,
                        httpEntity,
                        Employee.class
                );
}

My request have a body which is a text (string) that's why I added the Content-Type header.

RestTemplate 客户端不支持 GET 请求中的实体。考虑到这些类型的 API 很少见,GET 在客户端中很常见,缺少对 body 的支持。

More info on RestTemplate specifically:

I assume you are using a RestTemplate with a default ClientHttpRequestFactory. In this case, JDK's HttpURLConnection is the underlying HTTP client. HttpURLConnection does not send a request body for GET requests; I've verified that using Wireshark.

如果您真的受困于 RestTemplate,您的选择是使用不同的 ClientHttpRequestFactory。例如:

import org.apache.http.client.methods.HttpEntityEnclosingRequestBase;
import org.apache.http.client.methods.HttpUriRequest;
import org.springframework.http.HttpMethod;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;
import java.net.URI;

class HttpComponentsClientHttpRequestFactoryForGetWithBody extends HttpComponentsClientHttpRequestFactory {
    private static final class HttpGetRequestWithBody extends HttpEntityEnclosingRequestBase {
        public HttpGetRequestWithBody(URI uri) { super.setURI(uri); }
        @Override public String getMethod() { return HttpMethod.GET.name(); }
    }
    @Override
    protected HttpUriRequest createHttpUriRequest(HttpMethod httpMethod, URI uri) {
        if (HttpMethod.GET.equals(httpMethod)) {
            return new HttpGetRequestWithBody(uri);
        }
        return super.createHttpUriRequest(httpMethod, uri);
    }
}

用法:

restTemplate.setRequestFactory(new HttpComponentsClientHttpRequestFactoryForGetWithBody());