본문 바로가기
Spring MVC

[MVC] 웹 애플리케이션 이해

by 개발 Blog 2024. 10. 10.

공부 내용을 정리하고 앞으로의 학습에 이해를 돕기 위해 작성합니다.

 

웹 서버, 웹 애플리케이션 서버

 

서버(Web Server)

웹 서버는 HTTP 기반으로 동작하며, 정적 리소스를 제공하는 역할을 한다. 이 리소스는 HTML, CSS, JS, 이미지, 영상 등과 같은 파일들이다. 예를 들면 NGINX, APACHE 등이 웹 서버로 사용된다.

 

애플리케이션 서버(WAS - Web Application Server)

웹 애플리케이션 서버는 웹 서버와 마찬가지로 HTTP 기반으로 동작하지만, 웹 서버 기능을 포함하면서도 추가적으로 애플리케이션 로직을 실행한다. WAS는 동적인 HTML이나 HTTP API(JSON)와 같은 응답을 생성하는 역할을 하며, 서블릿, JSP, 스프링 MVC 등을 통해 프로그램 코드를 실행할 수 있다. 대표적인 WAS로는 톰캣(Tomcat), Jetty, Undertow 등이 있다.

 

웹 서버와 WAS의 차이

  • 웹 서버는 정적 리소스(파일)를 처리하고, WAS는 애플리케이션 로직을 처리한다.
  • 그러나 이 둘의 경계는 모호할 수 있다. 웹 서버도 프로그램을 실행하는 기능을 포함할 수 있고, WAS도 웹 서버의 기능을 제공할 수 있기 때문이다.
  • Java에서는 서블릿 컨테이너 기능을 제공하면 WAS로 분류된다.
  • WAS는 애플리케이션 코드를 실행하는데 더 특화되어 있다.

시스템 구성

WAS, DB

WAS와 DB만으로 시스템을 구성할 수 있다. 이 경우, WAS가 정적 리소스와 애플리케이션 로직을 모두 처리하게 된다. 그러나 이렇게 많은 역할을 담당할 경우 서버 과부하가 발생할 수 있다. 또한, WAS에 장애가 생기면 오류 화면조차 제공할 수 없는 상황이 발생한다.

 

WEB, WAS, DB

이 구성에서는 정적 리소스는 웹 서버가 처리하고, 애플리케이션 로직은 WAS가 처리한다. 이 방식은 리소스 관리가 효율적이며, 필요에 따라 웹 서버 또는 WAS를 증설할 수 있다. 예를 들어, 정적 리소스 사용량이 많으면 웹 서버를, 애플리케이션 리소스 사용량이 많으면 WAS를 증설하는 방식이다.

 

서버의 안정성과 확장성

  • 정적 리소스만 제공하는 웹 서버는 잘 죽지 않지만, 애플리케이션 로직이 동작하는 WAS는 상대적으로 서버가 죽는 경우가 더 많다.
  • WAS나 DB에 장애가 생길 경우에도 웹 서버는 오류 화면을 제공할 수 있다.

이를 통해, 정적 리소스 처리와 애플리케이션 로직 처리를 분리함으로써 서버 과부하를 방지하고 시스템의 안정성과 확장성을 확보할 수 있다.

 

서블릿

서블릿(Servlet)은 Java 웹 애플리케이션에서 클라이언트의 요청을 처리하고 응답을 제공하는 역할을 한다. 주로 HTTP 프로토콜을 사용하여 브라우저나 다른 클라이언트의 요청을 처리하는 데 사용된다. 서블릿은 HttpServletRequest와 HttpServletResponse 객체를 통해 HTTP 요청과 응답을 쉽게 처리할 수 있다.

 

특징

  • 특정 URL 패턴이 호출되면 서블릿 코드가 실행된다 (예: /hello).
  • HttpServletRequest 객체를 통해 HTTP 요청 정보를 편리하게 사용할 수 있다.
  • HttpServletResponse 객체를 통해 HTTP 응답 정보를 쉽게 제공할 수 있다.
  • 개발자는 HTTP 스펙을 매우 쉽게 사용할 수 있는 환경을 제공받는다.

HTML Form 데이터 전송

HTML 폼에서 데이터를 전송할 때 주로 GET이나 POST 방식을 사용한다. POST 방식은 사용자의 입력 데이터를 서버로 안전하게 전달할 수 있어, 주로 데이터를 서버에 저장하거나 업데이트할 때 많이 사용된다.

 

POST 전송 - 저장

HTML 폼을 통해 사용자가 입력한 데이터를 POST 방식으로 서버에 전송하면, 서버는 이를 받아서 처리하고 필요에 따라 데이터베이스에 저장할 수 있다. 예를 들어, 회원 가입 폼에서 사용자가 입력한 이름과 나이 등의 정보를 서버로 전송하여 이를 저장하는 과정이 이에 해당한다.

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

서버는 사용자가 전송한 요청을 받아 비즈니스 로직을 처리하고, 그 결과를 HTTP 응답 메시지로 클라이언트에게 돌려준다. 이 과정에서 서블릿이 중요한 역할을 한다.

 

애플리케이션 서버 직접 구현

서블릿을 이용해 웹 애플리케이션 서버를 직접 구현할 수 있다. 예를 들어, 데이터를 처리하고 저장하는 로직을 직접 구현하여 POST 요청을 처리할 수 있다.

 

서블릿을 지원하는 WAS 사용

톰캣과 같은 WAS(Web Application Server)는 서블릿을 쉽게 사용할 수 있도록 돕는 서블릿 컨테이너를 제공한다. 개발자는 이 WAS 위에서 서블릿을 개발하여 HTTP 요청을 처리할 수 있다.

 

서블릿 HTTP 요청과 응답 흐름

  • 클라이언트가 HTTP 요청을 보내면 WAS는 Request, Response 객체를 생성한다.
  • 생성된 객체를 서블릿에 전달하고, 서블릿은 이 객체들을 사용해 비즈니스 로직을 처리한다.
  • Request 객체에서 HTTP 요청 정보를 추출하여 처리하고, Response 객체에 응답 정보를 담아 클라이언트로 보낸다.

서블릿 컨테이너

서블릿 컨테이너는 서블릿의 생명주기를 관리하며, 이를 통해 서블릿의 효율적인 동작을 보장한다.

특징

  • 서블릿 컨테이너는 서블릿 객체를 생성, 초기화, 호출, 종료하는 생명주기를 관리한다.
  • 서블릿 객체는 싱글톤으로 관리되어 최초에 한 번 생성된 후 재사용된다.
  • 멀티 쓰레드를 지원하여 동시 요청을 처리할 수 있다.
  • JSP도 서블릿으로 변환되어 사용되며, WAS는 이를 자동으로 처리한다.

주의점

  • 공유 변수를 사용할 때는 동시성 문제에 주의해야 한다.
  • 서블릿 컨테이너 종료 시 서블릿 객체도 함께 종료된다.

이와 같이 서블릿을 사용한 웹 애플리케이션은 HTTP 요청과 응답을 편리하게 처리할 수 있으며, WAS를 통해 서블릿 컨테이너가 이 과정을 자동으로 관리하여 개발자는 비즈니스 로직 구현에 집중할 수 있다.

 

동시 요청 - 멀티 쓰레드

웹 애플리케이션에서 동시 요청이 발생할 때는 멀티 쓰레드를 사용하여 요청을 처리한다. 멀티 쓰레드를 사용하면 여러 사용자가 동시에 애플리케이션을 사용할 수 있으며, 각각의 요청이 독립적으로 처리된다.

 

쓰레드란?

  • 쓰레드는 애플리케이션 코드를 순차적으로 실행하는 작업 단위이다.
  • 자바에서 메인 메서드가 실행되면 main이라는 이름의 쓰레드가 실행된다.
  • 자바 애플리케이션은 쓰레드가 없으면 실행이 불가능하다.
  • 쓰레드는 한 번에 하나의 코드 라인만 처리할 수 있다.
  • 동시 처리가 필요하면 추가로 쓰레드를 생성하여 작업을 분배한다.

단일 요청 - 쓰레드 하나 사용

단일 요청이 발생하면, 서버는 하나의 쓰레드를 생성하거나 기존 쓰레드를 재사용하여 요청을 처리한다. 이 쓰레드는 클라이언트의 요청을 처리하고 응답을 반환한 후 종료되거나 휴식 상태로 돌아간다.

  • 클라이언트가 요청을 보냄
    클라이언트에서 요청이 발생하면 웹 애플리케이션 서버(WAS)로 전달된다.
  • WAS가 쓰레드 할당
    WAS는 쓰레드를 할당하여 해당 요청을 처리한다. 이 쓰레드는 요청 정보를 서블릿으로 전달하여 비즈니스 로직을 실행한다.
  • 응답 처리
    서블릿이 요청을 처리한 후, 응답 정보를 쓰레드를 통해 다시 클라이언트에게 반환한다.
  • 쓰레드 상태
    요청 처리 후, 쓰레드는 응답을 마치고 휴식 상태로 돌아가거나 종료된다. 다음 요청이 들어오면 동일한 쓰레드가 재사용되거나 새로운 쓰레드가 생성된다.

다중 요청 - 쓰레드 하나 사용

웹 애플리케이션 서버(WAS)에서 다중 요청이 발생할 때 하나의 쓰레드를 사용할 경우, 성능에 심각한 문제가 발생할 수 있다.

 

1. 단일 요청 처리

  • 첫 번째 클라이언트의 요청(요청1)이 발생하면, WAS는 쓰레드를 할당하여 요청을 처리한다.
  • 이때, 서블릿이 요청을 처리하는 동안 다른 요청은 대기 상태로 전환된다.
  • 즉, 첫 번째 요청이 완료되기 전까지 다른 요청을 처리할 수 없다.

2. 다중 요청 처리 문제

  • 두 번째 클라이언트의 요청(요청2)이 들어오면, 동일한 쓰레드가 사용 중이기 때문에 이 요청은 처리되지 않고 대기 상태에 머무른다.
  • 첫 번째 요청이 끝나지 않는 한 두 번째 요청은 처리가 지연되며, 사용자는 응답을 받을 수 없다.

요청마다생성

웹 애플리케이션에서 요청이 들어올 때마다 새로운 쓰레드를 생성하여 처리하는 방법은 동시 요청을 처리하는 간단한 방법 중 하나이다. 이 방법은 리소스가 허용하는 한 요청을 병렬로 처리할 수 있지만, 그에 따른 비용도 크다.

장점

  • 동시 요청 처리 가능: 여러 요청을 동시에 처리할 수 있어, 하나의 요청이 지연되더라도 다른 요청은 정상적으로 처리된다.
  • 리소스 허용 범위 내에서 처리 가능: CPU나 메모리가 허용하는 한, 요청이 올 때마다 새로운 쓰레드를 생성하여 처리할 수 있다.
  • 독립적인 처리: 하나의 쓰레드가 지연되더라도 다른 쓰레드는 영향을 받지 않고 동작한다.

단점

  • 비싼 쓰레드 생성 비용: 쓰레드를 생성하는 작업은 리소스 비용이 크기 때문에, 요청이 많아질수록 성능에 부담이 커진다.
  • 응답 속도 저하: 쓰레드를 생성할 때 소요되는 시간이 있어, 요청이 많을 경우 응답 속도가 느려질 수 있다.
  • 컨텍스트 스위칭 비용: 여러 쓰레드가 동시에 실행될 때는 CPU가 쓰레드 간 전환(컨텍스트 스위칭)을 해야 하는데, 이 작업에는 추가적인 비용이 발생한다.
  • 쓰레드 생성 제한 없음: 쓰레드를 무제한으로 생성할 수 있기 때문에, 요청이 급격하게 많아지면 CPU와 메모리 자원이 임계점을 넘어 서버가 다운될 위험이 있다.

쓰레드

쓰레드 풀(Thread Pool)은 웹 애플리케이션에서 요청마다 쓰레드를 생성하는 비용을 줄이기 위해 사용되는 중요한 개념이다. 쓰레드 풀은 미리 정해진 개수의 쓰레드를 생성하고, 요청이 들어오면 이 쓰레드를 재사용한다. 이는 시스템 성능을 최적화하는 데 중요한 역할을 한다.

특징

  • 쓰레드 보관 및 관리: 필요한 쓰레드를 미리 생성해 쓰레드 풀에 보관하고 관리한다. 쓰레드가 필요할 때마다 새로 생성하지 않고, 풀에 있는 쓰레드를 꺼내 사용한다.
  • 쓰레드 최대치 관리: 쓰레드 풀은 생성 가능한 쓰레드의 최대 개수를 관리한다. 예를 들어, 톰캣(Tomcat)은 기본적으로 200개의 쓰레드가 최대치로 설정되어 있으며, 이 값은 필요에 따라 조정할 수 있다.
  • 쓰레드 사용 및 반납: 쓰레드를 사용한 후에는 다시 쓰레드 풀에 반납된다. 이 과정은 매우 효율적이며, 쓰레드 생성과 소멸에 따른 비용을 줄일 수 있다.
  • 최대 쓰레드 도달 시: 만약 모든 쓰레드가 사용 중일 경우, 추가 요청은 거절되거나 일정 수의 요청만 대기할 수 있도록 설정할 수 있다.

장점

  • 쓰레드 생성 비용 절감: 쓰레드가 미리 생성되어 있어, 요청마다 쓰레드를 생성하고 종료하는 데 필요한 CPU 비용이 절약된다. 이로 인해 응답 시간이 빨라진다.
  • 안정적인 처리: 쓰레드 최대 개수를 관리하므로, 너무 많은 요청이 들어와도 시스템이 안정적으로 요청을 처리할 수 있다. 서버가 과부하되는 것을 방지할 수 있다.

실무 팁 - 쓰레드 풀 튜닝

쓰레드 풀의 튜닝은 성능을 최적화하기 위해 매우 중요하다. 특히 최대 쓰레드 수 설정이 핵심이다.

 

최대 쓰레드 수 튜닝

  • 값이 너무 낮으면: 동시 요청이 많을 경우 서버 리소스는 여유롭지만, 클라이언트는 응답 지연을 느끼게 된다.
  • 값이 너무 높으면: 동시 요청이 많을 경우 CPU와 메모리 리소스가 초과되어 서버가 다운될 수 있다.

장애 발생 시 대처

  • 클라우드 환경: 클라우드 환경에서는 서버를 빠르게 증설하여 대응할 수 있으며, 이후에 필요한 튜닝을 진행한다.
  • 온프레미스 환경: 클라우드가 아닌 환경에서는 서버 리소스 증설이 어렵기 때문에 적절한 튜닝이 필요하다.

쓰레드 - 너무 낮게 설정

쓰레드 풀에서 최대 쓰레드 수가 너무 낮게 설정된 경우, 서버의 동시 요청 처리 능력이 제한되어 성능이 저하될 수 있다. 하지만 쓰레드 풀의 적정 숫자를 설정하는 것은 애플리케이션과 시스템의 상황에 따라 달라진다.

적정 쓰레드 수 찾기

  • 애플리케이션 로직 복잡도: 애플리케이션이 처리해야 하는 로직의 복잡도에 따라, 더 많은 쓰레드가 필요할 수 있다. 로직이 단순하면 적은 쓰레드로도 많은 요청을 처리할 수 있지만, 복잡한 계산이나 데이터 처리를 요구하면 더 많은 쓰레드를 할당해야 한다.
  • CPU, 메모리, IO 리소스 상황: 서버의 CPU, 메모리, 입출력(IO) 자원 상태를 고려하여 적절한 쓰레드 수를 결정해야 한다. CPU 사용량이 높을 때 쓰레드를 너무 많이 생성하면 과부하가 발생할 수 있다.
  • 성능 테스트: 적정 쓰레드 수를 결정하기 위해 성능 테스트가 필수적이다. 실제 서비스 환경과 최대한 유사한 상황에서 테스트를 진행해야 한다.

성능 테스트 도구

  • Apache AB: 아파치 HTTP 서버 벤치마킹 툴로, HTTP 요청을 대량으로 보내 성능을 테스트할 수 있다.
  • JMeter: HTTP 요청뿐만 아니라 다양한 프로토콜의 성능을 테스트할 수 있는 오픈 소스 성능 테스트 도구이다.
  • nGrinder: 대규모의 웹 서비스나 시스템 성능을 테스트하기 위한 성능 테스트 도구로, 대규모 성능 테스트 시 유용하다.

WAS의 멀티 쓰레드 지원

핵심 사항

  • WAS가 멀티 쓰레드를 처리: 웹 애플리케이션 서버(WAS)는 멀티 쓰레드 처리를 자동으로 지원한다. 개발자는 WAS가 제공하는 멀티 쓰레드 기능을 이용하여 별도의 멀티 쓰레드 코드를 작성할 필요가 없다.
  • 싱글 쓰레드 프로그래밍 방식: 개발자는 멀티 쓰레드를 신경쓰지 않고도 마치 싱글 쓰레드 환경에서 프로그래밍하는 것처럼 코드를 작성할 수 있다. WAS가 요청을 처리할 때 적절히 쓰레드를 관리해 주기 때문이다.
  • 싱글톤 객체 주의: 서블릿이나 스프링 빈처럼 싱글톤으로 관리되는 객체는 멀티 쓰레드 환경에서 주의해서 사용해야 한다. 동시 요청 시 상태 관리가 잘못되면 데이터 충돌이나 동기화 문제가 발생할 수 있기 때문이다.

HTML, HTTP API, CSR, SSR

정적 리소스

정적 리소스는 고정된 콘텐츠를 의미하며, 웹 서버에서 클라이언트로 제공된다. 대표적인 예로 HTML 파일, CSS, JS 파일, 이미지, 영상 파일 등이 있다. 주로 웹 브라우저에서 사용되며, 이러한 리소스는 서버에 이미 저장된 상태로 클라이언트 요청에 따라 바로 전달된다.

HTML 페이지

동적으로 생성된 HTML 파일은 서버에서 필요한 정보를 기반으로 실시간으로 만들어지며, 이를 클라이언트에게 전달한다. 서버는 데이터베이스에서 필요한 정보를 조회한 후, JSP나 타임리프 같은 템플릿 엔진을 사용해 HTML 문서를 생성한다.

 

  • 서버에서 동적으로 생성된 HTML을 클라이언트에 전달한다.
  • 웹 브라우저는 HTML을 해석하여 사용자에게 화면을 제공한다.

HTTP API

HTTP API는 HTML을 전달하지 않고, 데이터를 클라이언트로 전달한다. 주로 JSON 형식을 사용하며, 앱, 웹 클라이언트, 서버 간 데이터 통신에 많이 활용된다. UI는 클라이언트 측에서 처리되며, 서버는 데이터만 제공한다.

  • 데이터를 JSON 형식으로 주고받는다.
  • UI 처리는 클라이언트가 담당한다.
  • 여러 시스템과 연동될 수 있다 (서버-to-서버, 클라이언트-to-서버).

HTTP API - 다양한 시스템 연동

HTTP API는 JSON 형태로 데이터를 주고받으며, 다양한 시스템에서 이를 활용할 수 있다. UI 클라이언트가 직접 HTTP API를 호출해 데이터를 주고받을 수 있으며, 서버 간 통신도 가능하다.

 

서버사이드 렌더링, 클라이언트 사이드 렌더링

SSR - 서버 사이드 렌더링

  • 서버에서 최종 HTML을 생성하여 클라이언트에 전달.
  • 주로 정적인 화면에 사용되며, 서버에서 화면을 미리 구성하여 응답 속도가 빠르다.
  • 관련 기술: JSP, 타임리프.

CSR - 클라이언트 사이드 렌더링

  • 클라이언트 측에서 자바스크립트를 통해 HTML을 동적으로 생성하고 렌더링.
  • 주로 동적인 UI에 사용되며, 웹 환경을 애플리케이션처럼 사용할 수 있다.
  • 관련 기술: React, Vue.js.

SSR과 CSR의 조합

  • React, Vue.js와 같은 프레임워크는 SSR과 CSR을 동시에 지원하기도 한다. SSR을 통해 초기 화면을 빠르게 렌더링 하고, 이후 CSR로 동적인 부분을 처리할 수 있다.

백엔드 개발자가 알아야 할 UI 기술

  • 서버 사이드 렌더링(SSR): 백엔드 개발자는 SSR 기술을 필수적으로 학습해야 한다. JSP, 타임리프와 같은 서버 사이드 렌더링 도구를 통해 서버에서 화면을 생성한다.
  • 클라이언트 사이드 렌더링(CSR): CSR 기술은 웹 프론트엔드 개발자의 전문 영역이며, 백엔드 개발자에게는 선택 사항이다. 복잡하고 동적인 UI 작업은 CSR을 통해 처리되며, React, Vue.js 같은 기술을 사용한다.
  • 선택과 집중: 백엔드 개발자는 서버, DB, 인프라 등의 기술에 집중해야 하며, 웹 프론트엔드 기술에 깊이 파고들지 않아도 된다.

자바 웹 기술 역사

1. 과거 기술

서블릿 (1997)

  • 자바 웹 기술의 첫 번째로 등장한 기술이다. 서버 측에서 HTTP 요청을 처리하고 HTML을 생성할 수 있지만, HTML 생성이 어렵다.

JSP (1999)

  • HTML 생성을 보다 편리하게 할 수 있는 기술이다. 그러나 JSP는 비즈니스 로직과 화면 로직을 동시에 처리해야 해서 역할이 혼재된다.

서블릿 + JSP 조합: MVC 패턴 도입

  • 서블릿과 JSP를 결합해 모델(Model), 뷰(View), 컨트롤러(Controller)의 역할을 분리하는 MVC 패턴을 사용한다. 서블릿은 컨트롤러, JSP는 뷰로 활용하여 개발의 편의성과 유지보수성을 향상시킨다.

MVC 프레임워크 춘추 전국 시대 (2000년 초 ~ 2010년 초)

  • 다양한 MVC 프레임워크들이 등장하며 복잡한 웹 애플리케이션 개발을 자동화하고 편리하게 만들기 시작한다. 주요 프레임워크로는 Struts, WebWork, Spring MVC(초기 버전)이 있다.

2. 현재 사용 기술

애노테이션 기반 스프링 MVC의 등장

  • 애노테이션(@Controller 등)을 사용한 스프링 MVC가 등장하면서 개발자들이 더 쉽고 직관적으로 웹 애플리케이션을 개발할 수 있게 된다. 이로 인해 MVC 프레임워크의 춘추 전국 시대가 끝난다.

스프링 부트의 등장

  • 스프링 부트(Spring Boot)는 내장 WAS(웹 애플리케이션 서버)를 포함한 빌드 결과물을 제공하여 배포가 매우 단순화된다.
  • 과거에는 WAS를 설치하고 War 파일을 배포하는 방식이 일반적이었지만, 스프링 부트는 Jar 파일에 WAS를 포함시켜 배포한다.
  • 이를 통해 배포와 서버 설정 관리의 복잡도가 크게 줄어든다.

3. 최신 기술 - 스프링 웹 기술의 분화

Web Servlet - Spring MVC

  • 기존의 스프링 MVC 기반으로 웹 애플리케이션을 개발한다.

Web Reactive - Spring WebFlux

  • 비동기(Asynchronous)와 넌블러킹(Non-blocking) 방식의 웹 애플리케이션을 지원한다.
  • 특징
    • 최소한의 쓰레드를 사용해 최대 성능을 낼 수 있으며, 쓰레드 컨텍스트 스위칭 비용을 절감한다.
    • 함수형 스타일로 코드를 작성할 수 있어 동시 처리에 효율적이다.
    • 서블릿 기술을 사용하지 않는다.
  • 그러나
    • WebFlux는 기술적 난이도가 높으며, 실무에서 아직 널리 사용되지 않는다(1% 이하).
    • RDBMS(관계형 데이터베이스)와의 지원이 부족하다.
    • 기존 MVC 모델도 충분히 빠르기 때문에 WebFlux는 아직은 특별한 경우에만 사용된다.

자바 뷰 템플릿의 역사

HTML 생성을 위한 뷰 템플릿 기술들이 발전해 왔다.

JSP

  • 가장 기본적인 뷰 템플릿 기술이다. 속도가 느리고 기능이 부족해 다른 템플릿 엔진들이 등장하게 된다.

프리마커(Freemarker)와 벨로시티(Velocity)

  • JSP의 속도 문제를 해결하고, 다양한 기능을 제공하는 뷰 템플릿 엔진이다. 퍼포먼스가 뛰어나며, 복잡한 뷰 생성에 사용된다.

타임리프(Thymeleaf)

  • HTML 문서의 구조를 그대로 유지하면서도 뷰 템플릿을 적용할 수 있는 내추럴 템플릿 기능을 제공한다. 스프링 MVC와의 강력한 통합 기능을 지원하여 현재 가장 많이 사용된다. 단, 성능 측면에서는 Freemarker나 Velocity가 더 빠르다는 평가가 있다.

'Spring MVC' 카테고리의 다른 글

[MVC] 서블릿(4)  (0) 2024.10.15
[MVC] 서블릿(3)  (0) 2024.10.14
[MVC] 서블릿(2)  (1) 2024.10.11
[MVC] 서블릿(1)  (3) 2024.10.10