Как создать 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¶meter2=value2¶meter3=value3¶meter4=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 способ сделать это и должен быть легко разобрать на стороне сервера:)