Skip to content

Instantly share code, notes, and snippets.

@floehopper
Created November 16, 2024 10:52
Show Gist options
  • Save floehopper/fe59485e96c6f856a9c060b892d03f00 to your computer and use it in GitHub Desktop.
Save floehopper/fe59485e96c6f856a9c060b892d03f00 to your computer and use it in GitHub Desktop.
Download file using multiple parallel Range requests
#!/usr/bin/env ruby
require "bundler/inline"
gemfile do
source "https://rubygems.org"
gem "typhoeus"
end
require "uri"
require "typhoeus"
abort "Usage: ruby download.rb <url>" if ARGV.empty?
url = ARGV.first
response = Typhoeus.head(url)
if response.success?
if content_length = response.headers["Content-Length"]
total_size = content_length.to_i
else
abort "Content-Length header is missing, unable to determine file size."
end
if content_disposition = response.headers["Content-Disposition"]
match = response.headers["Content-Disposition"].match(/filename="([^"]+)"/)
filename = match[1] if match
end
else
abort "HEAD request failed: #{response.code}"
end
unless filename
uri = URI.parse(url)
filename = File.basename(uri.path)
end
num_parts = 4
part_size = (total_size / num_parts.to_f).ceil
ranges = Array.new(num_parts) do |i|
start_byte = i * part_size
end_byte = [start_byte + part_size - 1, total_size - 1].min
"bytes=#{start_byte}-#{end_byte}"
end
hydra = Typhoeus::Hydra.new
parts = []
ranges.each_with_index do |range, index|
request = Typhoeus::Request.new(url, headers: { "Range" => range })
request.on_complete do |response|
if response.success?
file_name = "part_#{index}"
File.write(file_name, response.body)
parts << file_name
puts response.headers["Content-Range"]
puts "Downloaded part #{index}: #{response.code}"
else
puts "Failed to download part #{index}: #{response.code}"
end
end
hydra.queue(request)
end
hydra.run
File.open(filename, "wb") do |output|
parts.each do |part|
File.open(part, "rb") do |input|
IO.copy_stream(input, output)
end
end
end
parts.each { |part| File.delete(part) }
puts "Download complete: #{filename}"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment