• [인프런 김영한 로드맵4]스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술(2)

    2022. 5. 16.

    by. 웰시코더

    반응형

    -서블릿 방식 요청 처리를 학습한다.

    -요청 방식(http get 쿼리파라미터, html form 전송, api 전송 등)에 따른 처리 방법을 학습한다.

    -목차는 강의 순서대로 진행한다.

    -모든 소스는 깃허브에서 관리한다.(https://github.com/coderahn/Spring-Lecture4)


    2.서블릿

     

    스프링 MVC 방식을 살펴보기 전에 프레임워크 없이 서블릿 스타일의 클라이언트-서버 통신 과정을 살펴본다.

     

    1.프로젝트 생성

     

    start.spring.io에서 스프링 부트 프로젝트를 생성하고 인텔리제이 프로젝트 오픈 및 설정을 한다. 그리고 롬복도 설치한다. start.spring.io에서 Dependencies로 lombok을 추가하여 생성하자. 그리고 스프링부트는 Jar로 빌드되지만 JSP사용을 위해 War 선택 후 Generate하여 프로젝트를 생성한다.

     

    이후 인텔리제이의 Welcome to IntelliJ IDEA 창에서 Open을 클릭하여 압축을 푼 프로젝트 경로의 build.gradle을 선택하여 연다.

     

    우측상단 Open클릭 > 스프링 initializer로 생성한 프로젝트 압축경로에서 build.gradle선택 후 ok > 팝업 뜨면 '프로젝트로 열기' 클릭

     

    롬복 설치 후 다음과 같이 Settings > Annotation Processors의 Enable annotation processing을 체크한다.

     

    롬복 어노테이션 사용하기 위한 설정

     

    2.Hello 서블릿

     

    서블릿 컴포넌트 스캔을 위한 설정 어노테이션을 다음과 같이 추가한다.

     

    [ServletApplication.java]

    package hello.servlet;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.boot.web.servlet.ServletComponentScan;
    
    @ServletComponentScan //서블릿 자동 등록
    @SpringBootApplication
    public class ServletApplication {
    
    	public static void main(String[] args) {
    		SpringApplication.run(ServletApplication.class, args);
    	}
    
    }

     

    다음으로 hello.servlet.basic 패키지 밑에 HelloServlet.java을 생성하여 다음과 같이 코드를 작성한다. 

     

    • 클래스에 @WebServlet 선언(urlPattern 설정 가능)
    • HttpServlet 상속 : service 메소드 오버라이드
    • service 메소드 오버라이드 : urlPattern에 맞는 요청이 오면 자동 실행

     

    [HelloServlet.java]

    package hello.servlet.basic;
    
    import javax.servlet.ServletException;
    import javax.servlet.annotation.WebServlet;
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;
    
    @WebServlet(name="helloServlet", urlPatterns = "/hello")
    public class HelloServlet extends HttpServlet {
        
        //서블릿 호출시(urlPattern) service 호출
        @Override
        protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            System.out.println("HelloServlet.service");
            System.out.println("request = " + request); //org.apache.catalina.connector.RequestFacade@7aa3142
            System.out.println("response = " + response); //org.apache.catalina.connector.ResponseFacade@290b21c9
    
            //queryString 조회!
            String username = request.getParameter("username");
            System.out.println("username = " + username);
    
            //header정보에 넣기(response의 content-type)
            response.setContentType("text/plain");
            response.setCharacterEncoding("utf-8");
            
            //http message body에 데이터가 들어감 -> 화면에 hello + username 보여줌
            response.getWriter().write("hello " + username);
        }
    }

     

    3.HttpServletRequest - 개요

     

    HTTP요청 메세지를 직접 파싱하면 불편하기 때문에 서블릿이 제공하는 파싱 기능의 객체(HttpServletRequest)가 있다.
     
    • HTTP 요청메세지
      • 스타트라인 : HTTP메소드, URL, 쿼리스트링, 스키마, 프로토콜
      • 헤더 : 헤더정보(Host, Content-Type..)
      • 바디 : form파라미터 형식 조회, message body 데이터 직접 조회
    • HttpServletRequest는 파싱 기능 외에 부가기능도 있음
      • 임시저장소 기능세션 관리 기능
        • 저장 : request.setAttribute(name, value)
        • 조회 : request.getAttribute(name)
      • 세션 관리 기능
        • request.getSession(create : true)

     

    4.HttpServletRequest - 기본 사용법

     

    Header정보 및 스타트라인 정보 등을 꺼내는 방법을 알아본다.

     

    Http 요청 구성에서 스타트라인은 다음과 같은 메소드들로 확인할 수 있다.

     

    [RequestHeaderServlet.java]

    @WebServlet(name = "requestHeaderServlet", urlPatterns = "/request-header")
    public class RequestHeaderServlet extends HttpServlet {
        @Override
        protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            printStartLine(request);
            printHeaders(request);
            printHeaderUtils(request);
            printEtc(request);
        }
    
        //HTTP - 스타트라인 출력
        private void printStartLine(HttpServletRequest request) {
            System.out.println("--- REQUEST-LINE - start ---");
    
            System.out.println("request.getMethod() = " + request.getMethod()); //GET
            System.out.println("request.getProtocol() = " + request.getProtocol()); //HTTP/1.1
            System.out.println("request.getScheme() = " + request.getScheme()); //http
            // http://localhost:8080/request-header
            System.out.println("request.getRequestURL() = " + request.getRequestURL());
            // /request-test
            System.out.println("request.getRequestURI() = " + request.getRequestURI());
            //username=hi
            System.out.println("request.getQueryString() = " + request.getQueryString());
            System.out.println("request.isSecure() = " + request.isSecure()); //https 사용 유무
    
            System.out.println("--- REQUEST-LINE - end ---");
            System.out.println();
        }
        
        //...
     }

     

    Http Header정보는 다음과 같이 확인할 수 있다.

     

    [RequestHeaderServlet.java]

    @WebServlet(name = "requestHeaderServlet", urlPatterns = "/request-header")
    public class RequestHeaderServlet extends HttpServlet {
        @Override
        protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            printStartLine(request);
            printHeaders(request);
            printHeaderUtils(request);
            printEtc(request);
        }
    
        //HTTP - 스타트라인 출력
        //...
        
        //HTTP - Header 모든 정보
        private void printHeaders(HttpServletRequest request) {
            System.out.println("--- Headers - start ---");
    
            //예전방법
    //        Enumeration<String> headerNames = request.getHeaderNames();
    //        while(headerNames.hasMoreElements()) {
    //            String headerName = headerNames.nextElement();
    //            String value = request.getHeader(headerName);
    //            System.out.println(headerName + ": " + value);
    //        }
    
            //요즘방법
            request.getHeaderNames().asIterator().
                    forEachRemaining(headerName -> System.out.println(headerName + ": " +  request.getHeader(headerName)));
    
            System.out.println("--- Headers - end ---");
            System.out.println();
        }
     }

     

    [RequestHeaderServlet.java]

     

    @WebServlet(name = "requestHeaderServlet", urlPatterns = "/request-header")
    public class RequestHeaderServlet extends HttpServlet {
        @Override
        protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            printStartLine(request);
            printHeaders(request);
            printHeaderUtils(request);
            printEtc(request);
        }
    
        //HTTP - 스타트라인 출력
        //...
    
        //HTTP - Header 모든 정보
        //...
    
        //Header 편리한 조회
        private void printHeaderUtils(HttpServletRequest request) {
            System.out.println("--- Header 편의 조회 start ---");
    
            System.out.println("[Host 편의 조회]");
            System.out.println("request.getServerName() = " + request.getServerName()); //Host 헤더
            System.out.println("request.getServerPort() = " + request.getServerPort()); //Host Port
            System.out.println();
            System.out.println("[Accept-Language 편의 조회]");
            request.getLocales().asIterator().forEachRemaining(locale -> System.out.println("locale = " + locale));
            System.out.println("request.getLocale() = " + request.getLocale()); //우서순위값 높은 거 꺼냄(ko)
            System.out.println();
            System.out.println("[cookie 편의 조회]");
            if (request.getCookies() != null) {
                for (Cookie cookie : request.getCookies()) {
                    System.out.println(cookie.getName() + ": " + cookie.getValue());
                }
            }
            System.out.println();
            System.out.println("[Content 편의 조회]");
            System.out.println("request.getContentType() = " + request.getContentType()); //get방식 조회시 content를 거의 안 보내기 때문에 null 나올 것
            System.out.println("request.getContentLength() = " + request.getContentLength());
            System.out.println("request.getCharacterEncoding() = " + request.getCharacterEncoding());
            System.out.println("--- Header 편의 조회 end ---");
            System.out.println();
        }
    }

     

     

    기타 서버 관련 정보도 확인가능하다.

     

    [RequestHeaderServlet.java]

    @WebServlet(name = "requestHeaderServlet", urlPatterns = "/request-header")
    public class RequestHeaderServlet extends HttpServlet {
        @Override
        protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            printStartLine(request);
            printHeaders(request);
            printHeaderUtils(request);
            printEtc(request);
        }
    
        //HTTP - 스타트라인 출력
        //...
    
        //HTTP - Header 모든 정보
        //...
    
        //Header 편리한 조회
        //...
    
        //기타 정보(네트워크 커넥션 등에 대한 정보)
        private void printEtc(HttpServletRequest request) {
            System.out.println("--- 기타 조회 start ---");
    
            System.out.println("[Remote 정보]");
            System.out.println("request.getRemoteHost() = " + request.getRemoteHost()); //
            System.out.println("request.getRemoteAddr() = " + request.getRemoteAddr()); //
            System.out.println("request.getRemotePort() = " + request.getRemotePort()); //
            System.out.println();
            System.out.println("[Local 정보]");
            System.out.println("request.getLocalName() = " + request.getLocalName()); //
            System.out.println("request.getLocalAddr() = " + request.getLocalAddr()); //
            System.out.println("request.getLocalPort() = " + request.getLocalPort()); //
    
            System.out.println("--- 기타 조회 end ---");
            System.out.println();
        }
    }

     

    5~8.Http 요청 데이터 - 개요, GET쿼리파라미터, POST HTML Form, API 메시지 바디(단순 text)

     

    클라이언트에서 서버로 데이터 전달 방법은 크게 3가지가 있다.

     

    • 1)GET - 쿼리 파라미터
      • /url?username=hello&age=20과 같이 url에 붙여 보내는 방식
      • 메세지 바디가 없다(Content-Type도 당연히 없음)
      • 검색, 필터, 페이징 등에서 사용
      • 요청 데이터 뽑을 때는 request.getParameter() 사용
      • 요청데이터 중복일 때 request.getParameterValues() 사용
    • 2)POST - HTML Form
      • Content-Type : application/x-www-form-urlencoded
      • 메세지 바디에 쿼리 파라미터 형식으로 전달 username=hello&age=20
      • 회원가입, 상품주문 등
      • POST도 메세지 바디에 쿼리파라미터 형식으로 보내기 때문에 request.getParameter() 사용 가능
    • 3)HTTP message body에 데이터 직접 담아서 요청
      • HTTP API에서 주로 사용 : JSON, XML
      • POST, PUT, PATCH

     

    HTTP message body에 직접 담아서 요청을 보낼 때 다음과 같이 꺼내 쓸 수 있다.

     

    [RequestBodyStringServlet.java]

    @WebServlet(name = "requestBodyStringServlet", urlPatterns = "/request-body-string")
    public class RequestBodyStringServlet extends HttpServlet {
        @Override
        protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            //메세지 바디를 byte로 얻음(inputStream으로 데이터를 읽을 수 있음)
            ServletInputStream inputStream = request.getInputStream();
    
            //스프링 제공 유틸리티
            String messageBody = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8);
    
            System.out.println("messageBody = " + messageBody);
            response.getWriter().write("ok");
        }
    }

     

    9.HTTP 요청 데이터 - API 메시지 바디 - JSON

    • Content-type : application/json
    • message body : {"username":  "hello", "age":20}

     

    JSON 형식 파싱을 위한 객체 추가를 한다.

     

    [HelloData.java]

    @Getter
    @Setter
    public class HelloData {
        private String username;
        private int age;
    }

     

    JSON 요청을 읽기 위한 서블릿을 추가한다.

     

    [RequestBodyJsonServlet.java]

    /**
     * 제이슨 형식의 메시지 바디를 파싱해본다
     */
    @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);
    
            //jackson라이브러리로 vo 파싱
            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은 jackson라이브러리로 파싱한다. 위에서 ObjectMapper를 사용한다.

     

    10.HttpServletResponse - 기본 사용법

     

    HttpServletResponse는 응답관련된 헤더 셋팅이나 쿠키값,리다이렉트(302) 등을 사용할 수 있다.

     

    [ResponseHeaderServlet.java]

    @WebServlet(name = "responseHeaderSerlvet", 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-headers]
            response.setHeader("Content-Type", "text/plain;charset-utf-8");
            response.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
            response.setHeader("Pragma", "no-cache");
            response.setHeader("my-header", "hello"); //커스텀 헤더도 만들 수 있다.
    
            //[Header 편의 메서드]
            content(response);
            cookie(response);
            redirect(response);
    
            //[message body]
            PrintWriter writer = response.getWriter();
            writer.println("ok");
        }
    
        private void content(HttpServletResponse response) {
            response.setContentType("text/plain");
            response.setCharacterEncoding("utf-8");
            //response.setContentLength(2); //생략시 자동 생성
        }
    
        private void cookie(HttpServletResponse response) {
            //기존 방법
            //response.setHeader("Set-Cookie", "myCookie=good; Max-Age=600");
    
            //편리한 방법
            Cookie cookie = new Cookie("myCookie", "good");
            cookie.setMaxAge(600);
            response.addCookie(cookie);
        }
    
        private void redirect(HttpServletResponse response) throws IOException {
            //Status Code 302
            //Location: /basic/hello-form.html
    
            //기존 방법
    //        response.setStatus(HttpServletResponse.SC_FOUND);
    //        response.setHeader("Location", "/basic/hello-form.html");
    
            //편리한 방법
            response.sendRedirect("/basic/hello-form.html");
        }
    }

     

    11. HTTP 응답 데이터 - 단순 텍스트, HTML

     

    단순 텍스트 응답과 HTML 응답은 다음과 같이 PrintWriter를 통해 생성하여 소스보기로 처리할 수 있다.

     

    [ResponseHtmlServlet.java]

    @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");
    
            //화면에 안녕?이 뿌려짐. 소스보기하면 HTML구성
            PrintWriter writer = response.getWriter();
            writer.println("<html>");
            writer.println("<body>");
            writer.println("<div>안녕?</div>");
            writer.println("</body>");
            writer.println("</html>");
        }
    }

     

    12. HTTP 응답 데이터 - API JSON

     

    Content-Type은 "application/json"을 사용한다. 자체적으로 json은 캐릭터셋이 UTF-8이기 때문에 CharacterEncoding을 굳이 붙여줄 필요는 없다.

     

    다음과 같이 HelloData 객체 setter에 값을 넣은 후, jackson 라이브러리의 ObjectMapper를 사용한다.

     

    [ResponseJsonServlet.java]

    @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
            //json은 자체적으로 utf-8 캐릭터셋이 기본이라서 뒤에 utf-8붙여주는 것이 의미는 없음
            response.setContentType("application/json");
            response.setCharacterEncoding("utf-8");
    
            HelloData helloData = new HelloData();
            helloData.setUsername("kim");
            helloData.setAge(20);
    
            //{"username":"kim", "age":20}
            //스프링에서는 return helloData로 json을 반환할 수 있음
            String result = objectMapper.writeValueAsString(helloData);
            response.getWriter().write(result);
        }
    }
    반응형

    댓글