2. 서블릿

2025. 4. 21. 10:32Spring/MVC

[목차]

1. 서블릿
1) 서블릿 컨테이너 동작 방식
2) 서블릿 클래스 기본 코드

2. HTTP 요청/응답 메시지
1) 시작 라인
2) 헤더 
3) 메시지 바디 

3. 클라이언트가 서버로 요청 데이터를 전달하는 방식 
1) URL에 쿼리 파라미터 붙이기
2) HTTP 요청 메시지의 메시지 바디에 담기

4. HttpServletRequest 객체 다루기 
1) 요청 시작 라인 조회
2) 헤더 조회 
3) HTTP 요청 데이터 조회

5. HttpServletResponse 객체 다루기 
1) 요청 시작 라인, 헤더 세팅 
2) 메시지 바디 세팅 


1. 서블릿 

1) 서블릿 컨테이너 동작 방식 

웹 브라우저에서 HTTP 요청 메시지가 전달됨

-> 서버에서 HTTP 요청 메시지를 기반으로 request, response 객체 생성 

-> 서블릿 컨테이너에 요청 전달해서 요청 url에 맞는 서블릿 객체 받아서 실행

-> 서블릿에서 response 객체에 데이터 세팅

-> 서버에서 response 객체 정보로 HTTP 응답 메시지 생성

-> 웹 브라우저에 HTTP 응답 메시지 전달 

 

2) 서블릿 클래스 기본 코드 

@WebServlet(name = "helloServlet", urlPatterns = "/hello")
public class HelloServlet extends HttpServlet {
    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //HttpServletRequest로 요청 데이터 조회 
        String username = request.getParameter("username");
        System.out.println("username = " + username);
        
        //HttpServletResponse로 응답 데이터 세팅 
        response.setContentType("text/plain"); //헤더
        response.setCharacterEncoding("utf-8");
        response.getWriter().write("hello " + username); //메시지 바디
    }
}
  • extends HttpServlet 필수  
  • @WebServlet : 서블릿 애노테이션 (자바 표준 애노테이션) 
    • name: 서블릿 이름, urlPatterns: URL 매핑
    • @WebServlet은 이 클래스가 서블릿이라는 걸 서버(톰캣) 에게 알려주는 애노테이션
      그리고 동시에 이 서블릿을 어떤 URL 요청에 연결할지도 같이 설정하는 역할을 한다. 
    • 즉, 서버가 프로젝트를 시작할 때, @WebServlet을 보고
      이 HelloServlet 클래스는 서블릿 클래스이며 /hello 라는 URL 요청이 들어올 때 실행해야 한다고 매핑을 기억  
  • service() 오버라이드 
    • HTTP 요청을 통해 매핑된 URL이 호출되면 서블릿 컨테이너는 service() 메서드를 실행한다.
    • 따라서 해당 메서드를 오버라이딩 하여 내부에 코드를 작성해야 함.
  • HttpServletRequest, HttpServletResponse 객체 
    • 톰캣에서 개발자가 HTTP 요청/응답 메시지를 편리하게 사용할 수 있도록 파싱해놓은 객체이다. 
    • 서블릿의 매개변수로 사용할 수 있다. 

 

2. HTTP 요청/응답 메시지 

1) 시작 라인 (start-line) 

  • 요청 시작 라인, request-line
    HTTP메서드 공백 요청대상(절대경로) 공백 HTTP버전 엔터 
    예: GET /search?q=hello HTTP/1.1  
    • method: HTTP 메서드로 서버가 수행해야 하는 동작 지정, GET/POST 
    • request-target, 요청 대상: /로 시작하는 절대경로[?쿼리]  
  • 응답 시작 라인, status-line
    HTTP버전 공백 HTTP상태코드 공백 이유문구 엔터 
    예: HTTP/1.1 200 OK  
    • status-code: HTTP 상태코드로 요청 성공, 실패를 나타냄 
      200-성공, 400-클라이언트 요청 오류, 500-서버 내부 오류
    • reason-phrase, 이유 문구: 상태코드에 대한 짧은 설명 글 (보는 사람이 이해할 수 있게)

2) 헤더 (header)
필드네임 ":" OWS(띄어쓰기 허용) 필드값 OWS

  • field-name은 대소문자 구분X, field value는 당연히 구분O
  • http 전송에 필요한 모든 부가 정보가 다 들어가 있음
  • 표준 헤더 필더가 너무 많음

3) 메시지 바디 (message body)

  • 실제로 전송할 데이터
  • HTML 문서, 이미지, 영상, JSON 등등 byte로 표현할 수 있는 모든 데이터를 전송할 수 있다. 

 

3. 클라이언트가 서버로 요청 데이터를 전달하는 방식 

1. URL에 쿼리 파라미터 붙이기 
2. 메시지 바디에 담기
    2-1. HTTP Form 형식
    2-2. JSON 형식 (HTTP API 방식) 
    2-3. 파일, 이미지 등등

1) URL에 쿼리 파라미터 붙이기

  • HTTP 요청 메시지의 요청 시작 라인에 있는 요청 대상(절대 경로) 뒤에 쿼리 파라미터가 붙어 서버로 전달된다.
  • 쿼리 파라미터 형식: /url?key=value&key2=value2
  • 검색필터페이징 등에서 많이 사용하는 방식
  • 주로 GET, DELETE 요청에 필요한 데이터를 전달할 때 이렇게 많이 사용한다. 
    POST, PUT에서도 이론상 URL에 쿼리 파라미터를 붙일 수 있지만 잘 안 쓰고, 보통 GET, DELETE에서만 사용

2) HTTP 요청 메시지의 메시지 바디에 담기 

2-0) 공통적인 특징 

  • 데이터를 직접 메시지 바디에 담아서 요청하는 방식
  • 메시지 바디에 데이터를 담을 때는 반드시 헤더의 Content-Type을 명시해야 한다. 
    • Content-Type은 메시지 바디에 들어간 데이터의 형식을 설명하는 헤더
    • 데이터를 어떤 형식으로 담았는지 알려줘야 서버가 바디를 읽을 수 있기 때문에 반드시 명시해야 한다.
    • URL 쿼리 파라미터 형식으로 클라이언트에서 서버로 데이터를 전달할 때는 HTTP 메시지 바디를 사용하지 않기 때문에 content-type이 없음
    • 폼 데이터: application/x-www-form-urlencoded
      JSON 데이터: application/json
      파일 전송: multipart/form-data
  • 주로 POST, PUT 요청에 필요한 데이터를 전송할 때 많이 사용하는 방식이다.  

2-1) HTML Form 형식

  • 사용자가 HTML의 Form에 데이터를 입력하여 제출(submit)하는 경우 메시지 바디에 쿼리 파라미터 형식으로 데이터가 담긴다. 
    예) username=hello&age=20
  • 전통적인 웹 폼 제출으로 회원 가입상품 주문 등의 경우에서 많이 사용됨
  • Content-Type으로 application/x-www-form-urlencoded 명

2-2) JSON 형식 (HTTP API) 

  • 시스템 간에 HTML 화면이 아닌 JSON 데이터를 주고받는 통신을 통상적으로 HTTP API라고 부른다.
  • 프론트엔드 ↔ 백엔드, 서버 ↔ 서버 등 
  • JSON, XML, TEXT 등의 데이터 형식이 있으나 요즘은 대부분 JSON을 사용한다. 

2-3) 파일, 이미지 등

  • 그 외 파일, 이미지 등 바이너리 데이터를 전송할 때는 multipart/form-data를 사용하여 메시지 바디를 여러 파트로 나누어 전송한다.
  • 대용량 파일은 application/octet-stream 형식으로 메시지 바디 전체에 담아 스트리밍 방식으로 전송할 수도 있다.

 

4.  HttpServletResponse 객체 다루기

1) 요청 시작 라인 조회 

@WebServlet(name="requestHeaderServlet",urlPatterns = "/request-header")
public class RequestHeaderServlet extends HttpServlet {
    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) 
    throws ServletException, IOException {
        request.getMethod();	//GET
        request.getProtocol();	//HTTP/1.1
        request.getScheme();	//http
        request.getRequestURL(); //http://localhost:8080/request-header
        request.getRequestURI(); //request-header
        request.getQueryString(); //username=hi
        request.isSecure();	//https 사용 유무, false
    }
}

2) 헤더 조회 

@WebServlet(name="requestHeaderServlet",urlPatterns = "/request-header")
public class RequestHeaderServlet extends HttpServlet {
    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) 
    throws ServletException, IOException {
    	//Header 전체 조회 
        request.getHeaderNames().asIterator()
                .forEachRemaining(headerName 
                -> System.out.println(headerName + ": " + request.getHeader(headerName)));
                
        //Host 편의 조회
        request.getServerName(); //Host 헤더
        request.getServerPort(); //Host 헤더

        //Accept-Language 편의 조회
        request.getLocales().asIterator()
                .forEachRemaining(locale -> System.out.println("locale = " + locale));
        request.getLocale();
        
        //Cookie 편의 조회
        if (request.getCookies() != null) {
            for (Cookie cookie : request.getCookies()) {
                System.out.println(cookie.getName() + ": " + cookie.getValue());
            }
        }
        
        //Content 편의 조회
        request.getContentType();
        request.getContentLength();
        request.getCharacterEncoding();
        
        //Remote 정보 
        request.getRemoteHost();
        request.getRemoteAddr();
        request.getRemotePort();

        //Local 정보
        request.getLocalName();
        request.getLocalAddr();
        request.getLocalPort();
    }
}

3) HTTP 요청 데이터 조회 

3-1) 쿼리 파라미터, HTML Form 형식 

@WebServlet(name="requestParamServlet",urlPatterns = "/request-param")
public class RequestParamServlet extends HttpServlet {
    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //전체 파라미터 조회
        request.getParameterNames().asIterator()
                        .forEachRemaining(paramName -> System.out.println(paramName + " = " + request.getParameter(paramName)));

        //단일 파라미터 조회
        String username = request.getParameter("username");
        String age = request.getParameter("age");
        System.out.println("username = " + username);
        System.out.println("age = " + age);

        //이름이 같은 복수 파라미터 조회 (흔하지는 않음)
        //그냥 getParameter 사용하면 첫번째 파라미터만 조회됨
        //해당 이름을 갖는 파라미터 전체 조회하기 위해서는 getParameterValues 사용해야함
        String[] usernames = request.getParameterValues("username");
        for (String name : usernames) {
            System.out.println("username: "+ name);
        }
    }
}
  • 쿼리 파라미터와 HTML Form의 데이터 형식: username=hello&age=20
    둘은 데이터 형식이 같기 때문에 조회 메서드를 request.getParameter()로 동일하게 사용한다. 
  • 클라이언트(웹 브라우저) 입장에서는 두 방식에 차이가 있지만, 서버 입장에서는 두 방식 보며 전달받는 데이터의 형식이 동일하므로, request.getParameter() 로 편리하게 구분없이 조회할 수 있다.
  • 정리하면 request.getParameter()는 GET URL 쿼리 파라미터 형식도 지원하고, POST HTML Form 형식도 둘 다 지원한다.
  • 단, HTML Form 데이터는 요청 시 HTTP 방식을 POST으로, content-type을 x-www-form-urlencoded로 설정해야 한다. 

 

3-2) 단순 텍스트, JSON (HTTP API) 

@WebServlet(name="requestBodyStringServlet", urlPatterns = "/request-body-string")
public class RequestBodyStringServlet extends HttpServlet {
    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        ServletInputStream inputStream = request.getInputStream();
        String messageBody = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8);
        System.out.println("messageBody = " + messageBody);
    }
}
  • 단순 텍스트 형식 요청  
    • POST http://localhost:8080/request-body-string content-type: text/plain
    • message body: hello
    • 결과: messageBody = hello
  • inputStream은 byte 코드를 반환한다. byte 코드를 우리가 읽을 수 있는 문자(String)로 보려면 문자표 (Charset)를 지정해주어야 한다. 여기서는 UTF_8 Charset을 지정해주었다.
  • 참고로HTML form 데이터도 메시지 바디를 통해 전송되므로 직접 읽을 수 있다. 하지만 편리한 파리미터 조회 기능request.getParameter()을 이미 제공하기 때문에 파라미터 조회 기능을 사용하면 된다.
@WebServlet(name="requestBodyJsonServlet", urlPatterns = "/request-body-json")
public class RequestBodyJsonServlet extends HttpServlet {
    private ObjectMapper objectMapper = new ObjectMapper();

    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        ServletInputStream inputStream = request.getInputStream();
        String messageBody = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8);
        System.out.println("messageBody = " + messageBody);

        HelloData helloData = objectMapper.readValue(messageBody, HelloData.class);
        System.out.println("helloData.getUsername() = " + helloData.getUsername());
        System.out.println("helloData.getAge() = " + helloData.getAge());
    }
}
    • JSON 형식 요청 
      • POST http://localhost:8080/request-body-json
      • content-type: application/json
      • message body: {"username": "hello", "age": 20}
      • 결과: messageBody = {"username": "hello", "age": 20}
        data.username=hello

        data.age=20
    • JSON 결과를 파싱해서 사용할 수 있는 자바 객체로 변환하려면 Jackson, Gson 같은 JSON 변환 라이브러리를 추가해서 사용해야 한다. 스프링 부트로 Spring MVC를 선택하면 기본으로 Jackson 라이브러리인 ObjectMapper를 함께 제공한다.

 

5. HttpServletResponse 객체 다루기 

5-1. 응답 시작 라인, 헤더 세팅 

@WebServlet(name="responseHeaderServlet", urlPatterns = "/response-header")
public class ResponseHeaderServlet extends HttpServlet {
    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //status-line
        response.setStatus(HttpServletResponse.SC_OK);

        //response-header
        response.setHeader("Content-Type", "text/plain;charset=utf-8");
        //content 편의 메서드 제공됨 
        response.setContentType("text/plain");
        response.setCharacterEncoding("utf-8");
        //response.setContentLength(2); //(생략시 자동 생성)
        
        response.setHeader("Cache-Control", "no-cache, no-store, mustrevalidate");
        response.setHeader("Pragma", "no-cache");
        response.setHeader("my-header","hello");

        //기타 Header 편의 메서드
        cookie(response);
        redirect(response);
    }

    private void cookie(HttpServletResponse response) {
        //Set-Cookie: myCookie=good; Max-Age=600;
        
        //response.setHeader("Set-Cookie", "myCookie=good; Max-Age=600");
        Cookie cookie = new Cookie("myCookie", "good");
        cookie.setMaxAge(600); //600초
        response.addCookie(cookie);
    }
    private void redirect(HttpServletResponse response) throws IOException {
        //Status Code 302
        //Location: /basic/hello-form.html

        //response.setStatus(HttpServletResponse.SC_FOUND); //302
        //response.setHeader("Location", "/basic/hello-form.html");
        response.sendRedirect("/basic/hello-form.html");
    }
}

 

5-2. 메시지 바디 세팅 

[HTTP 응답 메시지의 메시지 바디에 들어가는 데이터 종류]
1. 단순 텍스트
2. HTML 코드  
3. JSON (HTTP API 방식)

 

1) 단순 텍스트

//message body에 단순 텍스트 데이터 입력 
PrintWriter writer = response.getWriter();
writer.print("ok");

 

2) HTML

@WebServlet(name = "responseHtmlServlet", urlPatterns = "/response-html")
public class ResponseHtmlServlet extends HttpServlet {
    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //Content-Type: text/html;charset=utf-8
        response.setContentType("text/html");
        response.setCharacterEncoding("utf-8");
        
        PrintWriter writer = response.getWriter();
        writer.println("<html>"); 
        writer.println("<body>"); 
        writer.println(" <div>안녕?</div>"); 
        writer.println("</body>"); 
        writer.println("</html>");
    } 
}
  • HTTP 응답으로 HTML을 반환할 때는 content-type을 text/html로 지정해야 한다.

3) API JSON

@WebServlet(name = "responseJsonServlet", urlPatterns = "/response-json")
public class ResponseJsonServlet extends HttpServlet {
    private ObjectMapper objectMapper = new ObjectMapper();
    
    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //Content-Type: application/json
        response.setHeader("content-type", "application/json");
        response.setCharacterEncoding("utf-8");

        HelloData data = new HelloData();
        data.setUsername("kim");
        data.setAge(20);

        //{"username":"kim","age":20}
        String result = objectMapper.writeValueAsString(data);
        response.getWriter().write(result);
    } 
}
  • HTTP 응답으로 JSON을 반환할 때는 content-type을 application/json 로 지정해야 한다.
  • Jackson 라이브러리가 제공하는 objectMapper.writeValueAsString()를 사용하면 객체를 JSON 문자로 변경할 수 있다.