LoginSignup
26
27

More than 5 years have passed since last update.

コールバックを使わずに、RubyでHTTPリクエストを並列発行する方法

Last updated at Posted at 2014-12-08

次のようなユースケースで、HTTPリクエストを並列発行したいことがあるでしょう。

  • URLのコンテンツ内容の監視(Webレスポンス監視)
  • コンテンツの応答監視(ページの更新チェック)
  • Webページのクロール(スクレイピング)

非同期処理と言ったらコールバックですね。ただ、コールバック地獄は避けたい。
そんな時には次のようにEventMachineとFiberを用いると、読みやすい非同期処理の実装が出来ます。

URLの数だけ並列実行する方法

とにかく速いですが、URLリストが増えた場合には次の問題が起きうるので注意が必要です。

  • ファイルディスクリプタの上限を叩く可能性
  • 接続先Webサーバの最大同時接続数を使い果たす可能性
  • NATルータ機器を通してインターネットに出ている場合には、そのNATテーブルが溢れる可能性
  • スパイク的なリクエストを発生させることで、下りネットワーク帯域を埋め尽くす可能性
require 'em-synchrony'
require 'em-synchrony/em-http'

urls = %w[
  http://localhost:3000/?page=1
  http://localhost:3000/?page=2
  http://localhost:3000/?page=3
  http://localhost:3000/?page=4
  http://localhost:3000/?page=5
  http://localhost:3000/?page=6
]

# カウンターを初期化
pending = urls.size

EM.synchrony do
  urls.each do |url|

    # 軽量スレッドであるFiberを呼び出す
    Fiber.new do

      # HTTPリクエストを発行する
      http = EM::HttpRequest.new(url).post :body => request_body

      # それぞれのURL取得後に実行する処理
      # これらはそれぞれのリクエストが入り交じること無く実行されます
      puts url
      puts http.response_header        # HTPレスポンスヘッダ
      puts http.response_header.status # HTPレスポンスコードを出力
      puts http.response               # HTPレスポンスボディ

      # カウンターをデクリメントする
      pending -= 1

      # 最後の実行時にEventMachineを止める
      EM.stop if pending == 0
    end.resume
  end  
end

# 全ての処理が終わったらこちらの処理が走ります
puts "All requests has finished."

最大並列実行数を指定してHTTPリクエストを発行する方法

サーバやネットワークに優しく通信をするコードは次の通りです。

require 'em-synchrony'
require 'em-synchrony/em-http'
require 'em-synchrony/fiber_iterator'

urls = %w[
  http://localhost:3000/?page=1
  http://localhost:3000/?page=2
  http://localhost:3000/?page=3
  http://localhost:3000/?page=4
  http://localhost:3000/?page=5
  http://localhost:3000/?page=6
]

EM.synchrony do
  # 並列実行数
  concurrency = 2

  # FiberIteratorを用いて非同期処理らしさを隠す
  EM::Synchrony::FiberIterator.new(urls, concurrency).each do |url|

    # HTTPリクエストを発行する
    http = EM::HttpRequest.new(url).get
    ## もしここでPOST通信する際には、内容を:bodyに渡します
    ## http = EM::HttpRequest.new(url).post :body => { name: 'hoge' }

    # それぞれのURL取得後に実行する処理
    # これらはそれぞれのリクエストが入り交じること無く実行されます
    puts url
    puts http.response_header        # HTPレスポンスヘッダ
    puts http.response_header.status # HTPレスポンスコードを出力
    puts http.response               # HTPレスポンスボディ
  end

  # 全ての処理が終わったらこちらの処理が走ります
  puts "All requests has finished."

  # EventMachineを止めます
  EM.stop
end

不備等ございましたら是非、編集リクエストなどからお知らせ頂けると幸いです。

参考サイト

26
27
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
26
27