[英]Stream the response body of an HTTP GET to an HTTP POST with Ruby
我正在尝试下载一个大文件,然后使用Ruby将该文件发布到REST端点。 该文件可能非常大,即可以存储在内存中,甚至可以存储在磁盘上的临时文件中。 我一直在尝试使用Net :: HTTP,但只要他们做我正在尝试做的事情,我就可以使用任何其他库(rest-client等)。
这是我试过的:
require 'net/http'
source_uri = URI("https://example.org/very_large_file")
source_request = Net::HTTP::Get.new(source_uri)
source_http = Net::HTTP.start(source_uri.host, source_uri.port, use_ssl: source_uri.scheme == 'https')
target_uri = URI("https://example2.org/rest/resource")
target_request = Net::HTTP::Post.new(target_uri)
target_http = Net::HTTP.start(target_uri.host, target_uri.port, use_ssl: target_uri.scheme == 'https')
source_response = source_http.request(source_request)
target_request.body = source_response.read_body
target_request.content_type = 'multipart/form-data'
target_response = target_http.request(target_request)
我想要发生的是source_response.read_body返回一个流,然后我可以以块的形式传递给target_request。
回答我自己的问题:这是我的解决方案。 请注意,为了使这项工作,我需要修补Net :: HTTP,以便我可以访问套接字,以便从响应对象手动读取块。 如果你有更好的解决方案,我仍然希望看到它。
require 'net/http'
require 'excon'
# provide access to the actual socket
class Net::HTTPResponse
attr_reader :socket
end
source_uri = URI("https://example.org/very_large_file")
target_uri = URI("https://example2.org/rest/resource")
Net::HTTP.start(source_uri.host, source_uri.port, use_ssl: source_uri.scheme == 'https') do |http|
request = Net::HTTP::Get.new source_uri
http.request request do |response|
len = response.content_length
p "reading #{len} bytes..."
read_bytes = 0
chunk = ''
chunker = lambda do
begin
if read_bytes + Excon::CHUNK_SIZE < len
chunk = response.socket.read(Excon::CHUNK_SIZE).to_s
read_bytes += chunk.size
else
chunk = response.socket.read(len - read_bytes)
read_bytes += chunk.size
end
rescue EOFError
# ignore eof
end
p "read #{read_bytes} bytes"
chunk
end
Excon.ssl_verify_peer = false
Excon.post(target_uri.to_s, :request_block => chunker)
end
end
通过使用excon
和rest-client
gem,您应该能够流式传输数据并将其上传到多个部分。
不幸的是,我无法找到一种方法来使用带有excon
multipart / form-data来使用rest-client
或post-data来传输数据,因此你必须将两者结合起来。
这是整个片段, 应该有希望。
require 'excon'
require 'rest-client'
streamer = lambda do |chunk, remaining_bytes, total_bytes|
puts "Remaining: #{remaining_bytes.to_f / total_bytes}%"
puts RestClient.post('http://posttestserver.com/post.php', :param1 => chunk)
end
Excon.get('http://textfiles.com/computers/ami-chts.txt', :response_block => streamer)
搞乱后,我可以得到以下代码有点工作(它似乎不一致,有时它发送所有,有时它不会。我相信它可能是因为它在完成之前结束了http post请求)
require 'excon'
require 'uri'
require 'net/http'
class Producer
def initialize
@mutex = Mutex.new
@body = ''
end
def read(size, out=nil)
length = nil
@mutex.synchronize {
length = @body.slice!(0,size)
}
return nil if length.nil? || length.empty?
out << length if out
length
end
def produce(str)
@mutex.synchronize {
@body << str
}
end
end
@stream = Producer.new
uri = URI("yourpostaddresshere")
conn = Net::HTTP.new(uri.host, uri.port)
request = Net::HTTP::Post.new uri.request_uri, {'Transfer-Encoding' => 'chunked', 'content-type' => 'text/plain'}
request.body_stream = @stream
Thread.new {
streamer = lambda do |chunk, remaining_bytes, total_bytes|
@stream.produce(chunk)
end
Excon.get('http://textfiles.com/computers/ami-chts.txt', :response_block => streamer)
}
conn.start do |http|
http.request(request)
end
归功于Roman ,我确实稍微修改了它,因为HTTP.start需要两个参数(Ruby Net:HTTP更改)。
没有异步I / O(在Ruby中很难),唯一的方法是通过FIFO管道使用两个线程。 一个要提取,另一个要上传。
FIFO通过作为环形缓冲区来工作。 你找回了一个读者和一个作家。 无论何时写入编写器,读取器都会获取数据,读取器将始终阻塞,直到有可用数据为止。 FIFO由真实文件句柄支持,因此I / O就像一个文件(不像StringIO
那样的“假”流)。
像这样的东西:
require 'net/http'
def download_and_upload(source_url, dest_url)
rd, wr = IO.pipe
begin
source_uri = URI.parse(source_url)
Thread.start do
begin
Net::HTTP.start(source_uri.host, source_uri.port, use_ssl: source_uri.scheme == 'https') do |http|
req = Net::HTTP::Get.new(source_uri.request_uri)
http.request(req) do |resp|
resp.read_body do |chunk|
wr.write(chunk)
wr.flush
end
end
end
rescue IOError
# Usually because the writer was closed
ensure
wr.close rescue nil
end
end
dest_uri = URI.parse(dest_url)
Net::HTTP.start(dest_uri.host, dest_uri.port, use_ssl: dest_uri.scheme == 'https') do |http|
req = Net::HTTP::Post.new(dest_uri.request_uri)
req.body_stream = rd
http.request(req)
end
ensure
rd.close rescue nil
wr.close rescue nil
end
end
我没有测试过这个,因为我目前没有端点,但这是它的原理。
请注意,我遗漏了错误处理。 如果下载程序线程失败,您将需要捕获错误并将其发送到上传程序线程。 (如果上传器出现故障,下载将停止,因为写入管道将关闭。)
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.