코딩/sparta TIL

TIL 19 : Web Application

americanoallday 2025. 3. 18. 11:41

 

Web Server

웹 서버는 HTTP 기반으로 동작하며 정적 리소스(HTML, CSS, JS, 이미지 등)를 제공한다.

🥕 정적 리소스란 리소스가 이미 완성된 채로 서버에 존재하여 원본 그대로 응답하는 데이터를 의미한다.

 

Web Server 구조

 

대표적인 Web Server

1. NGINX(엔진엑스)

2. Apache(아파치)

 

WAS(Web Application Server)

HTTP 기반으로 동작하며 웹 서버의 기능을 포함한다.

추가적으로 코드를 실행해서 Application 로직을 수행하고 DB와 상호작용하여 동적 컨텐츠를 생성한다.

 

WAS 구조

 

대표적인 Web Application Server

1. Tomcat - Srping Boot에 내장되어 있다.

2. Jetty

3. Undertow

 

Web Server와 WAS의 차이점

1. 실제로는 Web Server도 Application 로직을 포함할 수 있다.

2. WAS는 Application 코드를 실행하는 것에 더욱 특화되어 있다.

3. Java에서는 Servlet Container 기능을 제공하면 WAS이다.

 

Web System 구성

Web Server, WAS를 활용하여 Web System을 효율적으로 구성하는 방법을 알아봅니다.

 

WAS만 사용하는 경우

  1. WAS가 너무 많은 역할을 담당한다 (정적 리소스도 관리해야 하고, 데이터베이스도 관리해야해고, 로직도 처리해야하니)
    • 서버 과부하 발생 가능성이 높아진다.
  2. 실행에 가장 중요한 Application 로직이 정적 리소스로 인해 수행되지 않을 수 있다.
  3. WAS에 장애가 생기면 아무런 화면도 보여주지 못한다.
    • 오류 페이지를 클라이언트에게 응답할 수 없다.

 

실제 웹 시스템 구성

  1. 정적 리소스는 Web Server에서 처리한다.
  2. Web Server는 Application 로직이 필요한 요청만을 WAS에 전달한다.

 

실제 웹 시스템 구성의 장점

  1. 효율적으로 리소스를 관리할 수 있다.
    • 정적 자원이 많이 사용된다면 Web Server를 ScaleOut 한다.
    • Application 관련 자원이 많이 사용된다면 WAS를 ScaleOut 한다.
  2. 오류 화면을 제공할 수 있다.
    • Web Server는 오류가 발생할 확률이 아주 낮다.
    • WAS는 오류가 발생할 확률이 아주 높고, 장애가 자주 발생한다.
    • WAS는 DB와 상호작용 하기 때문에 DB에 문제가 생겨도 문제가 발생한다.

수평적 확장하면서 관리 가능

 

Servlet

Servlet은 HTTP 프로토콜 기반 요청(Request) 및 응답(Response)을 처리하는데 사용된다.

JAVA에서 Sevlet은 HttpServlet 클래스를 상속받아 구현되며, 웹 애플리케이션 개발의 핵심 기술 중 하나이다.

 

서블릿(Servlet)웹 서버에서 클라이언트의 요청을 받아 처리하고 응답을 반환하는 자바 프로그램

HTTP 요청을 받아서 동적으로 데이터를 생성하는 역할을 함.

✔ 예를 들어, 사용자가 웹사이트에서 로그인을 하면, 서블릿이 요청을 받아 검증하고 결과를 반환하는 방식.

 

Servlet의 역할

 클라이언트(브라우저)가 HTTP 요청을 보냄 → (GET, POST 등)

 웹 서버(Tomcat 등)가 서블릿에게 요청을 전달

 서블릿이 요청을 처리하고 응답을 생성

 웹 서버가 클라이언트에게 응답을 반환

더보기

“그냥 요청을 서버에 바로 꽂으면 안 돼?”

**“서버에 바로 꽂는다”**는 게, 요청을 서블릿 없이 바로 서버에서 처리한다는 의미라면? 🤔

✔ 가능은 하지만, 웹 애플리케이션 구조상 추천되지 않음 🚨

✔ 서블릿을 사용하면 클라이언트 요청을 효율적으로 관리할 수 있기 때문.

 

🔹 서블릿 없이 서버에서 직접 처리하는 경우의 문제점

1️⃣ 모든 요청을 서버가 직접 처리하면 유지보수가 어려움

2️⃣ 클라이언트가 보낸 데이터를 가공하거나 검증하는 과정이 필요할 수도 있음

3️⃣ 보안적으로 위험할 수 있음 (SQL Injection 같은 공격을 막기 어려움)

 

서버가 서블릿 없이 요청을 받으면 어떻게 될까?

✔ 웹 서버(Tomcat 같은)는 HTTP 요청을 받으면 처리할 프로그램이 필요

✔ 예를 들어, http://example.com/login 요청이 왔을 때,

✔ 서버는 이 요청을 처리할 코드가 어디 있는지 찾아야 해

✔ 그 역할을 하는 것이 바로 서블릿(Servlet) 이고, 현대 웹 프레임워크(Spring 같은 것)도 결국 서블릿을 기반으로 동작

 

서블릿을 “식당”, 서버를 “주방”, 클라이언트를 “손님” 🍽️

 

🍽️ 비유: 손님이 직접 주방에 가서 요리를 주문하는 상황!

 

✔ 손님(클라이언트)이 식당에 들어오자마자 바로 주방에 가서 요리를 시키려고 함

✔ 주방장(서버)은 갑자기 여러 손님이 몰려오면 혼란이 생김 😵

✔ 음식 주문을 누가 먼저 했는지 관리하기 어렵고, 주방이 너무 바빠짐

 

👉 그래서 “서빙하는 직원(서블릿)“이 필요

👉 서빙 직원이 손님의 주문을 받아서 주방에 전달하면 더 깔끔하게 관리할 수 있음

 

“서블릿은 요청을 받고 주는 것만 하는 거야?”

 

🍽️ 비유: 서빙 직원이 주문을 주방에 전달하고, 음식이 나오면 손님에게 가져다주는 역할!

 

✔ 손님(클라이언트)이 “짜장면 하나 주세요!” 라고 말하면

✔ 서빙 직원(서블릿)이 주문을 정리해서 주방(서비스 계층)에 전달

✔ 주방(서비스 계층)에서 짜장면을 요리한 후, 서빙 직원이 손님에게 가져다줌 ✅

 

👉 즉, 서블릿은 요청을 받지만, 직접 요리(비즈니스 로직)는 하지 않음!

👉 대신 주방(서비스 계층)에서 처리하게 하고, 결과만 전달함!

 

“그럼 서블릿이 요리까지 하면 안 돼?”

 

🍽️ 비유: 서빙 직원이 요리까지 하려는 상황!

 

✔ 서빙 직원(서블릿)이 직접 요리까지 하려고 하면

주문도 받고, 요리도 하고, 서빙도 해야 해서 너무 바빠짐 🏃💨

✔ 요리가 많아질수록 일 처리가 엉망이 되고, 관리가 힘들어짐

 

👉 그래서 서블릿은 주문을 받아 주방에 전달하는 역할만 하고, 요리는 주방(서비스 계층)에서 하게 해야 함

 

서블릿과 서비스 계층을 분리하는 이유

 

서블릿 = 주문을 받는 직원

서비스 계층 = 요리하는 주방

데이터베이스(DB) = 냉장고(재료 창고)

 

이렇게 역할을 나누면:

서블릿(주문받는 직원)은 클라이언트 요청을 정리해서 전달

서비스 계층(주방)은 비즈니스 로직(요리)을 처리

데이터베이스(DB, 창고)에서 필요한 데이터를 가져옴

 

이렇게 하면 시스템이 효율적으로 동작하고, 유지보수도 쉬워짐 ✅

 

서블릿 컨테이너(Servlet Container)란?

 

서블릿 컨테이너는 서블릿을 관리하고 실행하는 프로그램.

웹 서버(Tomcat 같은 것)에서 서블릿을 실행하고, HTTP 요청을 처리하는 역할.

✔ 쉽게 말해서, 서블릿이 제대로 동작할 수 있도록 도와주는 환경.

 

서블릿 컨테이너를 비유로 이해하기 🎭

 

📦 서블릿 컨테이너 = “연극 무대 + 연출가”

🧑‍🎤 서블릿 = “배우”

🎭 웹 서버(Tomcat) = “극장”

🎟️ 클라이언트 요청 = “관객의 티켓”

 

✔ 연극을 하려면 배우(서블릿) 가 필요함

✔ 하지만 무대(서블릿 컨테이너) 없이는 공연이 불가능 함.

연출가(서블릿 컨테이너)배우를 관리하고, 대본(요청)이 오면 연기(응답)하게 해 줌.

✔ 그리고 관객이 티켓을 사면 극장(웹 서버) 이 입장을 시키고, 관객이 배우의 연기를 볼 수 있도록 함.

 

👉 즉, 서블릿 컨테이너는 서블릿이 올바르게 동작하도록 환경을 제공하는 역할을 한다! 🚀

 

 

서블릿 컨테이너의 주요 역할

서블릿 컨테이너는 서블릿을 실행하고 관리하는 여러 가지 기능을 제공

 

✅ 1) HTTP 요청과 응답 처리

✔ 클라이언트가 요청하면 서블릿 컨테이너가 서블릿을 실행해서 응답을 반환!

✔ 서블릿 컨테이너가 없으면, 개발자가 직접 HTTP 요청을 파싱하고 응답을 처리해야 해서 불편함.

 

📌 예제 흐름

1️⃣ 사용자가 http://example.com/hello 에 접속

2️⃣ 웹 서버(Tomcat)가 요청을 서블릿 컨테이너에 전달

3️⃣ 서블릿 컨테이너가 HelloServlet 을 실행

4️⃣ 서블릿이 "Hello, World!" 응답을 생성

5️⃣ 서블릿 컨테이너가 응답을 클라이언트에게 전달

 

✅ 2) 서블릿 생명주기(Lifecycle) 관리

서블릿 컨테이너는 서블릿을 언제 생성하고, 언제 제거할지 자동으로 관리.

 

📌 서블릿 생명주기

1️⃣ 서블릿 생성 (init())

2️⃣ 클라이언트 요청이 오면 실행 (service())

3️⃣ 서버가 종료되거나 필요 없어지면 제거 (destroy())

 

✔ 개발자가 new Servlet() 해서 객체를 직접 생성하지 않아도 됨!

✔ 서블릿 컨테이너가 알아서 관리해줌!

 

✅ 3) 멀티스레드 처리 (Thread Pool 활용)

✔ 여러 사용자가 동시에 요청하면, 서블릿 컨테이너가 여러 개의 스레드를 생성하여 처리

✔ 하나의 서블릿 객체가 여러 요청을 동시에 처리할 수 있도록 관리

Thread Pool을 사용하여 성능을 최적화 함!

 

📌 예제

100명의 사용자가 동시에 /login 요청을 보냄

서블릿 컨테이너가 100개의 요청을 스레드 풀(Thread Pool)에서 가져온 스레드로 처리

요청이 끝나면 스레드를 다시 풀에 반환

 

👉 즉, 서블릿 컨테이너가 멀티스레드 환경을 자동으로 관리해 줌! 🚀

 

✅ 4) 보안(Security) 관리

 

✔ 서블릿 컨테이너는 인증(Authentication) 및 권한(Role) 관리 기능을 제공

✔ 예를 들어, 로그인해야 접근할 수 있는 페이지를 자동으로 보호할 수 있음

web.xml 설정이나 @ServletSecurity 어노테이션을 사용해서 설정 가능!

@WebServlet("/admin")
@ServletSecurity(@HttpConstraint(rolesAllowed = "ADMIN"))
public class AdminServlet extends HttpServlet {
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
        response.getWriter().println("관리자 페이지입니다.");
    }
}

rolesAllowed = "ADMIN"관리자만 접근 가능!

✔ 서블릿 컨테이너가 인증을 체크하고, 권한이 없는 사용자는 403 Forbidden 응답을 반환

 

서블릿 컨테이너 없이 웹 서버만 사용하면 안 돼?

 

가능하지만, 매우 불편하고 비효율적! 😵

 

✔ 웹 서버(Apache, Nginx)는 정적 파일(HTML, CSS, JS)만 처리 가능

✔ 동적인 데이터(로그인, DB 조회 등)를 처리하려면 HTTP 요청을 직접 분석하고 응답을 생성해야 함

✔ 하지만 서블릿 컨테이너를 사용하면 이런 작업을 자동으로 처리해 줌!

 

📌 서블릿 컨테이너 없이 직접 구현해야 하는 일들

HTTP 요청을 직접 파싱

멀티스레드 처리 (요청이 많아질 경우 직접 스레드 관리)

쿠키, 세션 관리 (로그인 상태 유지)

보안(인증, 권한) 처리

서블릿 객체 생성/삭제 관리

 

👉 이 모든 걸 직접 구현하려면 너무 힘듦!

👉 그래서 Tomcat 같은 서블릿 컨테이너가 존재 🚀

 

대표적인 서블릿 컨테이너

 

Tomcat (아파치 톰캣) → 가장 많이 사용됨 (Spring Boot도 기본적으로 Tomcat 사용)

Jetty → 가볍고 빠른 서블릿 컨테이너

Undertow → 초경량 서블릿 컨테이너 (Spring Boot에서 선택 가능)

WildFly (JBoss) → 대규모 엔터프라이즈 애플리케이션에 사용

 

👉 보통 Spring Boot를 사용하면 Tomcat이 내장되어 있어서 따로 설치할 필요 없음! 🚀

 

HTTP POST 요청으로 HTML Form 데이터를 전송하는 상황 예시

더보기

📌 HTML Form이란?

HTML Form(폼) 은 사용자가 입력한 데이터를 서버로 전송하는 HTML 요소

 

💡 HTML Form의 주요 특징

사용자 입력을 받을 수 있음 (예: 로그인, 회원가입, 검색)

서버로 데이터를 보낼 수 있음

GET, POST 방식으로 요청 가능

 

💡 HTML Form 기본 구조

<form action="/submit" method="POST">
    <input type="text" name="name" placeholder="이름 입력">
    <input type="email" name="email" placeholder="이메일 입력">
    <button type="submit">제출</button>
</form>

action="/submit" → 데이터를 전송할 서버 주소

method="POST"POST 방식 전송

<input> 태그 → 사용자 입력을 받을 수 있음

<button type="submit"> → 버튼을 누르면 데이터가 서버로 전송됨

 

📌 HTTP POST 요청으로 HTML Form 데이터를 전송하는 상황 예시

사용자가 웹사이트에서 로그인할 때, ID와 비밀번호를 입력한 후 “로그인” 버튼을 누르면 HTML Form을 통해 POST 요청이 서버로 전송

 

1️⃣ HTML Form을 사용한 로그인 예제

<!DOCTYPE html>
<html>
<head>
    <title>로그인</title>
</head>
<body>
    <form action="/login" method="POST">
        <label>아이디: <input type="text" name="username"></label><br>
        <label>비밀번호: <input type="password" name="password"></label><br>
        <button type="submit">로그인</button>
    </form>
</body>
</html>

📌 설명

<form> 태그는 사용자가 입력한 데이터를 서버로 전송하는 역할을 함.

action="/login" → 데이터를 보낼 서버의 URL.

method="POST"POST 방식으로 전송 (데이터가 URL에 노출되지 않음).

<input> 태그에 name 속성을 부여하면 서버에서 데이터를 받을 때 키값으로 사용할 수 있음.

 

2️⃣ 서블릿에서 HTML Form 데이터를 받는 예제

@WebServlet("/login")
public class LoginServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // HTML Form에서 전송된 데이터 받기
        String username = request.getParameter("username");
        String password = request.getParameter("password");

        // 간단한 로그인 검증 (실제로는 DB에서 조회해야 함)
        if ("admin".equals(username) && "1234".equals(password)) {
            response.getWriter().println("로그인 성공! 환영합니다, " + username);
        } else {
            response.getWriter().println("로그인 실패! 아이디 또는 비밀번호가 틀렸습니다.");
        }
    }
}

📌 설명

request.getParameter("username") → HTML Form에서 입력된 데이터를 받아옴.

간단한 검증 후, response.getWriter().println()을 사용해 응답을 출력.

ex) 회원가입 : POST + localhost:8080/api/users

 

HTTP Request Message 예시

POST /api/users HTTP/1.1
Host: localhost:8080
Content-Type: application/x-www-form-urlencoded

userId=아이디&pssword=비밀번호

 

HTTP Response Message 예시

HTTP/1.1 200 OK
Content-Type: text/html;charset=UTF-8
Content-Length: 3423

<html>
	<body>
		...
	</body>
</html>

 

 

서버에서 처리해야 하는 작업

  1. 서버와 TCP/IP(3 way handshake) 연결
  2. HTTP Request Message 필요한 형태로 변환하여 읽기
    1. HTTP Method 및 URL 분석
    2. HTTP Header 분석
    3. HTTP Message Body 읽기 및 변환
  3. 분석한 결과를 통해 프로세스 실행
  4. 비지니스 로직 실행(Servlet을 지원하는 WAS를 사용한다면)
  5. HTTP Response Message 생성
    1. HTTP Start Line 생성
    2. Header 생성
    3. HTTP Message Body에 응답 데이터를 요청한 형식에 맞춰 응답
    4. 처리가 불가하다면 예외처리
  6. 응답 전달
  7. 연결 종료

 

Servlet 동작 순서

HTTP 프로토콜 기반 요청 및 응답을 처리해주는 Servlet은 어떤 순서로 동작하는지 알아보며 백엔드 개발자가 실제로 해야하는일이 무엇인지 더욱 자세히 알아봅시다.

 

localhost:8080/example HTTP API를 호출하면 일어나는 일

 

Servlet 예시코드 

> URL(/example)이 호출되면 service() 메서드가 실행된다.

@WebServlet(name="ExampleServlet", urlPatterns = "/exmaple")
public class ExampleServlet extends HttpServlet { // HttpServlet을 상속받아 구현한다.
	
	@Override
	protected void service(
		HttpServletRequest request,  // HTTP 요청 정보를 쉽게 사용할 수 있게 만드는 Servlet
		HttpServletResponse response // HTTP 응답 정보를 쉽게 제공할 수 있게 만드는 Servlet
	) {
		// application logic
	}

}

 

Servlet 동작 순서

  1. WAS는 HTTP 요청 메세지를 기반으로 새로운 Request, Response 객체 생성
  2. WAS는 만들어진 Request, Response 객체를 서블릿 컨테이너에 넘겨주며 ExampleServlet 객체 호출
  3. ExampleServlet에서 비지니스 로직 처리
  4. 응답에 필요한 정보를 개발자가 입력
  5. WAS는 Response 객체 정보(개발자가 입력한 정보)로 HTTP 응답 메세지 생성
  • 개발자가 하는 일
    1. Request 객체에 담겨져있는 HTTP 요청 정보를 꺼내서 사용한다.
      • 요청 정보(URL, Method, Message Body)를 통해 필요한 기능(비지니스 로직)을 수행한다.
    2. 생선된 Response 객체에 HTTP 응답 정보를 입력한다.

 

Servlet Container

Servlet을 지원하는 WAS 내부에는 서블릿 컨테이너가 있다.

서블릿 컨테이너는 서블릿을 초기화, 생성, 관리, 호출, 종료하는 역할을 수행한다.

  • Servlet의 생명주기
    • Servlet은 서블릿 컨테이너가 생성 및 관리한다.
    • WAS(서블릿 컨테이너 포함)가 종료될 때 Servlet도 함께 종료된다.
  • Servlet 객체 생성시점
    • 개발자가 직접 인스턴스화 하여 사용하는것이 아닌, 코드만 작성하면 서블릿 컨테이너가 생성한다.

예시코드

@WebServlet(name="ExampleServlet", urlPatterns = "/example")
public class ExampleServlet extends HttpServlet { // HttpServlet을 상속받아 구현한다.
	
	@Override
	protected void service(
		HttpServletRequest request,  // HTTP 요청 정보를 쉽게 사용할 수 있게 만드는 Servlet
		HttpServletResponse response // HTTP 응답 정보를 쉽게 제공할 수 있게 만드는 Servlet
	) {
		// application logic
	}

}
@WebServlet(name="Example2Servlet", urlPatterns = "/example2")
// 위와 같은 코드

@WebServlet(name="Example3Servlet", urlPatterns = "/example3")
// 위와 같은 코드

@WebServlet(name="Example4Servlet", urlPatterns = "/example4")
// 위와 같은 코드
  • Servlet Container가 하는 일
    1. 서블릿을 초기화, 생성, 관리, 호출, 종료하는 역할을 수행한다.
      • Servlet 객체를 싱글톤으로 관리한다.
    2. 동시 요청에 대한 처리를 위해 Multi Thread를 지원한다.
      • 다음 강의에서 다룰 내용입니다.
💬 Q. 싱글톤이 무엇인가요?
A. 싱글톤은 객체를 하나만 생성하여 생성된 인스턴스를 공유하여 사용하는것을 의미합니다. 특정 클래스의 인스턴스가 여러개 생성되지 않도록 하여 자원의 낭비를 방지하고, 인스턴스를 공유함으로써 상태를 일관되게 유지하기 위함입니다. 하지만, 
공유 변수 사용을 주의해야 합니다.

 

Thread

애플리케이션 코드를 하나하나 순차적으로 실행하는 것, Java에서 main method를 실행하면 main이라는 이름을 가진 Thread가 실행되며 하나의 Thread는 한번에 하나의 코드 라인만 수행한다. 만약 동시 처리가 필요하다면 Thread를 추가적으로 생성 해야한다.

 

HTTP Request가 WAS로 전달되면 Servlet 객체는 누가 호출할까요?

  • Servlet 객체의 호출
    • 클라이언트에서 Request가 전달되면 Thread가 Servlet 객체를 호출한다.

 

단일 요청 - Single Thread

  1. 클라이언트 요청 및 TCP/IP 연결
  2. Thread 할당 후 Servlet 호출
  3. 응답 후 Thread 반환

 

동시 요청 - Single Thread

  1. 첫번째 요청의 작업을 Single Thread가 수행중이다.
  2. 두번째, 세번째 요청이 들어오고 연결을 완료했다.
  3. Thread를 사용하기 위해 작업이 끝날때 까지 대기한다.
  4. 요청이 모두 사라질 때 까지 (대기 → 작업 수행 → 스레드 반환)작업을 반복한다.

 

만약, 첫번째 요청의 작업이 지연되거나 Error가 발생한다면?

모든 요청이 Time out 오류가 발생한다.

 

Multi Thread

Single Thread 방식을 채택한다면 동시 요청이 발생한 경우, 하나의 작업이라도 지연되거나 서버에 문제가 발생한다면 나머지 요청들에도 영향을 끼치게 됩니다. 과연 이 문제를 어떻게 해결할 수 있을까요?

여러개의 Thread를 생성해서 사용하면 됩니다! WAS는 동시 요청에 대한 처리를 위해 Multi Thread를 지원합니다.

 

Multi Thread로 동시 요청에 대한 처리를 하는 방법

1. 요청마다 새로운 Thread 생성

  • 요청이 들어올 때 마다 Thread를 생성한다.
  • 요청 처리가 완료되면 Thread를 종료한다.
  • 장점
    • 동시 요청을 처리할 수 있다.
    • 하나의 Thread에 지연등의 문제가 발생하여도 나머지 Thread는 정상적으로 동작한다.
  • 단점
    • Thread 생성에 제한이 없고 생성 비용이 높다.
      • 수많은 동시 요청이 발생하면 리소스(Memory, CPU 등)부족으로 서버가 다운된다.
    • Thread를 사용하면 Context Switching 비용이 발생한다.
더보기

멀티 태스킹의 진실과 Context Switching

CPU(프로세서)는 한순간에 하나의 프로세스만 실행할 수 있다.

Task1에서 Task2로 Task2에서 Task1로 교체되는 시점마다 Task1이 Ready 상태로 돌아간다는 정보, 진행정보, Task2는 어디부터 작업을 시작하면 되는지에 대한 정보들을 로딩할 시간 이 필요하게 된다. 이 순간의 시점이 바로 Context Switching이다.

 

작업 관리자 (현재 실행중인 프로세스)

작업관리자에서 실행 중인 작업들은 모두 동시에 수행하는 것처럼 보이지만 사실 엄청나게 짧은 시간 안에 수십, 수천 번 실행할 프로세스를 교체하고 있는 것이다.

 

2. Thread Pool

이미 생성된 여러개의 Thread가 수영을 하며 쉬고있는 수영장과 같다.

  • 요청이 들어오면 Thread Pool에서 Thread를 받아 사용한다.
  • 사용 완료된 Thread는 Thread Pool에 반납한다.
Thread Pool에 존재하는 Thread가 모두 사용 중이라면 어떻게 되나요?
> Thread Pool에 Thread가 생길 때까지 대기하거나 거절할 수 있습니다. (ex) 수강신청 대기번호 1032번)


📌 Servlet과 Thread Pool의 관계
서블릿은 요청이 들어올 때마다 새로 생성되는 것이 아니라, “하나의 서블릿 객체”가 여러 개의 스레드에 의해 실행됨!
✔ 서블릿은 싱글턴(singleton)으로 관리됨
✔ 요청이 올 때마다 새로운 스레드가 서블릿의 service() 메서드를 실행
✔ 스레드는 Thread Pool에서 빌려와서 사용한 후 반납됨
✔ 하나의 서블릿 객체가 여러 요청을 동시에 처리할 수 있도록 멀티스레드 환경에서 동작


📌 Servlet이 요청을 처리하는 과정
1️⃣ 서블릿 컨테이너(Tomcat 등) 가 애플리케이션 실행 시 Servlet 객체를 하나 생성
2️⃣ 클라이언트 요청이 들어오면, Thread Pool에서 스레드를 할당
3️⃣ 해당 스레드가 Servlet의 service() 메서드를 실행
4️⃣ 요청이 GET이면 doGet(), POST이면 doPost() 실행
5️⃣ 요청 처리가 끝나면 스레드는 Thread Pool로 반환됨

📌 요청이 많으면 여러 개의 스레드가 같은 서블릿 객체의 service() 메서드를 동시에 실행할 수도 있음!

 

정리

더보기
  1. WAS는 Multi Thread를 지원한다.
    • 개발자가 Multi Thread 관련 코드는 고려하지 않아도 된다.
    • Multi Thread 환경이므로 싱글톤 객체(Servlet, Spring Bean)는 주의해서 사용해야한다.
      → 공유변수 사용 조심 또 조심!
  2. Thread Pool
    • Thread는 Thread Pool에 보관 및 관리한다.
    • Thread Pool에 생성 가능한 Thread 최대치를 관리한다. Tomcat은 최대 200개 기본 설정이 되어있다.(변경 가능)
      • 성능테스트를 통해 적정 Thread 수를 찾으면 된다.
    • 장점
      1. 요청 마다 Thread를 생성하는 단점을 보완하였다.
      2. Thread가 미리 생성되어 있어서 Thread를 생성, 종료하는 비용이 절약된다. → 응답이 빠름
      3. 생성 가능한 Thread 최대치가 제한되어 있어서 많은 요청이 들어와도 안전하게 처리할 수 있다.
    • 단점
      1. Thread Pool의 최대 Thread 수를 낮게 설정한다면 응답이 지연된다.
      2. 최대 Thread 수가 너무 높으면 즉, 요청이 많아지면 리소스 부족으로 서버가 다운된다.

 

SSR(Server Side Rendering)

서버에서 동적으로 HTML을 만들어 클라이언트에게 제공하는 기술로 백엔드 개발자 영역에 속한다.

Java에서는 JSP, Thymeleaf가 대표적으로 사용된다.

 

SSR 동작 흐름

  1. 서버(WAS)에 HTML을 요청한다.
  2. 서버(WAS)에서 로직을 거친 후 DB를 조회한다.
  3. 조회 결과를 기반으로 HTML을 동적으로 생성한다.
  4. 생성된 HTML을 응답한다.

 

SSR의 장단점

장점

  1. 초기 페이지 로드 시 서버에서 완전히 렌더링된 HTML을 반환하여 첫 페이지 로딩이 빠르다.
  2. 검색 엔진 크롤러가 완전한 HTML을 즉시 수집할 수 있어 SEO에 유리하다.
    ex) 전자상거래 사이트, 뉴스 사이트

단점

  1. 모든 요청에 대해 서버가 페이지를 렌더링해야 하므로, 높은 트래픽 상황에서 서버의 부하가 크게 증가할 수 있다.
  2. 초기 페이지 이외의 페이지들도 렌더링 후 반환되기 때문에 속도가 느리다.

 

SEO(Search Engine Optimization)

검색 엔진에서 상위에 노출될 수 있도록 최적화하는 과정을 말한다.

 

 

CSR(Client Side Rendering)

웹 브라우저에서 자바스크립트를 사용해 동적으로 HTML을 생성해서 적용하는 기술(프론트엔드 개발자 영역)로 웹을 모바일 앱 처럼 부분 부분 변경할 수 있게 해준다. 프론트엔드 개발자 영역에 속하는 React, Vue가 대표적으로 사용된다.

 

CSR 동작 흐름

 

1. HTML을 요청한다. 비어있는 HTML을 응답받는다. JS가 존재하는 주소 링크를 응답한다.

2. 자바스크립트(클라이언트 로직, 렌더링 포함)를 요청한다.

3. HTTP API 요청을 하고 화면에 필요한 데이터를 JSON 형태(JSON이 아니어도됨)로 응답받는다.

{
	"name": "황원욱",
	"job": "developer"
}

4. 응답받은 JSON 데이터로 HTML을 동적으로 그린다.

 

CSR의 장단점

장점

  1. 클라이언트 측에서 렌더링하므로 사용자 인터랙션(상호작용)에 빠르게 반응할 수 있습니다.
  2. 초기 로딩 후에는 서버와의 통신 없이 빠르게 페이지 간 전환이 가능합니다.
    ex) Google Map, SNS Application

단점

  1. 초기 로딩 시 필요한 모든 자바스크립트 파일을 다운로드하고 실행해야 하므로, 초기 로딩 시간이 길다.
  2. 검색 엔진 크롤러가 자바스크립트를 제대로 실행하지 못하면 SEO에 불리할 수 있다.
더보기

📌 크롤러(Crawler)란?

크롤러(Crawler)웹사이트의 정보를 자동으로 수집하는 프로그램이야.

✔ 보통 검색 엔진(Google, Naver 등) 이 웹페이지 정보를 수집하는데 사용돼.

✔ 크롤러는 웹페이지의 텍스트, 링크, 이미지 등을 분석해서 검색 결과에 반영해.

 

대표적인 크롤러

Google의 Googlebot

Naver의 Yeti

Bing의 Bingbot

 

📌 CSR(Client-Side Rendering)이 SEO에 불리한 이유

CSR(Client-Side Rendering)은 웹페이지의 HTML을 서버에서 완성하는 것이 아니라, 클라이언트(브라우저)에서 JavaScript를 실행해서 화면을 구성하는 방식이야.

-> 크롤러가 JavaScript 실행 전에 HTML을 분석하면, 내용이 없는 빈 페이지로 인식될 수 있음.

 

문제점: 검색 엔진 크롤러는 JavaScript를 제대로 실행하지 못할 수도 있음!

✔ 일반적인 크롤러는 HTML을 다운로드하고 바로 분석하는데,

✔ CSR 방식에서는 초기 HTML이 비어있고, JavaScript 실행 후에 내용이 채워짐.

✔ 만약 크롤러가 JavaScript를 실행하지 않으면 내용 없는 페이지로 인식할 수 있음.

 

📌 JavaScript 실행이 늦어지는 이유 (CSR이 SEO에 불리한 이유)

1. 크롤러가 JavaScript 실행을 나중으로 미룰 수도 있음

검색 엔진이 크롤링을 우선적으로 하고, JavaScript 실행은 리소스가 허용될 때 진행

즉, 검색엔진이 언제 JavaScript를 실행할지는 보장되지 않음!

2. 일부 검색 엔진은 JavaScript를 아예 실행하지 않음

구글은 실행할 수 있지만, 네이버나 일부 검색 엔진은 실행을 지원하지 않을 수도 있음.

3. CSR 방식에서는 JavaScript가 실행될 때까지 HTML이 비어 있음

크롤러가 div 태그만 있는 빈 HTML을 가져가면 콘텐츠가 없다고 판단할 수도 있음.

 

📌 “그럼 JavaScript 실행 후에 크롤러가 실행되도록 하면 안 돼?”

이론적으로는 가능하지만, 현실적으로는 어려움.

 

크롤러의 실행 시점을 우리가 직접 조정할 수 없음.

✔ Googlebot이 JavaScript를 실행하는 순서는 검색 엔진의 정책에 따라 자동으로 결정됨.

✔ 따라서 SEO 최적화를 위해서는 검색 엔진이 JavaScript 없이도 페이지를 제대로 읽을 수 있도록 하는 것이 중요함!

 

📌 해결 방법 (JavaScript가 늦게 실행돼도 SEO에 유리하게 만드는 방법)

 

CSR 방식에서 SEO 문제를 해결하는 방법 몇 가지.

 

1️⃣ SSR(Server-Side Rendering) 사용

서버에서 미리 HTML을 완성한 후, 브라우저에 제공

Next.js 같은 프레임워크에서 지원

검색 엔진이 즉시 HTML을 읽을 수 있음 → SEO에 유리

 

2️⃣ Static Site Generation(SSG) 사용

HTML을 미리 생성해서 저장해 놓고, 클라이언트가 접속하면 제공

Gatsby, Hugo 같은 정적 사이트 생성기를 사용

SEO 최적화에 좋음

 

3️⃣ Pre-Rendering 사용

Puppeteer 같은 도구를 이용해 JavaScript 실행 후 HTML을 미리 생성해서 검색 엔진에 제공

기존 CSR 웹사이트에서도 적용 가능

 

4️⃣ Google Search Console에서 확인

Google의 URL 검사 도구를 이용해 크롤러가 제대로 JavaScript를 실행하는지 확인 가능

 

마무리

더보기
  1. 네트워크 통신은 HTTP로 이루어진다.
  2. HTTP는 무상태 프로토콜이며 비연결성 특징을 가지고 있다.
  3. HTTP Message 구조
    1. HTTP Method
    2. 상태코드
    3. HTTP Header
  4. HTTP API는 Restful 하게 설계해야 한다. 최소 성숙도레벨 2를 지켜야 한다.
  5. Servlet은 Java에서 Request, Response를 쉽게 다루게 해주는 객체이다.
  6. Servlet Container는 Servlet 객체를 싱글톤으로 관리한다.
  7. WAS는 다중 요청 처리를 위해 Multi Thread를 지원한다.
  8. SSR 방식은 서버에서 동적인 페이지를 완성하여 응답한것을 브라우저에서 화면을 출력한다.
  9. CSR은 HTTP API 통신으로 얻은 결과를 통해 브라우저에서 동적으로 화면을 출력한다.