Skip to content

Commit

Permalink
Merge pull request #152 from BOOK-TALK/#151-openapi-timeout
Browse files Browse the repository at this point in the history
[feat, fix] #151 Open API 타임아웃 및 요청 재시도 로직 구현
  • Loading branch information
chanwoo7 authored Sep 19, 2024
2 parents c71fd26 + 14ade58 commit 927b989
Show file tree
Hide file tree
Showing 3 changed files with 49 additions and 5 deletions.
49 changes: 44 additions & 5 deletions src/main/java/com/book/backend/domain/openapi/service/OpenAPI.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.lang.reflect.Field;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.*;

@Component
@RequiredArgsConstructor
Expand All @@ -25,14 +27,44 @@ public class OpenAPI {
@Value("${openapi.authKey}")
private String authKey;

private final String format = "json";
@Value("${openapi.timeoutSeconds}")
private int TIMEOUT_SECONDS; // 타임아웃 시간 (초)

@Value("${openapi.maxRetryCounts}")
private int MAX_RETRY_COUNTS; // 최대 재시도 횟수

private final String format = "json";
private final ExecutorService executorService = Executors.newCachedThreadPool();

public JSONObject connect(String subUrl, OpenAPIRequestInterface dto, OpenAPIResponseInterface responseDto) throws Exception {
log.trace("OpenAPI > connect()");
URL url = setRequest(subUrl, dto); // 요청 만들기
InputStreamReader streamResponse = new InputStreamReader(url.openStream(), "UTF-8"); // 요청 보내기
return readStreamToJson(streamResponse, responseDto); // 응답 stream 을 json 으로 변환
int retryCount = 0;

while (retryCount < MAX_RETRY_COUNTS) {
try {
CompletableFuture<JSONObject> future = CompletableFuture.supplyAsync(() -> {
try {
URL url = setRequest(subUrl, dto); // 요청 만들기

log.trace("Request URL: " + url);

InputStreamReader streamResponse = new InputStreamReader(url.openStream(), StandardCharsets.UTF_8); // 요청 보내기
return readStreamToJson(streamResponse, responseDto); // 응답 stream 을 json 으로 변환
} catch (Exception e) {
throw new RuntimeException(e); // 커스텀 불필요, 런타임 에러로 처리
}
}, executorService);

// 타임아웃 설정 및 결과 가져오기
return future.get(TIMEOUT_SECONDS, TimeUnit.SECONDS);
} catch (TimeoutException e) {
log.warn("OPEN API 응답을 요청하는 중 타임아웃이 발생했습니다. 재시도합니다...(" + (retryCount + 1) + "/" + MAX_RETRY_COUNTS + ")");
retryCount++;
}
}

// 재시도 횟수를 초과하면 예외 던지기
throw new CustomException(ErrorCode.OPENAPI_REQUEST_TIMEOUT);
}

private URL setRequest(String subUrl, OpenAPIRequestInterface dto) throws Exception {
Expand All @@ -57,10 +89,17 @@ private URL setRequest(String subUrl, OpenAPIRequestInterface dto) throws Except
private JSONObject readStreamToJson(InputStreamReader streamResponse, OpenAPIResponseInterface responseDto) throws Exception {
log.trace("OpenAPI > readStreamToJson()");
String fullResponse = new BufferedReader(streamResponse).readLine();
JSONObject jsonObject;

// response JSON 파싱
JSONObject jsonObject = (JSONObject) (new JSONParser()).parse(fullResponse);
try {
jsonObject = (JSONObject) (new JSONParser()).parse(fullResponse);
} catch (Exception e) {
throw new CustomException(ErrorCode.INVALID_OPENAPI_RESPONSE);
}

JSONObject response = (JSONObject) jsonObject.get("response");

// API 일일 호출 횟수 초과 에러 (일 최대 500건)
if(response.get("error") != null){
throw new CustomException(ErrorCode.API_CALL_LIMIT_EXCEEDED);
Expand Down
3 changes: 3 additions & 0 deletions src/main/java/com/book/backend/exception/ErrorCode.java
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,13 @@ public enum ErrorCode {
MESSAGE_SAVE_FAILED(HttpStatus.INTERNAL_SERVER_ERROR, "500", "메시지 저장에 실패했습니다."),
USER_OPENTALK_NOT_FOUND(HttpStatus.NOT_FOUND, "404", "해당 오픈톡은 유저의 즐겨찾기 리스트에 없습니다."),
INVALID_MESSAGE_TYPE(HttpStatus.BAD_REQUEST, "400", "text, image, goal 중 하나를 입력해주세요."),

// 외부 API 에러
KAKAO_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "500", "카카오 서버에 오류가 발생했습니다."),
INVALID_OPENAPI_RESPONSE(HttpStatus.INTERNAL_SERVER_ERROR, "500", "OPEN API 서버에서 잘못된 응답을 전송했습니다."),
API_CALL_LIMIT_EXCEEDED(HttpStatus.TOO_MANY_REQUESTS, "429", "OPEN API 일일 호출 횟수를 초과했습니다. (일 최대 500건)"),
LIBCODE_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "500", "존재하는 도서관 코드인지 확인해주세요."),
OPENAPI_REQUEST_TIMEOUT(HttpStatus.REQUEST_TIMEOUT, "408", "OPEN API 응답을 요청하는 중 타임아웃이 발생했습니다."),

// OAuth2
HEADER_PARSING_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "500", "Header 파싱 중 에러가 발생했습니다."),
Expand Down
2 changes: 2 additions & 0 deletions src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ spring-doc:
openapi:
url: ${OPENAPI_URL}
authKey: ${OPENAPI_AUTH_KEY}
timeoutSeconds: ${OPENAPI_TIMEOUT_SECONDS}
maxRetryCounts: ${OPENAPI_MAX_RETRY_COUNTS}

kakao:
publicKeyUri: https://kauth.kakao.com/.well-known/jwks.json
Expand Down

0 comments on commit 927b989

Please sign in to comment.