스프링으로 API를 개발할 때 Request 처리의 경우 @RequestParam, @RequestPart, @RequestBody, @ModelAttribute를 사용합니다.
@ModelAttribute의 경우, JSP 및 thymeleaf와 같이 서버사이브 템플릿 엔진환경에서 view로 데이터를 바인딩하기 위해 사용하기도 하지만, 파라미터를 자바 객체로 바인딩 하는데 사용할 수 있습니다.
해당 어노테이션들은 클라이언트의 Request를 자동으로 처리 해주기 때문에 해당 어노테이션과 Content-Type에 대한 이해가 없어도 적당히 개발은 가능합니다.
다만 Content-Type에 대한 이해도가 부족할 경우, 폼 데이터, 파일 업로드 등의 기능을 다룰 때, 이를 어떻게 처리 해야하는지 헷갈리는 경우가 많습니다.
그리고 클라이언트의 application/x-www-form-urlencoded
형태의 타입을 @RequestBody로 안받아져서 고생했다는 글을 읽은적이 있는데, 이 또한 마찬가지입니다.
본 글에서는 클라이언트에서 주로 요청하는 Content-Type을 예시와 함께 설명하고, 다음 글에서는 Spring에서 제공하는 Request 어노테이션을 이용하여 클라이언트의 요청을 처리하는 여러가지 방법에 대해 다루도록 하겠습니다.
Content-Type은 HTTP 헤더에 명시되는 타입입니다.
클라이언트가 POST 요청 시 body에 데이터를 담거나, 서버에서 클라이언트로 데이터를 담아서 응답하는 경우, 해당 데이터의 타입을 나타내기 위해 사용됩니다.
헤더의 형태는 다음과 같습니다.
Content-Type: type/subtype
매개변수가 있는 경우 다음과 같이 파라미터를 넣을 수 있습니다.
Content-Type: type/subtype;parameter=value
[예시]
Content-Type: application/json;
Content-Type: text/html; charset=utf-8
API 요청 시 서버로 데이터를 전송할 때, 클라이언트에서 주로 사용하는 Content-Type은 3가지 정도가 있습니다.
아래 이미지와 같이 URL뒤에 붙는 쿼리 스트링과 비슷한 포맷입니다.
application/x-www-form-urlencoded
또한 마찬가지로 key=value 형태의 데이터로 표현되며, & 기호로 분리합니다. 그리고 영문자나 숫자가 아닌 문자들은 percent encoded 형태로 인코딩 됩니다.
예를 들면 포스트맨으로 아래와 같은 형태의 요청은,
아래와 같은 HTTP 메시지로 표현됩니다.
POST /api/form HTTP/1.1
Host: localhost:8123
Content-Type: application/x-www-form-urlencoded
Content-Length: 70
param1=in%20english¶m2=%ED%95%9C%EA%B8%80%EC%9D%B4%EC%97%90%EC%9A%94
한글의 경우, 인코딩 되었을 때 본문의 길이가 급격하게 늘어나므로 길이가 긴 데이터를 담아서 보내기엔 적합하지 않습니다.
이 둘의 데이터 포맷은 같지만, 쿼리 스트링은 body가 아닌 URL뒤에 붙여서 전송되고 application/x-www-form-urlencoded
는 POST 전송 시 body에 데이터가 담겨져 전송됩니다.
위의 예시를 GET, 쿼리 스트링 형태로 전송하면
아래와 같은 HTTP 메시지로 요청합니다.
GET /api/form?param1=in%20english¶m2=%ED%95%9C%EA%B8%80%EC%9D%B4%EC%97%90%EC%9A%94 HTTP/1.1
Host: localhost:8123
multipart는 단일 본문에 하나 이상 및 여러 종류의 데이터 파트가 포함되는 타입이며, 보통 파일 업로드 할때 많이 사용됩니다.
각 데이터 파트들은 개행과 바운더리 문자열로 구분합니다.
아래는 multipart/form-data
요청 예시입니다.
해당 요청의 HTTP 메시지는 아래와 같습니다.
POST /api/multipart HTTP/1.1
Host: localhost:8123
Content-Length: 297
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW
----WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="file"; filename="제목 없음.pdf"
Content-Type: application/pdf
(data)
----WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="text"
hello world
----WebKitFormBoundary7MA4YWxkTrZu0gW--
위 HTTP 예시의 헤더 Content-Type을 확인해보면 multipart/form-data
가 있고 파라미터로 boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW
바운더리 문자열이 설정되어있습니다.
이 바운더리 문자열은 필수로 포함되어있어야 하며, 클라이언트에서 해당 문자열로 각 파트를 구분해서 요청하고, 서버는 해당 바운더리 문자열로 파트를 구분해서 받습니다.
첫번째 파트에서 Content-Disposition
은 multipart에서 사용되는 파트의 헤더 중 하나입니다. 해당 파트의 정보를 추가적으로 담기위해 사용됩니다. (파일명과 같은)
그리고 각 파트 또한 헤더에 Content-Type이 붙는데, 해당 파트의 데이터 타입을 나타내기 위해 사용하며, 기본 값은 text/plain
입니다.
가장 익숙한 타입입니다. API 개발 시 가장 많이 사용되는 Content-Type 이며, body에 json 포맷으로 데이터가 담겨집니다.
아래는 application/json
요청 예시입니다.
해당 요청의 HTTP 메시지는 아래와 같습니다.
POST /api/json HTTP/1.1
Host: localhost:8123
Content-Type: application/json
Content-Length: 34
{
"text" : "Hello World!!"
}
간단하게 클라이언트 Content-Type에 대하여 알아보았습니다. 앞서 클라이언트에서 사용하는 Content-Type에 대해 자세히 설명한 이유는, 이를 이해하고 있어야 클라이언트 요청에 따라 알맞는 서버 코드를 작성할 수 있기 때문입니다.
물론 앞서 언급한 4가지 어노테이션 (@RequestParam, @RequestPart, @RequestBody, @ModelAttribute) 또한 각각의 차이점과 기능에 대해 잘 알고있어야 합니다.
다음 글에서는 해당 어노테이션의 기능 및 차이점과 예제 코드를 이용하여 Spring boot로 Request 처리 하는 방법에 대해 알아보도록 하겠습니다.