web-dev-qa-db-ja.com

RestTemplate PATCHリクエスト

PersonDTOには次の定義があります。

public class PersonDTO
{
    private String id
    private String firstName;
    private String lastName;
    private String maritalStatus;
}

サンプルレコードは次のとおりです。

{
    "id": 1,
    "firstName": "John",
    "lastName": "Doe",
    "maritalStatus": "married"
}

今、ジョン・ドーは離婚します。そのため、次のURLにPATCHリクエストを送信する必要があります。

http://localhost:8080/people/1

次のリクエストボディで:

{
    "maritalStatus": "divorced"
}

どうすればいいのかわかりません。ここに私がこれまで試したものがあります:

// Create Person
PersonDTO person = new PersonDTO();
person.setMaritalStatus("Divorced");

// Create HttpEntity
final HttpEntity<ObjectNode> requestEntity = new HttpEntity<>(person);

// Create URL (for eg: localhost:8080/people/1)
final URI url = buildUri(id);

ResponseEntity<Void> responseEntity = restTemplate.exchange(url, HttpMethod.PATCH, requestEntity, Void.class);

上記の問題は次のとおりです。

1)MaritalStatusのみを設定しているため、他のフィールドはすべてヌルになります。したがって、リクエストを印刷すると、次のようになります。

{
    "id": null,
    "firstName": "null",
    "lastName": "null",
    "maritalStatus": "married" // I only need to update this field.
}

これは、PATCHを行う前にGETする必要があるということですか?

2)次のスタックトレースを取得しています。

08:48:52.717 ERROR c.n.d.t.s.PersonServiceImpl - Unexpected Exception  : 
org.springframework.web.client.ResourceAccessException: I/O error on PATCH request for "http://localhost:8080/people/1":Invalid HTTP method: PATCH; nested exception is Java.net.ProtocolException: Invalid HTTP method: PATCH
    at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.Java:580) ~[spring-web-4.1.6.RELEASE.jar:4.1.6.RELEASE]
    at org.springframework.web.client.RestTemplate.execute(RestTemplate.Java:545) ~[spring-web-4.1.6.RELEASE.jar:4.1.6.RELEASE]
    at org.springframework.web.client.RestTemplate.exchange(RestTemplate.Java:466) ~[spring-web-4.1.6.RELEASE.jar:4.1.6.RELEASE]
    at com.sp.restclientexample..service.PersonServiceImpl.doPatch(PersonServiceImpl.Java:75) ~[classes/:na]
    at com.sp.restclientexample..service.PatchTitle.itDoPatch(PatchTitle.Java:53) [test-classes/:na]
    at Sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_20]
    at Sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.Java:62) ~[na:1.8.0_20]
    at Sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.Java:43) ~[na:1.8.0_20]
    at Java.lang.reflect.Method.invoke(Method.Java:483) ~[na:1.8.0_20]
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.Java:50) [junit-4.12.jar:4.12]
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.Java:12) [junit-4.12.jar:4.12]
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.Java:47) [junit-4.12.jar:4.12]
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.Java:17) [junit-4.12.jar:4.12]
    at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.Java:73) [spring-test-4.1.6.RELEASE.jar:4.1.6.RELEASE]
    at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.Java:82) [spring-test-4.1.6.RELEASE.jar:4.1.6.RELEASE]
    at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.Java:73) [spring-test-4.1.6.RELEASE.jar:4.1.6.RELEASE]
    at org.junit.runners.ParentRunner.runLeaf(ParentRunner.Java:325) [junit-4.12.jar:4.12]
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.Java:224) [spring-test-4.1.6.RELEASE.jar:4.1.6.RELEASE]
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.Java:83) [spring-test-4.1.6.RELEASE.jar:4.1.6.RELEASE]
    at org.junit.runners.ParentRunner$3.run(ParentRunner.Java:290) [junit-4.12.jar:4.12]
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.Java:71) [junit-4.12.jar:4.12]
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.Java:288) [junit-4.12.jar:4.12]
    at org.junit.runners.ParentRunner.access$000(ParentRunner.Java:58) [junit-4.12.jar:4.12]
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.Java:268) [junit-4.12.jar:4.12]
    at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.Java:61) [spring-test-4.1.6.RELEASE.jar:4.1.6.RELEASE]
    at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.Java:68) [spring-test-4.1.6.RELEASE.jar:4.1.6.RELEASE]
    at org.junit.runners.ParentRunner.run(ParentRunner.Java:363) [junit-4.12.jar:4.12]
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.Java:163) [spring-test-4.1.6.RELEASE.jar:4.1.6.RELEASE]
    at org.Eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.Java:50) [.cp/:na]
    at org.Eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.Java:38) [.cp/:na]
    at org.Eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.Java:459) [.cp/:na]
    at org.Eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.Java:675) [.cp/:na]
    at org.Eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.Java:382) [.cp/:na]
    at org.Eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.Java:192) [.cp/:na]
Caused by: Java.net.ProtocolException: Invalid HTTP method: PATCH
    at Java.net.HttpURLConnection.setRequestMethod(HttpURLConnection.Java:440) ~[na:1.8.0_20]
    at Sun.net.www.protocol.http.HttpURLConnection.setRequestMethod(HttpURLConnection.Java:517) ~[na:1.8.0_20]
    at org.springframework.http.client.SimpleClientHttpRequestFactory.prepareConnection(SimpleClientHttpRequestFactory.Java:209) ~[spring-web-4.1.6.RELEASE.jar:4.1.6.RELEASE]
    at org.springframework.http.client.SimpleClientHttpRequestFactory.createRequest(SimpleClientHttpRequestFactory.Java:138) ~[spring-web-4.1.6.RELEASE.jar:4.1.6.RELEASE]
    at org.springframework.http.client.support.HttpAccessor.createRequest(HttpAccessor.Java:76) ~[spring-web-4.1.6.RELEASE.jar:4.1.6.RELEASE]
    at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.Java:565) ~[spring-web-4.1.6.RELEASE.jar:4.1.6.RELEASE]
    ... 33 common frames omitted

SpringのRestTemplateを使用して、Restful Webサービスを利用するクライアントアプリケーションを作成した人々からのポインターを高く評価します。

完全を期すために、バックエンドの安らかなWebサービスにSpringDataRestを使用していることも述べておきます。

SGB

23
SGB

RestTemplateインスタンスに新しいHttpRequestFactoryを追加するだけでこの問題を解決しました。このような

RestTemplate restTemplate = new RestTemplate();

HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory();
requestFactory.setConnectTimeout(TIMEOUT);
requestFactory.setReadTimeout(TIMEOUT);

restTemplate.setRequestFactory(requestFactory);

TestRestTemplateの場合、追加します

@Autowired
private TestRestTemplate restTemplate;

@Before
public void setup() {
    restTemplate.getRestTemplate().setRequestFactory(new HttpComponentsClientHttpRequestFactory());
}

PS:プロジェクトにhttpClientコンポーネントを追加する必要があります

<dependency>
    <groupId>org.Apache.httpcomponents</groupId>
    <artifactId>httpclient</artifactId>
    <version>4.4.1</version>
</dependency>
59

RestTemplateRestTemplateBuilderから構築される場合、カスタムRestClientのコンストラクタは次のように記述できます。

public PersonRestClient(RestTemplateBuilder restTemplateBuilder) {
  this.restTemplate = restTemplateBuilder.requestFactory(new HttpComponentsClientHttpRequestFactory()).build();
}

また、org.Apache.httpcomponents.httpclient依存関係をpomに追加する必要があります。

1
sompnd

3.1.0よりも古いSpringバージョンを使用している場合、HttpMethodsにPATCHメソッドはありません。 ApacheからHttpClientを引き続き使用できます。ここに私がそれをどのようにしたかの短い例があります:

    try {

        //This is just to avoid ssl hostname verification and to trust all, you can use simple Http client also
        CloseableHttpClient httpClient = HttpClientBuilder.create().setSSLContext(new SSLContextBuilder().loadTrustMaterial(null, TrustAllStrategy.INSTANCE).build())
                .setSSLHostnameVerifier(NoopHostnameVerifier.INSTANCE).build();

        HttpPatch request = new HttpPatch(REST_SERVICE_URL);
        StringEntity params = new StringEntity(JSON.toJSONString(payload), ContentType.APPLICATION_JSON);
        request.setEntity(params);
        request.addHeader(org.Apache.http.HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
        request.addHeader(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE);
        //You can use other authorization method, like user credentials
        request.addHeader(HttpHeaders.AUTHORIZATION, OAuth2AccessToken.BEARER_TYPE + " " + accessToken);
        HttpResponse response =     httpClient.execute(request);            

        String statusCode = response.getStatusLine().getStatusCode();

    } catch (Exception ex) {
        // handle exception here
    }

これと同等の、RestTemplateの場合は次のようになります。

    HttpHeaders headers = new HttpHeaders();
    headers.setContentType(MediaType.APPLICATION_JSON);
    headers.add("Authorization", OAuth2AccessToken.BEARER_TYPE + " " + accessToken);
    final HttpEntity<String> entity = new HttpEntity<String>(JSON.toJSONString(payload), headers);
    RestTemplate restTemplate = new RestTemplate();
    try {
        ResponseEntity<String> response = restTemplate.exchange(REST_SERVICE_URL, HttpMethod.PATCH, entity, String.class);
        String statusCode =  response.getStatusCode();
    } catch (HttpClientErrorException e) {
        // handle exception here
    }

また、ペイロードに変更が必要な値のみが含まれていることを確認し、正しいURLにリクエストを送信していることを確認してください。 (これは/ api/guest/{id}のように終わる場合があります)

0
Dezso Gabos