BLOGTIMES
2009/12/12

XML-RPCを使ってTracのチケットに添付ファイルを投げ込む(Ruby編)

  ruby  trac  xml 
このエントリーをはてなブックマークに追加

以前に間に合わせで書いたTracにXML-RPCを投げるスクリプトがPerlだったのでRubyに書き換えようと思って作業を始めてみたら思ったより手間がかかってしまいました。

ブラウザはどちらも同じように認証用のダイアログを出してくるので分かりにくいのですが、TracLightningを使ってTracをインストールすると、認証はBasic認証ではなくてDigest認証になっています。このDigest認証をXML-RPC時にやるというのがかなりの曲者です。初めこれをすっかり忘れていて、XMLRPC::Clientを使って実装した後に認証が通らなくて色々調べる羽目になりました。

/usr/lib/ruby/1.8/xmlrpc/client.rb

If ((|user|)) and ((|password|)) are given, each time a request is send, a Authorization header is send. Currently only Basic Authentification is implemented no Digest.

上記の通り、rubyのXML-RPCライブラリであるXMLRPC::ClientはDigest認証に対応していません。
というか、僕の調べた限りではnet/httpなどのRubyの標準添付のライブラリでDigest認証に対応しているものはないようです。

MechanizeはDigest認証できるようだ

とりあえず自分でDigest認証のルーチンを書きたくはないので、何か良いものがないかと思って検索して見ると下記のエントリを見つけました。

RubyのMechanizeでBASIC認証・Digest認証

require 'rubygems' require 'mechanize' agent = WWW::Mechanize.new require 'logger' # ログ出力用 agent.log = Logger.new($stdout) # ログ出力用 agent.auth('hogeuser','hogepasswd') agent.get('http://www.example.com/authdir/')

とりあえずMechanizeがDigest認証できることは分かりましたが、MechanizeはPostのペイロードを任意の形式にするためのAPIがないので、「WWW::MechanizeでPOSTするデータを直接書くための野良拡張 - にたまごほうれん草」を参考にMechanizeにメソッドを追加してしまうことにしました。あと、XMLRPC::Clientで書いてしまった部分をそのまま使いたかったので、XMLRPC::ClientからリクエストのXMLを取り出すためのメソッドをXMLRPC::Clientに追加することにしました。

完成したコード

以前Perlで実装したtracAttachmentPost.plと同様の機能をRubyで書いてみたのが下記のスクリプト。
メソッドをアドホックに追加しているので、ちょっと汚いですが、とりあえず問題なく動作はします。

tracAttachmentPost.rb

#!/usr/bin/ruby -Ku require 'xmlrpc/client' require 'logger' require 'rubygems' require 'mechanize' require 'hpricot' WWW::Mechanize.html_parser = Hpricot if WWW::Mechanize.html_parser class XMLRPC::Client def get_request(method, *args) create().methodCall(method, *args) end end module WWW class Mechanize def post_xmlrpc(url, data) cur_page = Page.new( nil, {'content-type'=>'text/html'}) log.debug("data: #{ data.inspect }") if log # fetch the page page = fetch_page( :uri => url, :referer => cur_page, :verb => :post, :params => [data], :headers => { 'Content-Type' => 'text/xml', 'Content-Length' => data.size.to_s, }) page end end end (ARGV[0] && ARGV[1] && ARGV[2]) || puts('usage: tracAttachPost.rb ##TicketID## ##AttachmentFile## ##Comment##') || exit(1) ticket_id = ARGV[0].to_i file_path = ARGV[1] comment = ARGV[2] user = 'username' password = 'password' uri = 'http://trachost/trac/project_name/login/xmlrpc' client = XMLRPC::Client.new2( uri ) if File.exists?(file_path) then basename = File.basename(file_path) base64_content = XMLRPC::Base64.new(File.read(file_path)) begin request = client.get_request "ticket.putAttachment", ticket_id, basename, comment, base64_content agent = WWW::Mechanize.new agent.auth(user, password) page = agent.post_xmlrpc(uri, request) # p page.body rescue WWW::Mechanize::ResponseCodeError => e p e # p e.page # p e.page.header end else puts "#{file_path} not found" end

さらにDigest認証のバグにハマった

Mechanizeを使っているので、Digest認証がすんなり通るのかとおもいきや、それでもサーバから401が帰ってくるのでちょっと参りました。仕方がないので、Mechanizeが送信しているヘッダを眺めてみると、WWW-Authenticateヘッダのrealmとncの間にカンマが入っていないというバグでした。問題の部分については下記のようにちょっと書き換えています。

/usr/lib64/ruby/gems/1.8/gems/mechanize-0.9.0/lib/www/mechanize/chain

--- auth_headers.rb.org 2009-02-08 00:10:05.000000000 +0900 +++ auth_headers.rb 2009-12-12 05:17:07.000000000 +0900 @@ -68,7 +68,7 @@ "#{field}=\"#{params[field]}\"" }.compact.join(', ') - header << "nc=#{'%08x' % @@nonce_count}, " + header << ", nc=#{'%08x' % @@nonce_count}, " header << "cnonce=\"#{CNONCE}\", " header << "response=\"#{Digest::MD5.hexdigest(request_digest)}\""

ちなみにこのバグは、僕の使っているMechanizeが0.9.0だったのが原因のようです。
0.9.1のリリースノートには下記のような記述があるので、gemで最新板をインストールしていれば特に書き換えを行う必要はありません

RubyのWWW::Mechanize 0.9.1 が出ました - きたももんががきたん。

Nonce count fixed for digest auth requests. Thanks Adrian Slapa!

    トラックバックについて
    Trackback URL:
    お気軽にどうぞ。トラックバック前にポリシーをお読みください。[policy]
    このエントリへのTrackbackにはこのURLが必要です→https://blog.cles.jp/item/3330
    Trackbacks
    このエントリにトラックバックはありません
    Comments
    愛のあるツッコミをお気軽にどうぞ。[policy]
    古いエントリについてはコメント制御しているため、即時に反映されないことがあります。
    コメントはありません
    Comments Form

    コメントは承認後の表示となります。
    OpenIDでログインすると、即時に公開されます。

    OpenID を使ってログインすることができます。

    Identity URL: Yahoo! JAPAN IDでログイン