Выполнение нескольких HTTP-запросов асинхронно

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 подходящим для этого?

Ответ 1

Вот пример использования потоков.

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

Ответ 2

Я только что видел это, год и немного позже, но, надеюсь, не слишком поздно для какого-то гуглера...

Typhoeus безусловно лучшее решение для этого. Он обертывает libcurl в действительно элегантном стиле. Вы можете установить max_concurrency примерно до 200 без удушения.

Что касается тайм-аутов, если вы передадите флаг Typhoeus a :timeout, он просто зарегистрирует тайм-аут в качестве ответа... и тогда вы даже можете отправить запрос обратно в другую гидру, чтобы повторить попытку, если хотите.

Здесь ваша программа переписана с помощью 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

Ответ 3

Я написал подробное сообщение в блоге об этой теме, которое включает в себя ответ, который несколько похож на один август, но с несколькими ключевыми отличиями: 1) Отслеживает все ссылки на поток в массиве "thread". 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/

Ответ 4

Это можно сделать с помощью библиотеки C cURL. A ruby ​​binding для этой библиотеки существует, но, похоже, не поддерживает эту функциональность из коробки. Тем не менее, похоже, что патч добавляет/фиксирует его (пример кода доступен на странице). Я знаю, что это не здорово, но, возможно, стоит попробовать, если нет никаких лучших предложений.

Ответ 6

У вас может быть другой поток, выполняющий каждый из Net:: HTTP.get. И просто дождитесь окончания всех потоков.

URL-адреса печати BTW будут печатать как ссылку, так и содержимое.

Ответ 7

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

Ответ 8

С помощью 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)