Як створити RESTful пошук / фільтрацію? [зачинено]


456

Наразі розробляю та впроваджую API RESTful у 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, але це не здається дуже ВИПАДНИМ.


7
можливий дублікат RESTful URL-дизайну для пошуку
вихід

60
Будь обережний!!! Метод GET повинен бути IDEMPOTENT і повинен бути "кешованим". Якщо ви надсилаєте інформацію в тіло, як система може кешувати ваш запит? HTTP дозволяє кешувати GET-запит, використовуючи лише URL-адресу, а не тіло запиту. Наприклад, ці два запити: example.com {test: "some"} example.com {anotherTest: "some2"} вважаються однаковими системою кешу: обидва мають точно однакову URL-адресу
jfcorugedo

15
Для додання, ви повинні POST для / users (колекція), а не / user (один користувач).
Младен Б.

1
Ще один момент, на який слід звернути увагу, - це те, що більшість серверів додатків мають журнали доступу, що реєструють URL-адреси, і тому між ними може бути що завгодно. Таким чином, може бути деяка ненавмисна витік інформації на GET.
користувач3206144

2
Можливий дублікат дизайну RESTful URL для пошуку
ivan_pozdeev

Відповіді:


397

Найкращий спосіб здійснити пошук RESTful - це вважати сам пошук ресурсом. Тоді ви можете використовувати дієслово POST, оскільки ви створюєте пошук. Вам не потрібно буквально щось створювати в базі даних, щоб використовувати POST.

Наприклад:

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

Ви створюєте пошук з точки зору користувача. Деталі реалізації цього питання не мають значення. Деяким API RESTful може навіть не потрібна наполегливість. Це деталь реалізації.


209
Одне істотне обмеження використання запиту POST для кінцевої точки пошуку полягає в тому, що він не може бути встановлений у закладках. Результати пошуку закладок (особливо складні запити) можуть бути дуже корисними.
couchand

73
Використання POST для пошуку може порушити обмеження кеш-пам'яті REST. whatisrest.com/rest_constraints/cache_excerps
Filipe

55
Пошуки, за своєю природою, є тимчасовими: дані розвиваються між двома пошуками з однаковими параметрами, тому я думаю, що GET-запит не чітко відображається в шаблоні пошуку. Замість цього запитом пошуку повинен бути POST (/ Resource / search), тоді ви можете зберегти цей пошук і перенаправити до результату пошуку, наприклад / Resource / search / iyn3zrt. Таким чином, GET запити досягають успіху та мають сенс.
sleblanc

32
Я не вважаю, що публікація є підходящим методом пошуку, дані для звичайних GET-запитів можуть змінюватися і з часом.
дивуємось

82
Це абсолютно найгірша можлива відповідь. Не можу повірити, що в ньому так багато відгуків. Ця відповідь пояснює , чому: programmers.stackexchange.com/questions/233164 / ...
ричард

141

Якщо ви використовуєте тіло запиту в GET-запиті, ви порушуєте принцип REST, оскільки ваш GET-запит не зможе кешуватися, оскільки система кешу використовує лише URL-адресу.

І що ще гірше, вашу URL-адресу не можна встановити в закладки, оскільки URL-адреса не містить усієї інформації, необхідної для перенаправлення користувача на цю сторінку

Використовуйте параметри URL або запиту замість параметрів тіла запиту.

наприклад:

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

Фактично, HTTP RFC 7231 говорить, що:

Корисне навантаження в повідомленні запиту GET не має визначеної семантики; надсилання тіла корисного навантаження на запит GET може призвести до відхилення запиту в деяких існуючих реалізаціях.

Для отримання додаткової інформації подивіться тут


29
Вчіться на своїй помилці - я створив api, використовуючи пропозицію прийнятої відповіді (POSTing json), але переходжу до параметрів URL. Можливість закладки може бути важливішою, ніж ви думаєте. У моєму випадку виникла потреба спрямувати трафік на певні пошукові запити (рекламна кампанія). Також використання API історії має більше сенсу з параметрами URL.
Джейк

2
Це залежить від того, яким чином його використовували. Якщо ви посилаєтесь на URL-адресу, яка завантажує сторінку на основі цих параметрів, це має сенс, але якщо головна сторінка виконує AJAX-виклик лише для отримання даних на основі параметрів фільтра, ви все одно не можете це зробити закладкою, оскільки його ajax дзвінок і не має відношення. Природно, ви також можете встановити закладку URL-адреси, яка, коли ви їдете туди, створює фільтр та POST-адреси, що надходять до виклику Ajax, і це буде добре працювати.
Даніель Лоренц

@DanielLorenz Для найкращого користувацького досвіду URL-адресу в цьому випадку все-таки слід змінювати через API API. Я не витримую, коли веб-сайт не дозволяє використовувати функцію назад браузера для переходу до попередніх сторінок. І якщо це стандартна сторінка, створена на сервері, єдиний спосіб зробити її закладкою - використовувати GET-запит. Здається, що найкращі параметри запиту - найкраще рішення.
Натан

@Nathan Я думаю, що я неправильно прочитав цю відповідь. Я говорив про використання параметрів рядка запиту в get. Ніколи не слід використовувати параметри тіла під час виклику GET, оскільки це було б абсолютно марно. Я говорив більше про GET з рядком запиту, який може бути використаний / закладений у закладки, а потім при запуску сторінки ви можете використовувати ці параметри для створення фільтра до POST, використовуючи ці параметри для отримання даних. Історія все ще спрацювала б за цим сценарієм.
Даніель Лоренц

@DanielLorenz Ну добре, це має сенс. Я думаю, що я неправильно зрозумів, що ти говорив.
Натан

70

Здається, що фільтрація / пошук ресурсів може бути реалізована RESTful способом. Ідея полягає у запровадженні нової кінцевої точки під назвою /filters/або /api/filters/.

Використання цього фільтра кінцевих точок може розглядатися як ресурс і, отже, створюватися POSTметодом. Таким чином, звичайно, тіло можна використовувати для перенесення всіх параметрів, а також можна створити складні структури пошуку / фільтрування.

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

  1. Новий ресурс з унікальним ідентифікатором буде повернутий разом з 201 Createdкодом статусу. Тоді за допомогою цього ідентифікатора GETзапит може бути /api/users/таким:

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

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

Як зберегти створені фільтри?

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

Які переваги цієї ідеї?

Фільтри, відфільтровані результати є кешованими і можуть бути навіть закладені в закладки.


2
ну це має бути прийнятою відповіддю. Ви не порушуєте принципів REST і можете робити довгі складні запити до ресурсів. Це приємно, чисто і сумісні закладки. Єдиним додатковим недоліком є ​​необхідність зберігання пар ключів / значень для створених фільтрів, а також згадані два кроки запиту.
dantebarba

2
Такий підхід стосується лише того, якщо у вас є запити фільтрів дати (або значення, що постійно змінюється). Тоді кількість фільтрів для зберігання в db (або кеші) незліченна.
Rvy Pandey

17

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

За умовою, коли використовується метод GET, вся інформація, необхідна для ідентифікації ресурсу, кодується в URI. У HTTP / 1.1 немає умов щодо безпечної взаємодії (наприклад, пошук), коли клієнт постачає дані серверу в тіло об'єкта HTTP, а не в частину запитів URI. Це означає, що для безпечних операцій URI можуть бути довгими.


5
ElasticSearch також GET з тілом і працює добре!
Тарун Сапра

Так, але вони керують реалізацією сервера, можливо, це не tase xase на інтерв'ю.
користувач432024

7

Оскільки я використовую запуск laravel / php, я схиляюся до подібного:

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

PHP автоматично перетворює []парами в масив, тому в цьому прикладі я закінчую a$filter змінною, яка містить масив / об'єкт фільтрів, разом зі сторінкою та будь-якими пов'язаними ресурсами, які я хочу з нетерпінням завантажувати.

Якщо ви використовуєте іншу мову, це все-таки може бути хорошою умовою, і ви можете створити аналізатор для перетворення []в масив.


Цей підхід виглядає приємно, але можуть виникнути проблеми з використанням квадратних дужок у URL-адресах, дивіться, що символи-можуть-один-використовувати-в-URL
Небо

2
@Sky Цього можна уникнути кодуванням URI [та ]. Використання закодованих представлень цих символів для групування параметрів запиту є добре відомою практикою. Він навіть використовується в специфікації JSON: API .
Джельхан

6

Не бідайте занадто сильно, якщо ваш початковий API повністю ВІДКРИТИЙ чи ні (особливо, коли ви просто на стадії альфа). Почніть спочатку сантехніку працювати. Ви завжди можете зробити якесь перетворення / повторне написання URL-адрес, щоб відобразити зображення, ітеративно вдосконалюючись, поки не отримаєте щось досить стабільне для широкого тестування ("бета").

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

.ie. Зробіть тип пошуку "користувачем" з парам [i] = значення [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 (і тому, що люди зазвичай керують кешуванням із заголовками кешування.)

Як ти будеш говорити про це, було б дизайнерським рішенням, але це був би шлях, яким я б ішов. Це не ідеально, і я впевнений, що будуть випадки, коли робити це не найкраще (особливо для дуже складних критеріїв пошуку).


7
Йо, якби ти (хтось, хто б / ні) щось не схвалював моєї відповіді, чи не зашкодило б тобі его, щоб принаймні викласти коментар із зазначенням того, з чим саме ти не згоден? Я знаю, що це interweebz, але ...;)
luis.espinal

107
Я не спростував, але те, що питання починається з: "Я зараз розробляю та впроваджую API RESTful", і ваша відповідь починається з "Не дуже хвилюйтесь, якщо ваш початковий API повністю RESTful чи ні" неправильно для мене. Якщо ви розробляєте API, ви розробляєте API. Питання полягає в тому, як найкраще розробити API, а не про те, чи слід розробляти API.
gardarh

14
API - це система, спочатку працюйте над API, а не забіжною сантехнікою, перша реалізація могла / повинна бути просто макетом. HTTP має механізм передачі параметрів, ви пропонуєте його переосмислити, але ще гірше (впорядковані параметри замість пар значень імен). Звідси голосування проти.
Стівен Ірод

14
@gardarh - так, він почуває себе неправильно, але часом це прагматично. Основна мета - розробити API, який працює для бізнес-контексту. Якщо повністю РЕЗУЛЬТАТИВИЙ підхід є відповідним для бізнесу, тоді слід зайнятися цим. Якщо це не так, тоді не робіть цього. Тобто розробіть API, який відповідає вашим конкретним вимогам бізнесу. Якщо ви намагаєтеся зробити його RESTfull, його основна вимога не сильно відрізняється від запитання "як я використовую шаблон адаптера в X / Y". Не взувайте парадигми на ріжок, якщо вони не вирішують актуальних, цінних проблем.
luis.espinal

1
Я розглядаю ресурс як деяку колекцію стану та параметрів як засіб для маніпулювання поданням цього стану параметрично. Подумайте про це таким чином, якщо ви можете використовувати регулятори та перемикачі, щоб налаштувати відображення ресурсу (показати / приховати певні біти його, замовити по-іншому тощо), ці елементи керування є парамами. Якщо це насправді інший ресурс (наприклад, "/ альбоми" проти "/ виконавців"), саме тоді він повинен бути представлений на шляху. Це те, що мені інтуїтивно зрозуміло.
Ерік Елліотт

2

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

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

Я знаю, що це некрасиво, але я думаю, що це самий НАЙКРАЩИЙ спосіб зробити це, і він повинен легко розібратися на стороні сервера :)

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.