it-roy-ru.com

RESTful дизайн URL для поиска

Я ищу разумный способ представления поисковых запросов в виде RESTful URL.

Установка: у меня есть две модели, автомобили и гаражи, где автомобили могут быть в гаражах. Так что мои URL выглядят так:

/car/xxxx
  xxx == car id
  returns car with given id

/garage/yyy
  yyy = garage id
  returns garage with given id

Автомобиль может существовать сам по себе (отсюда и/автомобиль) или в гараже. Как правильно представлять, скажем, все машины в данном гараже? Что-то вроде:

/garage/yyy/cars     ?

Как насчет объединения автомобилей в гараже yyy и zzz?

Как правильно представить поиск автомобилей с определенными атрибутами? Скажи: покажи мне все синие седаны с 4 дверями:

/car/search?color=blue&type=sedan&doors=4

или это должно быть/автомобили вместо?

Использование «поиска» там кажется неуместным - что лучше/термин? Должно ли это быть просто:

/cars/?color=blue&type=sedan&doors=4

Должны ли параметры поиска быть частью PATHINFO или QUERYSTRING?

Короче говоря, я ищу руководство для кросс-модельного REST дизайна URL и поиска.

[Обновление] Мне нравится ответ Джастина, но он не охватывает случай поиска в нескольких полях:

/cars/color:blue/type:sedan/doors:4

или что-то типа того. Как мы идем от

/cars/color/blue

в случае нескольких полей?

387
Parand

Для поиска используйте строки запроса. Это совершенно ОТЛИЧНО:

/cars?color=blue&type=sedan&doors=4

Преимущество обычных строк запросов заключается в том, что они являются стандартными и широко понятными, и что они могут быть сгенерированы из form-get.

396
pbreitenbach

RESTful красивый URL-дизайн предназначен для отображения ресурса, основанного на структуре (каталогоподобная структура, дата: article/2005/5/13, объект и его атрибуты, ..), косая черта / указывает на иерархическую структуру, используйте вместо этого -id.

Иерархическая структура

Я бы лично предпочел:

/garage-id/cars/car-id
/cars/car-id   #for cars not in garages

Если пользователь удаляет часть /car-id, он обеспечивает предварительный просмотр cars - интуитивно понятный. Пользователь точно знает, где в дереве он находится, на что он смотрит. С первого взгляда он знает, что гаражи и машины взаимосвязаны. /car-id также обозначает, что он принадлежит вместе в отличие от /car/id.

Поиск

Поисковый запрос в порядке, как и, есть только ваши предпочтения, что следует учитывать. Самое смешное происходит при объединении поисков (см. Ниже). 

/cars?color=blue;type=sedan   #most prefered by me
/cars;color-blue+doors-4+type-sedan   #looks good when using car-id
/cars?color=blue&doors=4&type=sedan   #I don't recommend using &*

Или в основном что-нибудь, что не является косой чертой, как объяснено выше.
Формула: /cars[?;]color[=-:]blue[,;+&], * хотя я бы не использовал знак &, так как он не распознается по тексту на первый взгляд.

** Знаете ли вы, что передача объекта JSON в URI является RESTful? **

Списки опций 

/cars?color=black,blue,red;doors=3,5;type=sedan   #most prefered by me
/cars?color:black:blue:red;doors:3:5;type:sedan
/cars?color(black,blue,red);doors(3,5);type(sedan)   #does not look bad at all
/cars?color:(black,blue,red);doors:(3,5);type:sedan   #little difference

возможные особенности?

Отключить строки поиска (!)
Для поиска любых автомобилей, но не _ ​​черный и красный:
?color=!black,!red
color:(!black,!red) 

Объединенные поиски
Поиск красный или синий или черный автомобили с 3 дверьми в гаражах id 1..20 или 101..103 или 999, но не _ ​​5/garage[id=1-20,101-103,999,!5]/cars[color=red,blue,black;doors=3]
Затем вы можете создавать более сложные поисковые запросы. (Посмотрите на соответствие атрибутов CSS3 для идеи соответствия подстрок. Например, поиск пользователей, содержащих "bar" user*=bar.)

Заключение

В любом случае, это может быть самой важной частью для вас, потому что вы можете делать это так, как вам нравится в конце концов, просто имейте в виду, что RESTful URI представляет структуру, которую легко понять, например. похожий на каталог /directory/file, /collection/node/item, date /articles/{year}/{month}/{day} .. И когда вы пропускаете какой-либо из последних сегментов, вы сразу же знаете, что получаете.

Итак, все эти символы позволены незакодированы

  • незарезервировано: a-zA-Z0-9_.-~ 
  • зарезервировано: ;/?:@=&$-_.+!*'(), 
  • небезопасно *: <>"#%{}|\^~[]`

* Почему небезопасно и почему лучше кодировать: RFC 1738 см. 2.2

RFC 3986 см. 2.2
Несмотря на то, что я ранее сказал, здесь есть общее различие делимеров, означающее, что некоторые "are" более важны, чем другие.

  • общие разделители: :/?#[]@
  • подразделы: !$&'()*+,;=

Больше чтения:
Иерархия: см. 2.3 , см. 1.2.3
Синтаксис параметра пути URL
Соответствие атрибута CSS3
IBM: RESTful Web-сервисы - Основы
Примечание: RFC 1738 был обновлен RFC 3986

110
Qwerty

Хотя наличие параметров в пути имеет некоторые преимущества, IMO имеет некоторые перевешивающие факторы.

  • Не все символы, необходимые для поискового запроса, разрешены в URL. Большинство знаков препинания и Unicode должны быть URL-адресами в виде параметра строки запроса. Я борюсь с той же проблемой. Я хотел бы использовать XPath в URL, но не весь синтаксис XPath совместим с путем URI. Таким образом, для простых путей /cars/doors/driver/lock/combination было бы целесообразно найти элемент 'combination' в XML-документе двери водителя. Но /car/doors[id='driver' and lock/combination='1234'] не так дружелюбен.

  • Существует разница между фильтрацией ресурса по одному из его атрибутов и указанием ресурса. 

    Например, так как

    /cars/colors возвращает список всех цветов для всех автомобилей (возвращаемый ресурс является коллекцией цветовых объектов)

    /cars/colors/red,blue,green возвращает список цветных объектов красного, синего или зеленого цвета, а не коллекцию автомобилей.

    Чтобы вернуть автомобили, путь будет

    /cars?color=red,blue,green или /cars/search?color=red,blue,green

  • Параметры в пути труднее читать, потому что пары имя/значение не изолированы от остальной части пути, которая не является парами имя/значение. 

Последний комментарий Я предпочитаю /garages/yyy/cars (всегда во множественном числе) /garage/yyy/cars (возможно, это была опечатка в исходном ответе), потому что он избегает изменения пути между единственным и множественным числом. Для слов с добавленными 's' изменение не так уж плохо, но изменение /person/yyy/friends на /people/yyy кажется громоздким.

34
Doug Domeny

Чтобы расширить ответ Питера - вы можете сделать Поиск первоклассным ресурсом:

POST    /searches          # create a new search
GET     /searches          # list all searches (admin)
GET     /searches/{id}     # show the results of a previously-run search
DELETE  /searches/{id}     # delete a search (admin)

Ресурс поиска будет иметь поля для цвета, марки, модели, состояния гаража и т.д. И может быть указан в XML, JSON или любом другом формате. Как и ресурс Car and Garage, вы можете ограничить доступ к поискам на основе аутентификации. Пользователи, которые часто запускают одни и те же поиски, могут сохранять их в своих профилях, чтобы их не нужно было заново создавать. URL-адреса будут достаточно короткими, чтобы во многих случаях их можно было легко обменять по электронной почте. Эти сохраненные поиски могут быть основой пользовательских RSS-каналов и т.д.

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

Идея объяснена более подробно в этом Railscast .

30
Rich Apodaca

Ответ Джастина, вероятно, является правильным решением, хотя в некоторых приложениях может иметь смысл рассматривать конкретный поиск как самостоятельный ресурс, например, если вы хотите поддерживать именованные сохраненные поиски:

/search/{searchQuery}

или же 

/search/{savedSearchName}
11
Peter Hilton

Это не ОТДЫХ. Вы не можете определить URI для ресурсов внутри вашего API. Навигация по ресурсам должна быть управляемой гипертекстом. Хорошо, если вам нужны красивые URI и большое количество соединений, но просто не называйте это REST, потому что это напрямую нарушает ограничения архитектуры RESTful.

Смотрите эту статью от изобретателя REST.

5
aehlke

Я использую два подхода для осуществления поиска. 

1) Самый простой случай, для запроса связанных элементов и для навигации. 

    /cars?q.garage.id.eq=1

Это означает, что запросить автомобили с идентификатором гаража равным 1.

Также возможно создание более сложных поисков:

    /cars?q.garage.street.eq=FirstStreet&q.color.ne=red&offset=300&max=100

Автомобили во всех гаражах FirstStreet, которые не являются красными (3-я страница, 100 элементов на страницу).

2) Сложные запросы рассматриваются как обычные ресурсы, которые создаются и могут быть восстановлены. 

    POST /searches  => Create
    GET  /searches/1  => Recover search
    GET  /searches/1?offset=300&max=100  => pagination in search

Тело POST для создания поиска выглядит следующим образом:

    {  
       "$class":"test.Car",
       "$q":{
          "$eq" : { "color" : "red" },
          "garage" : {
             "$ne" : { "street" : "FirstStreet" }
          }
       }
    }

Он основан на Grails (критерии DSL): http://grails.org/doc/2.4.3/ref/Domain%20Classes/createCriteria.html

5
user2108278

Кроме того, я бы также предложил:

/cars/search/all{?color,model,year}
/cars/search/by-parameters{?color,model,year}
/cars/search/by-vendor{?vendor}

Здесь Search рассматривается как дочерний ресурс Cars.

1
aux

RESTful не рекомендует использовать глаголы в URL-адресах/cars/search, не успокаивая. Правильный способ фильтрации/поиска/разбивки на страницы вашего API - через параметры запроса. Однако могут быть случаи, когда вам приходится нарушать норму. Например, если вы осуществляете поиск по нескольким ресурсам, вам нужно использовать что-то вроде/search? Q = query

Вы можете пройти через http://saipraveenblog.wordpress.com/2014/09/29/rest-api-best-practices/ , чтобы понять лучшие практики для разработки RESTful API

1
java_geek

Хотя мне нравится ответ Джастина, я чувствую, что он более точно представляет фильтр, а не поиск. Что если я захочу узнать об автомобилях с названиями, начинающимися с cam?

На мой взгляд, вы можете встроить его в способ обработки определенных ресурсов:
/Авто/кулачковый *

Или вы можете просто добавить его в фильтр:
/Автомобили/двери/4/имя/кулачковые */цвет/красный, синий, зеленый

Лично я предпочитаю последнее, однако я ни в коем случае не являюсь экспертом по REST (впервые услышав об этом всего 2 или около того недели назад ...)

1
Nate

Есть много хороших вариантов для вашего случая здесь. Тем не менее, вам следует рассмотреть возможность использования тела POST.

Строка запроса идеально подходит для вашего примера, но если у вас есть что-то более сложное, например, произвольный длинный список элементов или логических условий, вы можете определить публикацию как документ, который клиент отправляет через POST.

Это позволяет более гибко описывать поиск, а также позволяет избежать ограничения длины URL-адреса сервера.

0
estani