Как спроектировать RESTful поиск / фильтрацию?

267

В настоящее время я разрабатываю и внедряю RESTful API в PHP. Тем не менее, я не смог реализовать свой первоначальный дизайн.

GET /users # list of users
GET /user/1 # get user with id 1
POST /user # create new user
PUT /user/1 # modify user with id 1
DELETE /user/1 # delete user with id 1

До сих пор довольно стандартный, не так ли?

Моя проблема связана с первой GET /users. Я рассматривал возможность отправки параметров в тело запроса для фильтрации списка. Это связано с тем, что я хочу иметь возможность указывать сложные фильтры, не получая сверхдлительный URL-адрес, например:

GET /users?parameter1=value1&parameter2=value2&parameter3=value3&parameter4=value4

Вместо этого я хотел иметь что-то вроде:

GET /users
# Request body:
{
    "parameter1": "value1",
    "parameter2": "value2",
    "parameter3": "value3",
    "parameter4": "value4"
}

который является более читаемым и дает большие возможности для установки сложных фильтров.

В любом случае file_get_contents('php://input') не вернул тело запроса для запросов GET. Я также пробовал http_get_request_body(), но для общего хостинга, который я использую, нет pecl_http. Не уверен, что это все равно помогло.

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

Итак, теперь я не уверен, что делать. Как вы создаете функцию RESTful search/filterng?

Я предполагаю, что могу использовать POST, но это не кажется очень RESTful.

  • 7
    возможный дубликат дизайна RESTful URL для поиска
  • 43
    Быть осторожен!!! Метод GET должен быть IDEMPOTENT и должен быть «кэшируемым». Если вы отправляете информацию в теле Как система может кешировать ваш запрос? HTTP позволяет кэшировать GET-запрос, используя только URL, а не тело запроса. Например, эти два запроса: example.com {test: "some"} example.com {anotherTest: "some2"} рассматриваются системой кэширования одинаково: оба имеют одинаковый URL
Показать ещё 3 комментария
Теги:
filter
rest
search
design

7 ответов

289

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

Например:

Accept: application/json
Content-Type: application/json
POST http://example.com/people/searches
{
  "terms": {
    "ssn": "123456789"
  },
  "order": { ... },
  ...
}

Вы создаете поиск с пользовательской точки зрения. Детали реализации этого не имеют значения. Некоторым API-интерфейсам RESTful может даже не понадобиться постоянство. Это деталь реализации.

  • 9
    Это должно быть принято в качестве ответа ОП. Это самый правильный ответ на вопрос. @ jason-harrelson не скрывает, говоря, что настойчивость не нужна для поддержки предполагаемого использования POST. Приятной частью использования POST для поискового ресурса является возможность использовать его в качестве будущей функции, такой как сохраненные поиски или предлагаемые поиски для пользователя вашего API.
  • 188
    Одним существенным ограничением использования запроса POST для конечной точки поиска является то, что он не может быть добавлен в закладки. Результаты поиска по закладкам (особенно сложные запросы) могут быть весьма полезными.
Показать ещё 25 комментариев
48

Если вы используете тело запроса в запросе GET, вы нарушаете принцип REST, потому что ваш запрос GET не сможет быть кэширован, потому что система кэширования использует только URL-адрес.

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

Используйте параметры URL или запроса вместо параметров тела запроса.

например:.

/myapp?var1=xxxx&var2=xxxx
/myapp;var1=xxxx/resource;var2=xxxx 

Фактически, HTTP RFC 7231 говорит, что:

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

Для получения дополнительной информации см. здесь

  • 17
    Учитесь на своей ошибке - я разработал API, используя предложенный ответ (POSTing JSON), но перехожу к параметрам URL. Возможность закладок может быть важнее, чем вы думаете. В моем случае возникла необходимость направлять трафик на определенные поисковые запросы (рекламная кампания). Кроме того, использование API истории имеет больше смысла с параметрами URL.
  • 0
    Это зависит от того, как его использовать. Если вы ссылаетесь на URL, который загружает страницу на основе этих параметров, это имеет смысл, но если главная страница выполняет вызов AJAX только для получения данных, основанных на параметрах фильтра, вы все равно не сможете добавить эту закладку в закладки, потому что она AJAX вызова и не имеет никакого отношения. Естественно, вы также можете добавить в закладки URL, который, когда вы идете туда, создает фильтр и отправляет POST, что к вызову ajax, и он будет работать просто отлично.
Показать ещё 3 комментария
24

Кажется, что фильтрация/поиск ресурсов может быть реализована RESTful способом. Идея состоит в том, чтобы ввести новую конечную точку, называемую /filters/ или /api/filters/.

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

После создания такого фильтра есть две возможности получить результат поиска/фильтра.

  • Новый ресурс с уникальным идентификатором будет возвращен вместе с кодом статуса 201 Created. Затем, используя этот идентификатор, GET можно запросить /api/users/, например:

    GET /api/users/?filterId=1234-abcd
    
  • После создания нового фильтра через POST он не будет отвечать с помощью 201 Created, но сразу с 303 SeeOther вместе с заголовком Location, указывающим на /api/users/?filterId=1234-abcd. Это перенаправление будет автоматически обрабатываться через базовую библиотеку.

В обоих сценариях необходимо выполнить два запроса, чтобы получить отфильтрованные результаты - это может считаться недостатком, особенно для мобильных приложений. Для мобильных приложений я бы использовал один вызов POST для /api/users/filter/.

Как сохранить созданные фильтры?

Они могут храниться в БД и использоваться позже. Они также могут храниться в некотором временном хранилище, например. redis и иметь некоторый TTL, после которого они истекают и будут удалены.

В чем преимущества этой идеи?

Фильтры, результаты фильтрации могут быть кэшируемыми и могут быть даже отмечены закладкой.

  • 1
    ну это должен быть принятый ответ. Вы не нарушаете принципы REST и можете делать длинные сложные запросы к ресурсам. Это красиво, чисто и совместимо с закладками. Единственным дополнительным недостатком является необходимость хранения пар ключ / значение для созданных фильтров и уже упомянутые два шага запроса.
11

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

По соглашению, когда метод GET используется вся информация, необходимая для определить ресурс, закодированный в URI. В конвенции нет HTTP/1.1 для безопасного взаимодействия (например, поиск), где клиент поставляет данные на сервер в объекте HTTP а не в части запроса URI. Это означает, что для безопасного операции, URI могут быть длинными.

  • 2
    ElasticSearch также делает GET с телом и работает хорошо!
  • 0
    Да, но они контролируют реализацию сервера, но не могут быть связаны между собой.
8

Не слишком беспокоитесь, если ваш первоначальный API полностью RESTful или нет (особенно, когда вы просто находитесь в альфа-сценах). Прежде всего, сделайте внутреннюю сантехнику. Вы всегда можете сделать какую-то трансформацию/переписывание URL-адресов, чтобы отобразить все, уточняя итеративно, пока не получите что-то достаточно стабильное для широкого тестирования ( "бета" ).

Вы можете определить URI, параметры которых закодированы положением и условным обозначением для самих URI, с префиксом, который вы знаете, который всегда будет отображаться на что-то. Я не знаю PHP, но я бы предположил, что такое средство существует (как оно существует на других языках с веб-фреймворками):

.ie. Сделайте "пользовательский" тип поиска с параметром [i] = значение [i] для я = 1..4 в хранилище # 1 (со значением 1, значением2, значением3,... как сокращением для параметров запроса URI):

1) GET /store1/search/user/value1,value2,value3,value4

или

2) GET /store1/search/user,value1,value2,value3,value4

или следующим образом (хотя я бы не рекомендовал его, подробнее об этом позже)

3) GET /search/store1,user,value1,value2,value3,value4

С помощью опции 1 вы сопоставляете все URI с префиксом /store1/search/user с обработчиком поиска (или в зависимости от назначения PHP) по умолчанию для поиска ресурсов в store1 (эквивалентно /search?location=store1&type=user.

По соглашению, документированному и введенному в действие API, значения параметров с 1 по 4 разделяются запятыми и представлены в этом порядке.

Вариант 2 добавляет тип поиска (в данном случае user) в качестве позиционного параметра # 1. Любой вариант - это просто косметический выбор.

Вариант 3 также возможен, но я не думаю, что мне это понравится. Я думаю, что способность поиска в определенных ресурсах должна быть представлена ​​в самом URI, предшествующем самому поиску (как если бы он четко указывал в URI, что поиск специфичен в ресурсе.)

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

Как только вы сделаете что-то подобное, вы можете использовать GET, и это будет ресурс только для чтения (поскольку вы не можете использовать POST или PUT - он обновляется, когда он GET'ed). Это также будет ресурс, который появляется только при его вызове.

Можно также добавить к нему дополнительные семантики путем кэширования результатов в течение определенного периода времени или с помощью DELETE, в результате чего кеш будет удален. Это, однако, может противоречить тому, что люди обычно используют DELETE для (и потому, что люди обычно контролируют кеширование с заголовками кеширования.)

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

  • 6
    Эй, если вы (кто-то, кто бы ни / что бы) не захотели преуменьшить мой ответ, было бы больно вам эго хотя бы оставить комментарий, указывающий, с чем именно вы не согласны? Я знаю, что это interweebz, но ...;)
  • 88
    Я не понизил голос, но тот факт, что вопрос начинается с: «Я сейчас занимаюсь проектированием и реализацией RESTful API», а ваш ответ начинается с «Не беспокойтесь слишком сильно, если ваш начальный API полностью RESTful или нет», чувствует неправильно со мной. Если вы разрабатываете API, вы разрабатываете API. Вопрос в том, как лучше спроектировать API, а не в том, должен ли быть спроектирован API.
Показать ещё 10 комментариев
0

FYI: Я знаю, что это немного поздно, но для всех, кто заинтересован. В зависимости от того, как вы хотите быть RESTful, вам придется реализовать свои собственные стратегии фильтрации, поскольку спецификация HTTP не очень понятна. Я хотел бы предложить URL-кодирование всех параметров фильтра, например.

GET api/users?filter=param1%3Dvalue1%26param2%3Dvalue2

Я знаю, что это уродливо, но я думаю, что это самый RESTful способ сделать это и должен быть легко разобрать на стороне сервера:)

-2

Поскольку я использую бэкэнд laravel/php, я имею тенденцию идти с чем-то вроде этого:

/ресурс фильтры [status_id] = 1 &? Фильтры [город] = Сидней & страница = 2 & включают = relatedResource

PHP автоматически превращает [] params в массив, поэтому в этом примере я получаю переменную $filter, которая содержит массив/объект фильтров, а также страницу и любые связанные ресурсы, которые я хочу загрузить.

Если вы используете другой язык, это может быть хорошим соглашением, и вы можете создать парсер для преобразования [] в массив.

  • 0
    Этот подход выглядит хорошо, но могут быть проблемы с использованием квадратных скобок в URL-адресах, посмотрите, что-символов-может-один-использовать-в-URL
  • 0
    @Sky Этого можно избежать с помощью URI, кодирующего [ и ] . Использование закодированных представлений этих символов для группировки параметров запроса является хорошо известной практикой. Он даже используется в спецификации JSON: API .

Ещё вопросы

Сообщество Overcoder
Наверх
Меню