본문 바로가기
Spring

HTTP 요청 데이터 정리 / Framework없이 순수 Java로 web MVC 구현해보기

by 리승우 2022. 12. 16.

Framework없이 순수 Java만으로 Web MVC를 구현하려고 하는 중이다.

그러기 위해서 Dispatcher Servlet을 스스로 구현해야 하는 상황인데, HTTP 요청 데이터에 관한 내용도 숙지해놓아야 하기에 공부를 시작하는 중이다!!

 

https://github.com/iswoos/no_DispatcherServlet_java

 

GitHub - iswoos/no_DispatcherServlet_java: 순수 Java로 Servlet 이용하기

순수 Java로 Servlet 이용하기. Contribute to iswoos/no_DispatcherServlet_java development by creating an account on GitHub.

github.com

 

 

HttpServletRequest 역할

HTTP 요청 메시지를 개발자가 직접 파싱해서 사용해도 되지만, 매우 불편할 것이다.

서블릿은 개발자가 HTTP 요청 메시지를 편리하게 사용할 수 있도록 개발자 대신에 HTTP 요청 메시지를 파싱한다.

그리고 그 결과를 HttpServletRequest 객체에 담아서 제공한다

 

HttpServletRequest, HttpServletResponse를 사용할 때 가장 중요한 점은

이 객체들이 HTTP 요청 메시지, HTTP 응답 메시지를 편리하게 사용하도록 도와주는 객체라는 점이다.

따라서 이 기능에 대해서 깊이있는 이해를 하려면 HTTP 스펙이 제공하는 요청, 응답 메시지 자체를 이해해야 한다

 

 

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 사용

 

> POST- HTML Form 그림예시

 

 

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

- HTTP API에서 주로 사용, JSON, XML, TEXT

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

- POST, PUT, PATCH

 

여기서 조금 더 자세히, 각자에 대해서 알아보자!

 

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

다음 데이터를 클라이언트에서 서버로 전송해보자.

 

전달 데이터

- username=hello

- age=20

 

메시지 바디 없이, URL의 쿼리 파라미터를 사용해서 데이터를 전달하자.

예) 검색, 필터, 페이징등에서 많이 사용하는 방식

 

쿼리 파라미터는 URL에 다음과 같이 ? 를 시작으로 보낼 수 있다. 추가 파라미터는 & 로 구분하면 된다.

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

 

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

 

 

쿼리 파라미터 조회 메서드

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;

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

 * 2. 동일한 파라미터 전송 가능
 * http://localhost:8080/request-param?username=hello&username=kim&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");
        
        /* 옛날방식 전체 파라미터 조회
             Enumeration<String> parameterNames = request.getParameterNames();
             while (parameterNames.hasMoreElements()) {
                 String paramName = parameterNames.nextElement();
                 System.out.println(paramName + "=" +
        request.getParameter(paramName));
             }
        */
        
        
        request.getParameterNames().asIterator()
                .forEachRemaining(paramName -> System.out.println(paramName + "=" + request.getParameter(paramName)));
        System.out.println("[전체 파라미터 조회] - end");
        
        
        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);
        }

        // 웹 브라우저 출력화면에 ok가 노출!
        response.getWriter().write("ok");

    }
}

 

[출력값]

http://localhost:8080/requests-param?username=hello&age=20&username=kim

 

 

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>

 

해당 코드를 실행하고 

http://localhost:8080/basic/hello-form.html 로 들어가면 아래와 같이 입력하는 칸이 나오게 된다.

입력을 한 뒤 전송을 누르면? 

 

기존에 만들어두었던 /request-param으로 이동되며, Form Data에 데이터가 기입되게 된다.

 

 

application/x-www-form-urlencoded 형식은 앞서 GET에서 살펴본 쿼리 파라미터 형식과 같기에,

쿼리 파라미터 조회 메서드를 그대로 사용하면 된다.

 

클라이언트(웹 브라우저) 입장에서는 두 방식에 차이가 있지만, 서버 입장에서는 둘의 형식이 동일하므로,

request.getParameter() 로 편리하게 구분없이 조회할 수 있다.

 

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

형식도 둘 다 지원한다.

 

※ 참고사항 ※

- content-type은 HTTP 메시지 바디의 데이터 형식을 지정한다.

- GET URL 쿼리 파라미터 형식으로 클라이언트에서 서버로 데이터를 전달할 때는 HTTP 메시지 바디를 사용하지 않기 때문에 content-type이 없다.

- POST HTML Form 형식으로 데이터를 전달하면 HTTP 메시지 바디에 해당 데이터를 포함해서 보내기 때문에 바디에 포함된 데이터가 어떤 형식인지 content-type을 꼭 지정해야 한다. 이렇게 폼으로 데이터를 전송하는 형식을 application/x-www-form-urlencoded 라 한다.

 

 

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

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

- HTTP API에서 주로 사용, JSON, XML, TEXT

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

- POST, PUT, PATCH

 

- 먼저 가장 단순한 텍스트 메시지를 HTTP 메시지 바디에 담아서 전송하고, 읽어보자.

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

 

RequestBodyStringServlet

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-body-string")
public class RequestBodyStringServlet extends HttpServlet {
    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

        // 메시지 바디의 내용을 바이트 코드로 바로 얻을 수 있음.
        ServletInputStream inputStream = request.getInputStream();

        // 바이트 코드를 UTF-8 스트링으로 변환
        String messageBody = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8);

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

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

 

포스트맨으로 전송 시 아래와 같이 뜬다!

 

메시지 바디는 hello! 로 잘 나온다.

 

 

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

이번에는 HTTP API에서 주로 사용하는 JSON 형식으로 데이터를 전달해보자.

 

JSON 형식 전송

- POST http://localhost:8080/request-body-json

- content-type: application/json

- message body: {"username": "hello", "age": 20}

- 결과: messageBody = {"username": "hello", "age": 20}

 

 

JSON 형식 파싱 추가

JSON 형식으로 파싱할 수 있게 객체를 하나 생성하자

hello.servlet.basic.HelloData

package hello.servlet.basic;

import lombok.Getter;
import lombok.Setter;

// Lombok 라이브러리를 이용하여 Getter Setter 설정
@Getter
@Setter
public class HelloData {

    private String username;
    private int age;

}

 

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-body-json")
public class RequestBodyJsonServlet extends HttpServlet {

	// jackson의 ObjectMapper 사용하여 HelloData 객체와 연결
    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 같은 JSON 변환 라이브러리를 추가해서 사용해야 한다. 스프링 부트로 Spring MVC를 선택하면 기본으로 Jackson 라이브러리( ObjectMapper )를 함께 제공한다.

댓글