Web/Basic

[Web] HTTP 메서드 (1) GET / POST 방식 + 멱등성, HTTP 요청바디

생각많은 프로그래머 2024. 2. 27. 23:24

보통 자원을 조회하거나 검색할 때는 GET 방식을,

자원을 생성(등록)하는 경우 POST 방식을 사용한다.

 

하지만 언제나 그런 것은 아니다.

그렇기 때문에 상황과 기능에 맞는 메서드를 지정하기

위해서 이번에는 GET과 POST 메서드의 상세한 특징에

대해 주요 키워드와 함께 살펴보고자 한다. 

 

'안전한' 메서드와 '멱등성'

 

GET 메서드를 사용할 경우에는

해당 요청이 '안전한 요청'이어야 한다. 

여기서 '안전하다'는 의미는 해당 요청이

서버의 자원 상태를 변경하지 않는 것을 말한다.

 

* HTTP/1.1 명세 문서를 참고하면,

GET, HEAD, OPTIONS가 각각

안전한 메서드라고 나와 있다.

 

GET 메서드 API를 '안전한 메서드'로 만드는

것도 매우 중요하다. 왜냐하면 GET 메서드는

특히 웹 브라우저에서 특정 URL에 접속하기만 해도

요청이 날아갈 수 있기 때문이다.

실수로도 얼마든지 요청이 날아갈 수 있으므로

자원의 상태를 변경하는 API에 GET 메서드를

사용하는 것은 위험한 API 설계이다.

 

GET은 대표적으로 멱등성이 있는 메서드이고,

POST 멱등성이 없는 메서드에 해당한다.

 

*  멱등성이란? 

 

한 번 호출한 것과 여러 번 호출한 것이 같은 자원의

상태를 가지는 것을 의미한다.

 

POST로 생성 요청을 보내면,

한 번 보냈을 때와 비교해서 두 번, 세 번, 계속 보내도

보낼 때마다 자원이 계속해서 하나씩 늘어나므로

POST는 대표적인 멱등성이 없는 메서드이다.

 

반면 멱등성이 있는 메서드로는 GET, PUT, DELETE가 있다.

GET은 안전한 메서드이면서 동시에 멱등성이 있는

메서드에 해당한다. 주로 데이터를 조회하거나 HTML 문서를

요청하는 경우 GET 메서드를 사용하기 때문에 호출해도

자원의 상태가 바뀌지 않는다.

 

PUT과 DELETE 역시 멱등성이 있는 메서드인데,

한 번 수정 요청을 한 경우, 이후 같은 수정 요청을 해도

해당 데이터는 계속 자원 상태를 유지하기 때문이다.

한 번 삭제 요청을 한 경우에도 이후 같은 삭제 요청을

반복해도 자원의 상태는 삭제 상태로, 이전과 달라지지 않는다.

 

예를 들어 3번 게시글을 삭제해달라고 요청하면,

이후 계속해서 3번 게시글을 삭제해달라고 해도

이미 삭제된 상태에서 없는 3번 게시글을 삭제해달라고

하는 요청은 결국 자원의 상태에 아무런 영향을 주지 않는다.

 

변경 역시 2번 게시글의 제목을 '새 제목'으로변경해달라고

요청한 뒤에 같은 내용으로 제목을'새 제목'으로 변경해달라고

요청해도 제목은이전과 같은 상태로 '새 제목'이라고 나와 있기

때문에 자원 상태에 변화가 없다고 본 것이다.

 

GET 메서드 사용 시 주의할 점

 

 GET 메서드는 HTTP 요청에서

주로 쿼리 매개변수를 사용하여 데이터를 전송한다. 

즉, URL의 일부로 데이터를 전송하는 방식을 말한다.

(쿼리 파라미터 혹은 쿼리 스트링이라고 한다.

(참고로 이때 쿼리문자열에 포함되어 전송되기

때문에 길이의 제한이 있다. 2^8미만으로

최대 255자까지 저장가능하다.)

 

쿼리 파라미터의 키(key)와 값(value)은 '='으로 구분되고

여러 개의 쿼리 파라미터를 전달할 때는 '&'으로

묶인다. 예) https://www.google.com/search?q=123&n=456&...

 

이 외에도 데이터를 경로(Path)의 일부인 패스 베리어블(Path variable)

전달하는 방법도 있다. 예) https://www.google.com/article/123

 

중요한 점은 GET 메서드의 경우 HTTP 요청 바디를

사용하는 것이 적절하지 않다는 점이다.

HTTP 요청의 바디에 데이터를 담아 전송하는 것은

일반적으로 GET 메서드의 사용 방식에 부합하지 않는다.

만약 데이터를 전송해야 한다면 POST나 다른 메서드를 고려해야 한다.

 

 GET 메서드의 경우 HTTP 요청 바디를 사용하는 것이

적절하지 않은 이유는 다음과 같이 3가지로 들 수 있다. 

 

1. 캐싱 관련

GET 요청은 캐싱될 수 있다. 캐싱은 같은 요청이 반복될 때

서버에 다시 요청하지 않고 이전에 받은 응답을 사용하여 

성능을 향상시키는 것을 말한다.

GET 요청에서는 쿼리 매개변수가 URL에 포함되므로

캐시가 요청을 식별하고 저장할 수 있습니다.

반면에 요청 바디는 캐시 키로 사용되지 않기 때문에

캐싱의 이점을 제공받지 못할 수 있다.

 

2. 북마크 관련 

GET 요청은 브라우저의 주소 표시줄에 직접 표시될 수 있다.

이는 사용자가 해당 URL을 북마크하거나 공유할 때 편리하다.

그러나 요청 바디에 데이터를 담으면 URL이 길어지고

가독성이 떨어지며, 북마크를 하기 어려울 수 있다.

 

3. 보안 관련

일반적으로 GET 요청에서는 데이터가 URL에 노출되므로

민감한 데이터를 전송하는 데 적합하지 않다.

URL은 브라우저 히스토리나 서버 로그와 같은 장소에

노출될 수 있으므로 보안상의 이슈가 발생할 수 있다.

따라서 GET 대신에 다른 메서드, 예를 들어 POST를 사용하여

요청 바디를 사용하면 데이터가 URL에 노출되지 않고

HTTP 요청의 일부로 전송되므로 보안적인 측면에서 더 안전하다.

 

HTTP 요청 바디

 

HTTP 요청 바디는 HTTP 요청 메시지의 일부로서,

클라이언트가 서버에게 전달하려는 데이터를 포함한다.

이 데이터는 일반적으로 HTML 폼 데이터, JSON, XML 등의

구조화된 정보에 해당한다. 요청 바디는 HTTP 메서드와

함께 사용되며, 주로 POST 및 PUT 메서드와 함께 사용된다.

 

요청 바디의 내용은 요청 헤더와 별도로 전송되며,

일반적으로 텍스트 또는 이진 데이터의 형태로 전송된다.

클라이언트가 서버로 데이터를 보내는 경우,

요청 바디를 사용하여 해당 데이터를 포함시키게 한다.

이는 웹 애플리케이션에서 사용자가 제출한 양식 데이터를

서버로 전송하거나, API를 통해 데이터를 전송하는 등의 경우에 사용된다.

 

요청 바디의 내용은 Content-Type 헤더에 의해 정의된다.

이 헤더는 요청 바디의 데이터 유형(예: 텍스트 또는 이진)과

형식(예: JSON, XML 등)을 명시한다.

따라서 요청을 처리하는 서버는 Content-Type 헤더를

확인하여 요청 바디의 데이터를 올바르게 해석한다.

 

HTML 요청 바디를 사용하는 경우

 

1. 데이터의 크기가 크거나 민감한 경우

GET 요청은 URL에 데이터를 노출시키므로, 민감한 경우,

즉 보안이 필요한 경우 요청 바디를 사용하여 데이터를

전송하는 것이 적절하다. 왜냐하면 POST방식의 HTTP 요청은

브라우저에 의해 캐시되지 않으며 브라우저 히스토리에도 남지 않기 때문이다.

만약  데이터가 크다면/길이가 길다면, POST방식으로 요청하는 것이 적합한데,

그 이유는 POST방식의 HTTP요청에 의한 데이터는 쿼리문자열과는 별도로

전송되기 때문이다. 따라서 데이터의 길이에 대한 제한이 없다. 

 

2. 서버에 상태 변경이 필요한 경우

데이터를 서버로 전송하여 상태 변경이 필요한 경우에는

요청 바디를 사용하는 것이 일반적이다.

예를 들어, 사용자가 로그인할 때는 사용자 이름과

비밀번호를 요청 바디에 담아 POST 요청을 보낸다.

 

3. RESTful API에서의 자원 생성 또는 업데이트

RESTful API에서는 POST 및 PUT 메서드를 사용하여

자원을 생성하거나 업데이트한다.

이러한 경우에는 요청 바디를 사용하여 데이터를 전송한다.

 

 
 

아래는 Java와 Spring Framework를 사용하여

HTTP POST 요청을 보내고 요청 바디에

JSON 데이터를 포함하는 예시 코드이다.

import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.web.client.RestTemplate;

public class Main {
    public static void main(String[] args) {
        // JSON 데이터 생성
        String json = "{\"name\": \"John\", \"age\": 30, \"city\": \"New York\"}";

        // HTTP 요청 헤더 설정
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);

        // HTTP 요청 바디에 JSON 데이터 포함
        HttpEntity<String> requestEntity = new HttpEntity<>(json, headers);

        // POST 요청 보낼 URL
        String url = "https://example.com/api/users";

        // RestTemplate을 사용하여 POST 요청 보내기
        RestTemplate restTemplate = new RestTemplate();
        try {
            // POST 요청 보내기
            restTemplate.postForObject(url, requestEntity, String.class);
            System.out.println("요청이 성공적으로 완료되었습니다.");
        } catch (Exception e) {
            System.out.println("오류가 발생했습니다: " + e.getMessage());
        }
    }
}

 

이 코드는 Spring Framework의 RestTemplate을

사용하여 POST 요청을 보내고 있다.

요청 바디에 포함할 JSON 데이터를 문자열로 생성하고,

HttpHeaders를 사용하여 요청의 Content-Type을

JSON으로 설정한다. 그리고 HttpEntity를 사용하여

요청 바디에 JSON 데이터를 포함시킨다.

RestTemplate의 postForObject() 메서드를 사용하여

POST 요청을 보내고 응답을 처리한다.

 

위 코드를 실행하려면 Spring Framework의 의존성을

포함한 Maven 또는 Gradle 프로젝트를 사용하여

프로젝트를 설정해야 한다.