Servlet

뚝딱이·2022년 7월 22일
1

스프링 MVC

목록 보기
2/23

Hello 서블릿

@WebServlet : 서블릿 어노테이션으로 안에 name과 urlPattern을 넣을 수 있다.

  • name : 서블릿 이름
  • urlPattern : URL 매핑
    name과 urlPattern은 중복되면 안된다.

@WebServlet(name = "helloServlet", urlPatterns = "/hello")
위와 같이 입력하면 웹에서 localhost:8080/hello를 입력했을 때 service 메소드가 실행된다.

서블릿을 사용하려면 HttpServlet을 상속받아야하는데, Ctrl + O를 누르고 service를 검색하면 메소드를 찾을 수 있다. service 메소드가 두개 나오는데 열쇠 모양(위에 있는 거)을 누르면 된다.

resource/application.properties에 logging.level.org.apache.coyote.http11=debug를 입력하면 요청 메세지를 확인할 수 있다. 참고로, 운영서버에 이렇게 모든 요청 정보를 다 남기면 성능저하가 발생할 수 있으니 개발 단계에서만 이용하는게 좋다.

HTTP 응답에서 Content-Length는 웹 애플리케이션 서버가 자동으로 생성해준다.


HttpServletRequest

역할

HTTP 요청 메시지를 개발자가 직접 파싱해서 사용해도 되지만, 매우 불편할 것이다. 서블릿은 개발자 대신에 HTTP 요청 메시지를 파싱해 HttpServletRequest 객체에 담아서 제공한다.

부가 기능

임시 저장소 기능

해당 HTTP 요청이 시작부터 끝날 때 까지 유지되는 임시 저장소 기능

  • 저장: request.setAttribute(name, value)
  • 조회: request.getAttribute(name)

세션 관리 기능

request.getSession(create: true)

Start-line

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-LINE - start ---
request.getMethod() = GET
request.getProtocol() = HTTP/1.1
request.getScheme() = http
request.getRequestURL() = http://localhost:8080//request-header
request.getRequestURI() = //request-header
request.getQueryString() = null
request.isSecure() = false ->true면 https, false면 http
--- REQUEST-LINE - end ---

Header 모든 정보

아래의 주석 부분은 옛날 방식이다.

//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();
    }
--- Headers - start ---
host: localhost:8080
connection: keep-alive
cache-control: max-age=0
sec-ch-ua: "Chromium";v="88", "Google Chrome";v="88", ";Not A Brand";v="99"
sec-ch-ua-mobile: ?0
upgrade-insecure-requests: 1
user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 11_2_0) AppleWebKit/537.36 
(KHTML, like Gecko) Chrome/88.0.4324.150 Safari/537.36
accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/
webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
sec-fetch-site: none
sec-fetch-mode: navigatesec-fetch-user: ?1
sec-fetch-dest: document
accept-encoding: gzip, deflate, br
accept-language: ko,en-US;q=0.9,en;q=0.8,ko-KR;q=0.7
--- Headers - end ---

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 헤더
        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();
    }
--- Header 편의 조회 start ---
[Host 편의 조회]
request.getServerName() = localhost
request.getServerPort() = 8080
[Accept-Language 편의 조회]
locale = ko
locale = en_US
locale = en
locale = ko_KR
request.getLocale() = ko
[cookie 편의 조회]
[Content 편의 조회]
request.getContentType() = null
request.getContentLength() = -1
request.getCharacterEncoding() = UTF-8
--- Header 편의 조회 end ---

기타 정보

//기타 정보
    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();
    }
--- 기타 조회 start ---
[Remote 정보]
request.getRemoteHost() = 0:0:0:0:0:0:0:1
request.getRemoteAddr() = 0:0:0:0:0:0:0:1
request.getRemotePort() = 54305
[Local 정보]
request.getLocalName() = localhost
request.getLocalAddr() = 0:0:0:0:0:0:0:1
request.getLocalPort() = 8080
--- 기타 조회 end ---

HTTP 요청 데이터

주로 다음 3가지 방법을 사용한다.

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

GET

request.getParameter(key) : value를 가져옴 예를 들어 username=kim이면 request.getParameter(username)하면 kim이 출력된다.

쿼리 파라미터는 ?를 시작으로 &로 파라미터를 추가한다.

http://localhost:8080/request-param?username=hello&username=hello2&age=20

전체 파라미터 조회

request.getParameterNames().asIterator()
                .forEachRemaining(paramName -> System.out.println(paramName + " = " + request.getParameter(paramName)));

단일 파라미터 조회

String username = request.getParameter("username");
        String age = request.getParameter("age");

이름이 같은 복수 파라미터 조회

        String[] usernames = request.getParameterValues("username");
        for (String name : usernames){
            System.out.println("username = " + name);
        }

출력 결과

[전체 파라미터 조회] - start
username=hello
age=20
[전체 파라미터 조회] - end
[단일 파라미터 조회]
request.getParameter(username) = hello
request.getParameter(age) = 20
[이름이 같은 복수 파라미터 조회]
request.getParameterValues(username)
username=hello
username=kim

중복일 때 전체 파라미터 조회를 하면 첫 번째 값만 반환한다.
중복은 잘 사용하지 않는다.


POST

src/main/webapp/basic/hello-form.html에 생성한 html은
http://localhost:8080/basic/hello-form.html로 볼 수 있다.

content-type: application/x-www-form-urlencoded
으로 메세지 바디엔 쿼리 파라미터 형식으로 데이터를 전달한다.

GET 또한 쿼리 파라미터 형식을 사용하므로 쿼리 파라미터 조회 메서드가 같다. 따라서 request.getParameter()로 조회할 수 있다.

참고로 GET은 메세지 바디가 없기 때문에 Content-type이 없다.


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

HTTP message body에 데이터를 직접 담아서 요청HTTP API에서 주로 사용
데이터 형식은 주로 JSON 사용
POST, PUT, PATCH에서 주로 사용된다.

        ServletInputStream inputStream = request.getInputStream(); //바이트 코드로 받아옴
        String messageBody = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8);

request.getInputStream()은 바이트 코드로 받아오기 때문에 StreamUtils.copyToString을 이용해 string으로 바꿔주었다. 이때 문자표를 지정해줘야하는데 위에서는 StandardCharsets.UTF_8로 UTF-8로 지정해주었다.

content-type은 단순 텍스트이므로, text/plain이다.

이 예제는 단순 텍스트를 전송하는 것에대해 알아보았는데, 요즘은 보통 JSON 형식을 많이 사용한다. JSON 형식일 때 사용법을 알아보자.


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

  • content-type: application/json
  • message body: {"username": "hello", "age": 20}
  • 결과: messageBody = {"username": "hello", "age": 20}
ServletInputStream inputStream = request.getInputStream();
String messageBody = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8);

위와 같은 메소드를 활용하면, 결국 JSON도 text이기 때문에 다음과 같이 출력된다.
messageBody={"username": "hello", "age": 20}
이것을 파싱해서 사용하면 되는데, 파싱하는 법은 아래와 같다. 먼저 HelloData를 lombok을 사용해서 username과 age를 선언하고 Getter,Setter도 생성해주었다.

private ObjectMapper objectMapper = new ObjectMapper();
 
 //...
 
HelloData helloData = objectMapper.readValue(messageBody, HelloData.class);

System.out.println("helloData.username = " + helloData.getUsername());
System.out.println("helloData,age = " + helloData.getAge());

그 후 objectMapper의 readValue를 이용하면 getter로 각각의 value를 가져올 수 있다.
출력결과는 아래와 같다.

data.username=hello
data.age=20

(참고) JSON 결과를 파싱해서 사용할 수 있는 자바 객체로 변환하려면 Jackson, Gson 같은 JSON 변환
라이브러리를 추가해서 사용해야 한다. 스프링 부트로 Spring MVC를 선택하면 기본으로 Jackson
라이브러리( ObjectMapper )를 함께 제공한다.


HttpServletResponse

setStatus를 통해 상태 코드를 지정할 수 있다.

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

redirect 편의 메서드

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 응답 데이터 - 단순 텍스트, HTML

단순 텍스트 응답은 앞서 다른 예제들에서도 많이 봤었다.
writer.println("ok"); 를 사용하는 것이다. 따라서 html에 대해 알아보도록 하자.

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

ContentType을 text/html로 지정해주는 것이 중요하다.


HTTP 응답 데이터 - JSON

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

Content-type을 application/json으로 지저해주어야 한다.

hellodata의 객체를 만들어 value들을 지정해주고 요청에서 json 파싱에 사용했던 objectMapper를 이용해서 json 형식을 만들어준다.

(참고) application/json 은 스펙상 utf-8 형식을 사용하도록 정의되어 있다. 그래서 스펙에서 charset=utf-8
과 같은 추가 파라미터를 지원하지 않는다. 따라서 application/json 이라고만 사용해야지
application/json;charset=utf-8 이라고 전달하는 것은 의미 없는 파라미터를 추가한 것이 된다.
response.getWriter()를 사용하면 추가 파라미터를 자동으로 추가해버린다. 이때는
response.getOutputStream()으로 출력하면 그런 문제가 없다.


출처 : 스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술

profile
백엔드 개발자 지망생

0개의 댓글