Какой код состояния перенаправления HTTP лучше всего подходит для этого сценария REST API?
Я работаю над 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://stackru.com/
(но закодировано)
Если bit.ly раньше не видел этот URL, он создаст для него новый идентификатор и перенаправит меня через 201 Created на новый идентификатор. Но если он увидит этот URL, он все равно перенаправит меня без внесения изменений. Таким образом, я могу попасть в это место перенаправления в любом случае, чтобы получить информацию / метаданные по сокращенному URL.
Как и этот пример сокращения URL, в моем приложении коллизии не имеют значения. Один URL-адрес соответствует одному идентификатору, и все. Так что на самом деле не имеет значения, был ли URL сокращен раньше или нет; в любом случае, имеет смысл указать клиенту идентификатор для него, нужно ли сначала создавать этот идентификатор или нет.
Поэтому я, вероятно, не буду менять этот подход; Я просто спрашиваю о лучшем методе перенаправления для него. Спасибо!
5 ответов
Я бы поспорил за 303. Предположим, прямо сейчас hello=world&hokey=pokey однозначно идентифицирует элемент foo, но позже значение hokey элемента foo изменится на "smokey"? Теперь эти исходные значения больше не являются уникальным идентификатором для этого ресурса. Я бы сказал, что временный редирект уместен.
Я думаю, что одна из причин, по которой вы боретесь с этим сценарием, заключается в том, что (если мы не пропустили некоторую ключевую информацию) взаимодействие не очень логично.
Позвольте мне объяснить, почему я так думаю. Исходная предпосылка заключается в том, что пользователь запрашивает создание чего-либо и предоставил некоторую ключевую информацию для ресурса, который он хочет создать.
Затем вы заявляете, что если эта ключевая информация относится к существующему объекту, вы хотите вернуть этот объект. Проблема в том, что пользователь не хотел получать существующий объект, который он хотел создать новый. Если они не могут создать ресурс, потому что он либо существует, либо произошло столкновение ключей, пользователь должен быть проинформирован об этом факте.
Выбор извлечения существующего объекта, когда пользователь попытался создать новый, кажется вводящим в заблуждение подходом.
Возможно, одной из альтернатив будет возвращение запроса 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-адресу может никогда не потребоваться фактический доступ к исходному серверу, поскольку прокси-сервер может полностью обработать запрос.
POST не поддерживает подход "поиск или создание". Сервер не может сказать клиенту: "Я бы создал это, но оно уже существовало. Ищите здесь существующую запись". Ни один из кодов 2xx не работает, потому что запрос не выполнен. Ни один из кодов 3xx не работает, потому что цель состоит не в том, чтобы перенаправить POST на новый ресурс. И 303 также не подходит, так как ничего не изменилось (см. Спецификацию 303).
Что вы можете сделать, это предоставить клиенту форму или шаблон для использования с PUT, который сообщает клиенту, как создать URI PUT. Если PUT приводит к 200, клиент знает, что ресурс существует, и если 201 возвращается, то новый ресурс был создан.
Например:
Шаблон для URI: http://service/items/{key}
[данные]
201 Создано
или же
[данные]
200 хорошо
Вы также можете сделать "создать, но не заменять, если существует", используя If-None-Match:
PUT http://service/items/456
If-None-Match: *
[data]
412 Precondition failed
январь
С точки зрения клиента, я думаю, что вы могли бы просто отправить 201 для случая 2 так же, как для случая 1, так как клиенту запись "создана".
HTTP 1.1. Spec (RFC 2616) предлагает 303:
303 См. Другое
Ответ на запрос может быть найден под другим URI и ДОЛЖЕН быть получен с использованием метода GET для этого ресурса. Этот метод существует главным образом для того, чтобы позволить выводу сценария, активированного POST, перенаправить пользовательский агент на выбранный ресурс. Новый URI не является заменой ссылки на первоначально запрошенный ресурс.