개요
자바 서블릿(Java Servlet)은 자바를 사용하여 웹페이지를 동적으로 생성하는 서버측 프로그램 혹은 그 사양을 말하며, 흔히 "서블릿"이라 불린다. 자바 서블릿은 웹 서버의 성능을 향상하기 위해 사용되는 자바 클래스이다.
서블릿은 서버에서 실행되다가 웹 브라우저에서 요청을 하면 해당 기능을 수행한 후 웹 브라우저에 결과를 전송한다.
쉽게 예를들면 로그인 시도를 할 때, 서버가 클라이언트에서 입력되는 아이디와 비밀번호를 확인하고 결과를 응답하는데 이러한 역할을 수행하는 것이 서블릿이다.
내가 정리하는 쉬운 해석
자바의 웹 프로그래밍 기술이다. 클라이언트로부터 요청이 들어오면 그에 맞는 결과를 전송해주어야 한다.
이때 전송해야 할 양식은 상당히 많은 개행문자와 양식들이 있다. 이를 개발자들은 일일이 해당 양식에 맞추어서 보내주기란 상당히 번거로운 일이다.
이를 간단한 메서드 호출만으로 결과를 전송할 수 있게 해주는 기술이다.
서블릿 동작 과정
- 클라이언트가 웹 서버(WS)에 요청하면 웹 서버는 그 요청을 WAS에 위임한다.
- WAS는 서블릿을 실행한다.
- 서블릿은 해당 요청에 대한 기능을 수행한 후 클라이언트에 결과를 전송한다.
실제로 직접적인 서블릿을 언제 사용해 보았을까?
사실 말이 좀 이상한데 SpringBoot, Express.js, NestJS를 사용하는 모두는 직접 WAS를 구현해보지 않는 이상 자연스럽게 사용하고 있다.
컨트롤러단에서 우리가 자연스럽게 사용하는 ResponseEntity, 혹은 @Get에 모두 달려있어서 알아서 쉽게 사용하고 있다.
그러면 개발을 할 때 HttpServlet은 나는 스프링 시큐리티로 OAuth를 구현할 때 사용했었기에 코드를 가져와보겠다. 우리는 요청 객체를 알아야 하며, 그로 인한 응답값을 보내주어야 한다.
@Override
public void onAuthenticationSuccess (HttpServletRequest request, HttpServletResponse response, Authentication authentication)
throws IOException, ServletException {
CustomOAuth2User oAuth2User = (CustomOAuth2User) authentication.getPrincipal();
String userId = oAuth2User.getName();
String accessToken = jwtProvider.generateAccessToken(Long.parseLong(userId));
Cookie accessTokenCookie = new Cookie("access_token", accessToken);
accessTokenCookie.setPath("/");
accessTokenCookie.setMaxAge(Math.toIntExact(accessTokenExpiration));
accessTokenCookie.setHttpOnly(true);
//accessTokenCookie.setSecure(true); // 240404 ldhbenecia | https 설정 이후 사용
response.addCookie(accessTokenCookie);
response.addHeader("Authorization", "Bearer " + accessToken);
response.sendRedirect(domainUrl);
response.setStatus(HttpServletResponse.SC_OK);
}
다음 코드를 봐보자. Java를 사용한 코드이다.
HttpServletRequest를 사용하지는 않았다. 로그인을 처리하는 과정이었고 OAuth였기 때문에 클라이언트로부터 요청받은 request 값을 사용할 일은 없었다.
HttpServletResponse를 통해서 쿠키와 헤더에 값을 집어넣어주고 그다음은 domainUrl이라는 링크로 리다이렉트를 시켜주고 상태 status 값으로 SC_OK를 보내는 것을 확인할 수 있다.
서블릿을 직접 구현하면 어떻게 될까? (TypeScript Version)
@Service()
export class BoardController {
private boardService: BoardService;
constructor(boardService: BoardService) {
this.boardService = boardService;
}
async createPost(req: HttpRequest, res: HttpResponse): Promise<Board> {
const title: string = req["title"] as string;
const content: string = req["content"] as string;
const nickname: string = req["nickname"] as string;
const memberId: number = (await getUserIdFromNickname(nickname)) as number;
res.setStatus(302).setHeader("Location", "/index.html").send();
return await this.boardService.createPost(new BoardDto({ title, content, nickname, memberId }));
}
언어는 다르지만 이 코드는 타입스크립트를 이용한 코드이다.
WAS를 직접 만들어보았던 프로젝트에서 사용한 내가 작성했던 코드이다.
createPost라는 기능을 수행하기 위해 HttpRequest에서 일일이 클라이언트로부터 요청받은 값을 파싱 한다.
그리고 반환 값으로는 HttpResponse에서 302 상태값을 날린다. 여기서 302는 리다이렉트를 시키는 상태코드이다. 그리고 이동시킬 경로를 헤더에 담아서 보내주어 처리한다.
이렇게 서블릿이 자동으로 처리를 해주지 않을 경우 개발자는 일일이 클라이언트로부터 요청받은 값을 구해서 파싱 하는 과정을 거쳐야 한다.
이는 실로 힘든 일이다..
서블릿의 특징
- 클라이언트의 Request에 대해 동적으로 작동하는 웹 애플리케이션 컴포넌트
- 기존의 정적 웹 프로그램의 문제점을 보완하여 동적인 여러 가지 기능을 제공
- JAVA의 스레드를 이용하여 동작
- MVC패턴에서 컨트롤러로 이용됨
- 컨테이너에서 실행
- 보안 기능을 적용하기 쉬움
서블릿 컨테이너
서블릿 컨테이너는 구현되어 있는 servlet 클래스의 규칙에 맞게 서블릿을 담고 관리해 주는 컨테이너다.
위에서 말했듯이 서블릿 컨테이너가 있기에 우리는 일일이 HttpRequest 객체를 파싱 해서 꺼내어 요청 값을 찾고 HttpResponse 양식에 맞추어서 반환해 줄 필요가 없다.
개발자는 이러한 잡다한 곳에 에너지를 쓸 일이 아니라 순수 개발 비즈니스 로직에 집중을 할 수 있다.
또한 서블릿 컨테이너는 스프링 컨테이너가 빈을 관리하는 것처럼 서블릿을 전체적으로 관리한다. 이로 인해 서블릿 클래스를 인스턴스화하고 다 사용한 서블릿은 버리는 과정까지 모두 처리해 준다.