| 
					
				 | 
			
			
				@@ -32,7 +32,28 @@ public final class RequestUrlData { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				             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 <T> RequestUrlDataResult<T> executeRequestWithRetries(String urlString, int connectTimeout, int readTimeout, int maxRetries, int retryDelaySeconds, TypeReference<T> jsonType, Class<T> xmlType) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    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) { 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -45,40 +66,41 @@ public final class RequestUrlData { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                 String contentType = getContentType(connection); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                 String response = readResponse(connection); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-//                log.info("요청 성공 ({}번째 시도): [{}] 상태 코드 {}, 응답 크기 {} bytes", attempt, urlString, responseCode, response.length()); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-//                log.info("Content-Type: {}", contentType); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-//                log.info("응답 내용: {}", response); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				- 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                 if (responseCode == HttpURLConnection.HTTP_OK) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                    T data = "application/json".equalsIgnoreCase(contentType) && jsonType != null 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                            ? parseJson(response, jsonType) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                            : parseXml(response, xmlType); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				- 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                    return new RequestUrlDataResult<>(RequestUrlDataError.SUCCESS, data); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    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); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                 } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-//                log.warn("요청 실패 ({}번째 시도): [{}] 상태 코드 {}, 메시지 {}", attempt, urlString, responseCode, connection.getResponseMessage()); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				- 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                if (shouldRetry(responseCode) && attempt < maxRetries) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                    log.info("재시도 진행 중... ({}/{})", attempt, maxRetries); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                    Thread.sleep(retryDelaySeconds * 1000L); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                    continue; // 재시도 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                // 오류 응답 처리 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                if (attempt < maxRetries && shouldRetry(responseCode)) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    int retryDelay = (responseCode == 429) ? getRetryDelay(connection, retryDelaySeconds) : retryDelaySeconds; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    log.info("서버에서 오류 발생[RESPONSE: {}]. {}초 후 재시도... ({}/{})", responseCode, retryDelay, attempt, maxRetries); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    retryWithDelay(retryDelay); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                else { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    break; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                 } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                return new RequestUrlDataResult<>(RequestUrlDataError.HTTP_ERROR, null); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				             } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				             catch (Exception e) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                 log.error("요청 오류 발생 ({}/{}): [{}] -> {}", attempt, maxRetries, urlString, e.getMessage()); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                if (attempt < maxRetries) { // 최대 횟수 도달 전이면 재시도 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                    log.info("네트워크 오류 발생. {}초 후 재시도... ({}/{})", retryDelaySeconds, attempt, maxRetries); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                    try { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                        Thread.sleep(retryDelaySeconds * 1000L); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                    } catch (InterruptedException ex) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                        log.error("재시도 대기 중 인터럽트 발생: {}", ex.getMessage()); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                        break; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                    } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                    continue; // 재시도 로직으로 이동 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                if (attempt < maxRetries) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    log.info("네트워크 요청 오류 발생. {}초 후 재시도... ({}/{})", retryDelaySeconds, attempt, maxRetries); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    retryWithDelay(retryDelaySeconds); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                else { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    return new RequestUrlDataResult<>(RequestUrlDataError.NETWORK_ERROR, e.getMessage(), null); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                 } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                return new RequestUrlDataResult<>(RequestUrlDataError.NETWORK_ERROR, null); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				             } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				             finally { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                 if (connection != null) { 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -87,11 +109,46 @@ public final class RequestUrlData { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				             } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        return new RequestUrlDataResult<>(RequestUrlDataError.RETRY_LIMIT_EXCEEDED, null); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        log.warn("최대 재시도 횟수({})를 초과했습니다: [{}]", maxRetries, urlString); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        return new RequestUrlDataResult<>(RequestUrlDataError.RETRY_LIMIT_EXCEEDED, "재시도 횟수 초과", null); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     private static boolean shouldRetry(int responseCode) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        return responseCode >= 500; // 서버 오류 (5xx) 발생 시 재시도 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        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 { 
			 |