Какой правильный `content-type` при редактировании постов?

Привет, ребята! Можно ли использовать application/json в качестве Content-Type при обновлении поста через API? В документации сказано, что можно, но я начинаю думать, что это невозможно

Я постоянно получаю ошибку ["BAD CSRF"] и понятия не имею, что это значит.


Если мне нужно использовать multipart/form-data, не могли бы вы подсказать, как сформировать PUT-запрос? Особенно в части data.

header = CaseInsensitiveDict()
header["Authorization"] = '{"api-key": "longapikey", "api-username": "myusername"}'
{
  "post": {
      "raw": "Крутой пост, но вот обновлённый текст тела поста",
      "edit_reason": "Я изменил это, потому что могу."
   }
}

resp = requests.put(url, headers=headers, data=data)

Спасибо!

Вам нужно отправлять Api-Key и Api-Username как отдельные заголовки, а не в заголовке Authorization. Вот пример, который должен сработать лучше:

header = CaseInsensitiveDict()
header["Api-Key"] = 'longapikey'
header["Api-Username"] = 'myusername'

Это выглядит как та же проблема, что и в вашей предыдущей теме:

Спасибо за ваш ответ, Дэвид.

Это очень странно: когда я форматирую заголовок так, как вы показали, я получаю:

{"errors":["У вас нет разрешения на просмотр запрошенного ресурса. Имя пользователя или ключ API недействительны."],"error_type":"invalid_access"}

Если я добавляю ["Authorization"], всё работает.

Это для простого GET-запроса, но я всё ещё не могу выполнить PUT.

Я ожидал, что заголовок, который работает для одной операции, будет работать для всех (при условии, что ключ является global — а он им и является). Поэтому я пока не слишком беспокоюсь о заголовке — или мне стоит?

Спасибо!

Скорее всего, ваш запрос PUT сформирован некорректно, особенно если GET работает, даже когда вы используете заголовок Authorization неправильно. Он работает, потому что для выполнения запроса GET авторизация вообще не требуется. Мы даже не проверяем заголовок Authorization, если вы его передаёте. Запросы GET к публичным конечным точкам будут работать корректно без каких-либо заголовков.

@pedroleaoc вот небольшой пример скрипта на Python, демонстрирующий, как выполнять аутентифицированные запросы и отправлять данные для PUT/POST-запросов.

# discourse-api-demo.py
import requests
from requests.structures import CaseInsensitiveDict

# Базовый GET-запрос к общедоступному URL, заголовки не требуются.
url = "http://localhost:3000/posts/10.json"

resp = requests.get(url)

print(resp.status_code)
print(resp.content)

# GET-запрос к приватной конечной точке. Требуются заголовки аутентификации.
url = "http://localhost:3000/admin/users/list/active.json"
headers = {'Api-Username': 'system', 'Api-Key': '5c1c57915e2...'}
resp = requests.get(url, headers=headers)

print(resp.status_code)
print(resp.content)

# PUT-запрос с телом запроса
url = "http://localhost:3000/posts/10.json"
data = { 'raw': "Крутой пост, но вот обновлённое содержимое поста", 'edit_reason': "Я изменил это, потому что могу." }

resp = requests.put(url, headers=headers, json=data)
print(resp.status_code)
print(resp.content)

Из Quickstart — Requests 2.33.1 documentation

Вместо того чтобы кодировать dict самостоятельно, вы также можете передать его напрямую, используя параметр json (добавлен в версии 2.4.2), и он будет закодирован автоматически:

url = 'https://api.github.com/some/endpoint' >>> payload = {'some': 'data'} >>> r = requests.post(url, json=payload)

Обратите внимание: параметр json игнорируется, если передан либо data, либо files.

Использование параметра json в запросе изменит Content-Type в заголовке на application/json.

Дьявол, конечно! Не все GET-запросы требуют авторизации!
Это очень полезный совет, я разберусь с этим. Спасибо!

Вот что я уже выяснил методом реверс-инжиниринга:

  • Используется api-key, а не api_key
  • Запрос PUT требует заголовок "content-type": "application/x-www-form-urlencoded"
  • Данные не в формате JSON (они закодированы, хотя я пока не могу найти правильный способ кодирования)

Пожалуйста, посмотрите мой пост, расположенный сразу над этим. Вы можете использовать формат json=data вместо data=data в вашем запросе, и библиотека python requests сама установит заголовок content-type как application/json, что и следует использовать.

Отлично, теперь я могу редактировать свой пост! Спасибо за помощь!

Есть ещё одна проблема: используемый мной ключ — global. Когда я пытаюсь использовать ключ только с правами write и read, получаю ошибку: Вы не имеете права просматривать запрошенный ресурс. Имя пользователя API или ключ недействительны. При редактировании поста через графический интерфейс, помимо запроса, который фактически редактирует пост (posts/post_id.json), отправляется ещё один PUT-запрос к URL темы, который я не могу воспроизвести с ограниченным ключом API — только с глобальным. Однако я не понимаю, почему без этого дополнительного PUT-запроса, который происходит в GUI, я не мог бы редактировать пост через API.

РЕДАКТИРОВАНИЕ: Технически мой ключ API охватывает путь /t/:slug/:topic_id, куда указывает этот PUT-запрос.

Выбрана ли для вашего API-ключа область действия “edit Posts”?

Нет! У меня даже нет такой опции! Разберусь с этим, спасибо ещё раз за помощь.