월루를 꿈꾸는 대학생

[MVC-1] 서블릿 본문

Programing/Spring Boot

[MVC-1] 서블릿

하즈시 2023. 7. 25. 22:55
728x90

서블릿

  • http 요청을 어떻게 받고 http응답을 어떻게 내려줄 것인지 개발자가 편하게 해주도록 서버에서 구현해준 것

프로젝트 생성

gradle

lomnbok


hello 서블릿

스프링 부트 서블릿 환경 구성
@SpringBootApplication  
@ServletComponentScan // 스프링이 내 패키지 하위부터 싹 훝어서 서블릿 자동등록 해줌 public class ServletApplication {  

   public static void main(String[] args) {  
      SpringApplication.run(ServletApplication.class, args);  
   }  

}

/hello http요청이 오면 request, response 객체를 was에서 만들어주고 이걸 서블릿에 전달해줌
서블릿은 service 메서드를 호출함

@WebServlet(name="helloServlet", urlPatterns = "/hello")  
public class HelloServlet extends HttpServlet {  

    // 서블릿이 호출되면 service메서드가 호출 됨  
    @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);  

        // 서블릿은 쿼리 파라미터를 쉽게 읽을 수 있음 ex localhost:8080/hello?username=choi        String username = request.getParameter("username");  
        System.out.println("username = " + username);  

        response.setContentType("text/plain");  
        response.setCharacterEncoding("utf-8");  
        response.getWriter().write("hello "+ username);  
    }  
}

서버에서 받은 로그

HelloServlet.service
request = org.apache.catalina.connector.RequestFacade@5fd594b3
response = org.apache.catalina.connector.ResponseFacade@36b54a34
username = choi

웹에서 확인

  • 보면 response 객체에 설정한 것 처럼 타입이 잘 설정되어 있고 웹 화면도 내가 원한 출력 결과물이 있는 거 확인 가능
  • 해당 클래스가 호출되는 경우 보니까 service가 자동 호출 되었음 왜 ?

    참고
    https://devraphy.tistory.com/495>
    또 보니까 @WebServlet이 발전된게 getmapping, postmapping 인듯

서블릿 작동원리

  1. 스프링부트 실행시 내장된 톰켓 서버 was 실행
  2. 톰캣 실행되면 ServletApplication위에 붙었던 애노테이션 @ServletComponentScan이 동작
  3. @ServletComponentScan이 Servlet Container에 스캔한 서블릿 객체들을 등록
  4. client에서 request가 들어오면 was에서 HttpServeletRequest객체를 만들어서 서블릿 객체에게 넘겨줌
  5. 서블릿 객체는 오버라이딩 되어 있는 service메서드를 호출하고 로직 수행
  6. 로직 수행 결과를 HttpServletResponse에 담아서 클라이언트에 전달해줌!!!
http 요청 메세지 로그로 확인
  • 요청 메세지들 전부 로그 확인이 가능해짐
  • application.properties에다가 설정 추가

    logging.level.org.apache.coyote.http11=debug

  • 다만 이거는 그냥 확인용 운영서버에 이만큼 남기면 성능 저하
서블릿 컨테이너 동작 방식 설명

내장 톰캣 서버 생성

  • 스프링부특가 기동하면 was가 실행되고 Was는 스캔된 서블릿 객체들을 서블릿 컨테이너에 등록한다

  • 다음과 같이 http 요청이 오는 경우 해당 요청을 서블릿 컨테이너에 등록된 서블릿 객체에게 전달해줌

부가적인 정보들은 http 응답할 때 was가 알아서 만들어서 생성해줌

webapp폴더 만글고 ==index.html== 파일 넣으니까 알아서 welcome page로 등록되나 보네 ;;


HttpServletReqeust - 개요

서블릿이 없으면 http 요청 메세지를 파싱하고 써야하는데
서블릿이 해당 작업을 대신 해주고 그 결과를 HttpServeletReqeust객체에 담아서 제공해줌

http 요청 메세지

POST /save HTTP/1.1 ##START LINE

Host: localhost:8080. ##HEADER
Content-Type: application/x-www-form-urlencoded ##HEADER
username=kim&age=20 ##BODY

START LINE
HTTP 메소드
URL
쿼리 스트링
스키마, 프로토콜

헤더
헤더 조회

바디
form 파라미터 형식 조회
message body 데이터 직접 조회

임시 저장소 기능
  • http 요청 시작-끝 동안 유지되는 임시 저장소
    • 저장 : request.setAttribute(name, value)
    • 조회 : request.getAttribute(name)
  • 생각보다 많이 쓰게 됨

중요

결국 해당 객체들을 사용하는 것은 http 요청 , 응답 메세지를 편리하게 사용하게 해주는 것임
깊이 있게 이해하려면 http 스펙이 제공하는 요청 , 응답 메세지를 이해해야함


HttpServletRequest - 기본 사용법

딱히 특별한 거 없음


HTTP요청 데이터 - 개요

  1. GET - 쿼리 파라미터
    1. ?username=hello&age=20
    2. 메세지 바디없이 url에다가 데이터 포함해서 전달
    3. ex 검색 , 필터 ,페이징 등
  2. POST -HTML Form
    1. content-type: application/x-www-form-urlencoded
    2. 메시지 바디에 쿼리 파리미터 형식으로 전달 username=hello&age=20
    3. 예) 회원 가입, 상품 주문, HTML Form 사용
  3. HTTP message body에 데이터 직접 담아서 전달
     1. API에 주로 사용 JSON
     2. POST , PUT ,PATCH

HTTP요청 데이터 - GET 쿼리 파라미터

  • url에다가 데이터 넣어서 전송
  • url 뒤에 ? 붙여서 작성하고 구분은 &으로 한다
@WebServlet(name ="requestParamServlet", urlPatterns = "/request-param")  
public class RequestParamServlet extends HttpServlet {  
    @Override  
    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {  
       // 전체 리퀘스트 파라미터 끄내기  
        System.out.println("RequestParamServlet.service");  
        System.out.println("전체 파라미터 조회 - start");  
        request.getParameterNames().asIterator()  
                        .forEachRemaining(paramName -> System.out.println(paramName + " = " + request.getParameter(paramName)));  
                                                                        // 키                  //값  
        System.out.println("전체 파라미터 조회 - end");  
        System.out.println();  

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

        //http://localhost:8080/request-param?username=hello&age=20&username=hello2  
        System.out.println("이름이 같은 복수 파라미터 조회");  
        String[] usernames = request.getParameterValues("username");  
        for (String name : usernames){  
            System.out.println("usernmae = "+ name);  
        }  

        response.getWriter().write("ok");  

    }  
}

출력

RequestParamServlet.service
전체 파라미터 조회 - start
username = hello
age = 20
전체 파라미터 조회 - end
단일 파라미터 조회
age = 20
username = hello
이름이 같은 복수 파라미터 조회
usernmae = hello
usernmae = hello2

복수 파라미터에 값이 세팅되어 있는 경우
?username=hello&age=20&username=hello2
request.getParametersValues() 사용해서 배열로 빼내면 됨


HTTP 요청 데이터 POST HTML Form

  • 서버로 데이터를 전송하는 회원가입 혹은 상품 주문에서 사용함
  • content-type: application/x-www-form-urlencoded
  • 메시지 바디에 쿼리 파리미터 형식으로 데이터를 전달 ex username=hello&age=20

application/x-www-form-urlencoded 형식은 앞에 GET방식이랑 거의 동일하니까 메서드도 같은 거 써서 정보를 조회 가능
클라이언트 입장에서는 차이가 있지만 서버 입장에서는 받는 형식이 같아서 ㄱㅊ


HTTP 요청 데이터 - API 메세지 바디 - 단순 텍스트

  • HTTP message body에 데이터 직접 담아서 보냄 JSON
  • POST, PUT, PATCH
  • HTTP 메세지 바디의 데이터를 InputStream 사용해서 읽기 가능
@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();  
        // 바이트 스트림을 스트링으롤 utf-8인코딩  
        String messageBody = StreamUtils.copyToString(inputStream,  
                StandardCharsets.UTF_8);  
        System.out.println("messageBody = " + messageBody);  
        response.getWriter().write("ok");  
    }  
}

request 온 거를 inputStream으로 받은 다음 그걸 byte코드로 변환
변환된 byte 코드를 읽을 수 있는 문자열로 utf-8로 인코딩해서 출력


HTTP 요청 데이터 - API 메세지바디 JSON

  • content-type : application/json

보통 json을 그냥 받아서 쓰는게 아니라 받은 데이터를 토대로 객체를 만들어서 코드 내에서 사용

@WebServlet(name = "requestBodyJsonServlet", urlPatterns = "/request-body-json")  
public class RequestBodyJsonServlet extends HttpServlet {  
    // 스프링이 기본으로 제공해주는 json 라이브러리 Jackson    

    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);  

        // json 문자열 값을 읽은 후에 helloData에 맞게 매핑해주나보네  
        HelloData helloData = objectMapper.readValue(messageBody,HelloData.class);  
        System.out.println("helloData.username = " + helloData.getUsername());  
        System.out.println("helloData.age = " + helloData.getAge());  
        response.getWriter().write("ok");  
    }  
}

JSON 데이터 읽은 다음 데이터 클래스에 맞게 읽기 위해서 스프링에서 제공해주는 Jakcson라이브러리의 ObjectMapper를 사용해서 클래스의 멤버변수를 뽑아서 읽고 사용 가능


HttpServletResponse - 기본 사용법

역할
  1. HTTP 응답 메세지 생성
    1. HTTP 응답 코드 지정
    2. 헤더 생성
    3. 바디 생성

그냥 200 넣어도 되는데 이미 자바 코드에서 만들어둔 거 있으니까 가시성도 있고하니 이거 쓰자

@WebServlet(name = "responseHeaderServlet", urlPatterns = "/response-header")  
public class ResponseHeaderServlet extends HttpServlet {  
    @Override  
    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {  
        //200 써도 되는데 의미있는 코드로 쓰자  
        //[status-line]  
        response.setStatus(HttpServletResponse.SC_OK);  

        //[response-headers]  
        response.setHeader("Content-Type", "text/plain;charset=utf-8");  
        // cache 무효화  
        response.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");  
        response.setHeader("Pragma", "no-cache");  
        // 그냥 내가 원하는 임의 헤더 추가 가능  
        response.setHeader("my-header","hello");  
        // 위에서 처럼 그냥 setHeader 메서드로 지정해도 되는데 밑에 편의 메서드 써도 동일하게 설정 가능하다  

        //[Header 편의 메서드]  
        content(response);  
        cookie(response);  
        redirect(response);  

        //[message body]  
        PrintWriter writer = response.getWriter();  
        writer.println("ok");  
    }  

    private void content(HttpServletResponse response) {  
        //Content-Type: text/plain;charset=utf-8  
        //Content-Length: 2        //response.setHeader("Content-Type", "text/plain;charset=utf-8");        response.setContentType("text/plain");  
        response.setCharacterEncoding("utf-8");  
        //response.setContentLength(2); //(생략시 자동 생성)  
    }  

    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");  
        // 302세팅하고 리다이렉트할 페이지 넣어주면 됨  
        response.sendRedirect("/basic/hello-form.html");  
    }  

}

content

  • 타입이랑 인코딩 할 때

    response.setContentType("text/plain");
    response.setCharacterEncoding("utf-8");
  • 쿠키 관련

    Cookie cookie = new Cookie("myCookie", "good");
    cookie.setMaxAge(600); //600초
    response.addCookie(cookie);
  • redirect편의 메서드

    response.sendRedirect("/basic/hello-form.html");

HTTP 응답 데이터 - 단순 텍스트 ,html

  1. 단순 텍스트 응답 -> writer.println("ok");
  2. html 응답
  3. HTTP API - MessageBody Json 응답

html 응답의 경우 그냥 writer 써서 한 줄 한 줄 html 코드 적어준 것

writer.println("<html>");
writer.println("<body>");
writer.println(" <div>안녕?</div>");

HTTP 응답 데이터 - API JSON

  • 응답을 반환할 때는 타입을 application/json 으로 지정
  • 객체에 있는 값들을 json 형식으로 바꿀려면 ObjectMapper 써야함

    objectMapper.writeValueAsString();

출처
https://inf.run/wFfL

728x90