2. 서블릿과 http 응답 , 요청

김현준·2023년 7월 14일

Gaounuri_Spring_Study

목록 보기
2/10

Project Code File

📌 목차

  1. 서블릿 사용하기

  2. HttpServletRequest 기본 사용법

  3. HttpServletRequest 개요

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

  5. HTTP 요청 데이터 - POST - HTML Form

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

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

  8. HttpServletResponse - 기본 사용법

  9. HTTP 응답 데이터 - 단순 텍스트, HTML

  10. HTTP 응답 데이터 - API JSON

사용 프로그램 & 정보

  • JDK : 11버전

  • postman

  • Spring : 2.7.13 버전 , War package , gradle

  • lombok

// build.gradle 

plugins {
	id 'java'
	id 'war'
	id 'org.springframework.boot' version '2.7.13'
	id 'io.spring.dependency-management' version '1.0.15.RELEASE'
}

group = 'hello'
version = '0.0.1-SNAPSHOT'

java {
	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'
}

tasks.named('test') {
	useJUnitPlatform()
}

📌 서블릿 사용하기

서블릿은 자바에서 동적 웹페이지를 만들 수 있는 기술이다.

서블릿은 톰캣 같은 웹 애플리케이션 서버를 직접 설치하고, 그 위에 서블릿 코드를 클래스 파일로 빌드해서 올린 다음, 톰캣 서버를 실행하면 된다. 하지만 이 과정은 매우 번거롭다.
스프링 부트는 톰캣 서버를 내장하고 있으므로, 톰캣 서버 설치 없이 편리하게 서블릿 코드를 실행할 수 있다.

// hello.servlet.ServletApplication

@ServletComponentScan // 서블릿 자동 등록
@SpringBootApplication
public class ServletApplication {

	public static void main(String[] args) {
		SpringApplication.run(ServletApplication.class, args);
	}
}

기존의 스프링 실행 코드에서 서블릿 자동 등록 어노테이션을 추가해준다.

//hello.servlet.basic.HelloServlet

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

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

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

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

        response.setContentType("text/plain");
        response.setCharacterEncoding("utf-8");
        response.getWriter().write("hello " + username);
    }
}

@WebServlet 애노테이션을 통해 서블릿 이름과 url 경로를 지정해줄 수 있다.
서블릿을 사용하기 위해서는 항상 HttpServlet 클래스를 상속 해야한다.

이후 service 클래스를 오버라이딩을 하고 구현을 해준다

HttpServletRequest request, HttpServletResponse response
이때 두개의 파라미터가 존재하는데 이 파라미터를 통해 여러가지 메서드를 사용할 수 있다.

image

위와같이 localhost 에 쿼리파라미터형식으로 http 요청메시지를 보내면 response.getWriter().write() 를 통해 응답메시지를 받을 수 있다.

image

resource/application.properties 경로에 logging.level.org.apache.coyote.http11=debug 내용을 추가하면 http 요청 메시지 로그를 콘솔창에서 확인할 수 있다.

📌 HttpServletRequest 기본 사용법

위와같은 http 요청 메시지를 분석하고 파싱해서 직접 응답메시지를 만들기에는 매우 어렵다. 따라서 HttpServletRequest 객체를 통해 관리가 가능하다.
HttpServletRequest 가 제공하는 기능과 메서드를 사용해보자.

//hello.servlet.basic.request.RequestHeaderServlet

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


    }

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

    //Header 모든 정보
    private void printHeaders(HttpServletRequest request) {
        System.out.println("--- Headers - start ---");
        request.getHeaderNames().asIterator()
            .forEachRemaining(headerName -> System.out.println(headerName + ": " + headerName));

        request.getHeader("host"); // 하나의 정보만 출력하기
        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();
    }

    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();
    }
}
--- 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() = username=hello
request.isSecure() = false
--- REQUEST-LINE - end ---

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

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

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

[cookie 편의 조회]
Idea-96a4e784: a6ebb691-6e80-4c17-b4b9-b0441d2e4999
Idea-e27941a4: a6ebb691-6e80-4c17-b4b9-b0441d2e4999

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

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

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

위와같은 아주 자세한 정보를 확인할 수 있다.
요청의 형식이 GET 방식이고 프로토콜, 링크 , 포트 , 언어 , 쿠기 , 인코딩 등과 같은 정보를 HttpServletRequest 객체를 통해 알 수 있다.

📌 HttpServletRequest 개요

HTTP 요청 메시지를 통해 클라이언트에서 서버로 데이터를 전송하는 방법은 3가지가 있다.

image

  • GET : 쿼리 파라미터
    • /url?username=hello&age=20
    • 메시지 바디 없이 URL 의 쿼리 파라미터를 통해 전달
    • 검색 , 필터에 쓰임
    image
  • POST : HTML form
    • content-type: application/x-www-form-urlencoded
    • 메시지 바디에 쿼리 파라미터 형식으로 전달
    • 회원가입 , 상품주문에 사용

image

  • HTTP : message body
    • HTTP API 에서 주로 사용 , JSON , XML , TEXT
    • POST , PUT , PATCH 요청만 사용가능

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

  • username = hello , age = 20 이라는 데이터를 클라이언트에서 서버로 전송해보자.

서버에서는 HttpServletRequest 가 제공하는 메서드를 통해 쿼리파라미터를 편리하게 조회할 수 있다.

//hello.servlet.basic.request.RequestParamServlet

/**
 * 1. 파라미터 전송 기능
 * http://localhost:8080/request-param?username=hello&age=20
 */

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

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

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

image

request.getParameterNames().asIterator()
.forEachRemaining(paramName -> System.out.println(paramName + "=" + request.getParameter(paramName))); 
// 전체 조회
String username = request.getParameter("username");
String age = request.getParameter("age");
// 단일 조회

이터레이터를 통해 전체 조회가 가능하고 getParameter 메서드를 통해 단일 조회가 가능하다

만약 username=hello&username=kim 처럼 중복 파라미터가 존재한다면 가장 왼쪽의 파라미터 하나만 조회가 되고 중복파라미터가 필요하다면 getParameterValues 를 통해 조회가 가능하다.

📌 HTTP 요청 데이터 - POST HTML Form

이번에는 회원가입에 주로 사용되는 HTMP Form 형식으로 데이터를 전송해보자.

<!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>

image

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

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

HTTP 메시지 바디의 데이터를 InputStream 을 사용해서 직접 읽을 수 있다.

// hello.servlet.basic.request.RequestBodyStringServlet

@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();
        String messageBody = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8);

        System.out.println("messageBody = " + messageBody);
        response.getWriter().write("ok");
    }
}

getInputStream() 메서드는 바이트 코드를 반환하기 때문에 문자로 보기 위해서는 utf-8 형식으로 변환해주어야 한다.

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

이번에는 http api 에서 주로 사용하는 json 형식으로 데이터를 전달해보자.

// hello.servlet.basic.HelloData
@Getter @Setter
public class HelloData {
    private String username;
    private int age;
}

json 형식으로 데이터를 파싱할 수 있게 클래스를 만들어 준다. 이때 lombok 을 사용하면 getter,setter 를 애노테이션으로 대체할수있다.

//hello.servlet.basic.request.RequestBodyJsonServlet;

@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 결과를 파싱해서 사용할 수 있는 자바 객체로 변환하려면 Jackson,Gson 같은 변환 라이브러리를 사용해야하는데 Spring boot 에서는 Jackson 라이브러리에서 ObjecyMapper 을 제공한다.

image

image

postman 으로 http 요청 메시지를 보내면 json 형식의 데이터를 조회하는것을 확인할 수 있다.

📌 HttpServletResponse - 기본 사용법

이번에는 http 응답 메시지를 작성해보자.

// hello.servlet.basic.reponse.ResponseHeaderServlet;

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

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

        // [Message body]
        PrintWriter writer = response.getWriter();
        writer.println("ok");

    }

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

}

먼저 response.setStatus(HttpServletResponse.SC_OK) 를 통해 상태 코드를 지정할 수 있다. OK 는 200 을 의미하는데 상수로 적어도 무관하다.

response.setHeader("Content-Type", "text/plain;charset=utf-8) 와 같이 setHeader 메서드를 톨해 Content-Type 를 지정할 수 있다.

쿠키를 통해 생명주기와 인코딩 설정을 할 수 있으며 리다이렉트 기능을 제공한다.

📌 HTTP 응답 데이터 - 단순 텍스트, HTML

HTTP 에서 응답 데이터를 HTML 로 해보자. 단순 텍스트는 writer.println("ok") 와 같은 형식으로 응답할 수 있다.

// hello.servlet.basic.reponse.ResponseHtmlServlet;

import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

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

image

위와같이 html 코드를 응답메시지로 사용할 수 있다. 실제 화면에서는 html 코드가 보이지 않는다.

📌 HTTP 응답 데이터 - API JSON

이번에는 json 형식의 데이터를 응답메시지로 보내보자. 이때 content-typeapplication/json 로 지정해야한다. Jackson 라이브러리가 제공하는 objectMapper.writeValueAsString() 를 사용하면 객체를 json 문자로 변경할 수 있다.

//hello.servlet.basic.reponse.ResponseJsonServlet;

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

image

위와같은 json 형식의 데이터를 응답함을 확인할 수 있다.

지금까지 Servlet 을 통해 http 요청메시지와 응답메시지에 대해 알아보았다.

profile
울산대학교 IT융합학부

0개의 댓글