HTTP 상태코드는 어떻게 응답되는가?

황인우·2025년 1월 26일

0. 결론

사용자의 요청은 서블릿 컨테이너의 역할을 하는 톰캣에 의해
DispatcherServlet으로 전달되어,
해당 요청을 처리할 적합한 핸들러 (보통 컨트롤러) 가 정해진다.

컨트롤러의 작업 결과상태 코드가 담긴 ModelAndView 객체가 생성되고,
DispatcherServlet 내부에서 이를 참조하여 실제 응답의 상태 코드로 설정된다.
그 후 클라이언트에게 상태 코드가 포함된 HTTP 응답이 반환된다.

요청에 따라 기본 상태 코드가 정해지며 (예: GET -> 200 OK, POST -> 201 Created),
예외 발생 시 적절한 상태 코드가 설정되거나 (@ResponseStatus 어노테이션에 의해),
ResponseEntity를 사용하여 명시적으로 상태 코드를 설정할 수 있다.

HTTP 응답은 상태 코드와 함께, 응답 바디를 통해 추가적인 정보를 제공할 수 있다.
예를 들어, 상태 메시지 (예: "작업이 성공적으로 수행되었습니다.") 를 바디에 담아서 전달하거나,
추가적인 데이터가 필요할 경우 응답 바디를 통해 전달할 수 있다.
예를들어 응답의 상태나 결과를 구체적으로 설명하는 커스텀 코드가 있는데
응답에 대한 더 세분화된 정보를 제공하거나
애플리케이션 내에서 발생할 수 있는 여러가지 상태를 구분하기 위해 사용한다.

표준 HTTP 프로토콜 규격에서는 상태 코드가 세 자릿수 숫자로 정의되어 있으며,
예를 들어 200 OK와 같은 형태를 따른다.
하지만, 일부 시스템에서는 특정한 의미를 전달하거나 유연성의 목적으로
비표준 상태 코드 (예: 200-1) 를 사용할 수 있다.
이 경우, 표준 HTTP 규격에 맞지 않기 때문에
호환성 문제나 일관성 부족으로 인해 유지보수가 어려울 수 있다.
따라서 커스터마이즈된 상태 코드를 사용할 경우,
클라이언트가 이를 적절히 처리할 수 있도록 명확하게 정의하고 문서화하는 것이 중요하다.


1. 톰캣, DispatcherServlet

사용자의 요청은 서블릿 컨테이너의 역할을 하는 톰캣에 의해
DispatcherServlet으로 전달되어,
해당 요청을 처리할 적합한 핸들러 (보통 컨트롤러) 가 정해진다.

1) 톰캣

톰캣 (Tomcat) 은 스프링부트에 기본으로 내장되어있는 웹서버 (WAS) 혹은 서블릿 컨테이너로,

클라이언트의 응답을 스프링부트에 전달하고

처리한 결과를 클라이언트에 전달하는 다리 역할을 한다.


서블릿 컨테이너로 톰캣은 서블릿이 제 역할을 할 수 있도록 실행 환경을 제공하고

요청 분배, 스레드 처리, 생명주기 관리 등을 대신 처리하는 역할을 한다.


(관련 개념 : 웹서버, WAS)

2) DispatcherServlet

서블릿 (Servlet) 은 웹 요청, 응답을 처리하는 작은 프로그램으로

웹 애플리케이션의 기본 단위가 된다.

DispatcherServlet 은 스프링 MVC에서 요청을 처리하는 중심 서블릿으로,

클라이언트 요청을 적절한 컨트롤러로 넘기고,

처리 결과를 다시 클라이언트에 응답으로 보낸다.

// 스프링 MVC의 요청 처리 과정을 시작하는 메서드
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
	(...생략...)
        
	try {
    	// doDispatch 메서드를 호출
		this.doDispatch(request, response);
	} finally {
		(...생략...)
	}
}
// 클라이언트 요청을 실제로 처리하는 메서드
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
	(...생략...)
        
	HandlerExecutionChain mappedHandler = null;
        
	(...생략...)

	try {
		try {
			ModelAndView mv = null;
                
			(...생략...)

			try {
				(...생략...)
                
				// 요청에 맞는 적정한 핸들러 (컨트롤러) 탐색
                mappedHandler = this.getHandler(processedRequest);
                    
				(...생략...)

				// 매핑된 핸들러를 실행하기 위해 어댑터 호출
                HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());
                    
				(...생략...)

				// 실제 컨트롤러 메서드 실행, 결과 반환
                mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
                    
				(...생략...)
			}
            
            (...생략...)
            
			this.processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
            }
            
		(...생략...)
}

2. DispatcherServlet, ModelAndView 객체

컨트롤러의 작업 결과상태 코드가 담긴 ModelAndView 객체가 생성되고,
DispatcherServlet 내부에서 이를 참조하여 실제 응답의 상태 코드로 설정된다.
그 후 클라이언트에게 상태 코드가 포함된 HTTP 응답이 반환된다.

1) ModelAndView 객체

스프링 MVC에서 컨트롤러가 처리한 결과를 반환할 때 사용하는 객체이다.

// handle 메서드의 결과로 실행된 컨트롤러 메서드의 결과인 ModelAndView 객체
modelAndView = handlerAdapter.handle(processedRequest, response, mappedHandler.getHandler());

(...생략...)

// 요청 처리 결과를 클라이언트에게 최종적인 응답으로 반환
this.processDispatchResult(processedRequest, response, mappedHandler, modelAndView, dispatchException);
// 뷰와 모델, 상태코드가 있는 ModelAndView
public class ModelAndView {
	@Nullable
    private Object view;
    @Nullable
    private ModelMap model;
    @Nullable
    private HttpStatusCode status;
	(...생략...)
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response, @Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv, @Nullable Exception exception) throws Exception {
	(...생략...)

	if (mv != null && !mv.wasCleared()) {
		this.render(mv, request, response);
		(...생략...)
	} (...생략...)
}
protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
	(...생략...)

	try {
		if (mv.getStatus() != null) {
			request.setAttribute(View.RESPONSE_STATUS_ATTRIBUTE, mv.getStatus());
            // 요청의 HTTP 상태코드를 설정하는 부분!
            response.setStatus(mv.getStatus().value());
		}
        
        // 뷰에 대한 최종 렌더링, HttpServletResponse 객체에 응답 본문을 작성
        view.render(mv.getModelInternal(), request, response);

		(...생략...)
	}
    
	(...생략...)
}

3. 상태코드의 설정

요청에 따라 기본 상태 코드가 정해지며 (예: GET -> 200 OK, POST -> 201 Created),
예외 발생 시 적절한 상태 코드가 설정되거나 (@ResponseStatus 어노테이션에 의해),
ResponseEntity를 사용하여 명시적으로 상태 코드를 설정할 수 있다.

1) 기본 상태 코드

요청 방식에 따라 기본적으로 설정되는 상태코드가 있다.

GET 요청은 "200 OK", POST 요청은 "201 Created" 등의 경우이다.

2) @ResponseStatus

작업 중 예외가 발생했을 경우 @ResponseStatus 어노테이션을 사용하여 적절한 상태 코드를 설정할 수 있다.

@ResponseStatus(HttpStatus.NOT_FOUND)
public class ServiceException extends RuntimeException {
    // 예외 처리 코드
}

3) ResponseEntity

응답 본문과 상태 코드를 명식적으로 설정할 수 있는 클래스이다.

@ExceptionHandler(ServiceException.class)
public ResponseEntity<RsData<Void>> handle(ServiceException exception) {
	RsData<Void> rsData = exception.getRsData();
	return ResponseEntity
    	// RsData 에서 넘어온 코드로 상태코드 설정
    	.status(rsData.getHttpCode())
		.body(rsData);
}

4. 표준 HTTP 프로토콜 규격과 비표준 상태 코드, 커스텀 코드

HTTP 응답은 상태 코드와 함께, 응답 바디를 통해 추가적인 정보를 제공할 수 있다.
예를 들어, 상태 메시지 (예: "작업이 성공적으로 수행되었습니다.") 를 바디에 담아서 전달하거나,
추가적인 데이터가 필요할 경우 응답 바디를 통해 전달할 수 있다.

예를들어 응답의 상태나 결과를 구체적으로 설명하는 커스텀 코드가 있는데
응답에 대한 더 세분화된 정보를 제공하거나
애플리케이션 내에서 발생할 수 있는 여러가지 상태를 구분하기 위해 사용한다.

표준 HTTP 프로토콜 규격에서는 상태 코드가 세 자릿수 숫자로 정의되어 있으며,
예를 들어 200 OK와 같은 형태를 따른다.

하지만, 일부 시스템에서는 특정한 의미를 전달하거나 유연성의 목적으로
비표준 상태 코드 (예: 420) 를 사용할 수 있다.
이 경우, 표준 HTTP 규격에 맞지 않기 때문에
호환성 문제나 일관성 부족으로 인해 유지보수가 어려울 수 있다.
따라서 커스터마이즈된 상태 코드를 사용할 경우,
클라이언트가 이를 적절히 처리할 수 있도록 명확하게 정의하고 문서화하는 것이 중요하다.

1) 표준 HTTP 상태 코드

https://developer.mozilla.org/ko/docs/Web/HTTP/Status

2) 커스텀 코드

@PostMapping
public RsData<MemberDTO> join(@RequestBody @Valid MemberJoinReqBody reqBody) {
	Member member = memberService.join(reqBody.username, reqBody.password, reqBody.nickname);
	return new RsData<>(
		"201-1",
		"환영합니다. %s님.".formatted(member.getNickname()),
		new MemberDTO(member)
	);
}

여기서 “200-1” 등의 코드는 커스텀 코드

응답에 추가적인 상태를 나타내기 위해 사용한다.


예를들어 200 OK 상태코드는 요청이 성공적으로 처리되었다는 내용만 담을 수 있지만,

  1. 200-1 : 일반적인 성공
  2. 200-2 : 부분적인 성공
  3. 200-3 : 캐시된 데이터 반환
  4. 200-4 : 리소스 업데이트
  5. 200-5 : 데이터 삭제
  6. 200-6 : 중복 요청 처리
  7. ...

등등 각각의 비즈니스 로직에 맞는 상세한 응답 상태를 전달할 수 있다.

0개의 댓글