Servlet(서블릿)
서블릿은 주로 웹 애플리케이션 서버에서 실행되며, HTTP 요청을 처리하고 동적 웹 페이지를 생성하는 데 사용하는 자바 기술입니다.
요약하자면 클라이언트의 요청을 처리하고 동적 페이지를 만드는 데 도움을 주는 기술이다.
만약 웹 애플리케이션 서버를 직접 만든다면 어떻게 구현할까?
어떤 데이터를 저장하기 위한 API를 하나 만든다고 가정해 보자.
해당 웹 애플리케이션 서버는 HTTP 요청을 받고 처리하기 위해서는 다음 단계를 거칠 것이다.
- 서버 TCP/IP 연결 대기, 소켓 연결
- HTTP 요청 메시지 파싱
- HTTP 메서드(GET, POST, PUT, DELETE 등) 확인, URL 확인
- Content-type 확인
- POST인 경우 HTTP MessageBody 파싱
- 비즈니스 로직 실행 (데이터베이스 조회, 저장, 업데이트 등)
- HTTP 응답(Response) 메시지 생성
- HTTP START 라인 작성
- Header 생성
- 메시지 바디 입력 - TCP/IP에 응답 전달 후, 소켓 종료
딱 봐도 많은 단계를 거쳐서 구현해야 함을 알 수 있다.
서블릿을 사용하는 WAS(웹 애플리케이션 서버)를 사용하면 단계를 훨씬 줄일 수 있다.
서블릿을 사용했을 때의 HTTP 요청 처리 단계를 알아보자
- WAS는 HTTP요청이 오면 Request, Response 객체를 생성하여 서블릿 객체를 호출한다.
- Request 객체에서 필요한 정보를 편하게 꺼내서 비즈니스 로직을 실행한다.
- Response 객체에 HTTP 응답 정보를 입력한다.
- WAS는 Response객체의 내용을 바탕으로 HTTP 응답 정보를 생성한다.
서블릿을 활용하면 HTTP 스펙을 편리하게 활용하여 많은 부분에서 이점이 있다.
서블릿만 만들어서는 자동으로 해주는 것이 아니라 이를 관리해 줄 수 있는 게 필요한 데 이를 서블릿 컨테이너라고 부른다.
Servlet Container(서블릿 컨테이너)
대표적인 서블릿 컨테이너로는 톰캣이 있는 데 이처럼 서블릿을 지원하는 WAS를 서블릿 컨테이너라고 한다.
서블릿 컨테이너는 서블릿 객체를 생성, 초기화, 호출, 종료하는 생명주기를 관리한다.
서블릿 객체는 싱글톤으로 생성된다. 왜냐하면 클라이언트가 요청할 때마다 객체를 생성한다면 비효율적이기 때문이다.
따라서 최초 로딩 시점에 서블릿 객체를 미리 생성하여 이를 재활용한다.
모든 클라이언트의 요청은 동일한 서블릿 객체 인스턴스에 접근하며, 싱글톤이기 때문에 공유 변수 사용은 주의해야 한다.
JSP 또한 서블릿으로 변환되어서 사용되며, 동시 요청을 위한 멀티 스레드 처리를 지원한다.
Spring Boot & Servlet
앞에서 톰캣이 대표적인 서블릿 컨테이너라고 하였는데 스프링 부트는 톰캣 서버를 내장하고 있어서톰캣 서버 설치 없이
편리하게 서블릿 코드를 실행할 수 있다.
스프링 부트는 서블릿을 직접 등록하여 사용할 수 있도록 @ServletComponentScan을 지원한다.
@ServletComponentScan
@SpringBootApplication
public class ServletApplication {
public static void main(String[] args) {
SpringApplication.run(ServletApplication.class, args);
}
}
다음과 같이 추가하면 서블릿을 등록할 수 있다. 서블릿 클래스를 작성해 보자.
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebServlet(name = "helloServlet",urlPatterns = "/hello")
public class HelloServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println("HelloServlet.service");
System.out.println("request = " + request);
System.out.println("response = " + response);
String username = request.getParameter("username");
System.out.println("username = " + username);
response.setContentType("text/plain");
response.setCharacterEncoding("utf-8");
response.getWriter().write("Hello " + username);
}
}
@WebServlet 어노테이션에서 서블릿의 이름과 URL을 매핑한다.
해당 클래스는 javax.servlet.http.HttpServlet을 상속받는다.
주요 핵심 내용은 HttpServletRequest, HttpServletResponse, 오버라이드한 service()이다.
서블릿은 HTTP 요청을 편하게 사용하도록 HTTP 요청 메시지를 대신 파싱해주며 파싱 한 결과를 HttpServletRequest 객체로 제공한다.
HttpServletRequest 객체는 임시 저장소 기능 및 세션 관리 기능도 부가적으로 제공한다.
HttpServletResponse는 HTTP 응답 메시지를 생성하고, 응답 코드 지정, 헤더 생성 및 바디를 생성한다.
또한 Content-type, 쿠키, Redirect 등의 편의 기능도 제공한다.
service() 메서드를 확인하기 위해 HttpServlet을 한 번 살펴보자.
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String method = req.getMethod();
if (method.equals(METHOD_GET)) {
long lastModified = getLastModified(req);
if (lastModified == -1) {
// servlet doesn't support if-modified-since, no reason
// to go through further expensive logic
doGet(req, resp);
} else {
long ifModifiedSince;
try {
ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);
} catch (IllegalArgumentException iae) {
// Invalid date header - proceed as if none was set
ifModifiedSince = -1;
}
if (ifModifiedSince < (lastModified / 1000 * 1000)) {
// If the servlet mod time is later, call doGet()
// Round down to the nearest second for a proper compare
// A ifModifiedSince of -1 will always be less
maybeSetLastModified(resp, lastModified);
doGet(req, resp);
} else {
resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
}
}
} else if (method.equals(METHOD_HEAD)) {
long lastModified = getLastModified(req);
maybeSetLastModified(resp, lastModified);
doHead(req, resp);
} else if (method.equals(METHOD_POST)) {
doPost(req, resp);
} else if (method.equals(METHOD_PUT)) {
doPut(req, resp);
} else if (method.equals(METHOD_DELETE)) {
doDelete(req, resp);
} else if (method.equals(METHOD_OPTIONS)) {
doOptions(req, resp);
} else if (method.equals(METHOD_TRACE)) {
doTrace(req, resp);
} else {
//
// Note that this means NO servlet supports whatever
// method was requested, anywhere on this server.
//
String errMsg = lStrings.getString("http.method_not_implemented");
Object[] errArgs = new Object[1];
errArgs[0] = method;
errMsg = MessageFormat.format(errMsg, errArgs);
resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg);
}
}
뭔가 복잡해 보이지만 핵심은 요청(HttpServletRequest)으로부터 메서드 이름을 확인하고, 해당 HTTP 메서드에 맞는 doGet, doPost 등을 실행하는 것이다.
상속받은 클래스는 HTTP 요청을 통해 매핑된 URL이 호출되면 서블릿 컨테이너는 오버라이드된 service 메서드를 실행한다.
한 번 실행해 보자. http://localhost:8080/hello?username=minsu
HelloServlet.service
request = org.apache.catalina.connector.RequestFacade@57de52cc
response = org.apache.catalina.connector.ResponseFacade@4782674a
username = minsu
더 자세한 정보를 확인하고 싶다면 application.properties에 해당 내용을 추가해 주면 된다.
logging.level.org.apache.coyote.http11=debug
WAS의 구조는 다음과 같다.
스프링 부트 실행 시 정의한 helloServlet을 생성한다.
helloServlet에 매핑한 URL로 클라이언트 요청이 오면 Request와 Response 객체를 생성하고, helloServlet의 서비스를 실행한다.
Reference
https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-mvc-1/dashboard
스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술 - 인프런 | 강의
웹 애플리케이션을 개발할 때 필요한 모든 웹 기술을 기초부터 이해하고, 완성할 수 있습니다. 스프링 MVC의 핵심 원리와 구조를 이해하고, 더 깊이있는 백엔드 개발자로 성장할 수 있습니다., 원
www.inflearn.com
'Back-End > Spring' 카테고리의 다른 글
빈 스코프 (0) | 2023.09.08 |
---|---|
빈 생명주기 콜백 (0) | 2023.09.08 |
컴포넌트 스캔 (0) | 2023.09.08 |
싱글톤 패턴과 싱글톤 컨테이너 (0) | 2023.09.08 |
스프링 핵심 원리 -2 (0) | 2023.09.01 |