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

В настоящее время я разрабатываю и внедряю 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 функцию поиска/фильтрации?

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

Ответ 1

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

Например:

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

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

Ответ 2

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

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

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

например:.

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

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

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

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

Ответ 3

Кажется, что фильтрация/поиск ресурсов может быть реализована 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, после которого они истекают и будут удалены.

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

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

Ответ 4

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

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

Ответ 5

Не слишком беспокоитесь, если ваш первоначальный 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

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

/resource?filters[status_id]=1&filters[city]=Sydney&page=2&include=relatedResource

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

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

Ответ 7

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

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

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