Hatena::ブログ(Diary)

Yet Another Hackadelic

2008-11-04

Blogger で Atompub してみる

ふとした用途*1で Blogger に対して Atompub で日記を書きたくなったのでメモ。

AuthSub で認証

Blogger への認証には Google の AuthSub or ClientLogin を使うみたいです。

今回はプロトコルが云々よりも Blogger に投稿するのが目的なので ClientLogin を使ってみます。

で、さすが Perl と言うか CPAN ですね。ちゃんと Net::Google::AuthSub と言うモジュールがあるじゃーないですか。

ClientLogin に対応しているのは login メソッドなので、次のように書きます。

#!/usr/bin/perl

use strict;
use warnings;

use Data::Dump qw(dump);
use Perl6::Say;
use Net::Google::AuthSub;

my ($user, $pass) = ("zigorou", "hogehoge"); # 値は適当
my %opts = (
  service => "blogger",
  accountType => "GOOGLE",
  source => "exampleCo-exampleApp-1",
);

my $auth = Net::Google::AuthSub->new(%opts);
my $res = $auth->login($user, $pass);

say dump($auth->auth_params) if ($res->is_success);

で成功すると Authorization ヘッダの設定に必要な値が配列として返って来る。

(
  "Authorization",
  "GoogleLogin auth=\"XXXXXXXXXXXXXXXX\"",
)

みたいな感じ。

で、これを LWP::UserAgent の default_header とか使って設定しておけばおkです。

だから最終的には次のようなスクリプトで新しい日記を投稿出来ます。

#!/usr/bin/perl

use strict;
use warnings;

use Perl6::Say;
use Atompub::Client;
use XML::Atom::Entry;
use Net::Google::AuthSub;

# 値適当
my ($user, $pass, $blog_id) = ('zigorou', 'okorareta', '0000000000000000');
my $collection_uri = "http://www.blogger.com/feeds/' . $blog_id . '/posts/default";

my $entry = XML::Atom::Entry->new();
$entry->title("ブログタイトル");
$entry->content("ブログの本文〜");

my $auth = Net::Google::AuthSub->new(
    service => "blogger",
    accountType => "GOOGLE",
    source => "exampleCo-exampleApp-1"
);

$auth->login($user, $pass);

my $cli = Atompub::Client->new;

# ここがポイント
$cli->ua->default_header($auth->auth_params);
# 日記の投稿
say $cli->createEntry($collection_uri, $entry);

SEE ALSO

*1:何となくなんだからねっ!

2008-03-11 嫌いな人にはっきりと嫌いと言うのは大人気ないそうです

Catalyst::Controller::Atompubのdispatch、Slugヘッダとか

Atompubの勉強始めました。

Catalyst::Controller::Atompubについて

こちらはid:teahut*1さん自身がPerl(Catalyst)でAtompubサーバーを作る解説記事を書かれていますので、そちらを見ると大体分かると思います。

で今の所、分かった事とか困ってる事とか書いてみる事にします。

URLが基本的にpackage名で固定されてしまう。

例えば、コレクションFeedを取得したい場合はコレクションリソースURIに対してGETするんですけど、そのCatalystアクションの書き方は、

sub get_feed :Atompub(list) {
  # implements
}

みたいに書くんですが、これが仮にpackage MyApp::Foo::Collectionだとすると/foo/collectionに固定されてしまいます。

と言うのもCatalyst::Controller::Atompub::Collectionにて、

  • create_action()にてAtompub(xxx)みたいなattributeがある場合はxxxに応じたhandlerとしてCODEREFを保持
  • do_xxx(list, read, create, update, delete)を生成。この際、実行される実体は先ほど保持したCODEREF
  • dispatchは基本的にdefault, edit_uriで行い、ここがURIが決めうちになる原因となっている。それぞれHTTP Methodに応じて適切な実体(do_xxx)を呼び出すようになっている

なので、自前のURIとして例えば、/foo/<user_id>/collection みたいな物を提供したいなと思った場合はかなり力技に頼らねばならない。

力技とは、大体こんな感じ。

まず前提としてコレクションリソースURIにアクセスしたのかエントリリソースURIにアクセスしたかで、起動するメソッドが違う

  1. コレクションURI - default()
    1. GET, HEAD - _list()
    2. POST - _create()
  2. リソースURI - edit_uri()
    1. GET, HEAD - read()
    2. POST - _create()
    3. PUT - _update()
    4. DELETE - _delete()

で、default, edit_uriのdispatchが固定されてるんだから上書きしちゃえば良い。

sub default :Regex('^foo/(\w+)/collection') {
  my ($self, $c) = @_;
  $self->NEXT::default($c);
}

sub edit_uri: Regex('^foo/(\w[\w_-]+\w)/collection/([^-?&#][^?&#]*)') {
  my ($self, $c) = @_;
  $self->NEXT::edit_uri($c);
}

そして、その後はdo_xxxを直接実装しちゃうのがいいのかなーと思います。

多分この辺りはたけまるさんやid:ikasam_aが適切な補足をしてくれると思います。

たけまるさんの指摘を元に$c->NEXT::xxxを$self->NEXT::xxxに修正しました。(2008-03-11T23:11:41+09:00)

ところで、

なお,ZIGOROu さんの例では,default メソッドを先に定義していますが, 探索順の関係から edit_uri を先にしてください Catalyst::Controller::Atompub - コレクション URI の変更

これってどういう事でしょう。ちょっと僕には分からなかったです。

追記 (2008-03-11T20:05:55+09:00)

id:ikasam_a反応してくれたです。なるほどChainedアクションですか。

先の例だと、

sub user: Chained PathPart('foo') CaptureArgs(1) {
  my ($self, $c) = @_;
}

sub default: Chained('user') PathPart('collection') {
  my ($self, $c) = @_;
}

sub edit_uri: Chained('user') PathPart('collection') Args(1) {
  my ($self, $c) = @_;

}

みたいな感じかな。

Catalyst::Controller::Resourcesも期待ageですね。

割り当てられるエントリリソースURIについて

この辺りはCatalyst::Controller::Atompub::Collectionのmake_edit_uri()にて実装されています。ソース見た方が早いので拝借。コメントは僕が勝手につけました。

sub make_edit_uri {
    my ( $self, $c, @args ) = @_;

    # XML::Atom::Feedオブジェクト
    my $collection_uri = $self->info->get( $c, $self )->href;

    my $basename;
    if ( my $slug = $c->req->slug ) { # Slugヘッダがある場合
        my $slug = uri_unescape $slug;
        $slug =~ s/^\s+//;
        $slug =~ s/\s+$//;
        $slug =~ s/[.\s]+/_/;
        $basename = uri_escape lc $slug;
    }
    else { # Slugヘッダが無い場合
        my ( $sec, $usec ) = gettimeofday;
        $basename = join '-', strftime( '%Y%m%d-%H%M%S', localtime($sec) ),
            sprintf( '%06d', $usec );
    }

    my @media_types = map { media_type($_) } ( 'entry', @args );

    my @uris;
    for my $media_type (@media_types) {
        my $ext = $media_type->extension || 'bin';
        my $name = join '.', $basename, $ext;
        push @uris, join '/', $collection_uri, $name;
    }

    return wantarray ? @uris : $uris[0];
}

って訳でSlugヘッダがあるか無いかで命名規則が違う実装みたいです。

この辺りはRFC 5023 Atom Publishing Protocol 日本語訳 - Slugヘッダを見ると良く分かります。

Slug は HTTP エンティティヘッダであり、コレクションに POST が行われるときのこのヘッダの存在は、これから作成されるエントリないしメディアリソースを参照するために通常使用される URI の一部としてそのヘッダの値を使ってほしい、というクライアントの要求を示す。 新しく作成されたリソースのメンバ URI を作るとき、サーバは Slug ヘッダの値を使ってもよい(MAY)。たとえば、最後の URI セグメントにその値の中の単語の一部、あるいはすべてを使う。また、atom:id を作成するとき、あるいはメディアリンクエントリのタイトルとしてその値を使ってもよい(MAY)(9.6 節参照)。 サーバは Slug エンティティヘッダを無視することを選択してもよい(MAY)。サーバはそれを使う前に、ヘッダの値を変えるかもしれない(MAY)。たとえば、サーバはある文字を取り除くか、あるいは強調されていない文字を強調された文字に置き換えるか、あるいは下線をスペースで置き換えるかもしれない。 RFC 5023 Atom Publishing Protocol 日本語訳 - Slugヘッダ

なので、Slugヘッダがあった場合は新しいエントリないしはメディアリソースのURIの一部として使用しても構わないと。

Atompub::Clientを使ってcreateEntryする場合は、

#!/usr/bin/perl

use strict;
use warnings;

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

sub create_entry {
    my ($title, $content) = @_;
    my $entry = XML::Atom::Entry->new;
    $entry->title($title);
    $entry->content($content);
    return $entry;
}

sub create {
    my ($collection_uri, $title, $content) = @_;
    my $client = Atompub::Client->new;
    return $client->createEntry(
        $collection_uri, 
        create_entry($title, $content), 
        $title ### ここがSlugヘッダの値になる
    );
}

print create(@ARGV);

みたいなソースになる。

参考

*1:たけまるさん