이전 포스팅에서 Servlet 과 Servlet Container의 개념 위주로 설명했다면 이번에는 코드 중심으로 직접 테스트하고 적용해보겠습니다. 간단히 Docket Tomcat Image를 활용해보겠습니다.
그리고 추가로 대부분 JSP를 처음 접할때 이클립스로된 예제가 많은데 저는 더욱 직관적인 이해를 위해javac
command line tool을 직접 활용해서 해보겠습니다.
Mac OS 에서 Docker 설치는 쉽게 검색해서 사용가능하니 생략하겠습니다.
목차는 아래와 같습니다.
class file
)만약 도커가 잘 설치 되었다면 터미널을 열어서 docker
명령을 사용할 수 있을 것 입니다.
이미지가 없을 경우, 자동으로 docker hub 를 통해 설치가 될 테니 명령 위주로 나열해보겠습니다.
docker -d -p 8080:8080 --name tomcat tomcat
위와 같이 로컬 포트 바인딩 (로컬 :8080과 컨테이너의 :8080)을 바인딩하고 컨테이너 이름을 명시적으로 tomcat
으로 지정하고 latest tomcat 이미지로 컨테이너를 올렸습니다.
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
ea018a5a0427 tomcat "catalina.sh run" 5 days ago Up 2 days 0.0.0.0:8080->8080/tcp tomcat
위와 같이 도커 이미지가 잘 올라갔습니다. catalina.sh
로 실행한 걸 보니 tomcat이 잘 올라갔습니다.
브라우저로 localhost:8080
로 접속하면 아직 컨텐츠를 올리지 않아 tomcat 404 에러 페이지가 뜨는 것이 정상입니다.
현재 실행 중인 tomcat version check를 위해 아래 명령으로 접속해보겠습니다.
docker exec -it tomcat /bin/bash
// in tomcat terminal
$ cd /usr/local/tomcat/lib
$ java -cp catalina.jar org.apache.catalina.util.ServerInfo
위와 같이 tomcat 컨테이너에서 ServerInfo를 실행해보면 현재 tomcat 버젼을 확인할 수 있습니다.
(tomcat 버젼을 보고 Servlet API의 어떤 버젼까지 사용할 수 있는지 확인할 수 있으니 중요합니다.)
Server version: Apache Tomcat/9.0.45
Server built: Mar 30 2021 10:29:04 UTC
Server number: 9.0.45.0
OS Name: Linux
OS Version: 5.10.25-linuxkit
Architecture: amd64
JVM Version: 11.0.11+9
JVM Vendor: Oracle Corporation
여기까지 docker tomcat 이미지를 올리고 버젼을 확인했습니다. 간단히 sample.war를 배포해보겠습니다.
sample.war 는 (apache sample.war link)[https://tomcat.apache.org/tomcat-6.0-doc/appdev/sample/] 위 사이트에서 간단히 다운받아서 사용할 수 있습니다.
다운받은 sample.war을 tomcat의 webapps
에 이동시켜주면 자동으로 배포될 것입니다. local에서 docker로 파일을 복사하는 방법은 아래와 같습니다.
// docker cp "[file path]" [docker name]:"[container path]"
$ docker cp "./sample.war" tomcat:"/usr/local/tomcat/webapps"
다음과 같이 sample.war을 webapps에 이동시키고 브라우저로 localhost:8080/sample
을 접속해보면 이번에는 제대로 배포가 된 것을 확인할 수 있습니다. war만 올려도 자동으로 배포되는 이유는 /usr/local/tomcat/config/server/xml
속 host 속성에 autoDeploy
가 true
로 설정되있기 때문입니다.
샘플까지 모두 올려봤으니 저희가 작성한 Servlet을 직접 올려보겠습니다.
각각 작성하는 Servlet과 Filter, Listener은 이전 포스팅에서 설명했으니 생략해서 compile후 배포만 확인해보겠습니다.
이미 올라가있는 sample application의 class파일만 올려서 동적으로 배포해보겠습니다.
우선, 저희는 tomcat에서 사용하는 Servlet API Library가 필요한데 아까 확인했던 tomcat 버젼에서 해당 라이브러리만 가져와서 개발 목적으로 사용해보겠습니다.
위와 같이 zip 파일을 다운 받아 lib 파일에 servlet-api.jar
를 가져와서 servlet을 컴파일하는데 활용합니다.
다음과 같이 위치시켜두고 mypackage 하위에 servlet을 작성할 예정입니다.
/
|----- src/
| |-mypackage
| |- HelloServlet.java
| |- HelloFilter.java
| |- HelloListener.java
|
|----- servlet-api.jar
|----- out/ # .class files
위 경로에서
$ javac -cp ./servlet-api.jar src/mypackage/**/*.java -d out
아래와 같은 컴파일 명령을 내려 아래 제공되는 java 파일들을 컴파일해서 .class file들을 생성시키고
docker tomcat image의 /usr/local/tomcat/webapps/sample/WEB-INF/classes/
에 class file들을 배포해줍니다.
$ docker cp ./out/mypackage tomcat:"/usr/local/tomcat/webapps/sample/WEB-INF/classes/"
소스 코드는 아래와 같습니다.
package mypackage;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import java.io.IOException;
import java.io.PrintWriter;
public class HelloServlet extends HttpServlet {
@Override
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
// Logging
ServletContext context = getServletContext();
context.log("HelloServlet.service() invoked!");
res.setContentType("text/html");
PrintWriter writer = res.getWriter();
writer.println("<h1>Hello This is Sample Servlet!</h1>");
}
}
package mypackage;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;
import java.io.PrintWriter;
public class HelloFilter implements Filter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
// Logging
ServletContext context = servletRequest.getServletContext();
context.log("HelloFilter.doFilter() invoked!!");
PrintWriter out=servletResponse.getWriter();
out.print("filter is invoked before");
filterChain.doFilter(servletRequest, servletResponse);
out.print("filter is invoked after");
}
}
package mypackage;
import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;
public class HelloServletContextListener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent sce) {
ServletContext context = sce.getServletContext();
context.log("HelloServletContextListener.contextInitailized() invoked!!");
ServletContextListener.super.contextInitialized(sce);
}
}
파일을 배포됐지만 아직 tomcat이 어떤 요청을 어떤 servlet으로 매핑해야할지는 모르는 상황입니다.
알맞은 설정은 [tomcat path]/webapps/sample/WEB-INF/web.xml
를 통해 그 정보를 설정할 수 있습니다.
web.xml에 아래와 같이 각 요소를 등록해주면 자동으로 적용이 가능합니다.
<servlet>
<servlet-name>HelloServlet</servlet-name>
<servlet-class>mypackage.HelloServlet</servlet-name>
</servlet>
<servlet-mapping>
<servlet-name>HelloServlet</servlet-name>
<url-pattern>/hello</url-pattern>
</servlet-mapping>
<filter>
<filter-name>HelloFilter</filter-name>
<filter-class>mypackage.HelloFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>HelloFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<listener>
<listener-class>mypackage.HelloContextListener</listener-class>
</listener>
위의 3가지 요소를 추가하는 방법만 알면 기능 개발에 큰 무리는 없을 것입니다. 이제 결과를 살펴보겠습니다.
각 Class 에 ServletContext를 통해서 로그를 남기도록 해놨습니다.
브라우저를 통해 localhost:8080/sample/hello
를 접속 시
filter 거쳐서 서블릿의 내용까지 적용된 것을 볼 수 있습니다.
그리고 HelloContextListener가 남기 Log를 확인하기 위해 [tomcat path]/logs/localhost.YYYY-MM-dd.log
를 체크해보면 servlet context가 생성될 때 로그가 찍힌 걸 확인할 수 있습니다.
root@ea018a5a0427:/usr/local/tomcat/logs# tail -f localhost.2021-05-17.log
17-May-2021 03:01:23.998 INFO [Catalina-utility-2] org.apache.catalina.core.ApplicationContext.log [Hello] Servlet Context Start!!
web.xml 설정이 다소 귀찮게 느껴지고 불편하다고 느껴질 수도 있습니다. Servlet 3.0 이상부터는 Annotation 을 활용해서 각 Component를 등록할 수도 있습니다.
Annotation을 적용하기 전에 web.xml의 버젼을 수정할 필요가 있습니다.
<web-app xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_3_0.xsd"
version="3.0">
다음과 같이 version이 3.0 이상이 됨을 확인해야합니다.
그리고 기존에 추가했던 요소는 주석처리를 하든 제거해줍니다.
그리고 각 클래스 파일에 알맞는 Annotation들을 추가해줍니다.
import javax.servlet.annotation.WebServlet;
@WebServlet("/hello")
public class HelloServlet extends HttpServlet {
import javax.servlet.annotation.WebFilter;
@WebFilter(filterName = "HelloFilter",
urlPatterns = {"/*"})
public class HelloFilter implements Filter {
import javax.servlet.annotation.WebListener;
@WebListener(value = "This Listener is invoked by container.")
public class HelloServletContextListener implements ServletContextListener {
이렇게 변경된 내용을 컴파일 후 아까와 동일한 방식으로 배포해주면 동일한 결과를 얻을 수 있습니다.
$ javac -cp ./servlet-api.jar src/mypackage/**/*.java -d out
$ docker cp ./out/mypackage tomcat:"/usr/local/tomcat/webapps/sample/WEB-INF/classes/"