curb(Rubyのcurlバインディング)で並列リクエストを試す

gemでインストールしたcurb(Rubycurlバインディング)のrdocを見ていたらCurl::Multiっていうクラスがあって、並列でURLリクエストできそうだったので試してみた。
※2010/06/15現在、WEB上に置いてあるcurbのオンラインドキュメントは結構古いようで(CURB_VERSION定数をみると"0.1.4"って書いてある)この頃はまだCurl::Multiが未実装っぽい。


直近でインストールしたcurbのライブラリバージョンは0.7.6

$ gem list curb -d

*** LOCAL GEMS ***

curb (0.7.6, 0.7.5)
    Authors: Ross Bamford, Todd A. Fisher
    Rubyforge: http://rubyforge.org/projects/curb
    Homepage: http://curb.rubyforge.org/
    Installed at (0.7.6): /usr/local/lib/ruby/gems/1.8
                 (0.7.5): /usr/local/lib/ruby/gems/1.8

    Ruby libcurl bindings


以下、テストスクリプト

test_curb.rb

#!/usr/bin/env ruby
# -*- coding:utf8 -*-
require 'rubygems'
require 'curb'

MyResponse = Struct.new(:url, :code, :body, :time)

responses = []
requests = [
  'http://dailynews.yahoo.co.jp/fc/rss.xml',
  'http://feeds.builder.japan.zdnet.com/builder/rss',
  'http://feeds.feedburner.com/hatena/b/hotentry',
  'http://japan.internet.com/rss/rdf/topics.rdf',
  'http://rss.rssad.jp/rss/wiredvision/feed/atom.xml',
  'http://rss.rssad.jp/hogehoge/hogehoge.xml', # 存在しないコンテンツのURL
  'http://hogehoge.example.com/hogehoge.xml'   # 存在しないホストのURL
]


puts "Start"
time_begin = Time.now

m = Curl::Multi.new
m.pipeline = true

requests.each do |url|
  m.add(
    Curl::Easy.new(url) do |curl|
      curl.follow_location = true
      curl.on_complete do |ch|
        responses << MyResponse.new(url, ch.response_code, ch.body_str, ch.total_time)
      end
    end
  )
end

m.perform do
  puts "idling... can do some work here, including add new requests"
end

responses.each do |res|
  puts res.url
  puts "  code = #{res.code}"
  puts "  body.size = #{res.body.size}"
  puts "  time = #{res.time}"
  puts '---'
end

puts "End time:#{Time.now - time_begin}"
puts


実行結果

$ ruby -v
ruby 1.8.7 (2010-01-10 patchlevel 249) [i686-linux]

$ ruby test_curb.rb
Start
http://hogehoge.example.com/hogehoge.xml
  code = 0
  body.size = 0
  time = 0.0
---
http://rss.rssad.jp/hogehoge/hogehoge.xml
  code = 404
  body.size = 1509
  time = 0.020261
---
http://japan.internet.com/rss/rdf/topics.rdf
  code = 200
  body.size = 5714
  time = 0.026651
---
http://rss.rssad.jp/rss/wiredvision/feed/atom.xml
  code = 200
  body.size = 107973
  time = 0.044281
---
http://dailynews.yahoo.co.jp/fc/rss.xml
  code = 200
  body.size = 3465
  time = 0.046129
---
http://feeds.builder.japan.zdnet.com/builder/rss
  code = 200
  body.size = 44936
  time = 0.07544
---
http://feeds.feedburner.com/hatena/b/hotentry
  code = 200
  body.size = 100764
  time = 0.187272
---
End time:0.188435


curbが入ってない環境で(Ruby標準添付ライブラリだけで)同じようなことをやるならNet::HTTPとThreadを使って↓こんな感じか。多分。

test_thread.rb

#!/usr/bin/env ruby
# -*- coding:utf8 -*-
require 'uri'
require 'net/http'
Net::HTTP.version_1_2

MyResponse = Struct.new(:url, :code, :body, :time)

responses = []
requests = [
  'http://dailynews.yahoo.co.jp/fc/rss.xml',
  'http://feeds.builder.japan.zdnet.com/builder/rss',
  'http://feeds.feedburner.com/hatena/b/hotentry',
  'http://japan.internet.com/rss/rdf/topics.rdf',
  'http://rss.rssad.jp/rss/wiredvision/feed/atom.xml',
  'http://rss.rssad.jp/hogehoge/hogehoge.xml', # 存在しないコンテンツのURL
  'http://hogehoge.example.com/hogehoge.xml'   # 存在しないホストのURL
]


puts "Start"
time_begin = Time.now

threads = []
requests.each do |url|
  threads << Thread.new do
    time_b = Time.now
    begin
      res = Net::HTTP.get_response(URI.parse(url))
      code = res.code
      body = res.body
    rescue StandardError => e
      code = 0
      body = ''
    end
    responses << MyResponse.new(url, code, body, Time.now - time_b)
  end
end

threads.each do |t|
  t.join
end

responses.each do |res|
  puts res.url
  puts "  code = #{res.code}"
  puts "  body.size = #{res.body.size}"
  puts "  time = #{res.time}"
  puts '---'
end

puts "End time:#{Time.now - time_begin}"
puts

実行結果

$ ruby -v
ruby 1.8.7 (2010-01-10 patchlevel 249) [i686-linux]

$ ruby test_thread.rb
Start
http://hogehoge.example.com/hogehoge.xml
  code = 0
  body.size = 0
  time = 0.001616
---
http://rss.rssad.jp/hogehoge/hogehoge.xml
  code = 404
  body.size = 1509
  time = 0.009805
---
http://japan.internet.com/rss/rdf/topics.rdf
  code = 200
  body.size = 5714
  time = 0.018922
---
http://dailynews.yahoo.co.jp/fc/rss.xml
  code = 200
  body.size = 3465
  time = 0.034052
---
http://rss.rssad.jp/rss/wiredvision/feed/atom.xml
  code = 200
  body.size = 107973
  time = 0.048426
---
http://feeds.builder.japan.zdnet.com/builder/rss
  code = 200
  body.size = 44936
  time = 0.102508
---
http://feeds.feedburner.com/hatena/b/hotentry
  code = 200
  body.size = 100764
  time = 0.189887
---
End time:0.196962