728x90
반응형

💥 1. HttpServerErrorException

🔹 상황: 타사 API가 500, 502, 503 등 **서버 오류(5xx)**를 반환

try {
    restTemplate.getForObject(url, String.class);
} catch (HttpServerErrorException e) {
    log.error("타사 API 서버 오류: {}, 응답 바디: {}", e.getStatusCode(), e.getResponseBodyAsString());
    throw new ExternalApiException("타사 시스템 오류입니다. 잠시 후 다시 시도해주세요.");
}
예외 상황설명
500 Internal Server Error 타사 시스템의 내부 예외
502 Bad Gateway API Gateway 문제 (예: Cloudflare, Nginx)
503 Service Unavailable 서버 과부하 또는 유지보수 중
 

💥 2. ResourceAccessException

🔹 상황: 타사 API 연결 자체가 되지 않을 때

catch (ResourceAccessException e) {
    log.warn("타사 API 연결 불가: {}", e.getMessage());
    throw new ExternalApiException("현재 외부 시스템과 통신할 수 없습니다.");
}
발생 원인설명
DNS 오류 도메인 이름을 못 찾음
타임아웃 연결/응답 시간이 너무 길어 끊김
네트워크 장애 사내 방화벽, VPN 이슈 등
 

💥 3. JsonProcessingException / HttpMessageConversionException

🔹 상황: 타사 응답 JSON이 예상과 다를 때

try {
    var response = restTemplate.exchange(...);
    return objectMapper.readValue(response.getBody(), ExternalDto.class);
} catch (JsonProcessingException e) {
    log.error("타사 JSON 파싱 오류: {}", e.getMessage());
    throw new ExternalApiException("타사 응답 포맷이 올바르지 않습니다.");
}
원인설명
누락된 필드 DTO에 @JsonIgnoreProperties(ignoreUnknown = true) 필요
필드 타입 불일치 숫자인 줄 알았는데 문자열 들어옴 등
응답 구조 변경 타사에서 응답 구조를 바꾸는 경우 (문서 변경 없이)
 

💥 4. UnknownHostException

🔹 상황: 아예 도메인을 못 찾을 때 (DNS 오류)

java.net.UnknownHostException: api.unknown-external-service.com
  • 발생 조건: 잘못된 도메인, VPN 밖에서 접근 불가한 내부 주소 등
  • 대처: 도메인 확인, 방화벽/ACL 정책 검토

🛡️ 실무 대응 전략 요약

예외 종류실무 대응 전략
4xx (HttpClientErrorException) 파라미터, 인증 관련 오류 → 요청 검증 강화 + 응답 로그
5xx (HttpServerErrorException) 타사 장애 → 리트라이, 백오프 적용
ResourceAccessException 타임아웃/네트워크 오류 → CircuitBreaker 또는 Fallback 적용
JsonProcessingException 응답 포맷 오류 → DTO 구조 유연하게 처리, 예외 포착
UnknownHostException DNS/네트워크 점검, 사전 검증 로직 구현
 

📌 결론

타사 API 연동 시 자주 발생하는 예외 TOP 5는 다음과 같습니다:

순위예외 클래스주요 원인
HttpClientErrorException 인증 누락, 파라미터 오류
HttpServerErrorException 타사 장애, 5xx 응답
ResourceAccessException 네트워크 오류, 타임아웃
JsonProcessingException JSON 구조 불일치
UnknownHostException DNS 또는 내부망 주소 문제
728x90
반응형
728x90
반응형

💥 예외: HttpClientErrorException (RestTemplate or WebClient 사용 시)


📍 언제 발생하나요?

  • Spring의 RestTemplate 또는 WebClient 등으로 타사 API를 호출했을 때,
    타사에서 **HTTP 4xx 오류 (예: 400, 401, 403, 404)**를 응답하면 이 예외가 발생합니다.

🎯 대표 상황 예시

RestTemplate restTemplate = new RestTemplate();
String url = "https://external.api.com/data";

try {
    ResponseEntity<String> response = restTemplate.getForEntity(url, String.class);
} catch (HttpClientErrorException e) {
    System.out.println("타사 API 오류 발생: " + e.getStatusCode());
    System.out.println("응답 본문: " + e.getResponseBodyAsString());
}

자주 발생하는 케이스:

상태코드원인
400 Bad Request 파라미터 누락, 잘못된 요청 형식
401 Unauthorized 인증 토큰 누락/만료
403 Forbidden 권한 부족, 접근 제한
404 Not Found 잘못된 URL, 존재하지 않는 리소스 요청
 

✅ 실무 대처 방안: 예외별로 상세 로그 + 사용자 정의 예외로 감싸기

 
public String callExternalApi(String token) {
    try {
        HttpHeaders headers = new HttpHeaders();
        headers.set("Authorization", "Bearer " + token);
        HttpEntity<?> request = new HttpEntity<>(headers);

        return restTemplate.exchange("https://api.example.com/data", HttpMethod.GET, request, String.class).getBody();

    } catch (HttpClientErrorException e) {
        log.error("[타사 API 오류] 상태코드: {}, 응답: {}", e.getStatusCode(), e.getResponseBodyAsString());

        if (e.getStatusCode() == HttpStatus.UNAUTHORIZED) {
            throw new ExternalApiAuthException("타사 인증 오류: 토큰 만료 또는 누락");
        } else {
            throw new ExternalApiException("타사 API 오류", e);
        }
    }
}

🎁 실무 팁

팁설명
타사 응답 바디도 저장 e.getResponseBodyAsString() 로그로 남기기
커스텀 예외로 감싸기 비즈니스 로직 코드가 HttpClientErrorException에 의존하지 않게 분리
공통 예외 어댑터 만들기 타사 API 전용 ApiClient 또는 ApiErrorHandler 클래스 활용
 

📦 정리

항목설명
예외명 HttpClientErrorException (Spring REST 호출 시)
주요 원인 타사 API가 4xx 에러 반환
자주 발생하는 코드 400, 401, 403, 404
대처 예외 로그 상세 기록 + 커스텀 예외로 래핑 + 사용자 안내 메시지 구성
주의사항 응답 바디 없이 401만 주는 경우 디버깅 어려우니 token 만료 여부 로깅 필수
728x90
반응형
728x90
반응형

❓ 언제 발생하나요?

  • 클라이언트가 서버가 지원하지 않는 Content-Type으로 요청할 때 발생합니다.

예를 들어 서버는 application/json만 받도록 설정되어 있는데, 클라이언트가 아래처럼 보낸다면?

POST /api/users
Content-Type: application/xml

<user><name>John</name></user>

→ 서버가 XML을 지원하지 않는 경우 이 예외가 발생합니다.


📛 예외 메시지 예시

Content type 'application/xml' not supported

✅ 왜 잘 안 발생하나요?

  • 대부분의 현대 프론트엔드/모바일 앱은 Content-Type: application/json을 기본으로 사용하고,
  • Spring Boot도 기본적으로 application/json만 처리하므로 잘 관리된 시스템에서는 거의 문제가 발생하지 않음

✅ 대처 방안: 사용자에게 명확한 응답 제공

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(HttpMediaTypeNotSupportedException.class)
    public ResponseEntity<ErrorResponse> handleUnsupportedMediaType(HttpMediaTypeNotSupportedException ex) {
        String msg = "지원하지 않는 Content-Type입니다. application/json으로 요청해주세요.";
        return ResponseEntity.status(HttpStatus.UNSUPPORTED_MEDIA_TYPE)
                .body(new ErrorResponse("UNSUPPORTED_MEDIA_TYPE", List.of(msg)));
    }
}

📦 요약

항목설명
예외 HttpMediaTypeNotSupportedException
원인 서버가 허용하지 않은 Content-Type으로 요청
발생 빈도 매우 낮음 (현대 프론트엔드 대부분 JSON 사용)
대처 사용자에게 Content-Type 안내 메시지 제공
HTTP 상태 코드 415 Unsupported Media Type
728x90
반응형
728x90
반응형

❓ 언제 발생하나요?

  • @Valid 또는 @Validated가 붙은 요청 객체에 대해 검증 실패 시 발생합니다.
  • 즉, 클라이언트가 유효성 조건을 지키지 않은 데이터를 보냈을 때입니다.

📌 예시 상황

📥 클라이언트 요청

POST /users
Content-Type: application/json

{
  "name": "",             // 이름이 공백
  "age": -5               // 나이가 음수
}

📦 요청 DTO

public record UserRequest(
    @NotBlank(message = "이름은 필수입니다.")
    String name,

    @Min(value = 0, message = "나이는 0 이상이어야 합니다.")
    int age
) {}

💣 발생 예외

org.springframework.web.bind.MethodArgumentNotValidException:
Validation failed for argument [0] in public ResponseEntity ...

✅ 대처 방안: 사용자에게 필드별 오류를 구체적으로 반환

1️⃣ 전역 예외 처리기 (@RestControllerAdvice)

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<ErrorResponse> handleValidationErrors(MethodArgumentNotValidException ex) {
        List<String> errorMessages = ex.getBindingResult()
            .getFieldErrors()
            .stream()
            .map(error -> error.getField() + ": " + error.getDefaultMessage())
            .toList();

        return ResponseEntity.badRequest().body(new ErrorResponse("VALIDATION_FAILED", errorMessages));
    }
}

2️⃣ 에러 응답 객체

public record ErrorResponse(String code, List<String> messages) {}

🔍 결과 응답 예시

HTTP/1.1 400 Bad Request
Content-Type: application/json

{
  "code": "VALIDATION_FAILED",
  "messages": [
    "name: 이름은 필수입니다.",
    "age: 나이는 0 이상이어야 합니다."
  ]
}

🔧 팁

목적구현 방법
다국어 메시지 처리 ValidationMessages.properties + MessageSource 사용
특정 필드만 포맷팅 Map<String, String> 형태로 반환
응답 포맷 통일 모든 예외에서 공통 ErrorResponse 사용
 

📦 요약

항목설명
예외명 MethodArgumentNotValidException
원인 요청 DTO의 유효성 검증 실패
자주 발생하는 경우 프론트에서 필수 값 누락, 포맷 오류
대처 방법 @ExceptionHandler로 잡아 FieldError들을 사용자 친화적으로 반환
728x90
반응형
728x90
반응형

❓ 언제 발생하나요?

  • 클라이언트가 잘못된 JSON 요청을 보냈을 때 발생합니다.
  • 즉, 역직렬화에 실패했을 때 발생합니다.

예를 들어 다음과 같이 필드 타입이 잘못된 JSON을 보냈다고 가정합시다:

POST /users
Content-Type: application/json

{
  "name": "Kim",
  "age": "스무살"     ← 정수여야 하는데 문자열!
}

📛 예외 메시지 예시

Resolved [org.springframework.http.converter.HttpMessageNotReadableException: 
JSON parse error: Cannot deserialize value of type `int` from String "스무살"]

 


📌 대처 방안: @ControllerAdvice + 사용자 친화적인 에러 메시지 반환

1️⃣ 사용자 정의 예외 핸들러 구현

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(HttpMessageNotReadableException.class)
    public ResponseEntity<ErrorResponse> handleBadRequest(HttpMessageNotReadableException ex) {
        String message = "요청 본문 형식이 잘못되었습니다. 숫자 필드에 문자열이 들어갔는지 확인하세요.";
        return ResponseEntity
                .badRequest()
                .body(new ErrorResponse("INVALID_JSON", message));
    }
}

2️⃣ 에러 응답 객체 정의

public record ErrorResponse(String code, String message) {}

✅ 결과: 사용자는 명확하고 한글로 된 안내를 받게 됨

HTTP/1.1 400 Bad Request
Content-Type: application/json

{
  "code": "INVALID_JSON",
  "message": "요청 본문 형식이 잘못되었습니다. 숫자 필드에 문자열이 들어갔는지 확인하세요."
}

🔧 확장 팁

기능구현 방법
에러 코드 분류 enum ErrorCode { INVALID_JSON, VALIDATION_FAILED, ... }
로깅 추가 log.warn("역직렬화 실패: {}", ex.getMessage()) 등으로 운영 추적
다국어 메시지 MessageSource로 다국어 처리 가능 (messages.properties)
 

📦 정리

항목내용
예외 HttpMessageNotReadableException
원인 JSON → Java 객체 역직렬화 실패
대표 상황 숫자 타입 필드에 문자열 전달 등
대처 방안 @ControllerAdvice를 통해 예외 잡고, 사용자 친화적 메시지 반환
결과 UX 개선 + 운영자 디버깅 용이
728x90
반응형
728x90
반응형

Nuxt 3 프로젝트를 **정적 SPA(Static Single Page Application)**로 빌드하고, 백엔드(API 서버)와 연동하려는 상황이군요. 아래에 전체 흐름과 반드시 필요한 설정, 그리고 MIME 오류 방지 + 백엔드 API 호출 연동 방법을 체계적으로 정리


✅ 1. Nuxt 3 정적 SPA 빌드 방법

npx nuxi generate
  • 결과: .output/public/ 또는 dist/ 경로 안에
    • index.html
    • /_nuxt/*.js, .css 등 정적 자산 생성됨

👉 이 구조는 npx serve .output/public로 즉시 실행 테스트 가능


✅ 2. 디플로이 구조 개요

역할구성
정적 SPA Nuxt에서 빌드한 index.html, /_nuxt/*.js
API 서버 별도 백엔드 서버 (예: Spring, NestJS, Express 등)
호스팅 대상 NGINX, AWS S3+CloudFront, SAP BTP, 혹은 자체 Node.js 서버
 

 


✅ 3. 서버에서 정적 자산 서빙할 때 주의점

반드시 정적 자산이 존재하는 경로에서 서빙해야 합니다:

  • ✅ /index.html → HTML
  • ✅ /_nuxt/*.js → JS with Content-Type: application/javascript
  • ❌ JS 요청인데 HTML을 반환 → MIME 오류

✅ 4. Nuxt에서 API 서버 연동 설정

📄 .env 파일

NUXT_PUBLIC_API_BASE=https://api.example.com

📄 nuxt.config.ts

export default defineNuxtConfig({
  ssr: false, // SPA 모드
  nitro: {
    preset: "static"
  },
  runtimeConfig: {
    public: {
      apiBase: process.env.NUXT_PUBLIC_API_BASE
    }
  },
  app: {
    baseURL: "/", // 일반적 SPA라면 루트
    buildAssetsDir: "/_nuxt/",
  },
})

✅ 5. $fetch나 axios 사용할 때 API base 설정

예: $fetch 사용하는 유틸 함수

export function useApiFetch<T>(url: string, options = {}) {
  const config = useRuntimeConfig();
  return $fetch<T>(url, {
    baseURL: config.public.apiBase,
    ...options,
  });
}
// 사용 예시
await useApiFetch("/api/v1/login", {
  method: "POST",
  body: { username, password },
});

✅ 6. 정적 SPA 라우팅 문제 방지 (서버 측 설정)

정적 SPA는 브라우저 라우팅이므로, 서버는 항상 index.html을 반환해야 합니다 (404 말고!)

📄 예: NGINX 설정

location / {
  root /var/www/html;
  index index.html;
  try_files $uri $uri/ /index.html;
}

location /_nuxt/ {
  root /var/www/html;
  access_log off;
}

✅ 7. MIME 오류 해결 포인트

요청 경로응답 내용MIME 타입
/index.html HTML text/html
/_nuxt/*.js JavaScript application/javascript ✅
잘못된 경로 요청 시 HTML ❌ MIME 오류 발생
 

즉, /_nuxt/*.js 요청을 반드시 해당 파일이 실제 존재하는 경로에서 제공해야 하며, HTML을 반환하면 안 됩니다.


🧪 체크리스트

  • nuxi generate로 index.html, _nuxt/*.js가 생성되었는가?
  • 서버가 JS 요청에 대해 HTML이 아닌 JS로 응답하는가?
  • API 호출은 useRuntimeConfig().public.apiBase를 사용했는가?
  • 상대 경로(/api/...)가 아니라 절대 경로로 API 호출 중인가?
  • history fallback (try_files /index.html)이 설정되어 있는가?
728x90
반응형
728x90
반응형

이 오류 메시지:

Uncaught (in promise) Error: A listener indicated an asynchronous response by returning true, but the message channel closed before a response was received

은 브라우저 확장 프로그램(특히 Chrome Extension)과 관련된 메시지이며, 당신의 코드나 Nuxt 앱 자체의 문제는 아닙니다.


🔍 원인 설명

이 오류는 보통:

  • 크롬 확장 프로그램(예: AdBlock, React DevTools, Vue DevTools, Grammarly 등)이
  • 브라우저의 content script나 background script에서
  • 메시지 응답을 비동기로 처리하겠다고 하고는, 실제 응답을 주지 않거나 중간에 메시지 채널이 닫혔을 때 발생합니다.

✅ 특징

  • DevTools 콘솔에서 발생
  • 실제 코드 흐름에는 영향을 주지 않는 경우가 많음
  • Vue/Nuxt 앱에서 발생한 것처럼 보이지만 크롬 확장에 의해 발생

✅ 해결 방법

방법설명
🔄 브라우저 확장 비활성화 특히 Vue Devtools, AdBlock, MetaMask, Grammarly 등
🧪 시크릿 모드에서 테스트 확장 프로그램 없는 환경에서 재현 여부 확인
✅ 무시 가능 여부 판단 앱 기능이 정상 동작한다면 무시해도 무방 (개발자 도구 출력일 뿐)
 

📌 실제로 Nuxt 코드상 확인할 사항 (혹시 모를 문제)

그래도 아래는 체크해보세요:

  • login() 호출 결과가 await로 감싸졌는지
  • useCFetch에서 $fetch()로 반환받은 Promise에 .then/.catch가 잘 처리되는지
  • console.log()가 예상 응답을 받고 있는지

✅ 결론

이 오류는 브라우저 확장 프로그램의 백그라운드 메시징 오류이며, Nuxt 앱 자체의 버그가 아닙니다.

앱 기능이 잘 동작하고 있다면 무시해도 괜찮습니다.
하지만 확실히 없애고 싶다면 시크릿 모드 또는 확장 프로그램 OFF 상태에서 다시 테스트

728x90
반응형

+ Recent posts