[Spring Boot][4] 2-1. 서블릿

sorzzzzy·2021년 9월 5일
0

Spring Boot - RoadMap 1

목록 보기
25/46
post-thumbnail

🏷 프로젝트 생성

스프링 부트를 사용하면 환경설정도 편하고, 톰캣 서버를 내장하고 있기 때문에 일단 스프링 부트를 이용해 프로젝트를 만들지만, 거의 사용하지 않을 예정이다 !

스프링 부트 스타터 사이트에서 프로젝트를 생성하는 부분은 이전에 여러번 다뤘기 때문에 이번에는 과감히 넘어가겠다😅❗️

✔️ 프로젝트 선택

  • Project: Gradle
  • Project Language: Java
  • Spring Boot: 2.4.x
    ✔️ Project Metadata
  • Group: hello
  • Artifact: servlet
  • Name: servlet
  • Package name: hello.servlet Packaging: War(JSP를 실행하기 위해 필요)
  • Java: 11
    ✔️ Dependencies
  • Spring Web
  • Lombok


🏷 Hello 서블릿

스프링 부트 환경에서 서블릿을 등록하고 사용해보자 ❗️

✔️ 스프링 부트 서블릿 환경 서버

hello.servlet.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);
	}

}

hello.servlet.basic.HelloServlet 생성

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;

// '/hello'로 오면 이 코드가 실행됨
@WebServlet(name = "helloServlet", urlPatterns = "/hello")
// HttpServlet을 상속
public class HelloServlet extends HttpServlet {
    @Override
    // ctrl+O -> 자물쇠로 잠겨있는 service 클릭해서 메서드 자동 생성
    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // 단축키 : soutm
        System.out.println("HelloServlet.service");
        // 단축키 : soutv
        System.out.println("request = " + request);
        System.out.println("response = " + response);

        // 쿼리 파라미터 사용
        String username = request.getParameter("username");
        System.out.println("username = " + username);

        // 응답 찍어보기
        // setContentType : 단순문자
        response.setContentType("text/plain");
        // setCharacterEncoding : 인코딩 정보
        response.setCharacterEncoding("utf-8");
        // getWriter().write : http 바디에 메시지가 들어감
        response.getWriter().write("hello " + username);
    }
}

⬇️ http://localhost:8080/hello?username=world 라고 웹 브라우저에 입력했을 때 콘솔 실행 결과

HelloServlet.service
request = org.apache.catalina.connector.RequestFacade@5e4e72
response = org.apache.catalina.connector.ResponseFacade@37d112b6
username = world

✔️ HTTP 요청 메시지 로그로 확인하기

application.properties 설정 추가

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

📌 참고 : 운영서버에 이렇게 모든 요청 정보를 다 남기면 성능저하가 발생할 수 있기 때문에 개발 단계에서만 적용하자 !!


⬆️ 서버를 다시 시작하고, 요청해보면 서버가 받은 HTTP 요청 메시지를 출력하는 것을 확인할 수 있다❗️


✔️ welcome 페이지 추가하기

📌 webapp 경로에 index.html 을 두면 http://localhost:8080 호출시 index.html 페이지가 열린다

main/webapp/index.html 생성
main/webapp/basic.html 생성


⬆️ 웰컴 페이지와 베이직 페이지 완성👍🏻



🏷 HttpServletRequest - 개요

✔️ HttpServletRequest 역할

  • HTTP 요청 메시지를 개발자가 직접 파싱해서 사용해도 되지만, 매우 불편할 것이다.
  • 서블릿은 개발자가 HTTP 요청 메시지를 편리하게 사용할 수 있도록 개발자 대신에 HTTP 요청 메시지를 파싱한다.
  • 그리고 그 결과를 HttpServletRequest 객체에 담아서 제공한다!

HttpServletRequest를 사용하면 ⬇️아래⬇️와 같은 HTTP 요청 메시지를 편리하게 조회할 수 있다!

POST /save HTTP/1.1
Host: localhost:8080
Content-Type: application/x-www-form-urlencoded
username=kim&age=20

  • START LINE
    • HTTP 메소드
    • URL
    • 쿼리 스트링
    • 스키마, 프로토콜
  • 헤더
    • 헤더 조회
  • 바디
    • form 파라미터 형식 조회
    • message body 데이터 직접 조회

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



🏷 HttpServletRequest - 기본 사용법

HttpServletRequest가 제공하는 기본 기능들을 알아보자 !

✔️ hello.servlet.basic.request.RequestHeaderServlet 생성

package hello.servlet.basic.request;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.*;
import java.io.IOException;

//http://localhost:8080/request-header?username=hello
@WebServlet(name = "requestHeaderServlet", urlPatterns = "/request-header")
public class RequestHeaderServlet extends HttpServlet {
    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // http 요청 메시지의 가장 첫 라인인 startline의 정보
        printStartLine(request);
        // 모든 헤더 정보
        printHeaders(request);
        // 헤더 편리한 조회
        printHeaderUtils(request);
        // 기타 정보 (기타 정보는 HTTP 메시지의 정보는 아님)
        printEtc(request);
        response.getWriter().write("ok");
    }

    private void printStartLine(HttpServletRequest request) {
        System.out.println("--- REQUEST-LINE - start ---");
        System.out.println("request.getMethod() = " + request.getMethod()); // GET
        System.out.println("request.getProtocal() = " + request.getProtocol()); // HTTP/1.1
        System.out.println("request.getScheme() = " + request.getScheme()); // 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();
    }

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

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

    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-line 정보 결과

--- REQUEST-LINE - start ---
  request.getMethod() = GET
  request.getProtocal() = HTTP/1.1
  request.getScheme() = http
  request.getRequestURL() = http://localhost:8080/request-header
  request.getRequestURI() = /request-header
  request.getQueryString() = username=hello
  request.isSecure() = false
--- REQUEST-LINE - end ---

✔️ 헤더 정보 결과

 --- 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: navigate
  sec-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 편의 조회 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 ---

📌 참고
로컬에서 테스트하면 IPv6 정보가 나오는데, IPv4 정보를 보고싶으면 다음 옵션을 VM options에
넣어주면 된다!
-Djava.net.preferIPv4Stack=true



🏷 HTTP 요청 데이터 - 개요

HTTP 요청 메시지를 통해 클라이언트에서 서버로 ❗️실제 데이터❗️를 전달하는 방법을 알아보자!

1️⃣ GET - 쿼리 파라미터

  • /url?username=hello&age=20
  • 메시지 바디 없이, URL의 쿼리 파라미터에 데이터를 포함해서 전달
    예) 검색, 내용 필터, 페이징 등에서 많이 사용

2️⃣ POST - HTML Form

  • content-type: application/x-www-form-urlencoded
  • 메시지 바디에 쿼리 파리미터 형식으로 전달 username=hello&age=20
    예) 회원 가입, 상품 주문, HTML Form 사용

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

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

📌 데이터 형식은 주로 JSON 사용



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

  • username=hello
  • age=20
    ⬆️ 위와 같은 데이터를 클라이언트에서 서버로, 메시지 바디 없이! URL의 쿼리 파라미터를 이용해 데이터를 전달해보자

📌 쿼리파라미터는 URL에 ? 를 시작으로 보낼 수 있다. 추가 파라미터는 &로 구분한다.


✔️ RequestParamServlet 생성

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;

@WebServlet(name = "requestParamServlet", urlPatterns = "/request-param")
public class RequestParamServlet extends HttpServlet {
    @Override
    protected void service(HttpServletRequest request, HttpServletResponse resp) throws ServletException, IOException {

        System.out.println("[전체 파라미터 조회] - start");
          /*
          Enumeration<String> parameterNames = request.getParameterNames();
          while (parameterNames.hasMoreElements()) {
                         String paramName = parameterNames.nextElement();
              System.out.println(paramName + "=" + request.getParameter(paramName));
          */

        request.getParameterNames().asIterator()
                // request.getParameter : Kim
                .forEachRemaining(paramName -> System.out.println(paramName + "=" + request.getParameter(paramName)));
        System.out.println("[전체 파라미터 조회] - end");
        System.out.println();

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

        String age = request.getParameter("age");
        System.out.println("request.getParameter(age) = " + age);
        System.out.println();

        System.out.println("[이름이 같은 복수 파라미터 조회]");
        System.out.println("request.getParameterValues(username)");
        String[] usernames = request.getParameterValues("username");
        for (String name : usernames) {
            System.out.println("username=" + name);
        }
        resp.getWriter().write("ok");
    }
}

📌 쿼리 파라미터 조회 메서드

String username = request.getParameter("username"); //단일 파라미터 조회 Enumeration<String> parameterNames = request.getParameterNames(); //파라미터 이름들 모두 조회
Map<String, String[]> parameterMap = request.getParameterMap(); //파라미터를 Map 으로 조회
String[] usernames = request.getParameterValues("username"); //복수 파라미터 조회

⬆️ http://localhost:8080/request-param?username=hello&username=kim&age=20 로 실행했을 때의 결과 👍🏻



🏷 HTTP 요청 데이터 - POST HTML Form

이번에는 HTML의 Form을 사용해서 클라이언트에서 서버로 데이터를 전송해보자!
(주로 회원 가입, 상품 주문 등에서 사용하는 방식)

  • content-type : `application/x-www-form-urlencoded
  • 메시지 바디에 쿼리 파리미터 형식으로 데이터를 전달한다
    예) username=hello&age=20

✔️ src/main/webapp/basic/hello-form.html 생성

<!DOCTYPE html>
    <html>
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
    <form action="/request-param" method="post">
username: <input type="text" name="username" /> age: <input type="text" name="age" /> <button type="submit">전송</button>
    </form>
    </body>
    </html>

POST의 HTML Form을 전송하면 웹 브라우저는 아래의 형식으로 HTTP 메시지를 만든다!

  • 요청 URL : http://localhost:8080/request-param
  • content-type : application/x-www-form-urlencoded
  • message body : username=hello&age=20

application/x-www-form-urlencoded 형식은 앞서 GET에서 살펴본 쿼리 파라미터 형식과 같다❗️
➡️ 따라서 쿼리 파라미터 조회 메서드를 그대로 사용하면 된다

클라이언트(웹 브라우저) 입장에서는 두 방식에 차이가 있지만,
서버 입장에서는 둘의 형식이 동일하므로,
request.getParameter()편리하게 구분없이 조회할 수 있다😉

정리하면, request.getParameter()GET URL 쿼리 파라미터 형식도 지원하고, POST HTML Form 형식도 둘 다 지원한다👍

📌 Postman 으로도 실행해봤다
⬆️ 결과 구우우우웃 👍🏻



다시...돌아온다....2탄으로.......

profile
Backend Developer

0개의 댓글