异步发送多个HTTP请求

12
require 'net/http'

urls = [
  {'link' => 'http://www.google.com/'},
  {'link' => 'http://www.yandex.ru/'},
  {'link' => 'http://www.baidu.com/'}
]

urls.each do |u|
  u['content'] = Net::HTTP.get( URI.parse(u['link']) )
end

print urls

这段代码以同步方式运行,首先是第一个请求,然后是第二个和第三个。我想异步发送所有请求并在完成所有请求后打印urls

最好的方法是什么?Fiber适用于此吗?

8个回答

17

我刚看到这个信息,虽然已经过去一年多了,但希望对一些谷歌用户来说不算太晚...

Typhoeus 是迄今为止最好的解决方案。它以非常优雅的方式包装了libcurl。您可以将max_concurrency设置为大约200而不会出错。

关于超时,如果您向Typhoeus传递:timeout标志,它将只注册响应超时... 然后您甚至可以将请求重新放入另一个hydra中再次尝试。

这是使用Typhoeus重写的程序。希望这能帮助以后遇到此页面的任何人!

require 'typhoeus'

urls = [
  'http://www.google.com/',
  'http://www.yandex.ru/',
  'http://www.baidu.com/'
]

hydra = Typhoeus::Hydra.new

successes = 0

urls.each do |url|
    request = Typhoeus::Request.new(url, timeout: 15000)
    request.on_complete do |response|
        if response.success?
            puts "Successfully requested " + url
            successes += 1
        else
            puts "Failed to get " + url
        end
    end
    hydra.queue(request)
end

hydra.run 

puts "Fetched all urls!" if successes == urls.length

15

这里有一个使用线程的示例。

require 'net/http'

urls = [
  {'link' => 'http://www.google.com/'},
  {'link' => 'http://www.yandex.ru/'},
  {'link' => 'http://www.baidu.com/'}
]

urls.each do |u|
  Thread.new do
    u['content'] = Net::HTTP.get( URI.parse(u['link']) )
    puts "Successfully requested #{u['link']}"

    if urls.all? {|u| u.has_key?("content") }
      puts "Fetched all urls!"
      exit
    end
  end
end

sleep

看起来它运行正常。但如果服务器在15秒内没有响应,如何终止线程? - NVI
1
你可以使用 Timeout.timeout(20) do .... end。不过这会抛出一个错误,因此你需要处理程序的流程,并且有一种标记请求已完成的方法,而不是检查 content 键是否存在。 - August Lilleaas

6

我已经写了一篇深入的博客文章,其中包括一个与August发布的答案有些相似但又有几个关键不同的答案: 1)将所有线程引用保存在“线程”数组中进行跟踪。 2)在程序结束时使用“join”方法来绑定线程。

require 'net/http'

# create an array of sites we wish to visit concurrently.
urls = ['link1','link2','link3']  
# Create an array to keep track of threads.
threads = []

urls.each do |u|  
  # spawn a new thread for each url
  threads << Thread.new do
  Net::HTTP.get(URI.parse(u))
    # DO SOMETHING WITH URL CONTENTS HERE
    # ...
    puts "Request Complete: #{u}\n"
  end
end

# wait for threads to finish before ending program.
threads.each { |t| t.join }

puts "All Done!"  

完整的教程(以及一些性能信息)在这里可用:https://zachalam.com/performing-multiple-http-requests-asynchronously-in-ruby/

你是否讨厌最佳答案却得到最少票数的情况? - Verty00

2
使用concurrent-ruby,您可以同时处理数据:
require 'net/http'
require 'concurrent-ruby'

class Browser
  include Concurrent::Async

  def render_page(link)
    sleep 5
    body = Net::HTTP.get( URI.parse(link) )
    File.open(filename(link), 'w') { |file| file.puts(body)}
  end

  private

  def filename(link)
    "#{link.gsub(/\W/, '-')}.html"
  end
end

pages = [
  'https://www.google.com',
  'https://www.bing.com',
  'https://www.baidu.com'
].map{ |link| Browser.new.async.render_page(link) }.map(&:value)

1
这可以通过C库cURL完成。该库的Ruby绑定存在,但似乎不支持此功能。然而,看起来有一个补丁可以添加/修复它(页面上提供了示例代码)。我知道这听起来不太好,但如果没有更好的建议,这可能值得一试。

0

work_queue gem 是在您的应用程序中异步和并发执行任务的最简单方法。

wq = WorkQueue.new 2 # Limit the maximum number of simultaneous worker threads

urls.each do |url|
  wq.enqueue_b do
    response = Net::HTTP.get_response(url)
    # use the response
  end
end

wq.join # All requests are complete after this

0

0

你可以让不同的线程执行每个Net::HTTP.get。然后等待所有的线程执行完毕。

顺便一提,打印URL将同时打印链接和内容。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接