Tomcat 은 어떻게 동작할까? - Spring 과의 연동을 중점으로 (2)

정원식·2023년 12월 16일
0

23년 5월에 작성한 글입니다.

개요

  • 본 시리즈에서는 사용자의 요청이 톰캣에서 어떻게 처리되어 우리가 작성한 비즈니스 로직에 도달하는지에 대해 다룹니다.
  • 두번째 편에서는 Spring Boot 에서 Embedded Tomcat 을 어떻게 설정하는지 살펴봅니다.

사용한 버전

  • Servlet: 4.0.1
  • Tomcat: 9.0.60
  • Spring Boot: 2.6.6
  • Spring WebMvc: 5.3.18

설정 과정

DispatcherServletAutoConfiguration

아래 Configuration 을 포함

  • DispatcherServletConfiguration: DispatcherServlet 을 빈으로 등록
  • DispatcherServletRegistrationConfiguration: DispatcherServletRegistrationBean 을 빈으로 등록
    • Servlet Container 초기화 과정에서 DispatcherServler 등록

ServletWebServerFactoryConfiguration

아래 빈을 설정한 TomcatServletWebServerFactory 생성

  • TomcatConnectorCustomizer: Connector 에 대한 설정 진행
  • TomcatContextCustomizer: Context 에 대한 설정 진행
  • TomcatProtocolHandlerCustomizer: Connector 의 ProtocolHandler 에 대한 설정 진행

ServletWebServerFactoryAutoConfiguration

아래 빈들을 정의하여 TomcatServletWebServerFactory 에 대한 Auto Configure 진행

  • ServletWebServerFactoryCustomizer: 아래 프로퍼티에 대한 설정 진행
    • server.port
    • server.address
    • server.server-header
    • server.shutdown
    • server.servlet.*
    • server.ssl.*
    • server.compression.*
    • server.http2.*
  • TomcatServletWebServerFactoryCustomizer: 아래 프로퍼티에 대한 설정 진행
    • server.tomcat.*
  • BeanPostProcessorsRegistrar
    • WebServerFactoryCustomizerBeanPostProcessor: 초기화 전에 WebServerFactoryCustomizer 을 적용
      • WebServerFactoryCustomizer: WebServerFactory 에 대해 설정
    • ErrorPageRegistrarBeanPostProcessor: 초기화 전에 ErrorPageRegistrar 을 적용
      • ErrorPageRegistrar: 에러 페이지 등록

생성 과정

ServletWebServerApplicationContext

  • #createWebServer
    • 웹 서버 생성을 TomcatServletWebServerFactory 에 위임

TomcatServletWebServerFactory

  • TomcatWebServer 서버 생성
public class TomcatServletWebServerFactory extends AbstractServletWebServerFactory
		implements ConfigurableTomcatWebServerFactory, ResourceLoaderAware {

    ...

    public WebServer getWebServer(ServletContextInitializer... initializers) {
        if (this.disableMBeanRegistry) {
            Registry.disableRegistry();
        }
        Tomcat tomcat = new Tomcat();
        File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat");
        tomcat.setBaseDir(baseDir.getAbsolutePath());
        for (LifecycleListener listener : this.serverLifecycleListeners) {
            tomcat.getServer().addLifecycleListener(listener);
        }

        // Connector 생성 & 설정
        Connector connector = new Connector(this.protocol);
        connector.setThrowOnFailure(true);

        // 내부적으로 Server 및 Service 생성 & 설정
        tomcat.getService().addConnector(connector);
        customizeConnector(connector);
        tomcat.setConnector(connector);

        // 내부적으로 Engine 및 Host 생성 & 설정
        tomcat.getHost().setAutoDeploy(false);
        configureEngine(tomcat.getEngine());
        for (Connector additionalConnector : this.additionalTomcatConnectors) {
            tomcat.getService().addConnector(additionalConnector);
        }

        // 내부적으로 Context 생성
        prepareContext(tomcat.getHost(), initializers);

        // 내부적으로 톰캣 실행
        // DispatcherServletRegistrationBean 이 Context 에 DispatcherServlet 삽입
        // Engine -> Host -> Context -> Wrapper(DispatcherServlet)
        return getTomcatWebServer(tomcat);
    }
}

XML 로 표현한다면

TomcatWebServer 설정

  • WebServerStartStopLifecycle#start 시점의 webServer 상태 확인
<Server address="localhost" port="-1" shutdown="SHUTDOWN">

  <!-- JNDI Global Resource -->
  <GlobalNamingResources>
    <!-- Empty -->
  </GlobalNamingResources>

  <Service name="Tomcat">
    <Connector port="8080" protocol="HTTP/1.1" connectionTimeout="60000"
               redirectPort="443" maxThreads="200" socket.directBuffer="false" />
    <Engine name="Tomcat" defaultHost="localhost">

      <!-- 메모리 기반의 사용자 정보 관리 -->
      <Realm className="org.apache.catalina.startup.Tomcat$SimpleRealm" />
      <Host name="localhost" appBase="webapps" unpackWARs="true" autoDeploy="false" workDir="null">
        <Context name="" docBase="{임시로 생성한 디렉토리}" path=""
                 className="org.springframework.boot.web.embedded.tomcat.TomcatEmbeddedContext">
          <Wrapper className="org.apache.catalina.core.StandardWrapper" servletClass="org.springframework.web.servlet.DispatcherServlet">
            <!-- 서블릿 매핑 정보를 관리 -->
            <Listener className="org.apache.catalina.mapper.MapperListener" />

            <Valve className="org.apache.catalina.core.StandardWrapperValve" />
          </Wrapper>

          <!-- web.xml 을 사용하지 않는 경우 사용 -->
          <Listener className="org.apache.catalina.startup.Tomcat$FixContextListener" />

          <!-- Jar 파일을 리소스에 추가 -->
          <Listener className="org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory$StaticResourceConfigurer" />

          <!-- Context 리로드시, 메모리 누수가 발생했는지 확인하기 위한 헬퍼 리스너 -->
          <Listener className="org.apache.catalina.core.StandardHost$MemoryLeakTrackingListener" />

          <!-- 종료시, 세션 정보 파기  -->
          <Listener className="org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory$DisablePersistSessionListener" />

          <!-- 'server.tomcat.resource.*' 프로퍼티 적용 -->
          <Listener className="org.springframework.boot.autoconfigure.web.embedded.TomcatWebServerFactoryCustomizer$lambda" />

          <!-- 서블릿 매핑 정보를 관리 -->
          <Listener className="org.apache.catalina.mapper.MapperListener" />

          <!-- 사용자 인증을 제외한 보안 검증 수행 -->
          <Valve className="org.apache.catalina.authenticator.NonLoginAuthenticator" />
          <Valve className="org.apache.catalina.core.StandardContextValve" />
        </Context>

        <!-- 에러페이지 제공 -->
        <Valve className="org.apache.catalina.valves.ErrorReportValve" />
        <Valve className="org.apache.catalina.core.StandardHostValve" />
      </Host>

      <Valve className="org.apache.catalina.core.StandardEngineValve" />
    </Engine>
  </Service>
</Server>

기존 기본 설정

<Server port="8005" shutdown="SHUTDOWN">
  <Listener className="org.apache.catalina.startup.VersionLoggerListener" />
  <Listener className="org.apache.catalina.core.AprLifecycleListener" SSLEngine="on" />
  <Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener" />
  <Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener" />
  <Listener className="org.apache.catalina.core.ThreadLocalLeakPreventionListener" />

  <!-- JNDI Global Resource -->
  <GlobalNamingResources>
    <Resource name="UserDatabase" auth="Container"
              type="org.apache.catalina.UserDatabase"
              description="User database that can be updated and saved"
              factory="org.apache.catalina.users.MemoryUserDatabaseFactory"
              pathname="conf/tomcat-users.xml" />
  </GlobalNamingResources>

  <Service name="Catalina">
    <Connector port="8080" protocol="HTTP/1.1"
               connectionTimeout="20000"
               redirectPort="8443" />
    <Engine name="Catalina" defaultHost="localhost">
      <!-- 동기화 기능 제공 -->
      <Realm className="org.apache.catalina.realm.LockOutRealm">
        <!-- JNDI 기반의 사용자 정보 관리 -->
        <Realm className="org.apache.catalina.realm.UserDatabaseRealm"
               resourceName="UserDatabase"/>
      </Realm>
      <Host name="localhost"  appBase="webapps"
            unpackWARs="true" autoDeploy="true">
        <!-- 액세스 로그 기능 제공 -->
        <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
               prefix="localhost_access_log" suffix=".txt"
               pattern="%h %l %u %t &quot;%r&quot; %s %b" />
      </Host>
    </Engine>
  </Service>
</Server>

결론

  • Spring Boot 는 다양한 Auto-Configuration 을 통해 Embedded 톰캣 서버를 설정합니다.
  • Tomcat 서버를 아래와 같이 설정합니다.

Reference

profile
매일매일 성장하고 싶은 백엔드 개발자입니다.

0개의 댓글