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

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

12 ответов

Решение

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

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

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

Дизайн привлекательного URL RESTful предназначен для отображения ресурса на основе структуры (структура, похожая на каталог, дата: 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 дверями в гаражах с идентификатором 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, даты /articles/{year}/{month}/{day}.. И когда вы пропустите какой-либо из последних сегментов, вы сразу узнаете, что получите.

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

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

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

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

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

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

Хотя наличие параметров в пути имеет некоторые преимущества, 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 кажется громоздким

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

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.

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

/search/{searchQuery}

или же

/search/{savedSearchName}

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

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

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

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

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

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

Вот, Search считается дочерним ресурсом Cars ресурс.

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

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

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

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

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

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

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

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

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

Мой совет будет таким:

/garages
  Returns list of garages (think JSON array here)
/garages/yyy
  Returns specific garage
/garage/yyy/cars
  Returns list of cars in garage
/garages/cars
  Returns list of all cars in all garages (may not be practical of course)
/cars
  Returns list of all cars
/cars/xxx
  Returns specific car
/cars/colors
  Returns lists of all posible colors for cars
/cars/colors/red,blue,green
  Returns list of cars of the specific colors (yes commas are allowed :) )

Редактировать:

/cars/colors/red,blue,green/doors/2
  Returns list of all red,blue, and green cars with 2 doors.
/cars/type/hatchback,coupe/colors/red,blue,green/
  Same idea as the above but a lil more intuitive.
/cars/colors/red,blue,green/doors/two-door,four-door
  All cars that are red, blue, green and have either two or four doors.

Надеюсь, это дает вам идею. По сути, ваш Rest API должен быть легко обнаруживаем и должен позволять вам просматривать ваши данные. Еще одним преимуществом использования URL-адресов, а не строк запроса, является то, что вы можете использовать встроенные механизмы кэширования, которые существуют на веб-сервере, для HTTP-трафика.

Вот ссылка на страницу, описывающую зло строк запросов в REST: http://web.archive.org/web/20070815111413/http://rest.blueoxen.net/cgi-bin/wiki.pl?QueryStringsConsideredHarmful

Я использовал кеш Google, потому что у меня не работала обычная страница, вот эта ссылка: http://rest.blueoxen.net/cgi-bin/wiki.pl?QueryStringsConsideredHarmful

Другие вопросы по тегам