개발 학습 실습실

이 사이트의 실습은 자바스크립트를 켜야 움직입니다. 아래는 같은 내용을 글로 적은 것입니다.

공부 › Spring › 웹 MVC

서블릿

HTTP 요청이 자바 코드에 닿기까지

브라우저가 보내는 것은 글자로 된 HTTP 요청입니다. 톰캣 같은 서블릿 컨테이너가 그 글자를 읽어 자바 객체로 바꾸고, 주소에 맞는 서블릿의 메서드를 불러 줍니다. 서블릿은 "요청 객체를 받아 응답 객체에 쓰는" 자바 클래스입니다.

실행으로 대조 · 톰캣 11.0.24와 스프링 부트 4.1.1에 48가지 요청을 보내 대조

이 실습에 나오는 개념

서블릿과 서블릿 컨테이너
서블릿은 HTTP 요청을 받아 처리하고 응답을 만드는 자바 표준 인터페이스다. 서블릿 컨테이너(톰캣 등)는 네트워크 연결을 받아 요청을 해석하고 알맞은 서블릿을 호출하며 서블릿의 생명주기와 요청 처리 스레드를 관리한다. Spring MVC는 DispatcherServlet이라는 서블릿 하나 위에서 동작한다.
DispatcherServlet과 프론트 컨트롤러
Spring MVC에서 모든 HTTP 요청을 가장 먼저 받는 서블릿이다. 요청을 처리할 컨트롤러를 찾아 호출하고 그 결과를 응답으로 만드는 전체 흐름을 조율한다. 이렇게 하나의 진입점이 모든 요청을 받아 공통 처리를 한 뒤 각 담당에게 넘기는 구조를 프론트 컨트롤러 패턴이라 한다.
내장 서버
Spring Boot 애플리케이션 안에 포함되어 함께 실행되는 웹 서버(기본은 톰캣)다. 별도로 서버를 설치하고 애플리케이션을 배포하는 과정 없이 java -jar 명령 하나로 실행할 수 있다.
웹 서버와 WAS
웹 서버는 HTTP 요청을 받아 이미지나 HTML 같은 정적인 파일을 응답하거나 요청을 다른 서버로 전달하는 프로그램이다(Nginx, Apache). WAS(웹 애플리케이션 서버)는 프로그램 코드를 실행해 요청마다 결과를 만들어 응답하는 프로그램이다(Tomcat). 웹 서버를 앞에 두고 WAS로 요청을 넘기는 구성이 흔하다.
톰캣의 스레드 모델
톰캣은 요청 처리용 스레드를 풀로 관리하며, 요청 하나를 스레드 하나가 맡아 응답이 끝날 때까지 처리한다. 동시에 처리할 수 있는 요청 수는 최대 스레드 수(기본 200)로 제한되고, 모든 스레드가 사용 중이면 새 요청은 대기한다. 요청 처리 중 데이터베이스나 외부 호출을 기다리는 동안에도 스레드는 점유된 상태다.
HTTP 메서드
요청이 자원에 대해 수행하려는 동작을 나타낸다. GET은 조회, POST는 생성이나 처리 요청, PUT은 전체 교체, PATCH는 일부 수정, DELETE는 삭제를 뜻한다. 메서드의 의미에 맞게 사용해야 캐시, 재시도 같은 HTTP의 동작이 기대대로 이뤄진다.

이렇게 바꿔 보세요

처음 설정으로 실행하면

  1. 서버가 뜹니다. 톰캣이 포트 8080에서 연결을 기다리고, "어느 주소는 어느 서블릿이 맡는다"는 표를 만듭니다. @WebServlet("/hello")가 붙은 HelloServlet을 /hello 담당으로 등록합니다. 서블릿 객체는 아직 만들지 않았습니다.
  2. 1번째 요청: 브라우저가 서버에 연결해 HTTP 요청을 보냅니다. 보내는 내용은 화면에 보이는 것 같은 글자 몇 줄입니다.
  3. 톰캣이 스레드 풀에서 스레드 하나를 배정하고, 받은 글자를 해석해 HttpServletRequest 객체로 만듭니다. 응답을 담을 빈 HttpServletResponse 객체도 함께 만듭니다. 서블릿은 글자를 직접 해석할 필요 없이 이 객체에서 값을 꺼내 쓰면 됩니다.
  4. 톰캣이 주소 /hello 담당 서블릿을 표에서 찾습니다. HelloServlet입니다.
  5. 첫 요청 때: 톰캣이 HelloServlet 객체를 만듭니다. 개발자가 new로 만드는 것이 아니라 컨테이너가 만듭니다. 이 객체 하나를 서버가 내려갈 때까지 계속 씁니다.
  6. HelloServlet의 init()을 한 번 호출합니다. 요청을 받기 전에 필요한 준비를 여기서 합니다.
  7. 톰캣이 HelloServlet의 service(request, response)를 호출합니다. service()는 요청의 HTTP 메서드를 보고 doGet()을 부릅니다.
  8. doGet() 안의 우리 코드가 실행됩니다. request에서 name을 꺼내고, response에 상태와 본문을 씁니다.
  9. 서블릿의 메서드가 끝났습니다. 톰캣이 HttpServletResponse에 담긴 내용을 HTTP 응답 글자로 바꿔 브라우저에 보냅니다. 이 요청에 쓴 request와 response 객체는 버리고, 스레드는 풀에 돌려줍니다. HelloServlet 객체는 그대로 남아 다음 요청을 기다립니다.
  10. 2번째 요청: 브라우저가 서버에 연결해 HTTP 요청을 보냅니다. 보내는 내용은 화면에 보이는 것 같은 글자 몇 줄입니다.
  11. 톰캣이 스레드 풀에서 스레드 하나를 배정하고, 받은 글자를 해석해 HttpServletRequest 객체로 만듭니다. 응답을 담을 빈 HttpServletResponse 객체도 함께 만듭니다. 서블릿은 글자를 직접 해석할 필요 없이 이 객체에서 값을 꺼내 쓰면 됩니다.
  12. 톰캣이 주소 /hello 담당 서블릿을 표에서 찾습니다. HelloServlet입니다.
  13. 이미 만들어 둔 HelloServlet 객체가 있습니다. 새로 만들지 않고 그 객체를 다시 씁니다. init()도 다시 부르지 않습니다.
  14. 톰캣이 HelloServlet의 service(request, response)를 호출합니다. service()는 요청의 HTTP 메서드를 보고 doGet()을 부릅니다.
  15. doGet() 안의 우리 코드가 실행됩니다. request에서 name을 꺼내고, response에 상태와 본문을 씁니다.
  16. 서블릿의 메서드가 끝났습니다. 톰캣이 HttpServletResponse에 담긴 내용을 HTTP 응답 글자로 바꿔 브라우저에 보냅니다. 이 요청에 쓴 request와 response 객체는 버리고, 스레드는 풀에 돌려줍니다. HelloServlet 객체는 그대로 남아 다음 요청을 기다립니다.
  17. 서버를 내립니다. 톰캣이 HelloServlet의 destroy()를 한 번 호출한 뒤 객체를 버립니다. 열어 둔 자원을 정리할 기회입니다.

실행 결과

코드

@WebServlet(urlPatterns = "/hello")
public class HelloServlet extends HttpServlet {

    @Override
    public void init() {                       // 객체가 만들어진 직후 한 번
        // 준비 작업
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp)
            throws IOException {
        String name = req.getParameter("name");   // 요청에서 꺼내고
        resp.setContentType("text/plain; charset=UTF-8");
        resp.getWriter().write("안녕하세요, " + name);   // 응답에 쓴다
    }

    @Override
    public void destroy() { }                  // 서버가 내려갈 때 한 번
}

이 모형의 전제

서블릿 컨테이너는 톰캣, 요청은 한 번에 하나씩 차례로 온다고 가정했습니다. 서블릿 객체가 선언 하나당 하나만 만들어진다는 점, init과 destroy가 한 번씩 호출된다는 점, 재정의하지 않은 doPost가 405를 돌려준다는 점은 Jakarta Servlet 명세와 HttpServlet의 문서를 따랐습니다. HTTP 요청과 응답의 글자는 주요 줄만 보였습니다. 서블릿을 직접 작성한 36가지 조합은 실제 톰캣 11.0.24에, 스프링 MVC 쪽 12가지 조합은 실제 스프링 부트 4.1.1에 요청을 보내, 응답 코드와 객체 생성·init·요청 처리 호출 횟수, 객체가 만들어진 때가 화면과 일치하는 것을 확인했습니다. 이 과정에서 스프링 쪽 내용 두 가지를 실제에 맞게 고쳤습니다. DispatcherServlet 객체는 첫 요청 때가 아니라 서버가 뜰 때 만들어지고(첫 요청 때 일어나는 것은 init뿐입니다), 404와 405 응답은 /error로 한 번 더 넘어가므로 요청 처리 호출이 요청마다 두 번입니다. 응답을 만든 곳 가운데 "톰캣의 기본 서블릿"과 "HttpServlet의 기본 doPost"는 응답 코드로 미루어 본 것이고 직접 관찰한 것은 아닙니다.

앞 실습: 커넥션 풀과 OSIV · 다음 실습: MVC 요청 흐름

이 실습에 나오는 개념

이 실습에서 보는 것

설정

코드


      
    

그림

실행 비교

이렇게 바꿔 보세요