f99aq8ove's blog

Springboot で http keepalive を一定時間で切る方法

tag: java, springboot, and http
06 September 2020

このエントリは 2020-09-06 に書かれました。 内容が古くなっていたり、もはや正しくないこともありますので、十分検証を行ってください。

Springboot で http keepalive を一定時間で切る方法

golang の net.http で http keepalive を一定時間で切る方法(transport を使い回す) で golang で一定間隔で http keepalive を切る方法を書きましたが、今度は Java 版です。

Springboot で org.apache.http.HttpConnection を使うと、自動的に connection pool が使用され、そこで http keepalive が使用されます(たしか)。 これには都合が悪いことがあるため、一定回数リクエストを行った場合にコネクションを切るようにしてみます。

/**
 * とりあえずのアプリケーション定義
 */
package net.f99aq8ove.keepalive;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class TestApplication {
    public static void main(String[] args) {
        SpringApplication.run(TestApplication.class, args);
    }
}
package net.f99aq8ove.keepalive;

import lombok.RequiredArgsConstructor;
import org.springframework.http.*;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

@RestController
@RequiredArgsConstructor
public class MyController {
    private final RestTemplate restTemplate;

    @RequestMapping("/hoge")
    public String hoge() {
        HttpHeaders headers = new HttpHeaders();
        headers.set("X-Test-Header", "Hoge");
        HttpEntity<Void> request = new HttpEntity<>(headers);

        // テスト用に localhost に(別途)サーバーを立てて叩くようにしておく
        ResponseEntity<String> response = restTemplate.exchange(
                "http://localhost:8081",
                HttpMethod.GET,
                request,
                String.class
        );

        if (response.getStatusCode() != HttpStatus.OK) {
            System.out.println("Request Failed");
            System.out.println(response.getStatusCode());
        }

        return "hoge";
    }
}
package net.f99aq8ove.keepalive;

import org.apache.http.ConnectionReuseStrategy;
import org.apache.http.HttpConnection;
import org.apache.http.impl.DefaultConnectionReuseStrategy;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.protocol.HttpCoreContext;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;

import java.time.Duration;
import java.util.concurrent.TimeUnit;

@Configuration
public class ClientHttpRequestConfiguration {
    @Bean
    public PoolingHttpClientConnectionManager poolingHttpClientConnectionManager() {
        PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager();
        connectionManager.setMaxTotal(400); // 全体で使用するコネクション数
        connectionManager.setDefaultMaxPerRoute(200); // ルートごとの最大コネクション数
        return connectionManager;
    }

    @Bean
    public RestTemplateBuilder restTemplateBuilder() {
        RestTemplateBuilder restTemplateBuilder = new RestTemplateBuilder();
        return restTemplateBuilder
                .requestFactory(() -> clientHttpRequestFactory(null))
                .setConnectTimeout(Duration.ofSeconds(10)) // 10 s コネクションタイムアウト
                .setReadTimeout(Duration.ofSeconds(3)); // 3 s 読み込みタイムアウト
    }

    @Bean
    public RestTemplate restTemplate() {
        RestTemplateBuilder restTemplateBuilder = restTemplateBuilder();
        return restTemplateBuilder.build();
    }

    @Bean
    public ClientHttpRequestFactory clientHttpRequestFactory(PoolingHttpClientConnectionManager connectionManager) {
        CloseableHttpClient httpClient = HttpClients
                .custom()
                .setConnectionManager(connectionManager)
                .setConnectionReuseStrategy(connectionReuseStrategy())
                .evictIdleConnections(30, TimeUnit.SECONDS) // 30 s 使用されない場合は捨てる
                .build();
        return new HttpComponentsClientHttpRequestFactory(httpClient);
    }

    public ConnectionReuseStrategy connectionReuseStrategy() {
        DefaultConnectionReuseStrategy defaultStrategy = DefaultConnectionReuseStrategy.INSTANCE;
        return (response, context) -> {
            // defaultStrategy は適用しておく
            boolean res = defaultStrategy.keepAlive(response, context);
            if (!res) {
                return false;
            }

            if (context instanceof HttpCoreContext) {
                HttpConnection connection = ((HttpCoreContext) context).getConnection();
                long requestCount = connection.getMetrics().getRequestCount();
                // リクエスト数が 10 回を超えたら切るようにする
                return requestCount < 10;
            }
            return false;
        };
    }
}

という感じで、ConnectionReuseStrategy を差し込むことで、動作を変更することができます。

Related Posts

Get new posts by email: