Web APIの次世代標準プロトコル「Atom Publishing Protocol」

第3回リソースの操作を追う

おさらい

前回の最後にサービス文書についてご紹介しました。このサービス文書はコレクション及びメンバリソースを操作する上で最初の手がかりとなるものです。サービス文書内においては、collection要素で、どういったコレクションがあるのか、またそのURIは何か、が記述されていました。

では前回のサービス文書の例をもとに、リソースの操作を追っていきましょう。

フィードの取得とページング

サービス文書内で規定されているコレクションのURIは、<http://example.org/blog/main>でした。これにGETメソッドを適用するとフィード文書が得られます(以下、HTTPヘッダはリクエスト、レスポンス共に一部主要部分のみ掲載をします⁠⁠。

フィード取得のためのリクエスト例
GET /blog/main HTTP/1.1
Host: example.org

するとサーバから以下のようなレスポンスが返ってきます。

フィード取得のレスポンス例
HTTP/1.1 200 Ok
Date: Fri, 8 Oct 2006 17:17:11 GMT

<feed xmlns="http://www.w3.org/2005/Atom" xmlns:app="http://www.w3.org/2007/app">
  <title>asakuraの記事一覧</title>
  <generator>feed generator version 1.0</generator>
  <icon>http://example.org/img/icon/feed.jpg</icon>
  <id>http://example.org/blog/main/1</id>
  <link rel="self" type="application/atom+xml" href="http://example.org/blog/main/1"/>
  <updated>2007-11-30T00:00:00+09:00</updated>
  <link rel="first" href="http://example.org/blog/main/1"/>
  <link rel="next"  href="http://example.org/blog/main/2"/>
  <link rel="last"  href="http://example.org/blog/main/25"/>
  <entry>
    <title>紅葉</title>
    <id>http://example.org/blog/main/article/20382</id>
    <published>2007-11-14T15:12:55+09:00</published>
    <updated>2007-11-30T00:00:00+09:00</updated>
    <app:edited>2007-11-30T00:00:00+09:00</app:edited>
    <summary>今日は紅葉を見に行きました。</summary>
    <link rel="alternate" type="text/html" href="http://example.org/blog/main/article/20382"/>
    <link rel="enclosure" type="image/png" href="http://example.org/img/fall.jpg"/>
    <link rel="edit" href="http://example.org/blog/main/1"/>
    <author>
      <name>asakura</name>
    </author>
  </entry>
  ... 同じようにエントリがあと29個続きますが割愛します ...
</feed>

ずらずらっと複数のエントリが入ったフィードが返されてきます。このときコレクションに入っているメンバリソースが少ない場合は良いのですが、多くの場合100件、200件、いやもっと多くのメンバリソースがコレクションの中に入っているかもしれません。このような場合、一度にフィードで返してしまうとサーバもクライアントも処理が大変です。このためページングの仕組みが用意されています。フィードに記述されるエントリは、サーバ側が決めたある件数以上については次のページに繰り越されます。このため次ページ、前ページ、最初のページ、最後のページを取得するためのURIが取得したフィードの中にatom:link要素として記述されています。atom:link要素はhref属性としてURIを持っています。そのURIがどのような意味を持つのかについては、rel属性にかかれています。表1にその対応をまとめてみました。

表1 ページング機能を表現するatom:link要素の一覧
rel属性の値意味
next次ページのフィード取得URIを示す
prev前ページのフィード取得URIを示す
first最初のページのフィード取得URIを示す
last最後のページのフィード取得URIを示す

app:editedとフィードにおけるエントリの順序

フィードに含まれるエントリの中には、app:editedという要素が入っているかもしれません。これは、最終更新日時を示すものです。atom:updatedも似たような要素ですが、重要な変更が行われた日時を指すもので、必ずしも全ての更新が対象ではありません。また、app:editedの値はHTTPヘッダにあるLast-Modifiedヘッダの値と同じとは限りません。

フィードの中に出てくるエントリの順番は大抵このapp:edited要素の時刻で最新のものからならんでいます。しかし実装によってはそうなっていない場合もあるので注意が必要です。

リソースの取得

では次にリソースを取得してみましょう。フィードに記載されているエントリが完全なリソースの表現であることは保障されていません。もしかしたら概要として表現されており、必須ではない要素が一部省略されているかもしれません。

取得したフィードの中から一番最初のエントリを取得してみます。

エントリ取得のためのリクエスト例
GET /blog/main/1 HTTP/1.1
Host: example.org

サーバからは指定されたエントリを取得することができます。

エントリ取得のレスポンス例
HTTP/1.1 200 Ok
Date: 

<entry xmlns="http://www.w3.org/2005/Atom" xmlns:app="http://www.w3.org/2007/app">
  <title>紅葉</title>
  <id>http://example.org/blog/main/article/20382</id>
  <published>2007-11-14T15:12:55+09:00</published>
  <updated>2007-11-30T00:00:00+09:00</updated>
  <app:edited>2007-11-30T00:00:00+09:00</app:edited>
  <content>今日は紅葉を見に行きました。もうこんな季節なんですね。でも例年よりも全然遅いそうです。</content>
  <link rel="alternate" type="text/html" href="http://example.org/blog/main/article/20382"/>
  <link rel="enclosure" type="image/png" href="http://example.org/img/fall.jpg"/>
  <link rel="edit" href="http://example.org/blog/main/1"/>
  <author>
    <name>asakura</name>
  </author>
</entry>

それぞれの要素の内容については第2回で簡単にご紹介しましたので割愛させていただきます。

リソースの追加

リクエスト

リソースの追加を行う場合には、コレクションURIに対してPOSTを行います。エントリリソースを追加する際はbody部分に、追加するリソースをXMLで表現したエントリ文書を記述します。MIMEタイプは"application/atom+xml"または"application/atom+xml;type=entry"を指定します。

メディアリソースを追加する際にバイナリデータをそのまま(エンコードなしで)記述します。

以下にエントリリソース追加のための例をご紹介します。

エントリリソース追加のためのリクエスト例
POST /blog/main HTTP/1.1
Host: example.org
Content-Type: application/atom+xml;type=entry
Content-Length: nnn
Slug: the first snow of this season

<entry xmlns="http://www.w3.org/2005/Atom" xmlns:app="http://www.w3.org/2007/app">
  <title>初雪</title>
  <id></id>
  <updated>2007-11-18T10:00:00+09:00</updated>
  <app:edited>2007-11-18T10:00:00+09:00</app:edited>
  <content>今日は寒いです。日本列島の中ではどこかは初雪を観測したところがあるんじゃないかな。</content>
  <author>
    <name>asakura</name>
  </author>
</entry>

Slugヘッダ

また、HTTPヘッダの拡張として、Slugヘッダが用意されています。これは追加されたリソースを参照するためのURIやatom:idなどを生成するために参考にされるものです。どのように使われるかはサーバの実装に依存します。もしかしたら使わない実装もあるかもしれません。実際にはファイル名(メディアリソースの場合)やエントリのタイトルを記述してリクエストを出すことが想定されているようです。

SlugヘッダはHTTPヘッダの一部であるため、日本語を記述する場合はエンコードしなければなりません。UTF-8で記述した文字列をさらに%-エンコーディングした文字列を利用します。

レスポンス

成功した場合の帰り値は必ず201 Createdになります。このとき、生成されたエントリ(=メンバリソース)のURIがHTTPのLocationヘッダに記述されて返ってきます。そして、body部分には生成されたエントリの内容が返ります。

メディアリソースを追加した場合は、メディアリンクエントリのURIがLocationヘッダに記載され、メディアリンクエントリの内容がbody部分に返ります。

エントリリソース追加のレスポンス例
HTTP/1.1 201 Created
Date: Sat, 18 Nov 2007 10:10:23 JST
Content-Length: XXX
Content-Type: application/atom+xml;type=entry;charset="utf-8"
Location: http://example.org/blog/main/2

<entry xmlns="http://www.w3.org/2005/Atom" xmlns:app="http://www.w3.org/2007/app">
  <title>初雪</title>
  <id>http://example.org/blog/main/article/73821</id>
  <published>2007-11-18T10:10:21+09:00</published>
  <updated>2007-11-18T10:10:21+09:00</updated>
  <app:edited>2007-11-18T10:00:00+09:00</app:edited>
  <content>今日は寒いです。日本列島の中ではどこかは初雪を観測したところがあるんじゃないかな。</content>
  <link rel="alternate" type="text/html" href="http://example.org/blog/main/article/73821"/>
  <link rel="edit" href="http://example.org/blog/main/2"/>
  <author>
    <name>asakura</name>
  </author>
</entry>

返ってくるエントリはPOSTした際のエントリの内容と異なるかもしれません(メディアリンクエントリが返る場合については、エントリの内容は自動生成されています⁠⁠。

特にサーバはatom:id要素やatom:updated要素などの内容について、リソース管理上の観点からサーバ側でPOSTしたときの内容から書き換える可能性があります。また、クライアント側が指定しなかった要素、属性などが追加されたり、指定した要素、属性などが削除されたりすることもあります。こういったサーバ側の書き換えは仕様上許されています。

メンバリソースURIの取得

今後、エントリを編集する場合は、返ってきたエントリの中にあるatom:link要素のうちrel属性値がeditであるURIを使います。

メディアリンクエントリが返ってくる場合も同様で、メディアリンクエントリを編集する場合はatom:link要素のうち、rel="edit"である要素のURIを使います。POSTしたメディアリソースを編集する場合は、atom:link要素のうち、rel="edit-media"である要素のURIを使います。このURIがない場合はメディアリソースが編集できないということになります。

表2にメンバリソースURIをどこから取得したら良いかについてまとめてみました。

通常のエントリリソースを追加した場合は、表中のA.のみが対象になります。

メディアリソースを追加した場合は、メディアリンクエントリとメディアリソースが生成されますので、表中のA.,B.,C.全てが対象になります。

表2 メンバリソースURIとその記載される場所の対応表
メンバリソースURI出現条件記載されている場所
A.生成されたエントリリソースURIMUSTLocationヘッダ
SHOULDatom:link要素(rel="edit")
B.生成されたメディアリソースを参照するためのURIMUSTatom:content要素のsrc属性
C.生成されたメディアリソースURISHOULDatom:link 要素(rel="edit-media")

AtomPubの仕様ではatom:link要素(rel="edit"及びrel="edit-media")は記載されるべき(SHOULD)となっており、含まれない場合もあります。このため、エントリリソースを登録した場合のレスポンスにatom:link要素(rel="edit")が含まれないときはLocationヘッダからエントリリソースURIを取得することになります。

メディアリソースをPOSTした場合もメディアリンクエントリはLocationヘッダで必ず返されます。そして、メディアリンクエントリの場合、必ずatom:content要素がsrc属性にURIを伴って記載されます。しかし、これはメディアリソースURIとは限りません。メディアとして参照できるキャッシュであったり、他のネットワーク上にあるサーバを指していることもあります。ですからメディアリソースURIは必ずatom:link要素(rel="edit-media")に記載されているものを使用します。

リソースの更新

リクエスト

リソースの更新はPUTを使って行うことができます。PUTのbody部分には、更新を行う内容を記載します。エントリリソースの場合は、変更したい内容をXMLであるエントリ文書として表現します。基本的には表現されたエントリ文書でそのまま更新されることになります。ただし、サーバ側の判断で許可されていない要素や内容についてはクライアント側のリクエスト通り更新されません。また、クライアント側は変更されていない要素を消失しないよう、先にGETしたエントリを必要な部分だけ書き換えてPUTするのが良いでしょう。

メディアリソースを更新する場合は、PUTリクエストのbody部分にバイナリデータを記述します。

以下にエントリリソースを更新するPUTの例をご紹介します。この例では、atom:conten要素の内容を少し書き換えています。また、編集した時刻(app:edited要素)も少し変更しています。

エントリリソース更新のためのリクエスト例
PUT /blog/main/2 HTTP/1.1
Host: example.org
Content-Type: application/atom+xml;type=entry
Content-Length: XXX

<entry xmlns="http://www.w3.org/2005/Atom" xmlns:app="http://www.w3.org/2007/app">
  <title>初雪</title>
  <id>http://example.org/blog/main/article/73821</id>
  <published>2007-11-18T10:10:21+09:00</published>
  <updated>2007-11-18T10:10:21+09:00</updated>
  <app:edited>2007-11-18T12:10:34+09:00</app:edited>
  <content>今日は寒いです。日本列島の中ではどこかは初雪を観測したところがあるんじゃないかな。・・・と思ったら本当に降ってたみたいですね。</content>
  <link rel="alternate" type="text/html" href="http://example.org/blog/main/article/73821"/>
  <link rel="edit" href="http://example.org/blog/main/2"/>
  <author>
    <name>asakura</name>
  </author>
</entry>

この結果、atom:content要素とapp:edited要素はクライアント側の指定した内容に書き換わることを期待しています。

レスポンス

成功した場合、200Okが返ります。

エントリリソース更新のレスポンス例
HTTP/1.1 200 Ok
Date: Sat, 18 Nov 2007 10:35:11 JST
Content-Length: XXX
Content-Type: application/atom+xml;type=entry;charset="utf-8"

<entry xmlns="http://www.w3.org/2005/Atom" xmlns:app="http://www.w3.org/2007/app">
  <title>初雪</title>
  <id>http://example.org/blog/main/article/73821</id>
  <published>2007-11-18T10:10:21+09:00</published>
  <updated>2007-11-18T10:35:08+09:00</updated>
  <app:edited>2007-11-18T12:10:34+09:00</app:edited>
  <content>今日は寒いです。日本列島の中ではどこかは初雪を観測したところがあるんじゃないかな。・・・と思ったら本当に降ってたみたいですね。</content>
  <link rel="alternate" type="text/html" href="http://example.org/blog/main/article/73821"/>
  <link rel="edit" href="http://example.org/blog/main/2"/>
  <author>
    <name>asakura</name>
  </author>
</entry>

この例では、期待どおりapp:editedとatom:content要素が書き換わっています。

また、atom:updatedやatom:published要素はサーバ側が適宜書き換えてくれていますが、これは実装依存です。atom:updatedやatom:published要素についても、クライアント側の指定どおりに書き換えてくれるサーバがあるかもしれません。

他の名前空間に属する要素の扱い

エントリリソースを更新する場合、エントリ文書をPUTします。エントリ文書は、atom:entry要素をルート要素としますが、決められた要素以外にもXML名前空間の仕組みを使って他の名前空間に属する要素を記載することもできます。ただし、実際にエントリとして保存するかどうかはサーバの実装に任されています。

また、サーバからリソースを取得した場合、他の名前空間に属する要素が入っている場合もあります。このときには未知のものであってもクライアントはその要素を保持し、そのままPUTリクエストのbody部分に記載をすべきです。これは編集によるデータの消失を防ぐ観点から重要なことです。

PUTした内容の扱い

エントリリソースを更新する場合、POSTの時と同様にPUTで送付したエントリ文書がそのまま反映されるとは限りません。例えばatom:id要素はそのエントリに対してユニークなものであることからクライアント側から書き換えができない場合がほとんどでしょう。このような要素を更新するためにクライアント側は内容を書き換えてPUTを行ったとしても、無視されて処理が実行されることになるでしょう。

リソースの削除

リクエスト

リソースの削除は、メンバリソースのURIに対してDELETEメソッドを適用します。多くの場合、何らかの方法(Basic認証やWSSE認証など)で認証され、削除権限があれば許可されるでしょう。削除する対象がメディアリンクエントリである場合はメディアリソースも、対象がメディアリソースである場合は、メディアリンクエントリも同時に削除されることが多いでしょう(厳密には実装依存です⁠⁠。

リソース削除のためのリクエスト例
DELETE /blog/main/2 HTTP/1.1
Host: example.org

レスポンス

成功した場合は200 OKが返ります。失敗した場合は、理由に応じたHTTPのエラーが返ることになります。

リソース削除のレスポンス例
HTTP/1.1 200 Ok
Date: Sun, 18 Nov 2007 23:50:13 JST

認証

本連載では認証については全く触れてきませんでした。しかし、AtomPubはサーバ上のリソースを編集することができるプロトコルです。通常はクライアント側の認証は必須となるでしょう。認証方法についてはAtomPubがどこでどういった使われ方をするかによって様々な選択肢があると思います。IETFのWG内でも議論の過程で様々な意見が出ました。しかし最終的には、⁠最低限HTTP及びHTTPS(TLS)上でのBasic認証が設定できること」というところで仕様を決着させました。ある意味、仕様としては現実解を取ったともいえます。

実際のAtomPubサーバはBasic認証以外の認証をサポートすることも多いと思います。色々な方法があると思いますが、AtomPubはREST志向のプロトコルですので、一回一回のCRUD操作で認証が走る方法(ステートレス)が望ましいでしょう。

エラー

AtomPubはHTTPベースのプロトコルですので、エラー時には基本的にHTTPのエラーで表現されます。サーバ側が400番台と500番台のエラーを返すときには人間が可読なエラーメッセージを含めるように実装することが求められていますが詳細までは決められていません。

まとめ

駆け足でAtomを見てきましたが、表層はすごく理解しやすいプロトコルだとお感じになられたのではないでしょうか?

AtomPubの本質はREST的なCRUD操作をAtomフィードを使って定義したところにあります。これさえ押さえてしまえば理解したも同然です。しかし、実際にRFCを読み深めていくと、サーバの実装に任されている部分が多いプロトコルであるとお気づきになるでしょう。このためサーバの設計者は(クライアントの設計者も)細かなところで難しい判断を迫られることになるかもしれません。今後は、様々な実装が出てくることで、実装経験が蓄積されていく(Best Current Practice)と思われます。またそれとともに新しい仕様がIETF draftとして出てくると予想されます。

必ずしも全てのwebサービスがこのコレクションとメンバリソースのモデルに親和性があるわけではありませんが、単純なデータモデルですから大半のwebサービスには適用できると思います。また、メンバリソースに関連するハイパーリンクをatom:link要素として表現しているのもwebの概念をうまく利用し、拡張性を豊かなものにしていると思います。是非皆様もAtomPubを利用してみてください。

次回は最後の連載になります。著者を交代しまして、弊社坂野のほうからサンプルプログラムによるAtomサーバへのアクセス事例をご紹介しましょう。

おすすめ記事

記事・ニュース一覧