BLOGTIMES
::
Home > XML-RPCを使ってTracのチケットに添付ファイルを投げ込む(Ruby編)
2009/12/12
[ by hsur at 23:55]

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

  

以前に間に合わせで書いた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が必要です→http://blog.cles.jp/item/3330
Trackbacks
このエントリにトラックバックはありません
Comments
愛のあるツッコミをお気軽にどうぞ。[policy]
古いエントリについてはコメント制御しているため、即時に反映されないことがあります。
コメントはありません
Comments Form

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

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

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

Web Services by Yahoo! JAPANPowered by NP_SpamBayesJP
★下記に2つの英単語をスペースで区切って入力してください
::
Home > XML-RPCを使ってTracのチケットに添付ファイルを投げ込む(Ruby編)