2. 서블릿

2025. 4. 21. 10:32Spring Framework/스프링 MVC

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 {
        System.out.println("HelloServlet.service");
        System.out.println("request = " + request);
        System.out.println("response = " + response);
		
        //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 데이터는 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 문자로 변경할 수 있다.