Выполнение нескольких 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 для этой библиотеки существует, но, похоже, не поддерживает эту функциональность из коробки. Тем не менее, похоже, что патч добавляет/фиксирует его (пример кода доступен на странице). Я знаю, что это не здорово, но, возможно, стоит попробовать, если нет никаких лучших предложений.
Ответ 5
Это зависит от того, что вы хотите сделать после функции после этого. Вы можете сделать это с помощью простых потоков:
см.: http://snipplr.com/view/3966/simple-example-of-threading-in-ruby/
Ответ 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)