Пейджинг в коллекции отдыха - PullRequest
126 голосов
/ 29 мая 2009

Я заинтересован в предоставлении прямого интерфейса REST коллекциям документов JSON (представьте себе CouchDB или Persevere ). Проблема, с которой я сталкиваюсь, заключается в том, как обработать операцию GET в корне коллекции, если коллекция большая.

В качестве примера притворяюсь, что я выставляю таблицу Questions StackOverflow, где каждая строка представлена ​​в виде документа (не обязательно, что такая таблица обязательно есть, просто конкретный пример значительной коллекции «документов»). Коллекция будет доступна на /db/questions с обычными CRUD API GET /db/questions/XXX, PUT /db/questions/XXX, POST /db/questions в игре. Стандартный способ получить всю коллекцию - это GET /db/questions, но если при этом наивно выводит каждую строку как объект JSON, вы получите довольно значительную загрузку и много работы со стороны сервера.

Решение, конечно, пейджинговое. Dojo решил эту проблему в своем JsonRestStore с помощью умного расширения, совместимого с RFC2616, с использованием заголовка Range с настраиваемой единицей измерения диапазона items. В результате получается 206 Partial Content, который возвращает только запрошенный диапазон. Преимущество этого подхода перед параметром запроса состоит в том, что он оставляет строку запроса для ... запросов (например, GET /db/questions/?score>200 или что-то подобное, и да, которые будут закодированы %3E).

Этот подход полностью охватывает поведение, которое я хочу. Проблема в том, что RFC 2616 указывает, что на 206 ответе (выделено мое):

Запрос ДОЛЖЕН включать поле заголовка Range ( section 14.35 ) указав желаемый диапазон, и МОЖЕТ включить If-Range поле заголовка ( раздел 14.27 ), чтобы сделать запрос условным.

Это имеет смысл в контексте стандартного использования заголовка, но является проблемой, потому что я хотел бы, чтобы ответом 206 было значение по умолчанию для обработки наивных клиентов / случайных людей, которые исследуют.

Я подробно рассмотрел RFC в поисках решения, но был недоволен моими решениями и заинтересован в том, чтобы SO взялась за эту проблему.

У меня были идеи:

  • Возврат 200 с заголовком Content-Range! - Я не думаю, что это неправильно, но я бы предпочел, чтобы более очевидный показатель того, что ответом является только частичное содержимое.
  • Return 400 Range Required - Специального кода ответа 400 для требуемых заголовков не существует, поэтому ошибка по умолчанию должна использоваться и считываться вручную. Это также усложняет исследование через веб-браузер (или другой клиент, такой как Resty).
  • Использовать параметр запроса - Стандартный подход, но я надеюсь разрешить запросы в духе Persevere, и это врезается в пространство имен запроса.
  • Просто верните 206! - я думаю, что большинство клиентов не пугались бы, но я бы не стал идти против MUST в RFC
  • Расширьте спецификацию! Return 266 Partial Content - ведет себя точно так же, как 206, но отвечает на запрос, который НЕ ДОЛЖЕН содержать заголовок Range. Я считаю, что 266 достаточно высоко, чтобы не сталкиваться с проблемами столкновения, и это имеет для меня смысл, но я не уверен, считается ли это табу или нет.

Я думаю, что это довольно распространенная проблема, и мне бы хотелось, чтобы это было сделано де-факто, чтобы я или кто-то другой не изобретал велосипед.

Какой лучший способ представить полную коллекцию по HTTP, если коллекция большая?

Ответы [ 12 ]

33 голосов
/ 23 июня 2009

Я не совсем согласен с некоторыми из вас, ребята. Я работал в течение нескольких недель над этими функциями для моей службы REST. То, что я в итоге сделал, действительно просто. Мое решение имеет смысл только для того, что люди REST называют коллекцией.

Клиент ДОЛЖЕН включать заголовок «Range», чтобы указать, какая часть коллекции ему нужна, или иным образом быть готовым обработать ошибку 413 REQUESTED ENTITY TOO LARGE, когда запрашиваемая коллекция слишком велика, чтобы ее можно было извлечь за один цикл .

Сервер отправляет ответ 206 ЧАСТИЧНОЕ СОДЕРЖАНИЕ, в котором заголовок Content-Range указывает, какая часть ресурса была отправлена, и заголовок ETag для идентификации текущей версии коллекции. Я обычно использую ETag {last_modification_timestamp} - {resource_id}, подобный Facebook, и считаю, что ETag коллекции - это самый последний измененный ресурс, который он содержит.

Чтобы запросить определенную часть коллекции, клиент ДОЛЖЕН использовать заголовок «Range» и заполнить заголовок «If-Match» ETag коллекции, полученной из ранее выполненных запросов, для получения других частей той же коллекции. , Поэтому сервер может проверить, что коллекция не изменилась, перед отправкой запрошенной части. Если существует более новая версия, возвращается ответ 412 PRECONDITION FAILED, чтобы пригласить клиента получить коллекцию с нуля. Это необходимо, поскольку это может означать, что некоторые ресурсы могли быть добавлены или удалены до или после запрашиваемой в данный момент детали.

Я использую ETag / If-Match в тандеме с Last-Modified / If-Unmodified-Since, чтобы оптимизировать кэш. Браузеры и прокси-серверы могут полагаться на один или оба из них для своих алгоритмов кэширования.

Я думаю, что URL должен быть чистым, если только он не включает поисковый запрос / фильтр. Если вы думаете об этом, поиск - это не что иное, как частичное представление коллекции. Вместо машин / поиска? Q = URL-адреса типа BMW, мы должны увидеть больше автомобилей? Изготовителя = BMW.

22 голосов
/ 29 мая 2009

Мне кажется, что расширения диапазона HTTP не предназначены для вашего случая использования, и поэтому вам не следует пытаться. Частичный ответ подразумевает 206, а 206 следует отправлять только в том случае, если клиент запросил его.

Возможно, вы захотите рассмотреть другой подход, например, использование в Atom (где представление по проекту может быть частичным, и возвращается со статусом 200, и, возможно, с поисковыми ссылками). См. RFC 4287 и RFC 5005 .

5 голосов
/ 14 ноября 2012

Вы все еще можете вернуть Accept-Ranges и Content-Ranges с кодом ответа 200. Эти два заголовка ответа дают вам достаточно информации для логического вывода той же информации, которую явно указывает код ответа 206.

Я бы использовал Range для нумерации страниц и просто вернул 200 для простого GET.

Это выглядит как 100% RESTful , а не делает просмотр более сложным.

Edit: Я написал в блоге об этом: http://otac0n.com/blog/2012/11/21/range-header-i-choose-you.html

5 голосов
/ 04 июня 2009

Если имеется более одной страницы ответов, и вы не хотите предлагать всю коллекцию одновременно, значит ли это, что существует несколько вариантов?

При запросе к /db/questions верните 300 Multiple Choices с Link заголовками, которые указывают, как добраться до каждой страницы, а также с объектом JSON или страницей HTML со списком URL-адресов.

Link: <>; rel="http://paged.collection.example/relation/paged"
Link: <>; rel="http://paged.collection.example/relation/paged"
...

У вас будет один Link заголовок для каждой страницы результатов (пустая строка означает текущий URL-адрес, а URL-адрес одинаков для каждой страницы, только для доступа с разными диапазонами), и отношение определяется как пользовательский на предстоящий Link spec . Эти отношения объяснят ваш обычай 266 или ваше нарушение 206. Эти заголовки являются вашей машиночитаемой версией, поскольку все ваши примеры в любом случае требуют понимания клиента.

(Если вы придерживаетесь маршрута «range», я думаю, что ваш собственный код возврата 2xx, как вы его описали, будет лучшим поведением здесь. От вас ожидают, что вы сделаете это для своих приложений и тому подобного [» Коды состояния HTTP являются расширяемыми. "], И у вас есть веские причины.)

300 Multiple Choices говорит, что вы ДОЛЖНЫ также предоставить телу способ выбора для пользовательского агента. Если ваш клиент понимает, он должен использовать заголовки Link. Если это пользователь, просматривающий вручную, возможно, HTML-страницу со ссылками на специальный «выгружаемый» корневой ресурс, который может обрабатывать отображение этой конкретной страницы на основе URL-адреса? /humanpage/1/db/questions или что-то такое отвратительное?


Комментарии к сообщению Ричарда Левассера напоминают мне о дополнительной опции: заголовок Accept (раздел 14.1). Когда появилась спецификация oEmbed, я удивился, почему она не была полностью реализована с использованием HTTP, и написал альтернативную версию, используя их.

Сохраняйте заголовки 300 Multiple Choices, Link и HTML-страницу для начального наивного HTTP GET, но вместо диапазонов используйте, чтобы ваши новые отношения подкачки определяли использование заголовка Accept. Ваш следующий HTTP-запрос может выглядеть следующим образом:

GET /db/questions HTTP/1.1
Host: paged.collection.example
Accept: application/json;PagingSpec=1.0;page=1

Заголовок Accept позволяет вам определить приемлемый тип контента (ваш возврат в JSON), а также расширяемые параметры для этого типа (номер вашей страницы). Судя по моим заметкам из моей записи oEmbed (здесь я не могу связать их, я перечислю их в своем профиле), вы можете быть очень откровенными и предоставить здесь версию спецификации / отношения в случае, если вам нужно переопределить то, что page параметр означает в будущем.

4 голосов
/ 03 июня 2009

Edit:

Подумав немного об этом, я склонен согласиться с тем, что заголовки Range не подходят для нумерации страниц. Логика заключается в том, что заголовок Range предназначен для ответа сервера, а не для приложений. Если вы обработали 100 мегабайт результатов, но сервер (или клиент) мог обрабатывать только 1 мегабайт за раз, это то, для чего предназначен заголовок Range.

Я также считаю, что подмножество ресурсов - это собственный ресурс (аналог реляционной алгебры), поэтому оно заслуживает представления в URL.

Таким образом, я отказываюсь от своего первоначального ответа (ниже) об использовании заголовка.


Я думаю, что вы ответили на свой вопрос, более или менее - верните 200 или 206 с диапазоном содержимого и при необходимости используйте параметр запроса. Я бы понюхал пользовательский агент и тип контента и, в зависимости от них, проверил параметр запроса. В противном случае требуются заголовки диапазона.

По сути, у вас противоречивые цели - позволить людям использовать свой браузер для исследования (который не позволяет легко настраивать заголовки) или заставить людей использовать специальный клиент, который может устанавливать заголовки (который не позволяет им исследовать).

Вы можете просто предоставить им специальный клиент в зависимости от запроса - если он выглядит как обычный браузер, отправьте небольшое приложение ajax, которое отображает страницу и устанавливает необходимые заголовки.

Конечно, также ведутся дебаты о том, должен ли URL содержать все необходимое состояние для такого рода вещей. Указание диапазона с использованием заголовков может считаться некомфортным.

Кроме того, было бы неплохо, если бы серверы могли ответить заголовком «Can-Specify: Header1, header2», а веб-браузеры предоставили бы пользовательский интерфейс, чтобы пользователи могли заполнять значения, если они этого хотят.

3 голосов
/ 05 июня 2009

Я думаю, что настоящая проблема здесь в том, что в спецификации нет ничего, что говорило бы нам о том, как сделать автоматическое перенаправление при столкновении с 413 - Requested Entity Too Large.

Недавно я боролся с этой же проблемой и искал вдохновение в книге RESTful Web Services . Лично я не думаю, что 206 подходит из-за требования заголовка. Мои мысли также привели меня к 300, но я подумал, что это было больше для разных типов пантомимы, поэтому я посмотрел, что Ричардсон и Руби должны были сказать по этому вопросу в Приложении B, стр. 377. Они предлагают, чтобы сервер просто выбрал предпочтительный представление и отправить его обратно с 200, в основном игнорируя представление о том, что это должно быть 300.

Это также согласуется с понятием ссылок на следующие ресурсы, которые мы имеем от atom. Решение, которое я реализовал, состояло в том, чтобы добавить «следующий» и «предыдущий» ключи к карте json, которую я отправлял обратно, и покончить с этим.

Позже я начал думать, что, возможно, нужно отправить 307 - Временное перенаправление на ссылку, которая будет выглядеть как / db / questions / 1,25 - которая оставляет исходный URI в качестве канонического имени ресурса, но это переводит вас к подчиненному ресурсу с соответствующим названием. Такое поведение я хотел бы видеть из 413, но 307 кажется хорошим компромиссом. На самом деле еще не пробовал это в коде, хотя. Что было бы еще лучше, так это перенаправление для перенаправления на URL, содержащий фактические идентификаторы последних заданных вопросов. Например, если каждый вопрос имеет целочисленный идентификатор, и в системе имеется 100 вопросов, и вы хотите отобразить десять самых последних, запросы к / db / questions должны быть с 307 до / db / questions / 100,91

Это очень хороший вопрос, спасибо, что задали его. Вы подтвердили мне, что я не схожу с ума от того, что провел дни, думая об этом.

3 голосов
/ 03 июня 2009

Вы могли бы рассмотреть возможность использования модели, подобной протоколу подачи Atom, так как он имеет разумную HTTP-модель коллекций и способы манипулирования ими (где безумие означает WebDAV).

Существует Протокол публикации Atom , который определяет модель коллекции и операции REST, плюс вы можете использовать RFC 5005 - Пейджинг и архивирование для просмотра больших коллекций.

Переход с содержимого Atom XML на JSON не должен влиять на идею.

1 голос
/ 15 августа 2014

Одна из больших проблем с диапазонными заголовками заключается в том, что многие корпоративные прокси отфильтровывают их. Я бы посоветовал использовать параметр запроса вместо этого.

1 голос
/ 27 июня 2014

С публикацией rfc723x , незарегистрированные единицы измерения диапазона идут вразрез с явной рекомендацией в спецификации . Рассмотрим rfc7233 (устарел rfc2616):

" Новые единицы измерения диапазона должны быть зарегистрированы в IANA " (вместе со ссылкой на Реестр единиц измерения HTTP ).

1 голос
/ 04 июня 2009

Вы можете обнаружить заголовок Range и имитировать Dojo, если он присутствует, и имитировать Atom, если его нет. Мне кажется, что это аккуратно разделяет варианты использования. Если вы отвечаете на запрос REST из вашего приложения, вы ожидаете, что он будет отформатирован с заголовком Range. Если вы отвечаете на случайный браузер, то, если вы вернете ссылки на пейджинг, это позволит инструменту легко исследовать коллекцию.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...