From 3e65b5eeaee89758558667787e48a5ab9908a46f Mon Sep 17 00:00:00 2001
From: Vitaly Takmazov
Date: Mon, 12 Apr 2021 22:00:11 +0300
Subject: ActivityPub: pooling HTTP client
---
pom.xml | 20 ++++
.../java/com/juick/config/ActivityPubConfig.java | 30 ++----
.../java/com/juick/config/HttpClientConfig.java | 111 +++++++++++++++++++++
3 files changed, 141 insertions(+), 20 deletions(-)
create mode 100644 src/main/java/com/juick/config/HttpClientConfig.java
diff --git a/pom.xml b/pom.xml
index 6325817d..333546b6 100644
--- a/pom.xml
+++ b/pom.xml
@@ -45,6 +45,16 @@
org.springframework.boot
spring-boot-starter-undertow
+
+
+ io.undertow
+ undertow-websockets-jsr
+
+
+
+
+ org.apache.httpcomponents
+ httpclient
org.apache.commons
@@ -67,6 +77,7 @@
net.sourceforge.htmlunit
htmlunit
+ test
xml-apis
@@ -175,6 +186,7 @@
com.google.code.findbugs
jsr305
3.0.2
+ provided
com.kotcrab.remark
@@ -251,6 +263,14 @@
org.springframework.boot
spring-boot-maven-plugin
+
+
+
+ com.google.code.findbugs
+ jsr305
+
+
+
org.codehaus.mojo
diff --git a/src/main/java/com/juick/config/ActivityPubConfig.java b/src/main/java/com/juick/config/ActivityPubConfig.java
index 661f6276..430dc8d1 100644
--- a/src/main/java/com/juick/config/ActivityPubConfig.java
+++ b/src/main/java/com/juick/config/ActivityPubConfig.java
@@ -21,20 +21,15 @@ import com.fasterxml.jackson.databind.ObjectMapper;
import com.juick.ActivityPubManager;
import com.juick.www.api.activity.model.Activity;
import com.juick.util.HeaderRequestInterceptor;
-import org.apache.http.client.config.CookieSpecs;
-import org.apache.http.client.config.RequestConfig;
-import org.apache.http.client.protocol.HttpClientContext;
-import org.apache.http.protocol.HttpContext;
+import org.apache.http.impl.client.CloseableHttpClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
-import org.springframework.http.HttpMethod;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.client.RestTemplate;
import javax.inject.Inject;
-import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
@@ -54,22 +49,17 @@ public class ActivityPubConfig {
public ActivityPubManager activityPubManager() {
return new ActivityPubManager();
}
+ @Inject
+ CloseableHttpClient httpClient;
+ @Bean
+ public HttpComponentsClientHttpRequestFactory clientHttpRequestFactory() {
+ HttpComponentsClientHttpRequestFactory clientHttpRequestFactory = new HttpComponentsClientHttpRequestFactory();
+ clientHttpRequestFactory.setHttpClient(httpClient);
+ return clientHttpRequestFactory;
+ }
@Bean
public RestTemplate apClient() {
- RestTemplate restTemplate = new RestTemplate();
- restTemplate.setRequestFactory(new HttpComponentsClientHttpRequestFactory() {
- @Override
- protected HttpContext createHttpContext(HttpMethod httpMethod, URI uri) {
- HttpClientContext context = HttpClientContext.create();
- context.setRequestConfig(getRequestConfig());
- return context;
- }
- RequestConfig getRequestConfig() {
- RequestConfig.Builder builder = RequestConfig.custom()
- .setCookieSpec(CookieSpecs.IGNORE_COOKIES);
- return builder.build();
- }
- });
+ RestTemplate restTemplate = new RestTemplate(clientHttpRequestFactory());
restTemplate.getMessageConverters().add(0, mappingJacksonHttpMessageConverter());
restTemplate.getMessageConverters().add(0, new StringHttpMessageConverter(StandardCharsets.UTF_8));
restTemplate.setErrorHandler(activityPubClientErrorHandler);
diff --git a/src/main/java/com/juick/config/HttpClientConfig.java b/src/main/java/com/juick/config/HttpClientConfig.java
new file mode 100644
index 00000000..0b23ca15
--- /dev/null
+++ b/src/main/java/com/juick/config/HttpClientConfig.java
@@ -0,0 +1,111 @@
+package com.juick.config;
+
+import java.util.concurrent.TimeUnit;
+
+import org.apache.http.HeaderElement;
+import org.apache.http.HeaderElementIterator;
+import org.apache.http.HttpResponse;
+import org.apache.http.client.config.CookieSpecs;
+import org.apache.http.client.config.RequestConfig;
+import org.apache.http.conn.ConnectionKeepAliveStrategy;
+
+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.message.BasicHeaderElementIterator;
+import org.apache.http.protocol.HTTP;
+import org.apache.http.protocol.HttpContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.scheduling.annotation.EnableScheduling;
+import org.springframework.scheduling.annotation.Scheduled;
+
+/**
+ * - Uses a connection pool to re-use connections and save overhead of creating connections.
+ * - Has a custom connection keep-alive strategy (to apply a default keep-alive if one isn't specified)
+ * - Starts an idle connection monitor to continuously clean up stale connections.
+ */
+@Configuration
+public class HttpClientConfig {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(HttpClientConfig.class);
+
+ // Determines the timeout in milliseconds until a connection is established.
+ private static final int CONNECT_TIMEOUT = 30000;
+
+ // The timeout when requesting a connection from the connection manager.
+ private static final int REQUEST_TIMEOUT = 30000;
+
+ // The timeout for waiting for data
+ private static final int SOCKET_TIMEOUT = 60000;
+
+ private static final int MAX_TOTAL_CONNECTIONS = 50;
+ private static final int DEFAULT_KEEP_ALIVE_TIME_MILLIS = 20 * 1000;
+ private static final int CLOSE_IDLE_CONNECTION_WAIT_TIME_SECS = 30;
+
+ @Bean
+ public PoolingHttpClientConnectionManager poolingConnectionManager() {
+ PoolingHttpClientConnectionManager poolingConnectionManager = new PoolingHttpClientConnectionManager();
+ poolingConnectionManager.setMaxTotal(MAX_TOTAL_CONNECTIONS);
+ return poolingConnectionManager;
+ }
+
+ @Bean
+ public ConnectionKeepAliveStrategy connectionKeepAliveStrategy() {
+ return new ConnectionKeepAliveStrategy() {
+ @Override
+ public long getKeepAliveDuration(HttpResponse response, HttpContext context) {
+ HeaderElementIterator it = new BasicHeaderElementIterator
+ (response.headerIterator(HTTP.CONN_KEEP_ALIVE));
+ while (it.hasNext()) {
+ HeaderElement he = it.nextElement();
+ String param = he.getName();
+ String value = he.getValue();
+
+ if (value != null && param.equalsIgnoreCase("timeout")) {
+ return Long.parseLong(value) * 1000;
+ }
+ }
+ return DEFAULT_KEEP_ALIVE_TIME_MILLIS;
+ }
+ };
+ }
+
+ @Bean
+ public CloseableHttpClient httpClient() {
+ RequestConfig requestConfig = RequestConfig.custom()
+ .setCookieSpec(CookieSpecs.IGNORE_COOKIES)
+ .setConnectionRequestTimeout(REQUEST_TIMEOUT)
+ .setConnectTimeout(CONNECT_TIMEOUT)
+ .setSocketTimeout(SOCKET_TIMEOUT).build();
+
+ return HttpClients.custom()
+ .setDefaultRequestConfig(requestConfig)
+ .setConnectionManager(poolingConnectionManager())
+ .setKeepAliveStrategy(connectionKeepAliveStrategy())
+ .build();
+ }
+
+ @Bean
+ public Runnable idleConnectionMonitor(final PoolingHttpClientConnectionManager connectionManager) {
+ return new Runnable() {
+ @Override
+ @Scheduled(fixedDelay = 10000)
+ public void run() {
+ try {
+ if (connectionManager != null) {
+ LOGGER.trace("run IdleConnectionMonitor - Closing expired and idle connections...");
+ connectionManager.closeExpiredConnections();
+ connectionManager.closeIdleConnections(CLOSE_IDLE_CONNECTION_WAIT_TIME_SECS, TimeUnit.SECONDS);
+ } else {
+ LOGGER.trace("run IdleConnectionMonitor - Http Client Connection manager is not initialised");
+ }
+ } catch (Exception e) {
+ LOGGER.error("run IdleConnectionMonitor - Exception occurred. msg={}, e={}", e.getMessage(), e);
+ }
+ }
+ };
+ }
+}
\ No newline at end of file
--
cgit v1.2.3