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 요청에 대해 배워두는 편이 경력에 도움이 될 것이다.
이런 것도 이미 정의돼 있나보네요. 감사합니다.
http는 보물상자인 걸까요:)…
좋아요좋아요
좀 더 흥미로운 것으로는 HTTP/1.1 확장으로 지원하는 Delta Encoding(RFC 3229)이란 기능이 있는데, 어떤 리소스가 변경되었을 때 변경된 부분만 diff를 떠서 보내주죠. 웹브라우저들이 지원하는지는 잘 모르겠네요.
좋아요좋아요
어플리케이션 단에서 요구되는 기능인 페이징 처리를 위해 네트워크 하위단인 http 프로토콜을 만지는 것이 더 일반적이지 않은 것 같군요. 그냥 이렇게도 해볼 수 있다는 정도에 그쳐야 하지 않을까 합니다.
좋아요좋아요
저는 HTTP가 네트워크 하위단이라고 생각하지 않습니다. HTTP는 OSI 7 레이어의 최상위인 응용 계층에 위치하고 있고 그에 걸맞게 다양한 기능들을 갖추고 있으므로 잘 활용한다면 불필요한 작업을 최소화할 수 있다고 봅니다.
좋아요좋아요
재밌는 내용이네요~
음, 하지만 페이징을 위한 파라메터들은 쿼리스트링으로 요청을 보내는게
사용자가 북마크 할 수 있어서 그 편이 더 좋을거 같아요 ^^
좋아요좋아요
네 맞습니다. 이 방법으로는 특정 페이지를 북마크 할 수가 없죠.
좋아요좋아요
오래된 포스트이라서 보실지는 모르겠습니다만 혹시나 해서 질문 드립니다.
기본적으로 ascending order인 듯 한데, descending order를 지원하도록 할려면 어떤 방식이 좋을까요?
좋아요좋아요
Ascending order -> descending order로 변경할 수도 있을까요?
좋아요좋아요
아마 각 항목들이 모두 descending order로 정렬되는 것을 원하시는 것 같은데, 그렇다면 제가 정의한 “pages” 가 아닌 다른 unit을 정의하시는 것이 좋겠습니다. 예를 들어 이런 가장 최근 항목 5개를 내림차순으로 가져오는 요청을 이런 식으로 정의하는 것도 가능할 것입니다.
Range: items=-1,-2,-3,-4,-5
RFC 7233가 정의한 unit은 “bytes”와 “none” 뿐이고 그 외의 다른 unit은 자유롭게 정의하여 쓸 수 있도록 하고 있으므로, 필요에 따라 적절히 정의하셔서 쓰시면 될 것 같습니다.
정의한 unit을 IANA에도 등록하면 더 좋겠지만, 추가로 등록된 것이 없는 것으로 보아 그렇게 하는 사람은 아직 없는 것 같군요… https://www.iana.org/assignments/http-parameters/http-parameters.xhtml#range-units
좋아요좋아요