Пагинация в веб-приложении REST


329

Это более общая переформулировка этого вопроса (с устранением отдельных частей Rails)

Я не уверен, как реализовать разбиение на страницы для ресурса в веб-приложении RESTful. Предполагая, что у меня есть ресурс под названием products, какой из следующих вариантов вы считаете лучшим и почему:

1. Использование только строк запроса

например. http://application/products?page=2&sort_by=date&sort_how=asc
Проблема здесь в том, что я не могу использовать полное кэширование страниц, а также URL-адрес не очень чистый и его легко запомнить.

2. Использование страниц в качестве ресурсов и строк запросов для сортировки

например. http://application/products/page/2?sort_by=date&sort_how=asc
В этом случае проблема заключается в том, что http://application/products/pages/1это не уникальный ресурс, поскольку использование sort_by=priceможет дать совершенно другой результат, и я все еще не могу использовать кэширование страниц.

3. Использование страниц в качестве ресурсов и сегмента URL для сортировки

например. http://application/products/by-date/page/2
Я лично не вижу проблем в использовании этого метода, но кто-то предупредил меня, что это не очень хороший способ (он не привел причину, поэтому, если вы знаете, почему это не рекомендуется, пожалуйста, дайте мне знать)

Любые предложения, мнения, критика приветствуются. Спасибо.


34
Это большой вопрос.
Iain Holder

7
Бонусный вопрос: как люди обычно указывают размеры страницы?
Хайко Рупп

Не забывайте о параметрах Matrix w3.org/DesignIssues/MatrixURIs.html
CMCDragonkai,

Ответы:


66

Я думаю, что проблема с версией 3 - это скорее проблема «точки зрения» - вы видите страницу как ресурс или продукты на странице.

Если вы видите страницу как ресурс, то это идеальное решение, так как запрос для страницы 2 всегда будет давать страницу 2.

Но если вы видите продукты на странице как ресурс, у вас есть проблема, связанная с тем, что продукты на странице 2 могут измениться (старые продукты удалены или что-то еще), в этом случае URI не всегда возвращает один и тот же ресурс (ы).

Например, клиент сохраняет ссылку на страницу со списком продуктов X, и в следующий раз, когда ссылка будет открыта, рассматриваемый продукт может больше не отображаться на странице X.


6
Хорошо, но если вы удалите что-то, не должно быть чего-то другого в том же URI. Если вы удалите все продукты со страницы X - страница X может все еще быть действительной, но теперь содержит товары со страницы X + 1. Таким образом, URI для страницы X стал URI для страницы X + 1, если вы видите это в «представлении ресурсов продукта». ».
Fionn

1
> Если вы рассматриваете страницу как ресурс, это идеальное решение, поскольку запрос на странице 2 всегда будет давать страницу 2. Имеет ли это смысл? Один и тот же URL (любой URL с упоминанием страницы 2) всегда будет давать страницу 2 независимо от того, какой ресурс вы используете.
Темото

2
Видя страницу как ресурс, вероятно, следует ввести POST / foo / page для создания новой страницы, верно?
Темото

18
Ваш ответ плавно переходит к «правильное решение - 1», но не утверждает его.
Темото

2
На мой взгляд, страница является плавающей концепцией и не связана с базовым доменом. И поэтому не следует рассматривать как ресурс. Я имею в виду плавающий в том смысле, что он плавный, что концепция страницы меняется в зависимости от контекста; один пользователь вашего API может быть мобильным приложением, которое может потреблять только 2 продукта на страницу, а другой - машинным приложением, которое может использовать весь этот чертов список. Короче говоря, страница является «представлением» базового доменного объекта (продукта) и не должна быть включена как часть URL; только в качестве параметра запроса.
Kingz

106

Я согласен с Fionn, но я сделаю еще один шаг и скажу, что для меня страница не является ресурсом, это свойство запроса. Это заставляет меня выбирать только строку запроса варианта 1. Это просто чувствует себя хорошо. Мне действительно нравится, как Twitter API структурирован спокойно. Не слишком просто, не слишком сложно, хорошо документировано. Что бы там ни было, это мой дизайн «идти к», когда я стою на пороге, делая что-то одно против другого.


28
+1: строки запроса не являются первоклассными идентификаторами ресурса; они просто уточняют порядок и группировку ресурса.
S.Lott

1
@ S.Lott Запрос является ресурсом. То, что вы называете «первоклассными ресурсами», определяется Филдингом как значения в разделе 5.2.1.1 его диссертации . Кроме того, в том же разделе Fielding приводит в качестве примера ресурса последнюю версию файла исходного кода. Как это может быть ресурсом, но последние 10 продуктов являются «свойствами запроса на ресурс продуктов»? Я понимаю, что ваша точка зрения более практична, но я думаю, что она менее RESTful.
Эдсиуфи

Обратите внимание, что мой комментарий не означает, что я не согласен с выбором использования строк запроса над URL-адресами: оба являются жизнеспособными решениями, если API управляется гипермедиа, как упомянул @RichApodaca в своем ответе. Я просто указываю на то, что Страница должна рассматриваться как ресурс с точки зрения REST.
Эдсиуфи

37

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

Range: pages=1

иметь только первую страницу. Это может заставить вас переосмыслить, что такое страница. Возможно, клиент хочет другой ассортимент товаров. Заголовок диапазона также работает, чтобы объявить заказ:

Range: products-by-date=2009_03_27-

чтобы все продукты были новее этой даты или

Range: products-by-date=0-2009_11_30

чтобы получить все продукты старше этой даты. «0», вероятно, не лучшее решение, но RFC, похоже, хочет что-то для начала диапазона. Могут быть развернуты парсеры HTTP, которые не будут анализировать единицы = -range_end.

Если заголовки не являются (приемлемым) вариантом, я считаю, что первое решение (все в строке запроса) - это способ работы со страницами. Но, пожалуйста, нормализуйте строки запроса (сортируйте (ключ = значение) пары в алфавитном порядке). Это решает проблему дифференциации «? A = 1 & b = x» и «? B = x & a = 1».


34
На первый взгляд заголовки могут выглядеть красиво, но они запрещают публиковать страницу (например, копируя URL). Так что для запроса ajax они могут быть хорошим решением (поскольку страницы, измененные с помощью ajax, в любом случае не могут быть разделены в их текущем состоянии), но я бы не стал использовать их для обычной нумерации страниц.
Маркус

3
И заголовок Range предназначен только для байтовых диапазонов. См. [ Спецификацию заголовков HTTP] ( w3.org/Protocols/rfc2616/rfc2616-sec14.html ), раздел 14.35.
Крис Вестин

16
@ChrisWestin w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.12 HTTP / 1.1 использует единицы измерения диапазона в полях заголовка Range (раздел 14.35) и Content-Range (раздел 14.16). range-unit = bytes-unit | other-range-unit Может быть, вы имеете в виду, The only range unit defined by HTTP/1.1 is "bytes". HTTP/1.1 implementations MAY ignore ranges specified using other units.что это не то же самое, что ваше заявление.
Темо

1
@Markus Я не могу представить себе вариант использования, когда вы
делитесь

@JakubKnejzlik Совместное использование не является проблемой, но использование заголовков HTTP для подкачки не позволяет использовать ссылки HATEOAS для подкачки.
xarx

25

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

Сказав это, схема URL является относительно незначительным. Если вы разрабатываете свое приложение на основе гипертекста (как все приложения REST должны быть по определению), тогда ваш клиент не будет создавать какие-либо URI самостоятельно. Вместо этого ваше приложение будет давать ссылки клиенту, а клиент будет следовать за ними.

Один из видов ссылок, которые может предоставить ваш клиент, - это ссылка на страницы.

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


3
Приятное напоминание об использовании гипермедиа-подобных ссылок в веб-сервисах REST.
Пол Д. Иден

11

Я всегда использовал стиль варианта 1. Кэширование не было проблемой, так как в любом случае данные часто меняются. Если вы позволяете настраивать размер страницы, данные снова не могут быть кэшированы.

Я не считаю URL запоминающимся или нечистым. Для меня это хорошее использование параметров запроса. Ресурс представляет собой список продуктов, а параметры запроса просто указывают, как вы хотите, чтобы список отображался - сортировался и на какой странице.


1
+1 Я думаю, что вы правы, и я пойду с параметрами запроса (вариант 1)
andi

«Я не нахожу URL, который трудно запомнить». Это наблюдение бесполезно в приложениях REST, поскольку у них обычно должна быть только одна закладка ... Если пользователь (или клиентское приложение) пытается «запомнить» URL-адрес, это хороший признак того, что API не успокаивается.
Эдсиуфи

8

Странно, что никто не указал, что вариант 3 имеет параметры в определенном порядке. http // application / products / Date / Descending / Name / Ascending / page / 2 и http // application / products / Name / Ascending / Date / Descending / page / 2

указывают на один и тот же ресурс, но имеют совершенно разные URL.

Для меня вариант 1 кажется наиболее приемлемым, поскольку он четко разделяет слова «Что я хочу» и «Как я хочу» (между ними даже есть знак вопроса). Кеширование на всю страницу может быть реализовано с использованием полного URL-адреса (в любом случае все параметры будут иметь одну и ту же проблему).

При подходе «Параметры в URL» единственным преимуществом является чистый URL. Хотя вы должны придумать какой-то способ кодирования параметров и декодирования без потерь. Конечно, вы можете пойти с URLencode / decode, но это снова сделает URL ужасными :)


1
Это два разных заказа. Первые сортируются по дате по убыванию, и разрывается только по имени по возрастанию; второй сортирует по возрастанию, и разрывает связи только по убыванию.
Имран Рашид

На самом деле два приведенных здесь примера URL-адреса отличаются не только по написанию, но и по смыслу. Так как обозначение пути не дает никаких гарантий, что вы найдете то же самое при повороте налево сначала и сразу после него, или наоборот. Сказав это, параметры сортировки как части пути URL имеют формальные преимущества по сравнению с параметрами URL, которые должны обмениваться коммутативно без изменения общего значения, но на самом деле страдают от ловушек кодирования, как сказано здесь.
Кристиан Гош

7

Я бы предпочел использовать параметры запроса offset и limit.

смещение : для индекса элемента в коллекции.

предел : для подсчета предметов.

Клиент может просто обновлять смещение следующим образом

offset = offset + limit

для следующей страницы.

Путь считается идентификатором ресурса. И страница не ресурс, а подмножество коллекции ресурсов. Поскольку разбиение на страницы обычно является запросом GET, параметры запроса лучше всего подходят для разбиения на страницы, а не для заголовков.

Ссылка: https://metamug.com/article/rest-api-developers-dilemma.html#Requesting-the-next-page


5

В поисках лучших практик я наткнулся на этот сайт:

http://www.restapitutorial.com

На странице ресурсов есть ссылка для загрузки .pdf, содержащего полные рекомендации REST, предложенные автором. В котором среди прочего есть раздел о нумерации страниц.

Автор предлагает добавить поддержку как при использовании заголовка Range, так и при использовании параметров строки запроса.

Запрос

Пример заголовка HTTP:

Range: items=0-24

Пример параметров строки запроса:

GET http://api.example.com/resources?offset=0&limit=25

Где смещение - это номер начального элемента, а ограничение - максимальное количество элементов для возврата.

отклик

Ответ должен включать заголовок Content-Range, указывающий, сколько элементов возвращается и сколько всего элементов еще предстоит получить.

Примеры заголовков HTTP:

Content-Range: items 0-24/66

Content-Range: items 40-65/*

В .pdf есть некоторые другие предложения для более конкретных случаев.


4

В настоящее время я использую схему, подобную этой в моих приложениях ASP.NET MVC:

например http://application/products/by-date/page/2

конкретно это: http://application/products/Date/Ascending/3

Тем не менее, я не очень доволен включением подкачки и сортировки информации в маршрут таким образом.

Список товаров (в данном случае товары) является изменчивым. т. е. в следующий раз, когда кто-то вернется к URL-адресу, который включает параметры подкачки и сортировки, полученные результаты могут измениться. Таким образом, идея http://application/products/Date/Ascending/3уникального URL-адреса, который указывает на определенный, неизменный набор продуктов, теряется.


1
Первая проблема, с сортировкой по нескольким столбцам, на мой взгляд, относится ко всем 3 методам. Так что на самом деле это не про / против ни для одного из них. Что касается второго вопроса: не может ли это произойти с любым ресурсом? Продукт, например, также можно редактировать / удалять.
Анди

Я думаю, что сортировка по нескольким столбцам действительно является «коном» для всех трех методов, так как URL становится больше и более неуправляемым - поэтому я рассматриваю одну из причин, по которой я перехожу к параметрам страницы / сортировки на основе форм. Что касается второго вопроса, я думаю, что есть принципиальная концептуальная разница между уникальным постоянным идентификатором, таким как идентификатор продукта, и временным списком продуктов. Для удаленных продуктов сообщение, например «Этот продукт не существует в системе», говорит вам что-то конкретное об этом продукте.
Стив Уиллкок,

1
Удаление всей страницы и сортировка информации из маршрута - это хорошо. И вставлять его в параметры POST плохо. Привет? Вопрос по поводу ОТДЫХА. Мы не используем POST, чтобы сделать URL-адрес короче в REST. Глагол имеет смысл.
Темото

1
Лично я бы не использовал параметры формы для запроса, потому что для этого почти требовался бы HTTP-метод POST или PUT (поскольку в запросе теперь есть тело). GET кажется мне более подходящим методом, поскольку POST и PUT подразумевают изменение ресурса. В связи с этим я бы добавил дополнительные параметры запроса к URL, когда необходима сортировка по нескольким столбцам.
Пол Д. Иден

1

Я склонен согласиться с slf, что «страница» на самом деле не ресурс. С другой стороны, вариант 3 чище, проще для чтения и может быть легче угадан пользователем и даже напечатан при необходимости. Я разрываюсь между вариантами 1 и 3, но не вижу причин не использовать вариант 3.

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


1
Что касается вашего упоминания о том, что легче угадать, это не должно иметь значения. При создании гипермедиа API пользователи никогда не должны угадывать URI.
JR Garcia

0

Я использовал решение 3 раньше (я пишу много приложений Django). И я не думаю, что с этим что-то не так. Он так же генерируемый, как и два других (в случае, если вам нужно сделать несколько операций по очистке или тому подобное), и выглядит чище. Кроме того, ваши пользователи могут угадывать URL-адреса (если это общедоступное приложение), и людям нравится иметь возможность идти прямо туда, куда они хотят, а угадывание URL-адресов дает им возможность.


0

Я использую в своих проектах следующие URL:

http://application/products?page=2&sort=+field1-field2

что означает - «дай мне страницу вторую страницу, упорядоченную по возрастанию по field1 и затем по убыванию по field2». Или, если мне нужна еще большая гибкость, я использую:

http://application/products?skip=20&limit=20&sort=+field1-field2

0

Я использую в следующих шаблонах, чтобы получить следующую страницу записи. HTTP: //? приложение / продукты lastRecordKey = & PAGESIZE = 20 & сортировки = ASC

RecordKey - это столбец таблицы, который содержит последовательное значение в БД. Это используется для получения только одной страницы данных за один раз из БД. pageSize используется для определения количества записей для выборки. sort используется для сортировки записи в порядке возрастания или убывания.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.