Servlet

유병익·2022년 10월 17일
0
post-thumbnail

1. 프로젝트 생성


1.1 사전 준비물


  • 프로젝트 선택
    • Project: Gradle Project
    • Spring Boot: 최신 버전 선택
    • Language: Java
  • Project Metadata
    • Group: hello
    • Artifact: servlet
    • Name: servlet
    • Package name: hello.servlet
    • Packaging: War (주의!)
    • Java: 11
    • Dependencies: Spring Web, Lombok
  • build.gradle 설정
    plugins {
    		id 'org.springframework.boot' version '2.4.3'
    		id 'io.spring.dependency-management' version '1.0.11.RELEASE'
    		id 'java'
    		id 'war'
    }
    group = 'hello'
    version = '0.0.1-SNAPSHOT'
    sourceCompatibility = '11'
    configurations {
    		compileOnly {
    				extendsFrom annotationProcessor
    		}
    }
    repositories {
    		mavenCentral()
    }
    dependencies {
    		implementation 'org.springframework.boot:spring-boot-starter-web'
    		compileOnly 'org.projectlombok:lombok'
    		annotationProcessor 'org.projectlombok:lombok'
    		providedRuntime 'org.springframework.boot:spring-boot-starter-tomcat'
    		testImplementation 'org.springframework.boot:spring-boot-starter-test'
    }
    test {
    		useJUnitPlatform()
    }

2. HttpServletRequest


2.1 역할


  • Servlet은 개발자 대신 HTTP 요청 메시지 파싱
  • 그 결과를 HttpServletRequest 객체에 담아서 제공
💡 HttpServletRequest를 사용하면 HTTP 요청 메시지를 편리하게 조회할 수 있다!
  • 참고
📌 HttpServletRequest, HttpServletResponse 기능에 대해서 깊이 있는 이해를 하려면 HTTP 스펙이 제공하는 요청, 응답 메시지 자체를 이해해야 한다.

2.2 사용법


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 {
				 printStartLine(request);
				 printHeaders(request);
				 printHeaderUtils(request);
				 printEtc(request);
				 response.getWriter().write("ok");
		 }
}

2.2.1 start-line 정보 조회


  • Code
    //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.getProtocal() = " + 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();
    }
  • Result
    --- 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 ---

2.2.2 전체 Header 정보 조회


  • Code
    //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();
    }
  • Result
    --- 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 ---

2.2.3 Header 편리한 조회


  • Code
    //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();
    }
  • Result
    --- 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 ---

2.2.4 기타 정보 조회


  • Code
    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();
    }
  • Result
    --- 기타 조회 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 ---

💡 지금까지 HttpServletRequest를 사용해서 HTTP Message의 start-line, header 정보 조회 방법을 학습했다. 이제 HTTP 요청 데이터를 어떻게 조회하는지 알아보자.

3. HTTP Request Data


📌 HTTP 요청 메시지를 통해 클라이언트에서 서버로 데이터를 전달하는 방법을 알아보자.
  • 주로 다음 3가지 방법을 사용한다.

3.1 GET - Query Parameter


  • URL의 Query Parameter에 데이터를 포함해서 전달

    • 예시) /url?username=hello&age=20
  • 검색, 필터, 페이징 등에서 많이 사용하는 방식

  • Query Parameter 조회 Methods

//단일 파라미터 조회
String username = request.getParameter("username");

//파라미터 이름들 모두 조회
Enumeration<String> parameterNames = request.getParameterNames();

//파라미터를 Map 으로 조회 
Map<String, String[]> parameterMap = request.getParameterMap(); 

//복수 파라미터 조회
String[] usernames = request.getParameterValues("username"); 

❓ 파라미터 이름은 하나인데 값이 중복이면? ex) username=hello&username=kim

request.getParameterValues() 사용

📌중복일 때 request.getParameter() 사용

request.getParameterValues()첫 번째 값 반환

3.2 POST - HTML Form


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

  • Message Body에 Query Parameter 형식으로 전달

    • ex) username=hello&age=20
  • 회원 가입, 상품 주문, HTML Form 사용

  • 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의 Query Parameter 형식
  • Query Parameter 조회 Method를 그대로 사용하면 됨
    • request.getParameter() → 두 방식 구분 없이 조회 가능

📌 content-type은 HTTP Message Body의 데이터 형식을 지정한다.

  • GET URL Query Parameter 형식으로 데이터를 전달
    • HTTP Message Body 사용 X
    • content-type이 없다.
  • POST HTML Form 형식으로 데이터를 전달
    - HTTP Message Body에 해당 데이터를 포함해서 보냄
    - HTTP Message Body에 포함된 데이터가 어떤 형식인지 content-type 지정 필수
    - Form으로 데이터를 전송하는 형식 = application/x-www-form-urlencoded

❗ HTTP Message Body 에 Form Data 형식으로 전송하는 것은 POST Method만 가능!!

3.3 HTTP Message Body


  • HTTP API에서 주로 사용, JSON, XML, TEXT
  • 데이터 형식은 주로 JSON 사용
  • POST, PUT, PATCH

3.3.1 HTTP Request Data - API Message Body - 단순 텍스트


  • Code
    package hello.servlet.basic.request;
    import org.springframework.util.StreamUtils;
    import javax.servlet.ServletException;
    import javax.servlet.ServletInputStream;
    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.nio.charset.StandardCharsets;
    
    @WebServlet(name = "requestBodyStringServlet", urlPatterns = "/request-bodystring")
    public class RequestBodyStringServlet extends HttpServlet {
    		 
    		 @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);
    				 response.getWriter().write("ok");
    		 }
    }
  • HTTP Request 전송

  • 결과
messageBody = hello
📌 HTTP Message Body의 데이터를 InputStream을 사용해서 직접 읽을 수 있음

3.3.2 HTTP Request Data - API Message Body - JSON


  • Code
    package hello.servlet.basic.request;
    import com.fasterxml.jackson.databind.ObjectMapper;
    import hello.servlet.basic.HelloData;
    import org.springframework.util.StreamUtils;
    import javax.servlet.ServletException;
    import javax.servlet.ServletInputStream;
    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.nio.charset.StandardCharsets;
    /**
     * http://localhost:8080/request-body-json
     *
     * JSON 형식 전송
     * content-type: application/json
     * message body: {"username": "hello", "age": 20}
     *
     */
    @WebServlet(name = "requestBodyJsonServlet", urlPatterns = "/request-bodyjson")
    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");
    		 }
    }
  • HTTP Request 전송

  • 결과
messageBody={"username": "hello", "age": 20}
data.username=hello
data.age=20
📌 참고
  • JSON 결과를 파싱해 자바 객체로 변환 → JSON 변환 라이브러리를 추가해서 사용
    • ex)Jackson, Gson
  • Spring MVC를 선택하면 기본으로 Jackson 라이브러리(ObjectMapper)를 함께 제공

4. HttpServletResponse & HTTP Response Data


4.1 HttpServletResponse 역할


  • HTTP Response Message 생성
  • HTTP Response Code 지정
  • Header 생성
  • Body 생성
  • 편의 기능 제공
    • ex) Content-Type, Cookie, Redirect

4.2 HttpServletResponse 사용법


4.2.1 HTTP Response Data - 단순 텍스트


  • Code
    package hello.servlet.basic.response;
    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.io.PrintWriter;
    /**
     * http://localhost:8080/response-header
     *
     */
    @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
    				 //[response-headers]
    				 response.setHeader("Content-Type", "text/plain;charset=utf-8");
    				 response.setHeader("Cache-Control", "no-cache, no-store, mustrevalidate");
    				 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");
    		 }
    }
  • Content 편의 Method
    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); //(생략시 자동 생성)
    }
  • Cookie 편의 Method
    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 편의 Method
    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");
    }

4.2.2 HTTP Response Data - HTML


  • Code
    package hello.servlet.basic.response;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.io.PrintWriter;
    
    @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 응답으로 HTML을 반환할 때는 content-type을 text/html로 지정해야함!!

4.2.3 HTTP Response Data - JSON


  • Code
    package hello.servlet.basic.response;
    import com.fasterxml.jackson.databind.ObjectMapper;
    import hello.servlet.basic.HelloData;
    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;
    /**
     * http://localhost:8080/response-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.setHeader("content-type", "application/json");
    				 response.setCharacterEncoding("utf-8");
    				 HelloData data = new HelloData();
    				 data.setUsername("kim");
    				 data.setAge(20); //{"username":"kim","age":20}
    				 String result = objectMapper.writeValueAsString(data);
    				 response.getWriter().write(result);
    		 }
    }

📌 HTTP 응답으로 JSON을 반환할 때content-type을 application/json로 지정해야함!!

📌 Jackson LibararyobjectMapper.writeValueAsString()

  • 객체를 JSON 문자로 변경할 수 있음
profile
Backend 개발자가 되고 싶은

0개의 댓글