はてなダイアリーAtomPub@20101019151353

* 本ドキュメントに関する注意事項

本ドキュメントははてなダイアリーにおける Atom Publishing Protocol の仕様を解説するものです。主にはてなスタッフがその作成と更新を行っています。

* 変更履歴

- 2008年8月28日 リリース
- 2008年9月2日 認証方式からCookieを廃止、WSSEのみに
- 2008年12月22日 説明の充実(エラーやAtomPubからの拡張部分など),および細かな修正
- 2010年01月06日 「下書きエントリーの日記への公開」リクエストURIが間違っていたため修正

* Atom Publishing Protocol とは

Atom Publishing Protocol(以下 AtomPub) はウェブリソースを公開、編集するためのアプリケーション・プロトコル仕様です。はてなダイアリーのAtomPubと通じて、開発者ははてなダイアリーの日記を参照、投稿、編集、削除するようなオリジナルのアプリケーションを作成できます。

AtomPub について詳しくは http://www.ietf.org/rfc/rfc5023.txt (英語)などを参照してください。

* URIの表記について

本仕様解説中に現れるURIはURI Templateの記法に基づいて以下のように表記されます。
>||
http://d.hatena.ne.jp/{はてなID}/atom/blog/{date}/{entry_id}
||<
各変数の意味と書式は次のようになります。
- はてなID
-- 意味:あなたのはてなid
- date:
-- 意味:日記の日付
-- 書式:YYYYMMDD (例: 20080101)
- entry_id:
-- 意味:エントリのID
-- 書式:epochを表す数値 (例: 1227232862) または、英数字文字列


* はてなダイアリーにおけるAtomPub実装の概要

HTTP の GET/POST/PUT/DELETE を特定のURIに対してリクエストし、そのリクエストに規定のXML文書を加えて送信することでインタフェースが用意している操作を行うことができます。

AtomPubには特定の操作の対象の集合を表す「コレクション」と個々の操作の対象を表す「メンバ」があります。コレクションとメンバはそれぞれにURIを持ち、そのURIに対して操作を行います。例えば、はてなダイアリーの日記エントリーのコレクションとメンバのURIは以下のようになります。

- コレクションURI: http://d.hatena.ne.jp/{はてなID}/atom/blog
- メンバURI: http://d.hatena.ne.jp/{はてなID}/atom/blog/{date}/{entry_id}

一部の操作はそのレスポンスとして規定のXML文書を返却します。また、下書きエントリーの公開操作を実現するためにAtomPubを拡張しています。

現時点でAPIがサポートしている操作は以下です。

- 日記エントリーの操作
-- 新規日記エントリーの投稿 (ブログ コレクションURI への POST)
-- 日記エントリーの一覧の取得 (ブログ コレクションURI への GET)
-- 日記エントリーの取得 (ブログ メンバURI の GET)
-- 日記エントリーのタイトル及び本文の変更 (ブログ メンバURI への PUT)
-- 日記エントリーの削除 (ブログ メンバURI への DELETE)
- 下書きエントリーの操作
-- 新規下書きエントリーの投稿 (下書きコレクションURI への POST)
-- 下書きエントリーの一覧の取得 (下書きコレクションURI への GET)
-- 下書きエントリーの取得 (下書きメンバURI の GET)
-- 下書きエントリーのタイトル及び本文の変更 (下書きメンバURI への PUT)
-- 下書きエントリーの削除 (下書きメンバURI への DELETE)
-- 下書きエントリーの公開 (下書きメンバURIヘの PUT および X-HATENA-PUBLISHヘッダの設定)


以下、はてなダイアリーAtomPubの詳細を解説します。

*auth* 認証

はてなダイアリーAtomPubを利用するために、クライアントはWSSE認証を行う必要があります。

** WSSE認証
AtomPubに良く用いられるWSSE認証が利用できます。WSSE認証の詳細に関しては http://www-128.ibm.com/developerworks/webservices/library/ws-secure/ (英語) を参照してください。ここではWSSE認証についての必要事項を簡単に解説します。

WSSE認証はHTTPのX-WSSEヘッダを用いて認証用文字列を送信する認証手段です。WSSE認証用文字列にはユーザー名とパスワードが含まれます。このとき、パスワードはSHA1アルゴリズムによって暗号化されたダイジェストとして送信されるため、HTTP基本認証などに比べてセキュアな認証が可能です。

送信するX-WSSEヘッダのサンプルは以下のようになります。

>||
X-WSSE: UsernameToken Username="hatena", PasswordDigest="ZCNaK2jrXr4+zsCaYK/YLUxImZU=", Nonce="Uh95NQlviNpJQR1MmML+zq6pFxE=", Created="2005-01-18T03:20:15Z"
||<

:Username:はてなID
:Nonce:HTTPリクエスト毎に生成したセキュリティ・トークン((ヘッダに含める際にbase64エンコーディングする必要があります))
:Created:Nonceが作成された日時をISO-8601表記で記述したもの
:PasswordDigest:Nonce, Created, パスワード(はてなアカウントのパスワード)を文字列連結しSHA1アルゴリズムでダイジェスト化して生成された文字列を、Base64エンコードした文字列

具体的な実装方法については、[[はてなフォトライフAtomAPI]]を参照してください。

* 文字コード
はてなダイアリーAtomPubでは文字コードとしてUTF-8を利用します。リクエストXML、レスポンスXML共にUTF-8として扱ってください。

はてなダイアリーの他の場所では文字コードとしてEUC-JPを利用していることが多いので、まちがえないよう注意してください。

* サービス文書

はてなダイアリーAtomPubで操作できるコレクションの一覧を含むサービス文書を取得できます。

** リクエスト

>||
GET /{はてなID}/atom
||<

** レスポンス

>|xml|
HTTP/1.1 200 OK
Content-Type: application/atomsvc+xml; charset=utf-8

<?xml version="1.0" encoding="utf-8"?>
<service xmlns="http://www.w3.org/2007/app">
  <workspace>
    <atom:title xmlns:atom="http://www.w3.org/2005/Atom">Hatena::Diary - はてなID</atom:title>
    <collection href="http://d.hatena.ne.jp/はてなID/atom/draft">
      <atom:title xmlns:atom="http://www.w3.org/2005/Atom">はてなIDさんの下書き</atom:title>
      <accept>application/atom+xml;type=entry</accept>
    </collection>
    <collection href="http://d.hatena.ne.jp/はてなID/atom/blog">
      <atom:title xmlns:atom="http://www.w3.org/2005/Atom">はてなIDさんの日記</atom:title>
      <accept>application/atom+xml;type=entry</accept>
    </collection>
  </workspace>
</service>
||<

* ブログ コレクション
はてなダイアリーの日記エントリーを操作するためのコレクションです。日記エントリーの投稿、取得、編集、削除、一覧の取得を行うことができます。コレクションURI、およびメンバURIは以下になります。

- コレクションURI: http://d.hatena.ne.jp/{はてなID}/atom/blog
- メンバURI: http://d.hatena.ne.jp/{はてなID}/atom/blog/{date}/{entry_id}

** 日記エントリー一覧の取得
コレクションURIをGETすることで、日記エントリー一覧を取得できます。一度に20件のエントリーを取得できます。また、pageパラメータに数値を指定することで、20件目以降のエントリーを取得できます。

*** リクエスト
>||
GET /{はてなID}/atom/blog
GET /{はてなID}/atom/blog?page=2
||<

*** レスポンス
指定ページに対応した Atom Feed 文書が返却されます。ここでは省略します。


** 日記エントリーの投稿
コレクションURIに対してXML文書をPOSTすることで、日記エントリーを投稿できます。
*** リクエスト
リクエストXML文書に必要なパラメータは以下です。

- title要素 日記エントリーのタイトル
- content要素 はてな記法で記述された日記エントリーの本文
-- type属性 text/plain
- updated要素 日記エントリーを投稿する日時を指定します。この指定を行わなかった場合、投稿を行った日時が利用されます。


>|xml|
POST /{はてなID}/atom/blog

<?xml version="1.0" encoding="utf-8"?>
<entry xmlns="http://purl.org/atom/ns#">
  <title>日記エントリータイトル</title>
  <content type="text/plain">
日記エントリー本文
- はてな
- 記法
  </content>
  <updated>2008-01-01T00:00:00+09:00</updated>
</entry>
||<

*** レスポンス
- レスポンスは正常時にHTTPステータス201を返します。
- Locationヘッダに新しく作成したエントリーのメンバURIが含まれます。
>|xml|
HTTP/1.1 201 Created
Content-Type: application/atom+xmlcharset=type=entry
Location: http://d.hatena.ne.jp/{はてなID}/atom/blog/{date}/{entry_id}

<?xml version="1.0" encoding="utf-8"?>
<entry xmlns="http://www.w3.org/2005/Atom">
  <id>tag:d.hatena.ne.jp,2008:diary-はてなID-{date}-{entry_id}</id>
  <link rel="edit" href="http://d.hatena.ne.jp/{はてなID}/atom/blog/{date}/{entry_id}"/>
  <link rel="alternate" type="text/html" href="http://d.hatena.ne.jp/{はてなID}/{date}/{entry_id}"/>
  <author>
    <name>はてなID</name>
  </author>
  <title>日記エントリータイトル</title>
  <updated>2008-01-01T00:00:00+09:00</updated>
  <published>2008-01-01T00:00:00+09:00</published>
  <app:edited xmlns:app="http://www.w3.org/2007/app">2008-01-01T00:00:00+09:00</app:edited>
  <content type="text/html">
                <div class="section">
                        <p>日記エントリー本文</p>
                        <ul>
                                <li> はてな</li>
                                <li> 記法</li>
                        </ul>
                        <p>  </p>
                </div>
</content>
</entry>
||<


** 日記エントリーの取得
メンバURIをGETすることで、日記エントリーを取得できます。

*** リクエスト
>||
GET /{はてなID}/atom/blog/{date}/{entry_id}
||<

*** レスポンス
- レスポンスは正常時にHTTPステータス200を返します。
- レスポンスXML文書はhatena名前空間(http://www.hatena.ne.jp/info/xmlns#)によって拡張されています。展開される前のはてな記法が格納されています。

>|xml|
<?xml version="1.0" encoding="utf-8"?>
<entry xmlns="http://www.w3.org/2005/Atom">
  <id>tag:d.hatena.ne.jp,2008:diary-{はてなID}-{date}-{entry_id}</id>
  <link rel="edit" href="http://d.hatena.ne.jp/{はてなID}/atom/blog/{date}/{entry_id}"/>
  <link rel="alternate" type="text/html" href="http://d.hatena.ne.jp/{はてなID}/{date}/{entry_id}"/>
  <author>
    <name>はてなID</name>
  </author>
  <title>日記エントリータイトル</title>
  <updated>2008-01-01T00:00:00+09:00</updated>
  <published>2008-01-01T00:00:00+09:00</published>
  <app:edited xmlns:app="http://www.w3.org/2007/app">2008-01-01T00:00:00+09:00</app:edited>
  <content type="text/html">
                <div class="section">
                        <p>日記エントリー本文</p>
                        <ul>
                                <li> はてな</li>
                                <li> 記法</li>
                        </ul>
                        <p>  </p>
                </div>
</content>
  <hatena:syntax xmlns:hatena="http://www.hatena.ne.jp/info/xmlns#">
日記エントリー本文
- はてな
- 記法</hatena:syntax>
</entry>
||<


** 日記エントリーの編集
メンバURIに対してXML文書をPUTすることで、日記エントリーを編集できます。投稿された日記エントリーの日時は投稿を行った日時になります。


*** リクエスト

リクエストXML文書に必要なパラメータは以下です。

- title要素 日記エントリーのタイトル
- content要素 はてな記法で記述された日記エントリーの本文
-- type属性 text/plain
- updated要素 日記エントリーを投稿する日時を指定します。この指定を行わなかった場合、投稿を行った日時が利用されます。

>|xml|
PUT /{はてなID}/atom/blog/{date}/{entry_id}

<?xml version="1.0" encoding="utf-8"?>
<entry xmlns="http://purl.org/atom/ns#">
  <title>あたらしい日記エントリータイトル</title>
  <content type="text/plain">
あたらしい日記エントリー本文
- はてな
- 記法
  </content>
  <updated>2008-01-01T00:00:00+09:00</updated>
</entry>
||<

*** レスポンス
>|xml|
HTTP/1.1 200 OK
Content-Type: application/atom+xmlcharset=type=entry

<?xml version="1.0" encoding="utf-8"?>
<entry xmlns="http://www.w3.org/2005/Atom">
  <id>tag:d.hatena.ne.jp,2008:diary-{はてなID}-{date}-{entry_id}</id>
  <link rel="edit" href="http://d.hatena.ne.jp/{はてなID}/atom/blog/{date}/{entry_id}"/>
  <link rel="alternate" type="text/html" href="http://d.hatena.ne.jp/{はてなID}/{date}/{entry_id}"/>
  <author>
    <name>はてなID</name>
  </author>
  <title>あたらしい日記エントリータイトル</title>
  <updated>2008-01-01T00:00:00+09:00</updated>
  <published>2008-01-01T00:00:00+09:00</published>
  <app:edited xmlns:app="http://www.w3.org/2007/app">2008-01-01T00:00:00+09:00</app:edited>
  <content type="text/html">
                <div class="section">
                        <p>あたらしい日記エントリー本文</p>
                        <ul>
                                <li> はてな</li>
                                <li> 記法</li>
                        </ul>
                        <p>  </p>
                </div>
</content>
</entry>
||<

** 日記エントリーの削除
メンバURIをDELETEすることで、日記エントリーを削除できます。

*** リクエスト
>||
DELETE /{はてなID}/atom/blog/{date}/{entry_id}
||<

*** レスポンス
>||
HTTP/1.1 200 OK
||<

* 下書き コレクション
はてなダイアリーの下書きを操作するためのコレクションです。下書きエントリーの投稿、取得、編集、削除、一覧の取得、および日記エントリーとしての公開を行うことができます。コレクションURI、およびメンバURIは以下になります。

- コレクションURI: http://d.hatena.ne.jp/{はてなID}/atom/draft
- メンバURI: http://d.hatena.ne.jp/{はてなID}/atom/draft/{entry_id}

下書きコレクションの操作は基本的にブログコレクションと同等なため詳しい説明は省略します。ただし、返却されるAtom Entryのcontent要素に含まれるのがはてな記法のみになっていることに注意してください。

はてなダイアリーAtomPubでは、AtomPubで規定されているapp:draft要素を使用しません。はてなダイアリーでは日記エントリと下書きエントリを別々に管理しています。そのため、日記エントリと下書きエントリを一つのコレクションとして扱い、app:draft要素によって下書きかどうかを区別するような構成は不自然になります。むしろ、下書きエントリを日記エントリとして公開するという操作を行う仕組みであるほうが自然であると考え、以下のような方法をとっています。

** 下書きエントリーの日記への公開
メンバURIに対してX-HATENA-PUBLISHヘッダを付与してPUTすることで、メンバURIで指定した下書きをもとに日記エントリーを投稿できます。投稿された日記エントリーの日時は投稿を行った日時になります。

*** リクエスト
- X-HATENA-PUBLISH ヘッダに1を含めます。

>||
PUT /{はてなID}/atom/draft/{entry_id}
X-HATENA-PUBLISH: 1
||<

*** レスポンス
- レスポンスは正常時にHTTPステータス201を返します。
- Locationヘッダに新しく作成した日記エントリーのメンバURIが含まれます。

実際のレスポンスの例はブログコレクションの日記エントリーの投稿を参照してください。

* エラーレスポンス
はてなダイアリーAtomPubに対して不正な操作を行った場合にエラーレスポンスが返却されます。各エラーレスポンスには次のような意味があります。

:400 Bad Request: リクエストが不正な場合に返却されます。リクエストに含まれるXMLの内容等を確認してください。
:401 Unauthorized: WSSEによる認証が失敗した際に返却されます。
:404 Not Found: 存在しないリソースにアクセスした際に返却されます。URLに含まれるはてなID、dateおよびentry_id等を確認してください。
:405 Method Not Allowd: リソースに対して許可されていないメソッドによるリクエストを発行した際に返却されます。指定のリソースに対して可能な操作を確認してください。
:500 Internal Server Error: はてなダイアリーAtomPub 側でなんらかの問題が発生した際に返却されます。

* はてなダイアリー AtomPub を利用したプログラムの例

** Perlを用いた例

CPANモジュールのXML::Atom::ClientはAtomPubクライアントを実装するための、WSSE認証やリクエスト、レスポンスに必要なXML文書の組み立てなどを抽象化したモジュールです。

XML::Atom::Clientを用いて、はてなダイアリーに日記を投稿するサンプルコードは以下です。

>|perl|
#!/usr/bin/env perl
use strict;
use warnings;

use XML::Atom::Entry;
use XML::Atom::Client;

my $username = shift or die "need username";
my $password = shift;

my $PostURI = "http://d.hatena.ne.jp/$username/atom/blog";

my $client = XML::Atom::Client->new;
$client->username($username);
$client->password($password);

my $entry = XML::Atom::Entry->new;
$entry->title('テスト日記だよー');
$entry->content(<<'ENDCONTENT');
わーい、はてな記法もかけるぞー
- こんな
- ふうに
- ね
ENDCONTENT

my $EditURI = $client->createEntry($PostURI, $entry)
    or die $client->errstr;

print $EditURI;
||<

XML::Atom::Client はWSSE認証を抽象化しているため、username/passwordメソッドでそれぞれをセットするだけで認証を通過できます。また、XML文書の組み立てはXML::Atom::Entryインスタンスを生成して行い、それを最後にXML::Atom::Clientインスタンスに渡せば完了です。

このスクリプトはコマンドラインから、

>||
$ perl atompost.pl はてなID password
||<

として実行できます。

** Rubyを用いた例

Ruby から API を利用するには、<a href="http://d.hatena.ne.jp/lyokato/20071211/1197353609">atomutil ライブラリ</a>を利用することによって投稿することが可能です。

まず、atomutil ライブラリを拡張し、X-Hatena-Publish に対応させ、下書きからの投稿を行えるようにします。

>|ruby|
require 'rubygems'
require 'atomutil'

module Atompub
  class HatenaClient < Client
    def publish_entry(uri)
      @hatena_publish = true
      update_resource(uri, ' ', Atom::MediaType::ENTRY.to_s)
    ensure
      @hatena_publish = false
    end

    private
    def set_common_info(req)
      req['X-Hatena-Publish'] = 1 if @hatena_publish
      super(req)
    end
  end
end
||<

エントリーの新規作成のサンプルコードは以下です。

>|ruby|
auth = Atompub::Auth::Wsse.new :username => 'はてなID', :password => 'hatena_password'
client = Atompub::HatenaClient.new :auth => auth
service = client.get_service 'http://d.hatena.ne.jp/%s/atom' % 'はてなID'
collection_uri = service.workspace.collections[1].href

entry = Atom::Entry.new(
  :title => 'My Entry Title',
  :updated => Time.now
)

entry.content = <<EOF
エントリー本文だよ
EOF

puts client.create_entry collection_uri, entry
||<

エントリー下書き投稿は、collection_uri を変更するだけです。
>|ruby|
collection_uri = service.workspace.collections[0].href
puts client.create_entry collection_uri, entry # 下書き投稿
||<

下書き投稿からパブリッシュするには、HatenaClient#publish_entry を使います

>|ruby|
client.publish_entry entry.edit_link
||<

* 参考

- [http://www.ietf.org/rfc/rfc5023.txt:RFC5023 Atom Publishing Protocol]
- [http://www.ricoh.co.jp/src/rd/webtech/rfc5023_ja.html:title]
- [http://search.cpan.org/perldoc?LWP::Authen::Wsse:title=LWP::Authen::Wsse]
- [http://search.cpan.org/perldoc?XML::Atom:title=XML::Atom]
- [http://search.cpan.org/perldoc?XML::Atom::Service:title=XML::Atom::Service]

- [[はてな技術文書]]