Lined Notebook

[스프링 MVC - 백엔드 웹 개발 기술] 07. HTTP 요청 방식 - GET, POST, HTTP API

by ymkim

01. HTTP 요청 데이터 - 개요

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

즉, 데이터를 전달하는 방식에 대해 숙지 한다.

01-1. 주로 다음 3가지 방법을 사용

GET, POST, HTTP API

  • 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

 

간단히 정리를 하고 가자면 Client에서 Server에 데이터를 요청하는 방식GET, POST, HTTP API 3가지 방식이 존재한다. GET은 쿼리 파라미터를 사용, POST는 Body에 데이터를 넣어 전송, HTTP API는 Body에 JSON형태의 데이터를 넣어 서버에 요청하는 식으로 동작한다.

 

POST - HTML Form 예시

  • POST 방식에서 서버에 요청하는 방식

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

이번 시간에는 위에서 말한 GET 요청에 대해 알아보자. 다음 데이터를 클라이언트에서 서버로 전송한다 가정한다.

전달 데이터

<http://localhost:8080/user?username=info&age=20>
  • username=info
  • age=20

조회 방식

메시지 바디 없이, URL의 쿼리 파라미터를 사용해서 데이터를 전달하자. 예) 검색, 필터, 페이징 등에서 많이 사용하는 방식 쿼리 파라미터는 URL에 다음과 같이 ‘?’ 를 시작으로 보낼 수 있다. 추가 파라미터‘&’ 로 구분하면 된다.

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

02-1. 대표적 쿼리 파라미터 조회 메서드

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

request.getParameterNames 사용

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>
 */
@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(); // 전체 파라미터 조회
        request.getParameterNames().asIterator()
                .forEachRemaining(paramNameKey -> System.out.println(paramNameKey + " = " + request.getParameter(paramNameKey))); // paramNameKey -> key

        System.out.println("[전체 파라미터 조회] - end");
    }
}
  • request.getParameterNames().asIterator() 메서드를 사용하여 모든 파라미터 출력
  • paramNameKey가 → username=ymkim 가 된다

request.getParameterNames 응답

2023-05-14 13:49:38.590  INFO 3756 --- [io-8080-exec-10] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring DispatcherServlet 'dispatcherServlet'
2023-05-14 13:49:38.590  INFO 3756 --- [io-8080-exec-10] o.s.web.servlet.DispatcherServlet        : Initializing Servlet 'dispatcherServlet'
2023-05-14 13:49:38.591  INFO 3756 --- [io-8080-exec-10] o.s.web.servlet.DispatcherServlet        : Completed initialization in 1 ms
[전체 파라미터 조회] - start
username = ymkim
age = 31
[전체 파라미터 조회] - end

request.getParameter 사용

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

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

        System.out.println("[단일 파라미터 조회] - end");
        System.out.println();
    }
}
  • request.getParameter() 메서드를 사용하여 단일 파라미터 출력
  • Client에서 요청한 Key(username, age) 값을 기반으로 데이터 반환

request.getParameter 응답

[단일 파라미터 조회] - start
username = ymkim
age = 31
[단일 파라미터 조회] - end

그렇다면 아래와 같이 하나의 Key(username) 값에 복수 파라미터가 들어오는 경우는 어떻게 해결을 해야할까?

다음 예시를 한번 살펴보자.

// username=aaa는 복수 파라미터 방식이다
<http://localhost:8080/request-param?username=hello&age=20&username=aaa>

이때는 복수 파라미터를 처리하는 request.getParameterValues() 를 사용하면 된다.

request.getParameterValues 사용

String[] usernames = request.getParameterValues("username"); // 복수 파라미터 처리
for (String name : usernames) {
    System.out.println("username = " + name);
}

request.getParameterValues 응답

[이름이 같은 복수 파라미터 조회] - start
username = ymkim
username = hello2
[이름이 같은 복수 파라미터 조회] - end

02-2. 복수 파라미터에서 단일 파라미터 조회

우선 다음과 같이 사용하는 경우는 거의 희박하기에 참고만 하면 될 것 같다.

  • username=hello&usernage=kim 과 같이 파라미터 이름은 하나인데 값이 중복이면 어떻게 해야 할까?
  • request.getParameterValues()를 사용해야 한다
  • 만약 request.getParameter(”username”)을 사용하면 우선 순위에 의해 첫 번째 값을 반환한다

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

이번에는 HTML Form을 사용해서 클라이언트에서 서버로 데이터를 전송해보자.

주로 회원 가입, 상품 주문URL에 노출이 되면 안되는 경우 주로 사용이 된다.

특징

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

03-1. hello-form.html 생성

<!DOCTYPE html>
<html lang="en">
<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>
  • username, age를 서버에 전송하는 Form 양식을 작성하였다
  • http://localhost:8080/request-param URL 요청 시 서버에 요청을 수행
  • 서버에 요청을 하면 다음과 같은 그림이 나온다
# POST 요청 시
요청 URL : <http://localhost:8080/request-param>
content-type : application/x-www-form-urlencoded // 요청 타입 형식 지정
message body : username=hello&age=20

근데..? 우리가 아까 만든 서블릿에서는 GET, POST 요청이 들어올 것이라고 지정해준 부분이 존재하지 않았는데 위와 같이 그냥 요청을 해도 되는건가? 이에 대한 답은 1,2번을 살펴보자.

  1. request.getParameter() 는 GET URL 쿼리 파리미터, POST HTML Form 형식도 둘 다 지원한다.
  2. 서블릿은 클라이언트가 요청을 수행하면 GET 또는 POST 요청에 따라서 doGet(), doPost() 함수로
    요청을 분기하기 때문에 상관이 없다.

서블릿 생명 주기

  • 우선 서버(WAS → Tomcat)는 메모리(Memory)에 서블릿이 존재하는지 확인 한다
  • 만약 메모리에 서블릿(Servlet)이 존재한다면 service() 메서드를 호출한다
  • 만약 메모리에 서블릿(Servlet)이 존재하지 않는다면 다음 플로우를 수행 한다
    • init() 메서드를 통해 서블릿 초기화 및 생성을 진행
    • 후에 생성된 서블릿의 service() 메서드를 호출
    • GET, POST 요청에 따라서 doGet(), doPost() 함수로 요청을 분기
  • 웹 애플리케이션이 종료 되거나, WAS가 갱신되는 경우 destroy() 메서드 호출
    • 메모리(Memory)에서 해당(사용 된) 서블릿(Servlet)을 제거 한다
  • 서블릿을 초기화(init) 하고 생성(create) 하는것은 많은 비용이 드는 작업이다
    • WAS(톰캣) 서블릿을 미리 생성하여 메모리(Memory)에 적재해두고 요청에 따라서
      클라이언트에게 응답 하는 방식으로 사용한다

테스트 방법

그러면 테스트 할때마다 일일이 HTML Form 하나 만들고 요청하고 또 하나 만들고 요청해야 하는건가?
→ 그건 아니다, 다음과 같은 curl 툴을 사용해 테스트가 가능하다.

  • curl 테스트
  • PostMan을 통한 테스트

04. HTTP 요청 데이터 - API 메시지 바디 문자

  • HTTP message Body에 데이터를 직접 담아서 요청
    • HTTP API에서 주로 사용 JSON, XML, TEXT
    • 데이터 형식은 주로 JSON
      • Front-end 개발자가 해당 데이터를 가져다 써야 하기에 JSON을 주로 사용
    • POST, PUT, PATCH
  • 먼저 가장 단순한 TEXT로 요청을 해보고 후에 JSON 요청을 수행 해보자

04-1. 대표적 POST - Body string message 파싱 방법

ServletInputStream inputStream = request.getInputStream();

request.getInputStream 사용

package hello.servlet.basic.request;

...

@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(); // parsing message body, 메시지 바디의 내용을 바이트코드(Byte Code)로 얻을 수 있다
        String messageBody = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8);// Spring이 제공하는 유틸리티 StreamUtils 클래스 사용, 바이트 -> String 변환

        System.out.println("messageBody = " + messageBody);
        response.getWriter().write("POST - parsing body is ok");
    }
}
  • request.getInputStream() 사용하면 Body 정보를 바이트코드(Byte Code)로 읽을 수 있다
  • 후에 StreamUtils 클래스를 사용하여 바이트코드(Byte Code)를 String로 변환 가능
  • 위와 같이 POST - Form String 요청 시 request 정보를 읽을 수 있다

request.getInputStream 응답

2023-05-14 14:57:34.556  INFO 25408 --- [           main] hello.servlet.ServletApplication         : Started ServletApplication in 2.569 seconds (JVM running for 6.744)
messageBody = ----------------------------699367090724136295927018
Content-Disposition: form-data; name="username"

kim
----------------------------699367090724136295927018
Content-Disposition: form-data; name="age"

20
----------------------------699367090724136295927018--
  • System.out.println("messageBody = " + messageBody) 결과는 위와 같다

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

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

05-1. JSON 형식 전송

  • POST http://locahost:8080/request-body-json
  • content-type: application/json
  • message body { “username“ : “kim“, “age“ : 10 }
  • 결과: messageBody = { ”username: “hello”, “age”: 20 }

JSON 형식 파싱 추가

  • JSON 형식 파싱을 위해 객체 생성

HelloData

package hello.servlet.basic;

import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
public class HelloData {

    private String username;
    private String age;

    /*public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getAge() {
        return age;
    }

    public void setAge(String age) {
        this.age = age;
    }*/
}
  • username, age를 갖는 클래스 생성
  • @Getter, @Setter 롬복 어노테이션 사용

RequestBodyJsonServlet

package hello.servlet.basic.request;

...

@WebServlet(name = "requestBodyJsonServlet", urlPatterns = "/request-body-json")
public class RequestBodyJsonServlet 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);
    }
}
  • Body 정보 읽는 방법은 동일하다
  • 다음으로는 Postman을 사용해서 POST - JSON 요청을 해보자

Postman

  • 요청 시 200 HTTP Code 반환 확인
  • 콘솔 로그 결과는 다음과 같다
2023-05-14 15:12:15.012  INFO 24392 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
2023-05-14 15:12:15.029  INFO 24392 --- [           main] hello.servlet.ServletApplication         : Started ServletApplication in 2.455 seconds (JVM running for 6.928)
messageBody = {
    "username": "kim",
    "age": 20
}

스프링은 기본적으로 Jackson이라는 라이브러리를 지원한다.
Jackson을 사용해서 JSON을 자동으로 파싱하도록 수정 해보자.

@WebServlet(name = "requestBodyJsonServlet", urlPatterns = "/request-body-json")
public class RequestBodyJsonServlet extends HttpServlet {

    private ObjectMapper mapper = 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 = mapper.readValue(messageBody, HelloData.class);
        System.out.println("helloData.username = " + helloData.getUsername());
        System.out.println("helloData.age = " + helloData.getAge());

        response.getWriter().write("ok");
    }
}
  • ObjectMapper(Jackson) 라이브러를 사용해서 직렬화 수행
  • 결과는 다음과 같다
2023-05-14 15:20:58.181  INFO 4116 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : Completed initialization in 0 ms
messageBody = {
    "username": "kim",
    "age": 20
}
// 아래 부분
helloData.username = kim
helloData.age = 20

 

블로그의 정보

기록하고, 복기하고

ymkim

활동하기