[Spring] MVC 1편 - 02. 서블릿

somyeong·2022년 4월 12일
0

Spring

목록 보기
12/17
post-thumbnail

이 글은 스프링 [스프링 MVC 1편]을 듣고 정리한 내용입니다

📌 프로젝트 생성

  • 환경 세팅
  • Packing: War (주의)
    • JSP 실행하기 위해서 필요함
  • 메인 클래스 실행하고, localhost:8080에 Whitelabel Error Page 나오면 정상 동작
  • Gradle대신에 InteliJ 에서 자바 직접 실행하는 설정
  • 롬복 적용(plugint 롬복 설치, Annotation Processor)
  • Postman 설치

🌵 서블릿 개념

서블릿 이란?

  • Dynamic Web Page를 만들때 사용되는 자바 기반의 웹 애플리케이션 프로그래밍 기술이다.
  • 웹을 만들때 다양한 요청(request)와 응답(response)가 있기 마련이고, 이 요청과 응답에는 규칙이 존재한다. 이러한 요청과 응답을 일일이 처리하려면 힘들고, 서블릿은 이러한 웹 요청과 응답의 흐름을 메서드 호출만으로 체계적으로 다룰수 있게 해주는 기술이다.

서블릿 동작 과정

  • 서블릿은 자바 클래스로 웹 애플리케이션을 작성한 뒤, 이후 웹 서버 안에 있는 웹 컨테이너에서 이것을 실행하고, 웹 컨테이너는 서블릿 인스턴스를 생성 후 서버에서 실행되다가 웹 브라우저에서 서버에 요청(request)를 하면 요청에 맞는 동작을 수행하고 웹 브라우저에 HTTP형식으로 응답(response)한다.

서블릿 컨테이너란?

  • 서블릿을 담고 관리해주는 컨테이너이다.
  • 서블릿 컨테이너는 구현되어있는 servlet 클래스의 규칙에 맞게 서블릿을 관리해주며 클라이언트에서 요청을 하면 컨테이너는 HttpServletRequest, HttpServletResponse 두 객체를 생성하며 post,get 여부에 따라 동적인 페이지를 생성하여 응답을 보낸다.

HttpServletRequest란?

  • http 프로토콜의 request정보를 서블릿에게 전달하기 위한 목적으로 사용한다.
  • 헤더정보, 파라미터, 쿠키, URI, URL 등의 정보를 읽어들이는 메서드와 Body Stream을 읽어 들이는 메서드를 가지고 있다.

HttpServletResponse란?

  • WAS는 어떤 클라이언트가 요청을 보냈는지 알고 있고, 해당 클라이언트에게 응답을 보내기 위한 HttpServletResponse 객체를 생성하여 서블릿에게 전달하고 이 객체를 활용하여 content type, 응답 코드, 메세지 등을 전송한다.

📌 Hello 서블릿

  • 서블릿 자동 등록
    • ServletApplication 파일
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);
	}

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

// servlet 어노테이션
@WebServlet(name = "helloServlet", urlPatterns = "/hello") // name: 서블릿 이름, urlPatterns: URL 매핑 (이름은 아무거나 해도됨)
public class HelloServlet extends HttpServlet {//원래 서블릿은 HttpServlet 클래스를 상속받아야 함.

    @Override // HTTP 요청을 통해 URL이 호출되면 서블릿 컨테이너는 다음 메서드(service)를 실행한다.
    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //control+O 에서 protected형 service 선택해야함

        System.out.println("HelloServlet.service");
        System.out.println("request = " + request);
        System.out.println("response = " + response);

        //아래와 같이 servlet이 개발자가 요구하는 값을 알아서 파싱해줌.
        String username = request.getParameter("username");//username 이라는 쿼리파라미터에 해당하는 값을 가져옴.
        System.out.println("username = " + username);


        response.setContentType("text/plain"); // http 헤더 정보 - content type
        response.setCharacterEncoding("utf-8");// 문자 인코딩은 요즘에는 utf-8을 보통 씀
        response.getWriter().write("hello " + username); //write란? -> http 메시지 바디에 데이터가 들어감
    }
}

  • HTTP 요청 로그로 확인하기
    • application.properties 파일에 다음 옵션을 추가하면 서버가 받은 HTTP 요청 메세지를 확인 할 수 있다.
logging.level.org.apache.coyote.http11=debug  

*참고

  • 운영 서버에 모든 요청 정보를 다남김녀 성능저하가 발생할 수 있으므로 개발할때, 참고용으로만 적용하자!

서블릿 컨테이너 동작 방식

  • 스프링 부트를 실행하면 스프링 부트가 내장 톰캣 서버를 띄워준다
  • 톰캣 서버는 내부에 Servlet Container 기능을 가지고 있다.
    • Servlet Container기능을 통해서 Servlet을 생성해줌.
    • 아래 그림에서는 서블릿 컨테이너안에 helloServlet이 생성된 상황!

  • 웹 브라우저가 HTTP 메세지를 다음과 같이 만들어서 서버쪽에 던져준다.

  • 그럼 서버는 reqeust,responser객체를 만들어서 helloServlet을 호출해준다. 거기에서 sevice메서드를 호출하면서 request,response를 넘겨준다.

  • 필요한 작업을 한 후, helloServlet이 종료되고 나가면서 WAS서버가 response를 가지고 HTTP 응답 메세지를 반환한다. -> 그럼 웹 브라우저에서 hello world메세지를 볼 수 있게 된다.

  • welcome 페이지 추가(index.html)

  • 학습할 내용 안내 페이지 추가(basic.html)


📌 HttpServletRequeset - 개요

HttpServletRequest 역할

  • 서블릿은 개발자가 HTTP 요청 메시지를 편리하게 사용할 수 있도록 개발자 대신에 HTTP 요청 메시지를 파싱한다.
  • 그리고 그 결과를 HttpServletRequest 객체에 담아서 제공한다.

HTTP 요청 메시지

 POST /save HTTP/1.1
  Host: localhost:8080
  Content-Type: application/x-www-form-urlencoded
  username=kim&age=20
  • 시작 라인
    • HTTP 메소드
    • URL
    • 쿼리 스트링
    • 스키마, 프로토콜
  • 헤더
    • 헤더 조회
  • 바디
    • form 파라미터 형식 조회
    • message body 데이터 직접 조회
  • HttpServletRequeset 객체는 추가로 다음 여러가지 부가기능도 제공한다.

    임시 저장소 기능

  • 해당 HTTP 요청이 시작부터 끝날 때까지 유지되는 임시 저장소 기능(뒤에 MVC패턴에서 나올 예정)
    • 저장: request.setAttribute(name,value)
    • 조회: request.getAttribute(name)

중요

  • HttpServletRequest, HttpServletResponse 객체들은 HTTP 요청,응답 메세지를 편리하게 사용하도록 도와주는 개체이다
  • HTTP 스펙이 제공하는 요청, 응답 메시지를 잘 이해해야한다.

📌 HttpServletRequest - 기본 사용법

  • HttpServletRequest가 제공하는 기본 기능들을 조회 해본다.
package hello.servlet.basic.request;


import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Enumeration;


@WebServlet(name= "requestHeaderServlet", urlPatterns = "/request-header")
public class RequestHeaderServlet extends HttpServlet {

    @Override //protected인 service를 만들어야 한다.
    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        printStartLine(request);
        printHeaders(request);
        printHeaderUtils(request);
        printEtc(request);


    }

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

    //Header 모든 정보
    private void printHeaders(HttpServletRequest request) {
        System.out.println("--- Headers - start ---");

   /*   Enumeration<String> headerNames = request.getHeaderNames();
      while (headerNames.hasMoreElements()) {
          String headerName = headerNames.nextElement();
          System.out.println(headerName + ": " + request.getHeader(headerName));
      }
*/
        request.getHeaderNames().asIterator()
                        .forEachRemaining(headerName -> System.out.println(headerName + ": " + request.getHeader(headerName)));

        System.out.println("--- Headers - end ---");
        System.out.println();
    }

    //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 헤더
         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());

        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());
        System.out.println("request.getContentLength() = " +
                request.getContentLength());
        System.out.println("request.getCharacterEncoding() = " +
                request.getCharacterEncoding());
        System.out.println("--- Header 편의 조회 end ---");
        System.out.println();
    }

    // 기타 정보
    // 기타정보는 HTTP 메시지의 정보는 아니다
    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();
    }


}
  • 이렇게 HttpServletRequest를 이용하여 HTTP 메시지의 시작라인, header 정보를 조회해보았다.
  • 이제 HTTP 요청 데이터 조회하는 방법을 보자

📌 HTTP 요청 데이터 - 개요

  • HTTP 요청 메시지를 통해 클라이언트에서 서버로 데이터를 전달하는 방법은 이 3가지가 전부이다!
  1. GET - 쿼리 파라미터
    ex) /url?username=hello&age=24
    • 메시지 바디 없이, URL의 쿼리 파라미터에 데이터를 포함해서 전달
      사용) 검색, 필터, 페이징 등등
  2. POST- HTML Frorm
    ex) content-type: application/x-www-form-urlencoded
    • 메시지 바디에 쿼리 파라미터 형식으로 전달 username=hello&age=24
      사용) 회원 가입, 상품 주문, HTML Form
  3. HTTP message body에 데이터를 직접 담아서 요청
    • HTTP API에서 주로 사용 -> JSON, XML, TEXT
    • 데이터 형식은 주로 JSON 사용
      • POST, PUT, PATCH
  • POST - HTML Form 예시

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

  • 쿼리 파라미터 조회 메서드
String username = request.getParameter("username"); //단일 파라미터 조회 
Enumeration<String> parameterNames = request.getParameterNames(); //파라미터 이름들 모두 조회
Map<String, String[]> parameterMap = request.getParameterMap(); //파라미터를 Map 으로 조회
String[] usernames = request.getParameterValues("username"); //복수 파라미터 조회
  • 전체, 단일, 복수 파라미터 전송
package hello.servlet.basic.request;

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;
import java.util.Enumeration;

/**
 * 1. 파라미터 전송 기능
 * http://localhost:8080/request-param?username=hello&age=20
 *
 * 2. 동일한 파라미터 전송 가능하다
 * http://localhost:8080/request-param?username=hello&age=20&username=hello2
 */
@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("[전체 파라미터 조회] - 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("[이름이 같은 복수 파라미터 조회]");
        String[] usernames = request.getParameterValues("username");
        for (String name : usernames) {
            System.out.println("name = " + name);
        }
        System.out.println();


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


    }
}
  • 복수 파라미터에서 단일 파라미터 조회
    • 하나의 파라미터에 대해 여러 값이 있으면 request.getParamterValues() 사용해야 한다.
    • 중복일 때 request.getParameter() 사용하면 첫번째 값을 반환한다.

📌 HTTP 요청 데이터 - 2. POST HTML Form

  • 주로 회원가입, 상품 주문 등에서 사용
  • 타입이 있다 -> contet-type: application/x-www-form-urlencoded
  • 메시지 바디에 쿼리파라미터 형식으로 데이터 전달한다.
    ex) username=hello&age=20
  • 서버입장에서는 앞의 GET 쿼리 파라미터 방식과 POST HTML Form 방식 둘의 형식이 동일하다.
  • requset.getParameter()는 GET URL 쿼리 파라미터 형식도 지원하고, POST HTML Form형식도 지원한다.

*참고

  • content-type: HTTP 메시지 바디의 형식을 지정한다
  • GET URL 쿼리 파라미터 방식에서는 메시지 바디를 사용 안하므로, content-type이 없다
  • POST HTML Form 방식에서는 메시지 바디에 데이터를 포함해서 보내므로 데이터가 어떤 형식인지 content-type를 지정해야 한다.
    (즉 포스트맨에서 body의 x-www-form-urlencoded 선택해야함)
    - 폼으로 데이터를 전송하는 형식을 application/x-www-form-urlencoded라 한다.

📌 HTTP 요청 데이터 - 3. API 메시지 바디

3-1. 단순텍스트 형식

  • HTTP message body에 직접 데이터를 담아서 요청한다.
package hello.servlet.basic.request;

@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(); //이렇게하면, message body의 내용을 바이트코드로 바로 얻을 수 있다.
        String messageBody = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8);// 바이트코드를 문자로 변환
        //inputStream은 byte코드를 반환한다.
        //byte코드를 우리가 읽을 수 있는 문자로 보려면 문자표(charset)을 지정해주어야 한다.
        

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

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

3-2. JSON 방식

  • HTTP API에서 주로 사용한다
  • JSON 형식으로 파싱할 수 있게 객체를 하나 생성한다.
package hello.servlet.basic;

import lombok.Getter;
import lombok.Setter;

@Getter @Setter //롬복 사용
public class HelloData {

    private String username;
    private int age;
}
package hello.servlet.basic.request;

@WebServlet(name="requestBodyJsonServlet", urlPatterns = "/request-body-json")
public class RequestBodyJsonServlet extends HttpServlet {

    //JSON 결과를 파싱해서 사용할 수 있는 자바 객체로 변환하려면 JSON 변환 라이브러리 추가해서 사용해야 한다.
    //스프링에서 기본으로 Jackson 라이브러리(objectMapper) 제공
    private ObjectMapper objectMapper = new ObjectMapper();


    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        ServletInputStream inputStream = req.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());

        resp.getWriter().write("okay");
        
    }
}


📌 HttpServletResponse - 기본 사용법

  • 요청에 대해서 알아봤으니까 이제 응답에 대해서 알아보자

HttpServletResponse 역할

  • HTTP 응답 메시지 생성

    • HTTP 응답코드 지정
    • 헤더 생성
    • 바디 생성
      - 편의 기능 제공
      • Content-Type, 쿠키, Redirect
  • HttpServletResponse 사용법

package hello.servlet.basic.response;

@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); //같은 200이긴 하지만, 200 보다는 이렇게 적는것이 나음

        //[response-headers] //이런식으로 지정해줘도 되고, 아래 편의메서드 내용처럼 지정해줘도 된다.
        response.setHeader("Content-Type", "text/plain;charset=utf-8"); //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();
        writer.println("오케이입니다");


    }

    //content 편의 메서드
    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");
        response.sendRedirect("/basic/hello-form.html"); //이렇게 편하게 사용가능 (위 두줄과 같음)
    }
}

📌 HTTP 응답데이터 - 1. 단순텍스트

  • 이제 헤더는 봤으니, 데이터 부분을 알아보자!!
  • HTTP 응답 메시지도 요청과 비슷하게 3가지가 있다.
    • 단순 텍스트 응답 (앞에서 봤던 것처럼 writer.println("ok"))
    • HTML 응답
      		- HTTP API - Message Body JSON 응답

📌 HTTP 응답데이터 - 2. HTML

  • 응답으로 HTML 반환하려면 content-type을 text/html로 지정한다.
package hello.servlet.basic.response;

@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 응답데이터 - 3. API JSON

  • HTTP 응답으로 JSON 반환할 때는 content-type을 application/json으로 지정하면 된다.
  • Jackson 라이브러리가 제공하는 objectMapper.writeValueAsString()을 통해 객체를 JSON 문자로 변경할 수 있다.
package hello.servlet.basic.response;

@WebServlet(name="responseJsonServlet", urlPatterns = "/response-json")
public class ResponseJsonServlet extends HttpServlet {

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

    }
}

*참고

  • application/json은 스펙상 utf-8형식을 사용한다. 그래서 스펙에서 charset=utf-8과 같은 추가 파라미터를 지원하지 않는다.
  • 그러므로 content-type을 지정할때,application/json이라고만 하면 된다. application/json;charset=utf-8이라고 전달하는건 의미없는 파라미터를 추가하는 것이 된다.

참고 사이트
https://coding-factory.tistory.com/742

profile
공부한 내용 잊어버리지 않게 기록하는 공간!

0개의 댓글