Какой код состояния перенаправления HTTP лучше всего подходит для этого сценария REST API? - PullRequest
12 голосов
/ 11 марта 2010

Я работаю над REST API. Ключевые объекты («существительные») являются «элементами», и каждый элемент имеет уникальный идентификатор. Например. получить информацию о товаре с идентификатором foo:

GET http://api.example.com/v1/item/foo

Новые элементы могут быть созданы, но клиент не может выбрать идентификатор. Вместо этого клиент отправляет некоторую информацию, которая представляет этот элемент. Итак, чтобы создать новый предмет:

POST http://api.example.com/v1/item/
hello=world&hokey=pokey

С помощью этой команды сервер проверяет, есть ли у нас элемент для информации hello=world&hokey=pokey. Итак, здесь есть два случая.

Случай 1: предмет не существует; это создано. Этот случай прост.

201 Created
Location: http://api.example.com/v1/item/bar

Случай 2: предмет уже существует. Вот где я борюсь ... не уверен, какой код перенаправления лучше всего использовать.

301 Moved Permanently? 302 Found? 303 See Other? 307 Temporary Redirect Location: http://api.example.com/v1/item/foo

Я изучил описания Википедии и RFC 2616 , и ни одно из них не кажется идеальным. Вот конкретные характеристики, которые я ищу в этом случае:

Перенаправление является постоянным, поскольку идентификатор никогда не изменится. Таким образом, для эффективности клиент может и должен направлять все будущие запросы к конечной точке идентификатора напрямую. Это говорит о 301, так как остальные три должны быть временными.

Перенаправление должно использовать GET, даже если этот запрос POST. Это предполагает 303, поскольку все остальные технически должны повторно использовать метод POST. На практике браузеры будут использовать GET для 301 и 302, но это REST API, а не веб-сайт, предназначенный для использования обычными пользователями в браузерах.

Это должно быть широко доступно и легко играть. В частности, 303 - это HTTP / 1.1, тогда как 301 и 302 - это HTTP / 1.0. Я не уверен, насколько это серьезная проблема.

На данный момент, я склоняюсь к 303 только для того, чтобы быть семантически правильным (используйте GET, не повторяйте POST) и просто поглощайте его во «временной» части. Но я не уверен, что 302 будет лучше, так как на практике это было то же поведение, что и 303, но без использования HTTP / 1.1. Но если я пойду по этой линии, мне будет интересно, будет ли 301 еще лучше по той же причине плюс «постоянная» часть.

Мысли приветствуются!


Редактировать: Позвольте мне попытаться лучше объяснить семантику этой операции "получить или создать" на более конкретном примере: сокращение URL. Во всяком случае, это намного ближе к моему приложению.

Для укорачивателей URL наиболее распространенной операцией на сегодняшний день является получение по идентификатору Например. для http://bit.ly/4Agih5, bit.ly получает идентификатор 4Agih5 и должен перенаправить пользователя на соответствующий ему URL.

bit.ly уже имеет API, но это не совсем RESTful. Для примера позвольте мне создать более RESTful API. Например, запрос идентификатора может вернуть всевозможную информацию о нем (например, аналитика):

GET http://api.bit.ly/item/4Agih5

Теперь, если я хочу отправить новый URL-адрес для сокращения bit.ly, я заранее не знаю идентификатор своего URL-адреса, поэтому я не могу использовать PUT. Я бы вместо этого использовал POST.

POST http://api.bit.ly/item/
url=http://stackoverflow.com/ (но закодировано)

Если bit.ly раньше не видел этот URL, он создаст для него новый идентификатор и перенаправит меня через 201 Created на новый идентификатор. Но если он видел этот URL, он все равно перенаправит меня без внесения изменений. Таким образом, я могу попасть в это место перенаправления, чтобы получить информацию / метаданные по сокращенному URL.

Как и в этом примере сокращения URL, в моем приложении коллизии не имеют значения. Один URL-адрес соответствует одному идентификатору, и все. Так что на самом деле не имеет значения, был ли URL сокращен раньше или нет; в любом случае, имеет смысл указать клиенту идентификатор для него, нужно ли сначала создавать этот идентификатор или нет.

Так что я, вероятно, не буду менять этот подход; Я просто спрашиваю о лучшем методе перенаправления для него. Спасибо!

Ответы [ 5 ]

11 голосов
/ 11 марта 2010

Я бы поспорил за 303. Предположим, прямо сейчас hello = world & hokey = pokey однозначно идентифицирует элемент foo, но позже значение hokey элемента foo изменится на «smokey»? Теперь эти исходные значения больше не являются уникальным идентификатором для этого ресурса. Я бы сказал, что временный редирект уместен.

10 голосов
/ 12 марта 2010

Я думаю, что одна из причин, по которой вы боретесь с этим сценарием, заключается в том, что (если мы не пропустили некоторую ключевую информацию) взаимодействие не очень логично.

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

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

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

Возможно, одной из альтернатив будет возвращение запроса 404 Bad, если ресурс уже существует, и включение ссылки на существующий объект в теле объекта. Клиентское приложение может решить проглотить ошибку неверного запроса и просто перейти по ссылке на существующую сущность и таким образом скрыть проблему от пользователя. Это было бы выбором клиентского приложения, но, по крайней мере, сервер ведет себя четко.


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

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

Итак, исходя из этого предположения, почему бы просто не использовать

GET /ShortUrl?longUrl=http://www.example.org/en/article/something-that-is-crazy-long.html&suggestion=crazyUrl

Если URL-адрес уже существует, вы можете вернуться

303 See Other
Location: http://example.org/ShortUrl/3e4tyz

Если раньше этого не произошло, вы можете получить

303 See Other
Location: http://example.org/ShortUrl/crazyurl

Я понимаю, что похоже, что мы нарушаем правила GET, создавая что-то в ответ на GET, но я считаю, что в этом случае в этом нет ничего плохого, потому что клиент не запрашивал создание сокращенного URL и на самом деле все равно в любом случае. Он идемпотентен, потому что не имеет значения, сколько раз вы его называете.

Один интересный вопрос, на который я не знаю ответа, будет ли прокси кэшировать начальный GET и перенаправлять. Это может быть интересным свойством, поскольку будущим запросам других пользователей по тому же URL-адресу может никогда не потребоваться фактический доступ к исходному серверу, поскольку прокси-сервер может полностью обработать запрос.

3 голосов
/ 11 марта 2010

POST не поддерживает подход «поиск или создание». Сервер не может сказать клиенту: «Я бы создал это, но оно уже существовало. Ищите здесь существующую запись». Ни один из кодов 2xx не работает, потому что запрос не выполнен. Ни один из кодов 3xx не работает, потому что цель состоит не в том, чтобы перенаправить POST на новый ресурс. И 303 также не подходит, так как ничего не изменилось (см. Спецификацию 303).

Что вы можете сделать, это предоставить клиенту форму или шаблон для использования с PUT, который сообщает клиенту, как создать URI PUT. Если результат PUT равен 200, клиент знает, что ресурс существует, и если возвращается 201, то был создан новый ресурс.

Например:

Шаблон для URI: http://service/items/{key}

PUT http://service/items/456

* * 1013 [Данные] * * 1014

201 Создано

или

PUT http://service/items/456

[данные]

200 ОК

Вы также можете сделать «создать, но не заменять, если существует», используя If-None-Match:


PUT http://service/items/456
If-None-Match: *

[data]

412 Precondition failed 

Jan

2 голосов
/ 28 мая 2014

HTTP 1.1. Spec (RFC 2616) предлагает 303:

303 См. Другое

Ответ на запрос можно найти под другим URI и ДОЛЖЕН быть получен с использованием метода GET на этом ресурсе. Этот метод существует прежде всего для разрешения вывода POST-активированного сценария перенаправить пользовательский агент на выбранный ресурс. Новый URI не является подставить ссылку на первоначально запрошенный ресурс.

2 голосов
/ 11 января 2012

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

...