본문 바로가기
네트워크

REST에서 resource와 representation은 다르다. (feat 삽질기)

by pius712 2023. 7. 13.

1. resource? representation? 의 개념

resource는 data의 추상화를 말하는 것으로 representation과는 구별됩니다. 특정 시점에 resource의 state (상태)를 representation이라고 합니다.

selected representation이란 말은 결국, resource의 상태는 하나가 아닌 여러개가 될 수 있고 HTTP 통신에 의해서 resource의 상태 중에 하나가 선택된다고 할 수 있습니다. 예를들어, 회사의 홈페이지에 회사 소개를 하는 페이지가 있다고 생각해봅시다. 회사의 소개 그 자체는 추상화된 개념으로 resource라고 할 수 있습니다. 하지만, 회사의 소개를 json으로 할 수도 있고, html로 할 수도 있고 혹은 이를 다국어 지원으로 제공할 수도 있는데 이는 resource에 대한 상태를 나타냅니다. 즉, 응답 각각은 representation이라고 할 수 있습니다.

좀 더 자세한 예로, 자기 소개라는 resource가 있을때, 아래 두가지 형태의 representation 이 존재할 수 있는 것입니다.

영어 문서

Content-Type: text/plain
Content-Language: en

hello

한국어 문서

Content-Type: text/plain
Content-Language: ko

안녕하세요

representation은 크게 구분하면, 아래로 구성이 됩니다.

  • representation data
  • representation metadata

어떻게 representation은 선택되는가?

위에 설명에서 representation은 특정 시점에 selected state라고 하였는데, 그러면 어떻게 선택될까? 이것을 선택하는 과정이 바로 content-negotiation 입니다.

2. content-negotitation

정의

 server는 resource에 대한 representation을 여러가지 방식으로 가지고 있을 수 있습니다. 예를 들어 언어, format, encoding 등이 있습니다.


 user 혹은 user agent 마다의 capability와, preference는 다를 수 있는데, 이것은 서버가 어떤 representation을 전달해주는지에 영향을 줄 수 있습니다.

 여기서 user agent란 크롬, 사파리와 같은 브라우저 혹은 mac, window 와 같은 os가 될 수도 있습니다. (아래에서 user-agent는 http 헤더를 표현하고, user agent 는 일반적인 의미로 사용하겠습니다.)

이렇게 다른 available 한 representation을 전달하는 메커니즘을 content-negotiation이라고 합니다.

이 negotiation에는 3가지 패턴이 있을 수 있는데,

  • proactive : 서버가, user agent의 명시된 preference에 따라 representation을 선택하는 방식
  • reactive : 서버가 representation에 대한 리스트를 주고, user-agent가 이를 취하는 방식.
  • request-content : user agent가 과거 응답에 명시된 서버의 기본 설정에 따라 향후 요청에 대한 representation을 선택하는 방식

2.1 Proactive Negotiation (a.k.a 사전 협상, 서버 주도 협상)

Proactive Negotiation 은 user agent가 자신의 preference를 전송하고, 이를 서버가 선택하는 것을 말한다. user agent가 명시적으로 필드를 표현하는 경우도 있지만, user-agent 헤더나 ip 주소와 같은 암묵적으로 표현되는 경우도 있다.

어떤 representation을 선택할지는 전적으로 서버에 달려있고, use agent는 자신의 선호도를 헤더로 표현할 뿐다. 즉, user agent는 서버가 선택할 수 있도록 자신의 선호도를 전달하지만, 어떤 representation을 선택하는지는 전적으로 서버가 결정하게 되는 것이다. 서버는 어떤 것도 부합되지 않는 경우 best guess 라고 하여, 이를 추측하여 전송합니다.

서버 주도 협상에서 사용되는 headers

아래의 request header 필드는 서버 주도 협상에 참여하는 user agent에 의해 보내진다. 해당 필드에 의해 보내진 preference 는 리소스의 representation 에 적용이 된다.

  • Accept: MIME type으로 표현되는 contents 의 타입을 선택할 수 있다.
  • Accept-Charset: 인코딩 선택
  • Accept-Encoding: 일반적으로는 압축 알고리즘 선택
  • Accept-Language: 언어 선택

장점

  • user agent에게 어떤 representation을 선택하는 알고리즘을 설명하기 어려울 때.
  • 다른 negotiation의 경우, 서버가 best guess로 응답을 주는게 아니라, 다시 클라이언트가 요청하는 경우가 있는데, 이런 경우 요청에 딜레이가 발생합니다. 이를 Round Trip Delay라고 하는데, 이 경우는 서버가 판단해서 주기 때문에, 이를 피할 수 있습니다.

단점

  • 서버가 특정 유저에게 “best” 가 무엇인지 정확하게 결정할 수 없다. 왜냐하면, user-agent 의 capability에 대해서 정확하게 알 수 없고, response 가 어떻게 사용될지도 모른다. 예를 들어, 특정 문서를 프린트 용으로 뽑으려고 하는건지, 스크린에서 보려고 하는건지 알 수 없다.
  • user agent가 매 요청마다 자신의 capability를 담아 보내는 것은 매우 비효율적이다. 또한 user privacy 이슈도 있음.
  • 서버 입장에서 user agent 의 요청 선호도를 모두 대응하기 어렵다.
  • 공유 캐시의 응답 재활용성을 제한함.

실제로는 어떨까?

대부분의 경우, 이런 헤더들은 크게 영향을 주지 못합니다. 아래처럼 네이버의 홈페이지를 요청해보아도, 전혀 달라지는게 없습니다.

en-US 요청 (accept-language)

curl '<https://www.naver.com/>' \\
-H 'Host: www.naver.com' \\
-H 'Connection: keep-alive' \\
-H 'Cache-Control: max-age=0' \\
-H 'Upgrade-Insecure-Requests: 1' \\
-H 'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36' \\
-H 'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7' \\
-H 'Referer: <https://search.naver.com/search.naver?where=nexearch&sm=top_hty&fbm=0&ie=utf8&query=%EB%84%A4%EC%9D%B4%EB%B2%84+%EC%98%81%ED%99%94>' \\
-H 'Accept-Language: en-US

보통은 locale 정보를 accept 헤더가 아닌, 다른 방식 (body나 cookie 등)으로 전달하는 경우가 대부분입니다.

json 으로 요청 (accep 헤더)

curl '<https://www.naver.com/>' \\
-H 'Host: www.naver.com' \\
-H 'Connection: keep-alive' \\
-H 'Cache-Control: max-age=0' \\
-H 'Upgrade-Insecure-Requests: 1' \\
-H 'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36' \\
-H 'Accept: application/json
-H 'Referer: <https://search.naver.com/search.naver?where=nexearch&sm=top_hty&fbm=0&ie=utf8&query=%EB%84%A4%EC%9D%B4%EB%B2%84+%EC%98%81%ED%99%94>' \\
-H 'Accept-Language: en-US

그나마 정적 리소스의 경우 accept-encoding의 경우 WAS 에서 제어하는게 아니라 웹서버에서 제어하기 때문에, 헤더 필드를 빼면 압축이 적용되지 않은채 오기는 합니다. 물론, gzip을 빼고 br 로 보낸다고 br로 오는 것은 아닙니다.

2.2 Reactive Negotiation (에이전트 주도 협상), request-content Negotiation

생략…

3. 삽질기

사내 개발중에, 외부 요청에 의해 들어온 응답의 charset 인코딩이 성공시에는 utf-8이, 실패시에는 euc-kr 로 떨어지는 이상한 경우가 있었다. 아마도 레거시 시스템과 신규 프로젝트가 섞여 있는게 아닐까 추측해본다.

기본적으로 라이브러리들이 euc-kr 을 지원하지 않기 때문에, 서드파티 라이브러리를 통해 변환을 해야했기에, hex 값으로 응답을 받은 후, 응답 헤더에 명시된 header에 따라 변환을 해줘야할 필요가 있었다.

hex 값을 charset으로 디코딩시 utf-8, euc-kr 둘 다 제대로 디코딩되지 않는 이슈가 있었는데, euc-kr 응답이 gzip 압축되어 응답으로 올때, 이런 현상이 발생하였다. 정확한 원인은 모르겠지만, gzip 압축을 해제하는 과정에서 패딩값 이슈가 아닐까 라는 추측이긴했다.

이를 해결하기 위해서, accept-encoding 의 gzip 필드를 제거하니 압축하지 않은채로 값이 내려왔고, 이를 디코딩하니 해결이 되었다. content-negotitation 이라는 것이 대부분 제대로 동작하지 않기 때문에, 잊고 있었는데 정적 리소스의 압축 알고리즘의 경우 대부분 웹서버에서 담당하기 때문에 표준대로 잘 동작하는게 아닐까 생각이든다.

3. 결론

결론적으로는, 표준은 그렇지만 현실은 그렇지 않다. 그렇지만, 평소에 이런 이론에 대해서 제대로 알지 못한다면, 현실에서의 문제 상황 해결에 어려움이 있을 수 있다. 표준에서 REST 에서 말하는 RE 는 representation이고 resource가 아니다. 하지만, 현실에서는 content-negotiation 은 잘 동작하지 않는 경우가 많고, 대부분은 resource = representation 인 경우가 대부분이다.