Skip to content

fix: 클라이언트 요청 오류를 500 대신 400 으로 응답 - #27

Merged
hyeonszz merged 1 commit into
mainfrom
fix/missing-header-400
Jul 10, 2026
Merged

fix: 클라이언트 요청 오류를 500 대신 400 으로 응답#27
hyeonszz merged 1 commit into
mainfrom
fix/missing-header-400

Conversation

@hyeonszz

@hyeonszz hyeonszz commented Jul 10, 2026

Copy link
Copy Markdown
Collaborator

미션 API 에 X-Device-Id 헤더를 빠뜨리면 MissingRequestHeaderException 이 핸들러에 안 잡혀 catch-all 이 500(서버 오류)으로 만들었다.

예외 3종을 400 으로 명시한다.

  • MissingRequestHeaderException → reasons 에 빠진 헤더명 표기
  • HttpMessageNotReadableException → 깨진 JSON / 빈 body
  • MethodArgumentTypeMismatchException → 경로/쿼리 파라미터 타입 불일치

검증: 헤더 없이 GET /api/missions/today(/status) 호출 시 400 + 빠진 헤더명 응답 확인. clean build 통과.

Summary by CodeRabbit

  • 버그 수정
    • 필수 요청 헤더가 누락된 경우 누락된 헤더명과 안내 메시지를 포함한 400 오류를 반환합니다.
    • 읽을 수 없는 요청 본문에 대해 명확한 400 오류를 제공합니다.
    • 경로 또는 쿼리 파라미터의 형식이 잘못된 경우 관련 항목을 포함한 400 오류를 반환합니다.

미션 API 에 X-Device-Id 헤더를 빠뜨리면 MissingRequestHeaderException 이
핸들러에 안 잡혀 catch-all 이 500(서버 오류)으로 만들었다. 프론트가 자기
요청 문제를 서버 탓으로 오해하게 된다.

클라이언트 잘못인 예외 3종을 400 으로 명시한다.
- MissingRequestHeaderException  → reasons 에 빠진 헤더명 표기
- HttpMessageNotReadableException → 깨진 JSON / 빈 body
- MethodArgumentTypeMismatchException → 경로/쿼리 파라미터 타입 불일치

검증: 헤더 없이 GET /api/missions/today(/status) 호출 시 400 + 빠진 헤더명
응답 확인. clean build 통과.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@hyeonszz hyeonszz self-assigned this Jul 10, 2026
@coderabbitai

coderabbitai Bot commented Jul 10, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

GlobalExceptionHandler가 누락된 필수 헤더, 읽을 수 없는 요청 본문, 경로·쿼리 파라미터 타입 불일치 예외를 400 응답으로 처리하도록 확장되었다.

Changes

클라이언트 입력 예외 처리

Layer / File(s) Summary
400 입력 오류 핸들러 추가
src/main/java/com/example/hackathon/global/exception/GlobalExceptionHandler.java
필수 헤더 누락, 요청 본문 읽기 실패, 메서드 인자 타입 불일치를 COMMON_ERROR_400_INVALID_INPUTErrorResponse로 처리하며 관련 정보를 reasons에 포함한다.

Estimated code review effort: 2 (Simple) | ~10 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 75.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 클라이언트 요청 오류를 500 대신 400으로 응답하도록 변경한 핵심 내용을 정확히 요약합니다.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/missing-header-400

Comment @coderabbitai help to get the list of available commands.

@hyeonszz
hyeonszz merged commit 0f332cf into main Jul 10, 2026
2 checks passed

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/main/java/com/example/hackathon/global/exception/GlobalExceptionHandler.java (1)

66-97: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

핵심 로직은 정확합니다.

세 핸들러 모두 ErrorCode.COMMON_ERROR_400_INVALID_INPUT으로 400을 반환하고, MissingRequestHeaderException은 헤더명, MethodArgumentTypeMismatchException은 파라미터명을 reasons에 담아 PR 목적에 부합합니다. getHeaderName()/getName() 모두 표준 Spring API이며 non-null이 보장되어 NPE 위험도 없습니다.

다만 세 핸들러가 ErrorCode code = ErrorCode.COMMON_ERROR_400_INVALID_INPUT; return ResponseEntity.status(code.getStatus()).body(...) 패턴을 반복하고 있습니다(기존 handleValidation, handleMethodNotAllowed까지 포함하면 5회 중복). 공통 로직을 private 헬퍼로 추출하면 유지보수성이 개선됩니다.

♻️ 중복 제거 제안
+    private ResponseEntity<ErrorResponse> badRequest(String message, HttpServletRequest request,
+                                                       Map<String, String> reasons) {
+        ErrorCode code = ErrorCode.COMMON_ERROR_400_INVALID_INPUT;
+        return ResponseEntity.status(code.getStatus())
+                .body(ErrorResponse.of(code, message, request.getRequestURI(), reasons));
+    }
+
     `@ExceptionHandler`(MissingRequestHeaderException.class)
     public ResponseEntity<ErrorResponse> handleMissingHeader(MissingRequestHeaderException e,
                                                              HttpServletRequest request) {
-        ErrorCode code = ErrorCode.COMMON_ERROR_400_INVALID_INPUT;
-        return ResponseEntity.status(code.getStatus())
-                .body(ErrorResponse.of(code, "필수 요청 헤더가 없습니다.", request.getRequestURI(),
-                        Map.of(e.getHeaderName(), "필수 헤더입니다.")));
+        return badRequest("필수 요청 헤더가 없습니다.", request, Map.of(e.getHeaderName(), "필수 헤더입니다."));
     }

또한 HttpMessageNotReadableException, MissingRequestHeaderException 핸들러에는 handleUnexpected와 달리 로깅이 전혀 없습니다. 스택트레이스까지는 불필요하지만, 클라이언트 연동 이슈 추적을 위해 log.debug/log.warn 정도는 남기는 것을 고려해볼 수 있습니다.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/main/java/com/example/hackathon/global/exception/GlobalExceptionHandler.java`
around lines 66 - 97, Extract the repeated 400-response construction used by
handleMissingHeader, handleUnreadableBody, handleTypeMismatch, handleValidation,
and handleMethodNotAllowed into a private helper that accepts the message,
request URI, and reasons, then delegate each handler to it while preserving
existing responses. Also add lightweight debug or warn logging in
handleMissingHeader and handleUnreadableBody, without logging unnecessary stack
traces.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@src/main/java/com/example/hackathon/global/exception/GlobalExceptionHandler.java`:
- Around line 66-97: GlobalExceptionHandler의 handleUnreadableBody와
handleTypeMismatch에 대한 `@WebMvcTest/MockMvc` 테스트를 추가하세요. 각 예외를 발생시키는 테스트 엔드포인트 또는
요청을 구성하고, HTTP 상태가 400인지와 응답 ErrorResponse의 에러 코드, 메시지, 요청 URI 및 필드 오류 정보가 기대값과
일치하는지 검증하세요.

---

Nitpick comments:
In
`@src/main/java/com/example/hackathon/global/exception/GlobalExceptionHandler.java`:
- Around line 66-97: Extract the repeated 400-response construction used by
handleMissingHeader, handleUnreadableBody, handleTypeMismatch, handleValidation,
and handleMethodNotAllowed into a private helper that accepts the message,
request URI, and reasons, then delegate each handler to it while preserving
existing responses. Also add lightweight debug or warn logging in
handleMissingHeader and handleUnreadableBody, without logging unnecessary stack
traces.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: e4572504-7231-46e9-ae55-d70302ef0c98

📥 Commits

Reviewing files that changed from the base of the PR and between df35c71 and 88b5c19.

📒 Files selected for processing (1)
  • src/main/java/com/example/hackathon/global/exception/GlobalExceptionHandler.java

Comment on lines +66 to +97
/**
* 필수 요청 헤더 누락. 예: 미션 API 에 X-Device-Id 를 빠뜨림.
* 클라이언트 잘못이므로 400. 잡지 않으면 catch-all 이 500 으로 만든다.
*/
@ExceptionHandler(MissingRequestHeaderException.class)
public ResponseEntity<ErrorResponse> handleMissingHeader(MissingRequestHeaderException e,
HttpServletRequest request) {
ErrorCode code = ErrorCode.COMMON_ERROR_400_INVALID_INPUT;
return ResponseEntity.status(code.getStatus())
.body(ErrorResponse.of(code, "필수 요청 헤더가 없습니다.", request.getRequestURI(),
Map.of(e.getHeaderName(), "필수 헤더입니다.")));
}

/** 요청 본문을 읽지 못했다. 깨진 JSON, 빈 body, 잘못된 타입 등. 클라이언트 잘못이므로 400. */
@ExceptionHandler(HttpMessageNotReadableException.class)
public ResponseEntity<ErrorResponse> handleUnreadableBody(HttpMessageNotReadableException e,
HttpServletRequest request) {
ErrorCode code = ErrorCode.COMMON_ERROR_400_INVALID_INPUT;
return ResponseEntity.status(code.getStatus())
.body(ErrorResponse.of(code, "요청 본문을 읽을 수 없습니다.", request.getRequestURI(), Map.of()));
}

/** 경로/쿼리 파라미터 타입 불일치. 예: Long 자리에 문자열. 클라이언트 잘못이므로 400. */
@ExceptionHandler(MethodArgumentTypeMismatchException.class)
public ResponseEntity<ErrorResponse> handleTypeMismatch(MethodArgumentTypeMismatchException e,
HttpServletRequest request) {
ErrorCode code = ErrorCode.COMMON_ERROR_400_INVALID_INPUT;
return ResponseEntity.status(code.getStatus())
.body(ErrorResponse.of(code, code.getMessage(), request.getRequestURI(),
Map.of(e.getName(), "타입이 올바르지 않습니다.")));
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# GlobalExceptionHandler 관련 테스트 존재 여부 확인
rg -n --type=java -C2 'HttpMessageNotReadableException|MethodArgumentTypeMismatchException|MissingRequestHeaderException' --glob '*Test*.java'

Repository: 8th-COKERTHON/server-team1

Length of output: 164


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# 테스트/소스 구조 확인
printf '\n== files ==\n'
git ls-files | rg '(^|/)(src/test|test)/|GlobalExceptionHandler|ErrorResponse|ErrorCode'

printf '\n== handler references ==\n'
rg -n --type=java 'GlobalExceptionHandler|HttpMessageNotReadableException|MethodArgumentTypeMismatchException|MissingRequestHeaderException' src test

printf '\n== test class names ==\n'
rg -n --type=java 'class .*Test|class .*Tests|class .*IT' src/test test

Repository: 8th-COKERTHON/server-team1

Length of output: 2986


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== handler references in tests ==\n'
rg -n --type=java 'GlobalExceptionHandler|HttpMessageNotReadableException|MethodArgumentTypeMismatchException|MissingRequestHeaderException|MockMvc|WebMvcTest|SpringBootTest' src/test

printf '\n== test file contents (short list) ==\n'
for f in $(git ls-files 'src/test/java/**/*.java'); do
  printf '\n--- %s ---\n' "$f"
  sed -n '1,220p' "$f"
done

Repository: 8th-COKERTHON/server-team1

Length of output: 50382


GlobalExceptionHandler의 400 예외 처리 테스트를 추가해 주세요 (GlobalExceptionHandler.java 80-90)
HttpMessageNotReadableExceptionMethodArgumentTypeMismatchException은 서비스 테스트만으로는 커버되지 않으니, @WebMvcTest/MockMvc로 400 응답과 에러 바디를 검증하는 테스트가 있으면 좋겠습니다.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/main/java/com/example/hackathon/global/exception/GlobalExceptionHandler.java`
around lines 66 - 97, GlobalExceptionHandler의 handleUnreadableBody와
handleTypeMismatch에 대한 `@WebMvcTest/MockMvc` 테스트를 추가하세요. 각 예외를 발생시키는 테스트 엔드포인트 또는
요청을 구성하고, HTTP 상태가 400인지와 응답 ErrorResponse의 에러 코드, 메시지, 요청 URI 및 필드 오류 정보가 기대값과
일치하는지 검증하세요.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant