これは Movable Type Advent Calendar 2015 の18日目の記事です。
先月末に開催された MTDDC Meetup TOKYO 2015 に参加してきました。色々なセッションがあり大変面白かったです。コミュニティがプロダクトを盛り上げている感がダイレクトに伝わってきて、今後のMovable Typeの展望みたいな物も参加者の方々と話せたのが自分にとっての良い経験になりました。
特に野田さんの発表は興味深く聞かせて頂きました。自分もほぼ同意の発表内容でしたが、プラグインで解決してしまうと自分が居なくなった後プロジェクトの負の遺産になる事を考えて、できる事はMTMLで済ませてしまっています。これはPerlプログラマが圧倒的に市場に足りていないという事と、継続的インテグレーションの中にMTのプラグインを入れてしまって良いかという宗教論的な部分も含んできてしまいます。ここではこの程度で終わらせます。
さて、今回はMT6の目玉とも言えるDataAPIについて書きたいと思います。先日MT Liveにて「AWSネタとDataAPIネタならどっちがいいですかね?」とSix ApartのS女史に聞いたところ、食い気味に「DataAPIっしょ」と返ってきたので、問答無用でDataAPIネタとなりましたw
まずはMTのプラグイン作成のTipsですが、こちらは私がSix Apart在籍中にまとめたドキュメントを読んで頂ければと思います。
Movable Type 開発者向けガイド - プラグイン開発ガイド
DataAPIの拡張プラグインを書くためのドキュメントはSix Apart公式の物が無いようです。自分は藤本壱さんのドキュメントを読んで勉強しました。いつも参考にさせて頂いております、ありがとうございますm(_ _)m
Data APIを使ってみる(その5) - The blog of H.Fujimoto
藤本さんも書かれていますが、config.yamlの書き方からおさらいしたいと思います。
1. config.yamlの書き方
YAMLに書くのは applications: > data_api: > endpoints: 以下になります。以下は今回の例です。
applications:
data_api:
endpoints:
- id: get_hot_entries
route: /sites/:site_id/hot-entries
verb: GET
version: 2
handler: $DataAPIHotEntriesLite::DataAPIHotEntriesLite::EndPoint::HotEntries::get_hot_entries
requires_login: 0
ここで、endpoints: 以下の要素は以下のような物を指しています。
キー | 内容 |
id |
エンドポイントのID |
routes |
エンドポイントのURIのうちmt-data-api.cgi/vn以降 |
verb |
GET, POST, PUT, DELETE(HTTPのプロトコル) |
version |
APIのバージョン |
handler |
エンドポイントを処理する関数(ハンドラ) |
requires_login |
ログインが必要なら1、不必要なら0 |
(1) id
idはエンドポイントのIDを示します。他のエンドポイントと同じ物を付けないように気を付けましょう。
(2) routes
例えば標準で実装されている get_category のroutesは以下の様になります。
/sites/:site_id/categories/:category_id
これは":site_id(ブログ、ウェブサイトのID)"と":category_id(カテゴリーID)"を指定してエンドポイントを呼び出す事になります。
実際のURIは以下のようになります。
http://www.example.com/cgi-bin/mt/mt-data-api.cgi/v2/sites/2/categories/15
ここでは:site_idを2、:category_idを15で呼び出したことになります。REST APIの標準の呼び出し方ですね。
(3) verb
HTTPプロトコルでGETやPOST、PUT、DELETEによってエンドポイントの挙動を換える事ができます。例えばGETやPOSTでは情報の取得、PUTでは作成や更新、DELETEでは削除といった風にです。最初はGETだけ指定し、必要ならPOSTを使う程度に考えておけば良いでしょう。
(4) version
DataAPIのバージョンを指定します。MT 6.2.2でのDataAPIのバージョンは3ですが、基本上位互換ですのでversionに2を指定すると、3以降でも利用出来るようになっています。私は通常2を指定するようにしています。
2を指定したら
http://www.example.com/cgi-bin/mt/mt-data-api.cgi/v2/sites/2/categories/15
3を指定したら
http://www.example.com/cgi-bin/mt/mt-data-api.cgi/v3/sites/2/categories/15
のように、mt-data-api.cgiの後に"/v2/"や"/v3/"として指定することになります。
(5) handler
エンドポイントを処理する関数(ハンドラ)を指定します。ここはプラグイン開発では基本ですね。
最初に上げた例では以下のように指定しました。
handler: $DataAPIHotEntriesLite::DataAPIHotEntriesLite::EndPoint::HotEntries::get_hot_entries
$(プラグイン名)::(モジュール名)::(関数名)という形で指定します。
(6) requires_login
エンドポイントを呼ぶのにログインが必要かどうかを指定します。単純なGETであればログインは必要ないので"0"を指定すれば良いでしょう。逆に外部のユーザーが閲覧できないような情報を扱ったり、情報を更新したりする場合はログインが必要ですので"1"を指定しましょう。
2. ハンドラの書き方
非常に単純化して書くと以下のようになります。
package DataAPIHotEntriesLite::EndPoint::HotEntries;
use strict;
use MT::DataAPI::Endpoint::Common;
use MT::DataAPI::Resource;
sub get_hot_entries {
my ($app, $endpoint) = @_;
my ( $blog ) = context_objects(@_) or return;
my $ret_val;
(do something...)
return $ret_val;
}
1;
ハンドラは $app と $endpoint を値として取ります。
context_objects(@_) を呼ぶと、routesで指定したリソースを返します。ここでは"/sites/:site_id/hot-entries"とroutesを指定していると想定しているので、返ってくるのは":site_id"に紐付いたオブジェクトです。ここではブログ、もしくはウェブサイトのオブジェクトが返ってきます。
":foobar_id"とした場合は、"foobar"が返ってきます。これはリソースといって基本的な物は標準で用意されていますが、用意されていないリソースをroutesに指定したい場合は、別途リソースを追加する必要があります。詳しくは藤本さんのドキュメントを参考にしてみてください。
例えば、リソースを複数指定したとします。
/sites/:site_id/categories/:category_id
この場合、以下のように context_objects(@_) を記述します。
my ( $blog, $category ) = context_objects(@_) or return;
このように、用意されているリソースについては context_objects(@_) を呼ぶだけで簡単に取得できます。便利ですね。
:site_id で指定されたブログ・ウェブサイトが存在しない場合は404エラーが、それ以外の context_objects(@_) が失敗した場合は単純にreturnしていますが、これで500エラーとなります。そのあたりもDataAPIがよしなに動いてくれるのであまり悩まされる事はありません。
必要な処理を行って情報を収集し、$ret_valの中に格納。それをreturnすると自動的にJSONが構築され呼び出し元に返されます。
少し言い換えると$ret_valは配列やハッシュ、もしくは配列のハッシュや、ハッシュの配列など自由に構築でき、MT::Util::to_json()を経由してJSONとして書き出されます。
3. 少し進んだ使い方
特に requires_login を1にしたときに必要となるのが、ブログやウェブサイト、その他のオブジェクトへの権限です。
例えばファイルをアップロードする処理を行う場合に権限も無いのにアップロードできてしまったら、セキュリティホールになります。その当たりは気を付けて自分でふさぐ必要があります。
例えばアセットの情報を取得する処理をする場合、以下のようにすることになります。
package DataAPIAssetInfo::EndPoint::Asset;
use strict;
use MT::DataAPI::Endpoint::Common;
use MT::DataAPI::Endpoint::Asset;
use MT::DataAPI::Resource;
sub get_asset_info {
my ($app, $endpoint) = @_;
return $app->error(403) unless $app->can_do('upload');
my ( $blog, $asset ) = context_objects(@_) or return;
run_permission_filter( $app, 'data_api_view_permission_filter',
'asset', $asset->id, obj_promise($asset)) or return;
(do something...)
return { item => $asset };
}
1;
$app->can_do('upload') をチェックすると、そのユーザーがアップロードする許可があるか確認します。許可が無い場合は403エラーを返しています。ここでは単純化のために「最低でもユーザーがアップロード権限を持っていること」としました。
その後、run_permission_filter() を呼んで、ログインしているユーザーがそのアセットの閲覧権限があるかを確認します。権限が無い場合500エラーとなります。
run_permission_filter() は少し難しくなりますのでさわり程度とします。MTのソースから permission_filter を検索してこの辺りで何をやっているか追ってみるのも良いでしょう。
4. プラグイン開発
今回、この記事を書くにあたって DataAPIHotEntriesLite というプラグインを書きました。
https://github.com/uehatsu/mt-plugin-data-api-hot-entries-lite
Google Analytics連携のされているブログの人気記事ランキング(過去7日分の集計)をリアルタイムに取得します。このエンドポイントにアクセスするとGAのAPIにアクセスしに行くので、最悪の場合API上限に達する可能性があります。アクセスの多いブログなどに設置する場合、それらの事を認識した上で配置をお願いします。このプラグインは今後機能を充実させて行く予定です。
エンドポイント例:
https://uehatsu.info/cgi-bin/mt/mt-data-api.cgi/v2/sites/3/hot-entries
5. まとめ
DataAPIはそれ自体の活用事例はいくつか出てきました。MT for iOS 愛用してます ;-)
そろそろDataAPI拡張の事例が出てきても良いのではないかと考え、今回このエントリーをまとめてみました。皆でDataAPI拡張プラグインを書いてみませんか?(^^)ノシ