httpClient 수정

This commit is contained in:
cho
2026-02-12 14:15:07 +09:00
parent 86ccc7047a
commit d1c2b75e18
3 changed files with 131 additions and 101 deletions
@@ -132,7 +132,7 @@ public class AlarmStateManager {
payload = UmsRequestPayload.builder()
.content(ObpMonitoringUmsTemplate.builder().message(event.getMessage()).build())
.cellphone(cellPhone).build();
logger.info("Alarm is disabled, UmsRequestPayload = {}", payload);
usmService.send(payload);
}
@@ -1,6 +1,7 @@
package com.eactive.eai.custom.alarm.ums;
import java.util.Queue;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
@@ -49,7 +50,17 @@ public class UmsSendManager {
void sendMessage() throws Exception {
UmsRequestPayload payload;
while ((payload = queue.poll()) != null) {
umsService.send(payload);
final UmsRequestPayload finalPayload = payload;
CompletableFuture.runAsync(() -> {
UmsCallResult result = null;
try {
result = umsService.send(finalPayload);
} catch (Exception e) {
logger.error("UMS 전송 실패", e);
}finally {
logger.info(result);
}
});
}
}
@@ -1,48 +1,68 @@
package com.eactive.eai.custom.alarm.ums;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.Arrays;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.nio.charset.StandardCharsets;
import javax.annotation.PreDestroy;
import org.apache.hc.client5.http.classic.methods.HttpPost;
import org.apache.hc.client5.http.config.RequestConfig;
import org.apache.hc.client5.http.entity.EntityBuilder;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.HttpClients;
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuilder;
import org.apache.hc.client5.http.io.HttpClientConnectionManager;
import org.apache.hc.core5.http.ContentType;
import org.apache.hc.core5.http.ProtocolException;
import org.apache.hc.core5.http.io.entity.EntityUtils;
import org.apache.hc.core5.util.TimeValue;
import org.apache.hc.core5.util.Timeout;
import org.springframework.stereotype.Component;
import org.springframework.util.StreamUtils;
import com.eactive.eai.common.property.PropManager;
import com.eactive.eai.common.util.Jsons;
import com.eactive.eai.common.util.Logger;
import com.eactive.eai.custom.alarm.ums.payload.UmsRequestPayload;
import com.eactive.eai.custom.alarm.ums.payload.ValidationUtil;
import com.google.gson.JsonSyntaxException;
@Component
public class UmsService {
protected static Logger logger = Logger.getLogger(Logger.LOGGER_DEFAULT);
// 설정 상수
private final static String UMS_GROUP_PROP_NAME = "UMS";
private final static String UMS_PROP_HOST = "host";
private final static String UMS_PROP_URL = "url";
private final static String UMS_PROP_API_KEY = "apiKey";
private final static String UMS_URL = "http://172.31.35.144:9021";
private final static String API_KEY = "7cca6d58-e4f7-474d-9edd-525806bc99ff";
private final static String SEND_URL = "/ums/openapi/send";
private final static String CONTENT_TYPE = "application/json; charset=utf-8";
private final static String UMS_PROP_CONNECTION_TIMEOUT = "timeout.connection.ms";
private final static String UMS_PROP_TIMEOUT = "timeout.ms";
// 기본값
private final static String DEFAULT_UMS_URL = "http://172.31.35.144:9021";
private final static String DEFAULT_SEND_URI = "/ums/openapi/send";
private final static String DEFAULT_API_KEY = "7cca6d58-e4f7-474d-9edd-525806bc99ff";
private final static String AUTHORIZATION_FMT = "Bearer %s";
public Future<UmsCallResult> send(UmsRequestPayload payload) throws Exception {
// 재사용되는 HttpClient (Thread-Safe)
private final CloseableHttpClient httpClient;
public UmsService() {
// 커넥션 풀 설정: 매번 소켓을 열고 닫지 않고 재사용함
HttpClientConnectionManager cm = PoolingHttpClientConnectionManagerBuilder.create()
.setMaxConnTotal(100) // 전체 최대 커넥션 수
.setMaxConnPerRoute(20) // 호스트당 최대 커넥션 수
.setConnectionTimeToLive(TimeValue.ofMinutes(3)) // TTL
.setValidateAfterInactivity(TimeValue.ofSeconds(5)) // 🔥 중요
.build();
this.httpClient = HttpClients.custom()
.setConnectionManager(cm)
.evictExpiredConnections() // 만료 제거
.evictIdleConnections(TimeValue.ofSeconds(20)) // 20초 idle 제거
.build();
}
public UmsCallResult send(UmsRequestPayload payload) throws Exception {
if (payload.getContent() == null) {
throw new IllegalArgumentException("content is null");
}
@@ -57,79 +77,78 @@ public class UmsService {
return call(payload);
}
private CloseableHttpClient createHttpClient() {
return HttpClients.createDefault();
}
private Future<UmsCallResult> call(UmsRequestPayload requestPayload) throws Exception {
ExecutorService executor = Executors.newSingleThreadExecutor();
private UmsCallResult call(UmsRequestPayload requestPayload) throws Exception {
// 1. 프로퍼티 로드
PropManager prop = PropManager.getInstance();
String host = prop.getProperty(UMS_GROUP_PROP_NAME, UMS_PROP_HOST, DEFAULT_UMS_URL);
String uri = prop.getProperty(UMS_GROUP_PROP_NAME, UMS_PROP_URL, DEFAULT_SEND_URI);
String apiKey = prop.getProperty(UMS_GROUP_PROP_NAME, UMS_PROP_API_KEY, DEFAULT_API_KEY);
long connectionTimeoutMs = Long.parseLong(prop.getProperty(UMS_GROUP_PROP_NAME, UMS_PROP_CONNECTION_TIMEOUT, "3000"));
long timeoutMs = Long.parseLong(prop.getProperty(UMS_GROUP_PROP_NAME, UMS_PROP_TIMEOUT, "3000"));
// 2. 요청 생성
HttpPost httpPost = new HttpPost(host + uri);
httpPost.setHeader("Authorization", String.format(AUTHORIZATION_FMT, apiKey));
String json = Jsons.GSON.toJson(requestPayload);
httpPost.setEntity(EntityBuilder.create()
.setText(json)
.setContentType(ContentType.APPLICATION_JSON)
.setContentEncoding("UTF-8")
.build());
String host = PropManager.getInstance().getProperty(UMS_GROUP_PROP_NAME, UMS_PROP_HOST, UMS_URL);
String url = PropManager.getInstance().getProperty(UMS_GROUP_PROP_NAME, UMS_PROP_URL, SEND_URL);
String apiKey = PropManager.getInstance().getProperty(UMS_GROUP_PROP_NAME, UMS_PROP_API_KEY, API_KEY);
HttpPost httpPost = new HttpPost(host + url);
httpPost.setHeader("Content-Type", CONTENT_TYPE);
httpPost.setHeader("Authorization", String.format(AUTHORIZATION_FMT, apiKey));
httpPost.setEntity(EntityBuilder.create().setText(json).setContentType(ContentType.APPLICATION_JSON).build());
// 3. 타임아웃 설정 (설정파일 값 적용)
RequestConfig config = RequestConfig.custom()
.setConnectTimeout(Timeout.ofMilliseconds(connectionTimeoutMs))
.setResponseTimeout(Timeout.ofMilliseconds(timeoutMs))
.build();
httpPost.setConfig(config);
if (logger.isDebugEnabled()) {
try {
logger.debug("Header - {}", httpPost.getHeader("Content-Type"));
logger.debug("Header - {}", httpPost.getHeader("Authorization"));
logger.debug("Request Body : {}",
StreamUtils.copyToString(httpPost.getEntity().getContent(), Charset.defaultCharset()));
} catch (ProtocolException | IOException e) {
throw new RuntimeException(e);
}
logger.debug("UMS Request to: {}", host + uri);
logger.debug("UMS Request Body: {}", json);
}
httpPost.setConfig(RequestConfig.custom().setConnectTimeout(Timeout.ofSeconds(3))
.setResponseTimeout(Timeout.ofSeconds(3)).build());
Future<UmsCallResult> future;
try (CloseableHttpClient httpClient = createHttpClient()) {
future = executor.submit(() -> {
return httpClient.execute(httpPost, res -> {
// 4. 실행 (HttpClient 재사용)
try {
UmsCallResult result;
String body = EntityUtils.toString(res.getEntity());
return httpClient.execute(httpPost, response -> {
String body = EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8);
if (logger.isDebugEnabled()) {
logger.debug("Response - {}", body);
logger.debug("UMS Response Status: {}", response.getCode());
logger.debug("UMS Response Body: {}", body);
}
result = Jsons.GSON.fromJson(body, UmsCallResult.class);
UmsCallResult result = Jsons.GSON.fromJson(body, UmsCallResult.class);
result.setUmsMessageId(requestPayload.getMessageIdentityNo());
result.setSuccess(true);
// HTTP 200인 경우만 성공으로 간주 (필요에 따라 조정)
result.setSuccess(response.getCode() == 200);
if (response.getCode() != 200) {
logger.warn("UMS Call failed with status: {}", response.getCode());
}
return result;
} catch (JsonSyntaxException e) {
logger.error("UMS Call error(JSON Syntax)", e);
return UmsCallResult.builder().umsMessageId(requestPayload.getMessageIdentityNo())
.isSuccess(false).throwable(e).build();
});
} catch (Exception e) {
logger.error("UMS Call error", e);
return UmsCallResult.builder().umsMessageId(requestPayload.getMessageIdentityNo())
.isSuccess(false).throwable(e).build();
} finally {
logger.debug("Response status - {}", res.getCode());
if (res.getCode() != 200) {
Arrays.asList(res.getHeaders()).forEach(o -> {
logger.warn("[Header] {}: {}", o.getName(), o.getValue());
});
logger.error("UMS call exception: " + e.getMessage(), e);
return UmsCallResult.builder()
.umsMessageId(requestPayload.getMessageIdentityNo())
.isSuccess(false)
.throwable(e)
.build();
}
}
});
});
return future;
} catch (Exception e) {
logger.error("UMS call exception", e);
throw new Exception("UMS call failed", e);
} finally {
executor.shutdown();
}
@PreDestroy
public void destroy() {
try {
if (httpClient != null) {
httpClient.close();
logger.info("UMS HttpClient closed.");
}
} catch (IOException e) {
logger.error("Error closing UMS HttpClient", e);
}
}
}