はじめに
Slackのruby-jpで、S3上のデカいzipファイルを解凍しながらダウンロードしたいと書いてる人がいて、それに対してHTTP Range Headerをいい感じにするIOを作るというアイデアを提案してる人がいたので作ってみた。
ちなみに s3io というgemがまさにそれなのだが、aws-sdkに依存している(しかも古いバージョンのまま更新されてない)ので、HTTP Range Headerを直接使ってS3以外でも使えるものを作った。
完成品
require 'net/http'
require 'uri'
class HTTPRangeIO
def initialize(url, clazz = Net::HTTP)
uri = URI.parse(url)
@http = clazz.start(uri.hostname, uri.port, use_ssl:(uri.scheme == 'https'))
@path = uri.request_uri
@pos = 0
end
def tell
@pos
end
def seek(offset, whence = IO::SEEK_SET)
case whence
when IO::SEEK_CUR
@pos += offset
when IO::SEEK_END
@pos = size + offset
else
@pos = offset
end
raise Errno::EINVAL if @pos < 0
end
def size
@content_length ||= @http.request_head(@path).content_length
end
def read(length = nil, outbuf = "")
first = @pos
last = length ? (@pos + length) : size
return "" if first == last
request = Net::HTTP::Get.new(@path)
request.range = Range.new(first, last, true)
response = @http.request(request)
@pos += response.content_length
outbuf.replace(response.body)
end
def close
@http.finish unless @cloned
end
def initialize_copy(obj)
@cloned = true
super
end
def method_missing(name, *args)
# do nothing
end
end
IOと言いながら読み込みにしか対応してないのがダサいところではあるが、今回の要件では tell
, seek
, size
, read
, close
を実装すれば動いたのでこれだけで。
initialize_copy
はdupされたインスタンスがcloseされたときにセッションをクローズしないようにするため、method_missing
はサポートされてない(主に書き込み用の)メソッドを呼ばれても落ちないようにするためにある。
使い方
考え方は StringIO
と同じで、URLをコンストラクタに渡すと、それをIOとして扱うことができるようになる。
プログラム
require 'zip'
require 'rangeio'
url = "https://s3.amazonaws.com/aws-cli/awscli-bundle.zip"
io = HTTPRangeIO.new(url)
zf = Zip::File.new(io, false, true)
zf.each do |entry|
puts entry.name
end
zf.close
io.close
ここではzipライブラリとして rubyzip
を使っているが、読み込み専用の Zip::InputStream
を使ってもおそらく問題はない。
Zip::File.open_buffer
はIOを引数にとるが、zipファイルを更新するためのメソッドなので、読み込みのみでも書き込み用のメソッドが走ってしまうらしいので、使わないことにした。
実行結果
$ ruby sample.rb
awscli-bundle/install
awscli-bundle/packages/urllib3-1.22.tar.gz
awscli-bundle/packages/PyYAML-3.13.tar.gz
awscli-bundle/packages/futures-3.3.0.tar.gz
awscli-bundle/packages/python-dateutil-2.8.0.tar.gz
awscli-bundle/packages/s3transfer-0.2.1.tar.gz
awscli-bundle/packages/colorama-0.4.1.tar.gz
awscli-bundle/packages/six-1.12.0.tar.gz
awscli-bundle/packages/colorama-0.3.9.tar.gz
awscli-bundle/packages/PyYAML-5.1.2.tar.gz
awscli-bundle/packages/python-dateutil-2.6.1.tar.gz
awscli-bundle/packages/rsa-3.4.2.tar.gz
awscli-bundle/packages/argparse-1.2.1.tar.gz
awscli-bundle/packages/simplejson-3.3.0.tar.gz
awscli-bundle/packages/awscli-1.16.256.tar.gz
awscli-bundle/packages/docutils-0.15.2.tar.gz
awscli-bundle/packages/pyasn1-0.4.7.tar.gz
awscli-bundle/packages/virtualenv-15.1.0.tar.gz
awscli-bundle/packages/urllib3-1.25.6.tar.gz
awscli-bundle/packages/botocore-1.12.246.tar.gz
awscli-bundle/packages/jmespath-0.9.4.tar.gz
awscli-bundle/packages/ordereddict-1.1.tar.gz
awscli-bundle/packages/setup/setuptools_scm-1.15.7.tar.gz