はてなブログのAtomPub APIで既存記事のカテゴリだけ変えようとしてPUTしたら、400 Bad Requestが返ってきました。
原因はAtomPubのPUTが「部分更新」ではなく「エントリ全体の置換」だから。content を含めた全フィールドを送る必要があります。
何が起きたか
投稿済みの記事にカテゴリを追加したくて、カテゴリだけ入れたXMLをPUTで送ったんですよ。
# カテゴリだけのXMLを用意して送った
curl -s -X PUT \
-H "Content-Type: application/atom+xml;type=entry" \
-u "${HATENA_USER}:${HATENA_API_KEY}" \
--data-binary '<entry xmlns="http://www.w3.org/2005/Atom">
<category term="新カテゴリ" />
</entry>' \
"https://blog.hatena.ne.jp/${HATENA_USER}/${HATENA_BLOG_DOMAIN}/atom/entry/${ENTRY_ID}"
返ってきたのがこれ。
400 Bad Request
XMLの書式ミスかと思って何度か書き直したんですけど、そこじゃなかった。
原因
AtomPub(RFC 5023)のPUTは「リソースの部分更新」ではなく「リソース全体の置換」です。
HTTPの仕様としても、PUTは対象リソースの現在の表現をリクエストの内容で完全に置き換える操作。部分更新はPATCH(RFC 5789)の役割なんですが、はてなブログのAtomPubはPATCHをサポートしていません。
つまりこういうことです。
| やったこと | サーバの解釈 |
|---|---|
| カテゴリだけ送った | 「タイトルも本文もないエントリに置き換えろ」 |
| → 不完全なエントリ | → 400 Bad Request |
UIだとカテゴリだけ編集できるので「APIでもできるでしょ」と思いがちですが、APIの意味論はUIの粒度と一致しないんですよね。
対策
GETしてから全体をPUTする(確実)
正しい手順は「現在のエントリを丸ごと取得 → カテゴリだけ編集 → 全体をPUT」です。
# 1) 現在のエントリを取得
curl -s -u "${HATENA_USER}:${HATENA_API_KEY}" \
"https://blog.hatena.ne.jp/${HATENA_USER}/${HATENA_BLOG_DOMAIN}/atom/entry/${ENTRY_ID}" \
-o /tmp/entry.xml
取得したXMLの <category> 要素を編集します。
# 2) カテゴリを追加(既存のcategoryの後に追加する例)
# /tmp/entry.xml を編集して <category term="新カテゴリ" /> を追加
# 3) 編集後のエントリ全体をPUT
curl -s -X PUT \
-H "Content-Type: application/atom+xml;type=entry" \
-u "${HATENA_USER}:${HATENA_API_KEY}" \
--data-binary @/tmp/entry.xml \
"https://blog.hatena.ne.jp/${HATENA_USER}/${HATENA_BLOG_DOMAIN}/atom/entry/${ENTRY_ID}"
ポイントは --data-binary を使うこと。-d だと改行がストリップされてMarkdown本文が壊れます。
Content-Typeは application/atom+xml;type=entry を指定しています。application/atom+xml だけでも動きますが、明示した方が安全です。
PUT前の検証
送信前に content が残っているか確認しておくと事故を防げます。
if ! grep -q '<content' /tmp/entry.xml; then echo "content要素がありません。PUTすると記事本文が消えます" exit 1 fi
件数が少ないならWebUIで(速い)
カテゴリ変更が数件だけなら、正直WebUIから手動でやった方が早いです。GET→編集→PUTの手順を組むコストと見合わないケースもあるので、自動化する前に件数を確認するのがおすすめです。
まとめ
はてなブログAtomPubのPUTはエントリ全体の置換なので、カテゴリだけ送ると400になります。 「GET→編集→PUT」を標準手順にしておけば安全です。この「PUTは置換、部分更新はPATCH」というHTTPの意味論は他のAPIでも同じなので、覚えておくと同種のハマりを減らせます。