123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192 |
- package com.utic.incident.common.url;
- import com.fasterxml.jackson.core.JsonProcessingException;
- import com.fasterxml.jackson.core.type.TypeReference;
- import com.fasterxml.jackson.databind.DeserializationFeature;
- import com.fasterxml.jackson.databind.ObjectMapper;
- import lombok.extern.slf4j.Slf4j;
- import javax.xml.bind.JAXBContext;
- import javax.xml.bind.JAXBException;
- import javax.xml.bind.Unmarshaller;
- import java.io.BufferedReader;
- import java.io.IOException;
- import java.io.InputStreamReader;
- import java.io.StringReader;
- import java.net.HttpURLConnection;
- import java.net.URL;
- import java.nio.charset.StandardCharsets;
- import java.util.stream.Collectors;
- @Slf4j
- public final class RequestUrlData {
- private static final ObjectMapper objectMapper = new ObjectMapper()
- .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
- public static <T> RequestUrlDataResult<T> fetchJsonDataFromUrl(String urlString, int connectTimeout, int readTimeout, int maxRetries, int retryDelaySeconds, TypeReference<T> responseType) {
- return executeRequestWithRetries(urlString, connectTimeout, readTimeout, maxRetries, retryDelaySeconds, responseType, null);
- }
- public static <T> RequestUrlDataResult<T> fetchXmlDataFromUrl(
- String urlString, int connectTimeout, int readTimeout, int maxRetries, int retryDelaySeconds, Class<T> responseType) {
- return executeRequestWithRetries(urlString, connectTimeout, readTimeout, maxRetries, retryDelaySeconds, null, responseType); // ✅ rootElement 추가
- }
- private static void retryWithDelay(int retryDelaySeconds) {
- if (retryDelaySeconds > 0) {
- try {
- log.info("{}초 동안 대기 후 재시도...", retryDelaySeconds);
- Thread.sleep(retryDelaySeconds * 1000L);
- } catch (InterruptedException ex) {
- Thread.currentThread().interrupt();
- log.error("재시도 대기 중 인터럽트 발생: {}", ex.getMessage());
- }
- }
- }
- private static <T> RequestUrlDataResult<T> executeRequestWithRetries(
- final String urlString,
- final int connectTimeout,
- final int readTimeout,
- final int maxRetries,
- final int retryDelaySeconds,
- final TypeReference<T> jsonType,
- final Class<T> xmlType) {
- int attempt = 0;
- while (attempt < maxRetries) {
- attempt++;
- HttpURLConnection connection = null;
- try {
- connection = createConnection(urlString, connectTimeout, readTimeout);
- int responseCode = connection.getResponseCode();
- String contentType = getContentType(connection);
- String response = readResponse(connection);
- if (responseCode == HttpURLConnection.HTTP_OK) {
- if ("application/json".equalsIgnoreCase(contentType) && jsonType != null) {
- // JSON 응답 처리
- return new RequestUrlDataResult<>(RequestUrlDataError.SUCCESS, "SUCCESS", parseJson(response, jsonType));
- }
- else if ("application/xml".equalsIgnoreCase(contentType) && xmlType != null) {
- // XML 응답 처리
- return new RequestUrlDataResult<>(RequestUrlDataError.SUCCESS, "SUCCESS", parseXml(response, xmlType));
- }
- else {
- String errorMessage = String.format("지원되지 않는 Content-Type: %s", contentType);
- log.warn("{}.", errorMessage);
- return new RequestUrlDataResult<>(RequestUrlDataError.UNSUPPORTED_CONTENT_TYPE, errorMessage, null);
- }
- }
- // 오류 응답 처리
- if (attempt < maxRetries && shouldRetry(responseCode)) {
- int retryDelay = (responseCode == 429) ? getRetryDelay(connection, retryDelaySeconds) : retryDelaySeconds;
- log.info("서버에서 오류 발생[RESPONSE: {}]. {}초 후 재시도... ({}/{})", responseCode, retryDelay, attempt, maxRetries);
- retryWithDelay(retryDelay);
- }
- else {
- break;
- }
- }
- catch (Exception e) {
- log.error("요청 오류 발생 ({}/{}): [{}] -> {}", attempt, maxRetries, urlString, e.getMessage());
- if (attempt < maxRetries) {
- log.info("네트워크 요청 오류 발생. {}초 후 재시도... ({}/{})", retryDelaySeconds, attempt, maxRetries);
- retryWithDelay(retryDelaySeconds);
- }
- else {
- return new RequestUrlDataResult<>(RequestUrlDataError.NETWORK_ERROR, e.getMessage(), null);
- }
- }
- finally {
- if (connection != null) {
- connection.disconnect();
- }
- }
- }
- log.warn("최대 재시도 횟수({})를 초과했습니다: [{}]", maxRetries, urlString);
- return new RequestUrlDataResult<>(RequestUrlDataError.RETRY_LIMIT_EXCEEDED, "재시도 횟수 초과", null);
- }
- private static boolean shouldRetry(int responseCode) {
- return (responseCode >= 500 && responseCode <= 504) || responseCode == 429;
- }
- private static int getRetryDelay(HttpURLConnection connection, int retryDelaySeconds) {
- String retryAfter = connection.getHeaderField("Retry-After");
- if (retryAfter != null) {
- try {
- int retrySeconds = Integer.parseInt(retryAfter);
- return Math.min(retrySeconds, retryDelaySeconds); // 최대 제한 적용
- } catch (NumberFormatException e) {
- log.warn("Retry-After 값 파싱 오류: {}", e.getMessage());
- }
- }
- return 0; // Retry-After 값이 없거나 오류가 발생한 경우 기본값 반환
- }
- private static boolean shouldRetry(int responseCode, HttpURLConnection connection) {
- if (responseCode >= 500 && responseCode <= 504) {
- return true; // 서버 오류로 인해 재시도 가능
- }
- if (responseCode == 429) {
- String retryAfter = connection.getHeaderField("Retry-After");
- if (retryAfter != null) {
- try {
- int retrySeconds = Integer.parseInt(retryAfter);
- log.info("서버 요청 제한(429). {}초 후 재시도 권장.", retrySeconds);
- Thread.sleep(retrySeconds * 1000L);
- return true;
- } catch (NumberFormatException | InterruptedException e) {
- log.warn("Retry-After 값을 파싱하는 중 오류 발생: {}", e.getMessage());
- Thread.currentThread().interrupt();
- }
- }
- return false; // Retry-After 정보가 없으면 재시도하지 않음
- }
- return false; // 기타 오류 코드에 대해서는 재시도하지 않음
- }
- private static HttpURLConnection createConnection(String urlString, int connectTimeout, int readTimeout) throws IOException {
- URL url = new URL(urlString);
- HttpURLConnection connection = (HttpURLConnection) url.openConnection();
- connection.setConnectTimeout(connectTimeout*1000);
- connection.setReadTimeout(readTimeout*1000);
- connection.setRequestMethod("GET");
- connection.setRequestProperty("Accept", "application/json, application/xml");
- connection.setRequestProperty("User-Agent", "Mozilla/5.0");
- return connection;
- }
- private static String getContentType(HttpURLConnection connection) {
- String contentType = connection.getContentType();
- return contentType != null ? contentType.split(";")[0].trim() : "";
- }
- private static String readResponse(HttpURLConnection connection) throws IOException {
- try (BufferedReader br = new BufferedReader(new InputStreamReader(connection.getInputStream(), StandardCharsets.UTF_8))) {
- return br.lines().collect(Collectors.joining());
- }
- }
- private static <T> T parseJson(String json, TypeReference<T> responseType) throws JsonProcessingException {
- return objectMapper.readValue(json, responseType);
- }
- private static <T> T parseXml(String xml, Class<T> responseType) throws Exception {
- // String cleanedXml = xml.replaceFirst("^\\uFEFF", ""); // UTF-8 BOM 제거
- try {
- JAXBContext jaxbContext = JAXBContext.newInstance(responseType);
- Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
- return responseType.cast(unmarshaller.unmarshal(new StringReader(xml)));
- // return (T) unmarshaller.unmarshal(new StringReader(xml));
- } catch (JAXBException e) {
- return null; // XML 파싱 실패 시 null 반환
- }
- }
- }
|