Spring boot

[스프링 MVC] Part2 (서블릿)

코앤미 2023. 3. 14. 13:17

서블릿은 http에서 비즈니스 로직 외의 영역을 담당

 

서블릿에 대한 구현은 WAS가 처리한다. (개발자는 비즈니스 로직에 집중하면 된다)

 

서블릿 자동 등록 어노테이션 @ServletComponentScan 추가

 

 

helloServlet ( request를 기반으로 서블릿 컨테이너가 request, response 객체가 생성 후 파라미터로 아래 메소드에 전달한다.)

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


    //서블릿 메서드 호출시 서비스가 호출된다.
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("helloServlet.service");
        // super.service(req, resp);
    }
}

/hello 로 요청 수신 시, service() 메소드를 동작한다.

 

~~~~~~/hello?username=shyswy    ->.  username, shyswy라는 key, value 쌍을 파라미터로 전달한다.  

 

 

 

 

스프링 부트가 내장 웹서버 톰캣을 띄운다.

톰켓은 내부에 서블릿 컨테이너를 가지고 있다. 이를 통해 서블릿 생성(HelloServlet 생성)

~~/hello를 통해 요청 시(helloServlet이 매핑되는 url) request, response객체가 생성되고 파라미터로 이 두 객체를 넘겨준다.

 

서블릿의 기능: 임시 저장소 기능

해당 HTTP 요청이 시작부터 끝날 때 까지 유지되는 임시 저장소 기능 저장: request.setAttribute(name, value)
조회: request.getAttribute(name)

 

세션 관리 기능

request.getSession(create: true)

 

HttpServletRequest, HttpServletResponse를 사용할 때 가장 중요한 점은 이 객체들이 HTTP 요청 메시지, HTTP 응답 메시지를 편리하게 사용하도록 도와주는 객체라는 점이다. 따라서 이 기능에 대해서 깊이있는 이해를 하려면 HTTP 스펙이 제공하는 요청, 응답 메시지 자체를 이해해야 한다.

 

이번엔 HttpServletRequest, HttpServletResponse 의 메소드에 대해 살펴보자

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-header
        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();
    }

 

결과

 

 

request에서 헤더 정보를 가져오는 법

 

예전 방식

private void printHeaders(HttpServletRequest request){
        System.out.println("-----Headers -----start");
        Enumeration<String> headerNames = request.getHeaderNames(); //헤더들을 받는다.
        while (headerNames.hasMoreElements()){//1개씩 넘어가며 탐색
            String headerName = headerNames.nextElement();
            System.out.println(headerName);
        }
        System.out.println("-----Headers -----end");
    }

 

새로운 방식

request.getHeaderNames().asIterator().forEachRemaining(headerName -> System.out.println(headerName));

 

 

결과

-----Headers -----start
host
connection
cache-control
sec-ch-ua
sec-ch-ua-mobile
sec-ch-ua-platform
upgrade-insecure-requests
user-agent
accept
sec-fetch-site
sec-fetch-mode
sec-fetch-user
sec-fetch-dest
accept-encoding
accept-language
-----Headers -----end

 

 

 

헤더 편의 조회 메서드

--- Header 편의 조회 start ---
[Host 편의 조회]
request.getServerName() = localhost
request.getServerPort() = 8080

[Accept-Language 편의 조회]
locale = ko_KR
locale = ko
locale = en_US
locale = en
request.getLocale() = ko_KR

[cookie 편의 조회]

[Content 편의 조회]
request.getContentType() = null
request.getContentLength() = -1
request.getCharacterEncoding() = UTF-8
--- Header 편의 조회 end ---

위와 같은 메소드들 존재 ( 쿠키, locale의 경우는 아래와 같이 구현함)

System.out.println("[Accept-Language 편의 조회]");
        request.getLocales().asIterator()
                .forEachRemaining(locale -> System.out.println("locale = " + locale));
        System.out.println("request.getLocale() = " + request.getLocale());
        System.out.println();



        System.out.println("[cookie 편의 조회]");
        if (request.getCookies() != null) {
            for (Cookie cookie : request.getCookies()) {
                System.out.println(cookie.getName() + ": " + cookie.getValue());
            }
        }

쿠키: HTTP 헤더에 담긴다. 쿠키안에는 다양한 타입의 데이터가 존재한다.

 

request.getContentType() = null -> get 방식은 보통 contenttype을 세팅하지 않는다.( 보통 body에 넣을 때 세팅 )

 

기타 정보

--- 기타 조회 start ---
[Remote 정보]
request.getRemoteHost() = 0:0:0:0:0:0:0:1
request.getRemoteAddr() = 0:0:0:0:0:0:0:1
request.getRemotePort() = 61575

[Local 정보]
request.getLocalName() = localhost
request.getLocalAddr() = 0:0:0:0:0:0:0:1
request.getLocalPort() = 8080
--- 기타 조회 end ---

 

HTML Form 이란?

HTML Form 데이터는 사용자가 웹 페이지에서 입력한 정보를 웹 서버에 제출하는 방법 중 하나.

HTML Form은 사용자가 입력하는 데이터를 받기 위한 텍스트 상자, 선택 상자, 라디오 버튼, 체크 박스 등을 포함한다.

이러한 구성 요소들을 통해 사용자로부터 데이터를 수집하고, 이를 웹 서버에 전송한다.

GET - 쿼리 파라미터

/url?username=hello&age=20
메시지 바디 없이, URL의 쿼리 파라미터에 데이터를 포함해서 전달

) 검색, 필터, 페이징등에서 많이 사용하는 방식

 

POST - HTML Form

content-type: application/x-www-form-urlencoded     

메시지 바디에 쿼리 파리미터 형식으로 전달 username=hello&age=20

HTML Form 데이터를 전송하는 것은 Post 방식으로만 가능

) 회원 가입, 상품 주문, HTML Form 사용

 

HTTP message body에 데이터를 직접 담아서 요청

HTTP API에서 주로 사용, JSON, XML, TEXT

데이터 형식은 주로 JSON 사용

POST, PUT, PATCH

 

 

 

[Request 메세지 읽기]

 protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

        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("username = " + username);
        System.out.println("age = " + age);
        System.out.println();

        System.out.println("[이름이 같은 복수 파라미터 조회]"); //ex:   ~~~~?username=hello1 & username=hello2
        String[] usernames = request.getParameterValues("username"); //"username" 이라는 이름 가진 파라미터 값들을 조회
        for (String name : usernames) {
            System.out.println("username = " + name);
        }

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


    }

 

 

결과

 

body의 내용을 바로 바이트 코드로 받고 string 으로 변환하기

@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(); // 메세지 body의 내용을 바이트 코드를 바로 받을 수 있다.
        String messageBody = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8); // 바이트를 문자로 바꿀때는 어떤 인코딩인지 알려줘야한다(UTF-8)

        System.out.println("messageBody = " + messageBody);

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

 

getParamter()은 항상 String 타입이기에, 다른 타입으로 받고 싶다면 파싱해줘야한다.

int age = Integer.parseInt(request.getParameter("age")); 
//getParameter의 리턴은 항상 String. int로 파싱한다.

 

그럼 JSON으로는 어떻게 주고 받을까?

-> 보통 객체로 파싱해서 사용한다. 

JSON 은 결국 key, value의 쌍. 

key: username, value: shy

key: age, value: 20

-> objectMapper등을 통해 json -> 적절한 자바 객체로 바로 파싱 가능

(ObjectMapper:  jackson, Gson 등 라이브러리 가져와야함 ).

@Getter @Setter
public class HelloData {

    private String username;
    private int age;

}

 

body의 JSON 데이터 읽기 

@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.username = " + helloData.getUsername());
        System.out.println("helloData.age = " + helloData.getAge());

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

 

json도 결국은 문자열이다. json 형식의 string으로 inputStream에서 받아온다.

messageBody = {"username": "hello", "age":20}
helloData.username = hello
helloData.age = 20

받아온 JSON 스트링을 ObjectMapper로 앞서 만든 helloData 객체로 파싱 해준다.

Controller 에서 파라미터를 받아 JSON 객체를 자동으로 엔티티로 파싱하는 것도 이와 같은 구조로 진행된다. 

 

[HttpServletResponse]

다양한 메소드들을 활용해서 response http 메세지를 생성할 수 있다.

 

  @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //[status-line]
        response.setStatus(HttpServletResponse.SC_OK); // ok message.

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

        PrintWriter writer = response.getWriter(); //response body에 작성
        writer.println("ok");
    }

 

 

 

 @WebServlet(name = "responseHeaderServlet", urlPatterns = "/response-header")
public class ResponseHeaderServlet extends HttpServlet {

 ........................
 
 private void content(HttpServletResponse response) { //response의 헤더 세팅 설정
        //Content-Type: text/plain;charset=utf-8
        //Content-Length: 2
        //response.setHeader("Content-Type", "text/plain;charset=utf-8");
        response.setContentType("text/plain"); //setHeader로 이름 지정 말고 따로 메소드 존재
        response.setCharacterEncoding("utf-8");
        //response.setContentLength(2); //(생략시 body의 길이만큼 자동 생성)
    }

    private void cookie(HttpServletResponse response) { //response 에 쿠키 추가
        //Set-Cookie: myCookie=good; Max-Age=600;
        //response.setHeader("Set-Cookie", "myCookie=good; Max-Age=600");
        //굳이 이렇게 할 필요없이 ,Cookie 라는 객체를 임포트 해서 사용하자. 
        
        Cookie cookie = new Cookie("myCookie", "good");
        cookie.setMaxAge(600); //600초
        response.addCookie(cookie);
    }

    private void redirect(HttpServletResponse response) throws IOException {
    //redirect. response에   ~~~/hello-form.html 로 리다이렉트할거라는 정보를 담는다.
    
        //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"); //이 한줄로 리다이렉트 가능
    }
}

 

 

[HTTP 응답 데이터: 단순 텍스트, HTML]

단순 텍스트

PrintWriter writer = response.getWriter();
writer.println("ok");

 

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"); //html로 타입 세팅
        response.setCharacterEncoding("utf-8");

        PrintWriter writer = response.getWriter();  //이제부터 html을 한줄한줄 입력
        writer.println("<html>");
        writer.println("<body>");
        writer.println("  <div>안녕?</div>");
        writer.println("</body>");
        writer.println("</html>");
    }
}

 

 

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.setContentType("application/json");
        response.setCharacterEncoding("utf-8");

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

        //{"username":"kim", "age":20}
        String result = objectMapper.writeValueAsString(helloData);
        response.getWriter().write(result);
    }
}

JSON과 파싱으로 호환되는 자바 객체에 값을 넣고,    objectMapper.writeValueAsString(자바 객체); 로 String으로 body에 담는다.

결국 JSON 도 문자열. 타입을 JSON 으로 지정하고 JSON 형식을 String으로 쏴주면 된다. 

 

 

 

JSON 으로 데이터 주고 받는 방식 간단 요약

ServletInputStream inputStream = request.getInputStream();
        // [JSON 읽기]
        String messageBody = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8);
			//inputStream으로 body에 적힌 내용의 바이트 코드를 받고, String으로 변환
       

        HelloData helloData = objectMapper.readValue(messageBody, HelloData.class);
        //받은 String( JSON ) 을 적절한 객체로 파싱. 이후에 객체로 사용하면 된다.
        
        
        // [JSON 쓰기]
        
        String result = objectMapper.writeValueAsString(helloData);
        //받는 쪽에서 요구하는 Response JSON 과 파싱되는 자바 객체를 String으로 변환
        ( {"username":"kim","age":20}   이런식의 JSON String으로 파싱됨)
        response.getWriter().write(result); //해당 JSON 스트링을 body에 작성