
SOFTMOA TECHNOLOGY
curl HTTP 404에도 성공으로 끝날 때 종료 코드 확인 방법
소프트모아가 정리한 기술 기록입니다.
Contents
curl 요청이 404를 받았는데 배치가 성공으로 끝난다면 HTTP 상태와 명령의 종료 코드를 따로 확인해야 합니다. curl은 기본적으로 HTTP 오류 응답도 정상적인 송수신으로 취급합니다.[2] 오류 본문을 남기면서 실패를 전달하려면 --fail-with-body를 사용합니다. 이 옵션은 HTTP 400 이상에서 종료 코드 22를 반환하며, 7.76.0부터 지원됩니다.[1][4]
아래 예시는 macOS의 curl 8.7.1과 Python 3로 확인했습니다. 외부 API 대신 이 컴퓨터의 127.0.0.1에 가상 응답 서버를 띄워 비교합니다. 운영 데이터나 실제 주문은 처리하지 않습니다. 소프트모아의 개발 문서입니다.
같은 404 응답으로 원인부터 확인합니다
빈 연습 디렉터리에 다음 내용을 demo_http.py로 저장하세요. 첫 번째 터미널에서 python3 demo_http.py를 실행하고, 실습이 끝나면 Ctrl+C로 종료합니다. 18765번 포트가 사용 중이면 기존 프로그램을 종료하지 말고 서버와 요청 양쪽의 포트 번호를 다른 빈 번호로 바꿉니다.
from http.server import BaseHTTPRequestHandler, HTTPServer
class Demo(BaseHTTPRequestHandler):
def do_GET(self):
code = {"/200": 200, "/302": 302,
"/400": 400, "/500": 500}.get(self.path, 404)
self.send_response(code)
self.end_headers()
self.wfile.write(f'{{"code":{code}}}\n'.encode())
HTTPServer(("127.0.0.1", 18765), Demo).serve_forever()
두 번째 터미널도 같은 디렉터리에서 엽니다. curl -q --version으로 버전을 확인한 뒤 아래 명령을 실행합니다. 기본 동작 비교이므로 오류 즉시 종료 설정이 없는 별도 sh 세션을 사용하세요.
curl -q --noproxy 127.0.0.1 -sS --max-time 3 \
-o body.json -w 'http=%{response_code}\n' \
http://127.0.0.1:18765/404
rc=$?
printf 'exit=%s\n' "$rc"
실행 결과는 http=404와 exit=0이었습니다. body.json에는 {"code":404}가 저장됐습니다. HTTP 상태 404는 응답이 알려 준 결과이고, 0은 curl 프로세스의 종료 값입니다. 파일이 생성됐거나 명령이 0으로 끝났다는 이유만으로 업무 성공을 기록하면 이 둘을 혼동하게 됩니다.
-w는 전송 뒤 정보를 출력하며 %{response_code}는 HTTP 응답 코드를 표시합니다. 본문은 -o로 파일에 분리해 숫자와 섞이지 않게 했습니다.[3] -q는 첫 옵션에 놓아 개인 curlrc 설정을 제외하고, --noproxy는 이 로컬 주소의 프록시 사용을 막습니다. -sS는 진행 표시를 숨기되 오류 메시지를 표시하며, 여기서는 전송 시간을 3초로 제한했습니다.[1]
실패 코드와 오류 본문을 함께 남깁니다
다음 내용을 check_http.sh로 저장하고 sh check_http.sh로 실행합니다. 마지막 exit는 호출한 터미널이 아니라 이 스크립트를 끝냅니다. 본문 파일은 매번 덮어쓰므로 실제 업무 파일이 있는 디렉터리에서는 그대로 실행하지 마세요.
if curl -q --noproxy 127.0.0.1 -sS --max-time 3 \
--fail-with-body -o body.json \
-w 'http=%{response_code}\n' http://127.0.0.1:18765/404
then
rc=0
else
rc=$?
fi
printf 'exit=%s\n' "$rc"
exit "$rc"
이번에는 http=404, exit=22가 표시됐고 스크립트 자체도 22로 종료됐습니다. 오류 메시지는 표준 오류로 나왔지만 body.json의 {"code":404}는 유지됐습니다. 실패한 호출을 성공으로 바꾼 것이 아니라, 같은 응답을 호출자에게 실패로 전달하도록 고친 결과입니다.
if의 실패 분기에서 다른 명령을 실행하기 전에 $?를 보관합니다. 출력만 한 뒤 끝내면 마지막 printf의 성공이 스크립트 결과가 될 수 있어 exit "$rc"로 보관한 값을 반환했습니다. 이 스크립트는 sh -e에서도 오류 본문을 남기고 22로 끝나는 것을 확인했습니다.
-f 또는 --fail도 HTTP 오류를 실패로 처리하지만 오류 본문을 출력하지 않습니다. JSON 오류 설명을 보관해야 한다면 --fail-with-body를 선택하고, 두 옵션을 함께 넣지 않습니다.[1] 구버전에서 옵션을 인식하지 못하면 이 예제의 동작을 전제로 적용하지 말고 사용 중인 실행 파일부터 확인하세요.
종료 코드 0이 보장하지 않는 것
같은 서버에서 요청 경로만 바꿔 --fail-with-body로 비교한 결과입니다. 302는 Location 헤더 없는 상태 코드 비교용 응답이며 실제 리디렉션 경로를 시험한 것은 아닙니다.
요청 경로 HTTP 상태 curl 종료 코드
/200 200 0
/302 302 0
/400 400 22
/404 404 22
/500 500 22
이 옵션은 2xx만 허용하는 업무 규칙이 아닙니다.[2] 302를 성공으로 처리하면 안 되는 점검이라면 허용할 HTTP 상태도 따로 검사해야 합니다. 예를 들어 API가 HTTP 200과 {"ok":false}를 함께 보내는 설계라면 JSON 필드 확인이 더 필요합니다. 이 가상 예시의 code 필드는 업무 성공 여부를 판정하는 필드가 아닙니다.
- 예제는 인증 없는 URL 하나와 GET만 확인했습니다. 여러 URL을 한 curl 명령에 나열하면 마지막 전송의 성공이 앞선 실패를 가릴 수 있으므로 같은 결과를 가정하지 마세요.[1]
- 본문을 보관한다는 것은 누구에게나 로그로 공개해도 된다는 뜻이 아닙니다. 실제 API의 개인정보와 인증 정보는 저장 범위와 접근 권한을 따로 검토합니다.
실패를 반환했다고 서버에서 이미 수행한 변경이 취소된 것도 아닙니다. 이 읽기 전용 실습을 주문 생성 같은 POST에 그대로 확대해 자동 재시도하지 마세요. 실제 처리 여부 조회와 중복 요청 방지 규칙이 먼저 필요합니다. 수정 후에는 호출한 상위 배치가 22를 무시하지 않는지까지 확인해야 합니다.
확인한 문서
- [1] curl 매뉴얼: 실패·출력·설정 옵션
- [2] everything curl: HTTP 응답과 종료 코드
- [3] everything curl: --write-out
- [4] curl 작성자: --fail-with-body 도입
소프트모아는 해당 시스템을 구축합니다. 문의하기
