HTTP/1.1의 Range 요청과 이를 활용한 Pagination

HTTP/1.1은 Range 요청을 지원한다. 클라이언트가 Range 헤더를 통해 어떤 리소스의 일부분만을 요청하면, 서버는 그 부분만을 반환하는 방식으로 동작한다.

/file.zip를 100바이트에서 200바이트까지만 가져오는 클라이언트의 요청과 그에 대한 서버의 응답은 다음과 같다.

요청

GET /file.zip HTTP/1.1
Range: bytes=100-200
...

응답

HTTP/1.1 206 Partial Content
Content-Range: bytes 100-200/500
...

위의 예에서 단위는 ‘bytes’를 사용했다. 사실 HTTP/1.1을 정의한 RFC 2616에서는, HTTP/1.1에서 정의한 단위는 bytes 뿐이며 HTTP/1.1 구현체는 bytes 이외의 다른 단위는 무시할 수 있다(MAY)고 되어있다. 이해할 수 없는 단위에 대해서 서버와 클라이언트가 어떻게 동작해야 하는지에 대해 명확히 정의가 되어있지 않아서 직접 단위를 정의해서 쓰기가 매우 겁이 난다. 무슨 일이 벌어질 지 확신할 수 없기 때문이다.

다행히 httpbis(곧 제출될 HTTP/1.1의 개정판)에서는 훨씬 명확하게 정의되어있다.

If a range unit is not understood in a request, a server MUST ignore
the whole Range header field (Section 5.4).  If a range unit is not
understood in a response, an intermediary SHOULD pass the response to
the client; a client MUST fail.

만약 서버가 요청의 range 단위를 이해할 수 없다면, 반드시(MUST) 모든 Range
헤더 필드를 무시해야 한다. 만약 응답의 range 단위를 이해할 수 없다면, 중개자 (( 캐시 같은 것들 ))
는 응답을 클라이언트로 넘겨줘야 하며, 클라이언트는 반드시(MUST) 실패해야 한다.

Range 요청을 이용한 pagination 구현

이러한 HTTP의 미래에 조금 용기를 얻어서, 나는 Range 요청을 이용하여 웹 애플리케이션의 pagination (( 목록 하단에 표시되는 Prev 1 2 3 4 5 Next 이런 것.)) 을 구현했다. 직접 정의한 “pages”라는 단위와 HTTP/1.1의 Accept-Ranges, Range, Content-Range 헤더를 이용한다. 각 헤더의 사용방법은 다음과 같다.

Accept-Ranges

어떤 리소스가 pagination을 지원한다면, 서버는 그 사실을 알리기 위해 해당 리소스에 대한 일반적인 GET 요청에 대한 응답에 “pages” 값을 갖는 Accept-Ranges 헤더를 포함시킨다.

예:

Accept-Ranges: pages

Range

클라이언트는 다음과 같은 형식의 Range 헤더를 이용해 이 리소스의 특정 페이지만을 요청할 수 있다.

Range             = pages-unit "=" page-number
pages-unit        = "pages"
page-number       = 1*DIGIT
DIGIT             = <any US-ASCII digit "0".."9">

예:

Range: pages=1

Content-Range

서버는 Range 요청에 대해 다음과 같은 형식의 Content-Range 헤더를 통해 206 Partial Content로 응답한다. (HTTP/1.1의 bytes-range-spec과 차이가 있음에 유의하라)

Content-Range     = pages-unit SP page-number "/" complete-length
pages-unit        = "pages"
page-number       = 1*DIGIT
complete-length   = 1*DIGIT
SP                = <US-ASCII SP, space (32)>

예를 들어 총 두 페이지로 이루어진 목록에서 첫번째 페이지만을 반환하는 응답에서의 Content-Range는 다음과 같다.

Content-Range: pages 1/2

서버는 상황에 따라 클라이언트가 요청한 것과 다른 페이지를 돌려줄 수도 있다. 이러한 상황에 대한 예외처리의 책임은 클라이언트에게 있다.

이 구현에 대한 전체 문서는 여기를 보라.

왜 굳이 Range 요청을 이용하는가?

왜 Pagination을 구현할 때 굳이 Range 요청을 이용해야 하는가? 단지 동작하게 만든다는 이유 뿐이라면 query string에 page=1 같은 key-value를 넣기만 해도 충분할 것이다.

그러나 내가 원하는 것은 표준화된 인터페이스와 동작이다. HTTP를 이해하는 개발자라면, “Range 요청을 활용해서 pagination을 구현했다”는 한마디로도 대강의 동작방식을 이해할 수 있을 것이며, 예외상황에 대한 처리도 명세를 따르면 되기 때문에 매번 개발자들끼리 토론하고 협의할 필요도 줄어들 것이다. 혹시 HTTP를 모르는 개발자라고 할 지라도, 특정 애플리케이션만의 pagination 방식을 익혀서 사용하는 것 보다는, 널리 쓰이고 있으며 앞으로도 널리 쓰일 표준화된 Range 요청에 대해 배워두는 편이 경력에 도움이 될 것이다.

Advertisements

HTTP/1.1의 Range 요청과 이를 활용한 Pagination”에 대한 6개의 생각

    1. 좀 더 흥미로운 것으로는 HTTP/1.1 확장으로 지원하는 Delta Encoding(RFC 3229)이란 기능이 있는데, 어떤 리소스가 변경되었을 때 변경된 부분만 diff를 떠서 보내주죠. 웹브라우저들이 지원하는지는 잘 모르겠네요.

      좋아하기

  1. 어플리케이션 단에서 요구되는 기능인 페이징 처리를 위해 네트워크 하위단인 http 프로토콜을 만지는 것이 더 일반적이지 않은 것 같군요. 그냥 이렇게도 해볼 수 있다는 정도에 그쳐야 하지 않을까 합니다.

    좋아하기

    1. 저는 HTTP가 네트워크 하위단이라고 생각하지 않습니다. HTTP는 OSI 7 레이어의 최상위인 응용 계층에 위치하고 있고 그에 걸맞게 다양한 기능들을 갖추고 있으므로 잘 활용한다면 불필요한 작업을 최소화할 수 있다고 봅니다.

      좋아하기

  2. 재밌는 내용이네요~
    음, 하지만 페이징을 위한 파라메터들은 쿼리스트링으로 요청을 보내는게
    사용자가 북마크 할 수 있어서 그 편이 더 좋을거 같아요 ^^

    좋아하기

답글 남기기

아래 항목을 채우거나 오른쪽 아이콘 중 하나를 클릭하여 로그 인 하세요:

WordPress.com 로고

WordPress.com의 계정을 사용하여 댓글을 남깁니다. 로그아웃 / 변경 )

Twitter 사진

Twitter의 계정을 사용하여 댓글을 남깁니다. 로그아웃 / 변경 )

Facebook 사진

Facebook의 계정을 사용하여 댓글을 남깁니다. 로그아웃 / 변경 )

Google+ photo

Google+의 계정을 사용하여 댓글을 남깁니다. 로그아웃 / 변경 )

%s에 연결하는 중