[Spring] 웹 서버와 웹 애플리케이션 서버 그리고 Servlet

벼랑 끝 코딩·2025년 3월 26일

Spring

목록 보기
7/16

지금까지는 Spring의 스프링 컨테이너에 대해 배우면서
클래스를 어떻게 설계하고, 각 클래스가 어떻게 의존 관계를 가지는지에 대해 학습했다.
지금까지는 각각의 기능들이 어떻게 상호작용 하는지에 대해 알아봤다.

이제는 이러한 기능들이 모여져 만든 서비스가
어떻게 Client의 HTTP Request에 Response하는지, 웹에서의 동작을 학습해야 한다.
본격적으로 내가 만든 기능을 결합체인 웹 애플리케이션 관점에서 살펴보자.

개발 순서와 포지션

개발 포지션은 크게 웹 디자이너, 웹 퍼블리셔, 프론트엔드 개발자, 백엔드로 나눌 수 있다.
먼저 애플리케이션의 기능이 정의되면 웹 디자이너가 화면 이미지를 디자인한다.
그 후 웹 퍼블리셔는 디자인한 화면의 이미지를 실제로 구현한다.

화면 디자인까지 완성되면 백엔드 개발자
디자인한 화면이 정상 동작하도록 화면과 서비스의 기능을 구현한다.
웹 퍼블리셔가 아닌 동적인 화면을 개발하는 프론트엔드 개발자가 있는 경우에는
백엔드 개발자는 화면을 구현하는 대신 화면 구현에 필요한 데이터를 전송해주면 된다.

이번 시간에는 백엔드 개발자가 해야하는 일인
화면을 구현하거나 구현을 위한 데이터를 전송하기 위해
어떻게 Spring을 사용해야 하는지에 대해 알아볼 것이다.

Server

사용자가 요청하면 서버는 백엔드가 설계한 기능을 바탕으로 적절하게 응답해야 한다.
서버는 크게 웹 서버와 웹 애플리케이션 서버(WAS)로 나눌 수 있다.

웹 서버는 정적인 리소스를 제공하며, 요청 시 데이터를 전달하면 끝이다.
하지만 웹 애플리케이션 서버는 정해진 데이터를 전달하는 것이 아닌,
사용자가 전달한 데이터를 동적으로 처리하여 적절한 응답을 내보낸다.

Client Request → Web Server(Front-End Server) → Web Application Server(Back-End Server)

보통 정적 리소스를 처리하는 프론트엔드 서버인 웹 서버가 먼저 프록시를 이용하여
사용자의 요청 처리를 시도하고, 동적으로 처리해야 하는 경우
백엔드 서버인 웹 애플리케이션 서버에 요청을 전달하여 처리 후 응답한다.

다양한 웹 애플리케이션 서버가 존재하는데, Spring은 Tomcat이라는 WAS를 내장하고 있다.

백엔드 개발자는 Spring을 사용하여 웹 애플리케이션을 제작할 때,
Tomcat이 내장되어 있기 때문에 서버의 설정과 배포를 자동화하고
비즈니스 로직을 설계하는 데에 집중할 수 있다.

Client Side Rendering(CSR)

사용자가 정적 리소스를 받아온 후, WAS로부터 데이터만을 전달받아(HTTP API)
데이터를 기반으로 동적으로 화면을 구성하는 것을 의미한다.
여기서 정적 리소스는 JavaScript와 같은 언어가 사용되며,
WAS가 HTML이 아닌 데이터만 제공 후 언어를 사용해
클라이언트가 동적으로 화면을 구성하는 것이 핵심이다.

JavaScript와 같은 정적 리소스를 초기에 받아오는 데에 오랜 시간이 걸릴 수 있으나
이후 빠르게 동작하여 우수한 사용자 경험을 제공한다.
프론트엔드 개발자의 업무에 해당하는 부분이 바로 이것이다.

Server Side Rendering(SSR)

사용자가 요청하면 서버가 전체 HTML을 직접 렌더링하는 것을 의미한다.
클라이언트는 별도로 화면을 재구성하지 않고,
서버가 제공해주는 파일을 바로 받기 때문에 빠르게 렌더링할 수 있다.

하지만 서버가 모든 HTML파일을 생성해야하는 만큼 서버 부담이 크다.
퍼블리셔 코드를 토대로 화면과 기능을 구현하는 백엔드의 업무가 바로 이것이다.


이번에는 서버가 HTML을 전달하는 SSR에 대해 주로 알아볼 예정이다.

Web Application Server(WAS)

결론적으로 백엔드 개발자는 사용자의 요청이 들어오면 적절하게 응답할 수 있도록
서버를 설정하고 비즈니스 로직을 설계하는 것이 주요 임무이다.
Spring은 Tomcat이라는 서버를 내장하고 있어 비즈니스 로직만을 구현하면 된다.
그렇다면 어떻게 비즈니스 로직을 구현하면 되는걸까?

Servlet Container

Spring을 실행하면 스프링 컨테이너가 생성되고
@Component 애노테이션으로 선언한 객체가
@ComponentScan을 통해 Bean으로 등록된다고 했다.
서블릿 컨테이너도 이와 유사한 과정을 거친다.

@WebServlet(name = homeServlet, urlPatterns = "/home")
class HomeServlet extends HttpServlet {
	
    @Override
    protected void service(HttpServletRequest request, 
    		HttpServletResponse response) throws ServletException, IOException {
            
            // 메서드 바디
    }
}

@ServletComponentScan
@SpringBootApplication
class Application {
	
    // 코드
}

서블릿 컨테이너는 서블릿을 관리하는 저장소이다.
Spring에서 main() 메서드를 실행하면 WAS인 Tomcat 서버가 생성되는데,
Tomcat은 Servlet Container라는 것을 생성한다.
@WebServlet 애노테이션으로 선언한 객체는
@ServletComponentScan을 통해 이 서블릿 컨테이너에 등록된다.


이러한 과정이 수행되어 웹 애플리케이션 서버를 서블릿 컨테이너라 부르기도 한다. 서블릿이라는 것이 무엇이길래 이런 과정이 수행되는걸까?

Servlet

Servlet이란 Client가 URL을 입력하여 Request가 발생했을 때,
적절하게 Response를 수행하기 위한 객체를 의미한다.
즉, 서블릿 컨테이너란 URL마다 대응하는 응답 저장소라는 것이다!

웹 서버는 사용자의 요청에 대해 정적 리소스만을 전달하면 되지만,
웹 애플리케이션 서버는 요청에 대해 Servlet으로 반응하여
사용자가 전달한 데이터를 동적으로 활용해 HTML이나 HTTP API등으로 응답할 수 있다.

Spring Servlet

Spring은 Servlet을 싱글톤으로 관리하고 멀티스레드 환경을 지원한다.
또한 사용자의 요청을 HttpServletRequest 객체로 파싱 후 제공하여
URL, 헤더 값 등의 정보를 추출하여 사용할 수 있으며,
HttpServletResponse 객체를 제공하여 HTTP 응답 코드, 헤더, message body,
쿠키와 redirect 같은 요소를 편리하게 생성하여 응답할 수 있다.
Servlet Container 종료 시 함께 종료된다.

@WebServlet(name = homeServlet, urlPatterns = "/home")
class HomeServlet extends HttpServlet {
	
    @Override  // ** HttpServletRequest, HttpSevletResponse 제공 **
    protected void service(HttpServletRequest request, 
    		HttpServletResponse response) throws ServletException, IOException {
            
            // 메서드 바디
    }
}

HTTP Request 데이터 전송

HTTP Request가 발생하고 Response를 받기까지의 과정은 다음과 같다.

먼저 사용자가 URL을 입력한다.
웹 브라우저는 Client HTTP Request Message를 생성해서 URL 서버에 전달한다.
개발자는 Servlet의 HttpServletRequest를 통하여 요청을 확인한다.
비즈니스 로직에 따라 HttpServletResponse에 적절하게 내용을 생성하고,
웹 브라우저에게 HTTP Response Message를 응답한다.

핵심은 Client의 HTTP Request Message의 내용을 확인하는 것인데,
사용자의 요청에 따라 동적으로 응답을 생성하기 위해서는
그 중에서도 사용자가 전달하는 데이터를 확인하는 것이 중요하다.

사용자의 데이터는 어떤 방식으로 전달되는걸까?


Spring에서 HTTP Request Message 로그로 확인하기

Spring에서 HTTP Request Message를 로그로 확인하고 싶은 경우,
application.properties 파일에 다음 내용을 작성하자.

logging.level.org.apache.coyote.http11=trace (SpringBoot 3.2 미만인 경우 debug)

GET + query

사용자가 HTTP Request Message에 데이터를 전송하는 방법 중 하나는
HTTP Method 중 GET 방식과 함께 URL의 query에 데이터를 전송하는 것이다.
query의 데이터 형식은 key=value 형식을 따른다.

Servlet의 HttpServletRequest에서 getParamater() 메서드를 통해
query의 값만을 추출하여 Client 전송 데이터를 토대로 동적으로 응답을 생성한다.

POST + HTML Form

두번째 방법은 HTTP Method 중 POST 방식과 함께 HTML Form을 활용하여
message body에 데이터를 전송하는 것이다.
GET + query 방식과 마찬가지로 데이터 형식은 key=value 형식을 따른다.
형식이 동일하기 때문에 HTTP Method에 따라 추출하는 위치만 구분하면 되므로
동일하게 HttpServletRequest의 getParameter() 메서드로 추출 가능하다.

HTML Form을 활용하여 데이터를 전송하기 때문에,
헤더에는 Content-type: application/x-www-form-urlencoded를 포함해야 한다.

POST + HTTP API(JSON)

세번째 방법은 HTTP Method 중 POST 방식과 함께
정해진 규약인 HTTP API에 따라 message body에 데이터를 전송하는 방식을 말한다.
대표적인 규약으로는 JSON 형식이 있다.

// ** JSON 형식 **
// ** Content-type: application/json **

{
	"key1": value1,
	"key2": value2
}

특정한 형식의 데이터를 적절하게 읽기 위해서는
데이터를 특정 형태로 변환해주는 도구의 라이브러리가 필요하다.
JSON으로 변환하려는 경우 Jackson 라이브러리를 추가한 후 ObjectMapper를 사용한다.
SpringBoot로 Spring MVC를 선택하면 Jackson 라이브러리가 포함되어 있다.

private ObjectMapper objectMapper = new ObjectMapper();

Model model = objectMapper.readValue(messageBody, Model.class);

ObjectMapper에 JSON 형식으로 작성된 messageBody와
추출하려는 형태의 클래스를 전달하면 JSON 데이터를 자동으로 변환해준다.

HTTP Response 데이터 전송

HTTP Request에서 사용자가 전달한 데이터 전송 방식을 알아봤다면,
Response에는 어떻게 데이터를 전송하면 좋을지 알아보자.

Text

단순 텍스트로 응답하는 방식이다.
HttpServletResponse에서 OutputStream에 String 데이터를 작성한다.

HTML Form

HTML 코드를 전달하는 방식이다.
HTML 코드를 전달 받은 Client는 HTML 페이지를 확인할 수 있다.

헤더에는 Content-type: text/html을 포함하여
Text파일처럼 한줄 한줄 String 데이터를 HTML 형식에 따라 작성한다.

HTTP API(JSON)

정해진 규약인 HTTP API 형식대로 데이터를 전송하는 방식이다.
대표적인 HTTP API로는 JSON 형식이 있다.

헤더에는 Content-type: application/json을 포함해야 한다.
전송한 객체를 JSON 형태의 데이터로 변환할 라이브러리가 필요하다.
HTTP Request와 마찬가지로 Jackson 라이브러리를 사용한다.

class Model {
	
    String name;
    int age;
    
    public Model(String name, int age) {
    	this.name = name;
        this.age = age;
    }
}

public void responseMethod() {
	
    private ObjectMapper objectMapper = new ObjectMapper();

	Model model = new Model("홍길동", 20);
	String response = objectMapper.writeValueAsString(model);
}

writeValueAsString() 메서드에 객체를 전달하면
자동으로 JSON 형태로 데이터를 변환해준다.
변환한 데이터를 OutputStream을 통해 작성하면 응답할 수 있다.

마무리

Client가 URL 입력을 통해 HTTP Request를 생성하고,
서버가 비즈니스 로직을 기반으로 동적으로 데이터를 생성해 응답하는
WAS Response의 구조에 대해 알아봤다.

느꼈겠지만 이 구조에는 불편한 점이 한 두가지가 아니다.
URL마다 Servlet 클래스를 설계해야 하는 것부터,
직접 Response Message를 하나 하나 생성해야 하는 것 등 개선의 여지가 다분하다.
구조를 파악했으면 이제 마법같은 Spring의 자동화를 알아보자.

profile
복습에 대한 비판과 지적을 부탁드립니다

0개의 댓글