Spring RestTemplateでAPI呼び出し時のプロキシとタイムアウト設定

RestTempleteを使って外部APIを呼び出すために、プロキシ経由で接続する必要がありましたので、調べました。

追記
GETメソッドでボディを送ることができない問題に対する対処方法を追加しました。

バージョン
SpringBoot 2.1.6.RELEASE
HttpClient 4.5.9

# RestTemplateの設定を行うRestTemplateCustomizerインターフェース
customizeメソッドに設定処理を実装します。

## プロキシの設定
HttpClientBuilderにプロキシホスト(ホスト、ポート番号)を設定します。
認証(ユーザ名、パスワード)が必要な場合には、CredentialProviderに認証情報をセットしてHttpClientBuilderにセットします。

## タイムアウトの設定
タイムアウトの設定は、HttpComponentsClientHttpRequestFactoryにセットします。

# クラス全体のコード

```RestTemplateConfig.java
import org.apache.http.HttpHost;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.apache.http.impl.client.HttpClientBuilder;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.web.client.RestTemplateCustomizer;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;

@Component
public class RestTemplateConfig implements RestTemplateCustomizer {

    @Value("${proxy.host:#{null}}")
    private String proxyHost;

    @Value("${proxy.port:0}")
    private int proxyPort;

    @Value("${proxy.user:#{null}}")
    private String proxyUser;

    @Value("${proxy.password:#{null}}")
    private String proxyPassword;

    @Value("${api.connect.timeout:0}")
    private int connectTimeout;

    @Value("${api.read.timeout:0}")
    private int readTimeout;

    @Override
    public void customize(RestTemplate restTemplate) {

        HttpClientBuilder builder;
        if (proxyHost != null && !proxyHost.isEmpty() && proxyPort != 0) {
            builder = HttpClientBuilder.create().setProxy(new HttpHost(proxyHost, proxyPort));
            if (proxyUser != null && !proxyUser.isEmpty() && proxyPassword != null && !proxyPassword.isEmpty()) {
                AuthScope authScope = new AuthScope(proxyHost, proxyPort);
                UsernamePasswordCredentials usernamePassword = new UsernamePasswordCredentials(proxyUser, proxyPassword);
                BasicCredentialsProvider credentialsProvider = new BasicCredentialsProvider();
                credentialsProvider.setCredentials(authScope, usernamePassword);
                builder.setDefaultCredentialsProvider(credentialsProvider);
            }
        }
        else {
            builder = HttpClientBuilder.create();
        }

        HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(builder.build());
        if (connectTimeout > 0) {
            requestFactory.setConnectTimeout(connectTimeout);
        }
        if (readTimeout > 0) {
            requestFactory.setReadTimeout(readTimeout);
        }

        restTemplate.setRequestFactory(requestFactory);
    };
}
```

## GETメソッドでボディを送れるようにする
デフォルトのままだと、GETメソッドでリクエストボディを送ることができない。HttpComponentsClientHttpRequestFactoryを拡張してGETメソッドでリクエストボディを送れるようになった。

参照
[Making a GET request with request body using Spring RestTemplate](https://cleanprogrammer.net/making-get-request-with-body-using-spring-resttemplate/)

```java
        class HttpEntityEnclosingRequest extends HttpEntityEnclosingRequestBase {
            HttpEntityEnclosingRequest(URI uri) {
                setURI(uri);
            }
            @Override
            public String getMethod() {
                return HttpMethod.GET.name();
            }
        }

        HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(builder.build()) {
            @Override
            protected HttpUriRequest createHttpUriRequest(HttpMethod httpMethod, URI uri) {
                if (httpMethod == HttpMethod.GET) {
                    return new HttpEntityEnclosingRequest(uri);
                }
                return super.createHttpUriRequest(httpMethod, uri);
            }
        };
```