0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Webサーバのファイルを、HTTP Range Headerを使ってRuby IOクラスに擬態化させて読み込む

Last updated at Posted at 2019-10-10

はじめに

Slackのruby-jpで、S3上のデカいzipファイルを解凍しながらダウンロードしたいと書いてる人がいて、それに対してHTTP Range Headerをいい感じにするIOを作るというアイデアを提案してる人がいたので作ってみた。

ちなみに s3io というgemがまさにそれなのだが、aws-sdkに依存している(しかも古いバージョンのまま更新されてない)ので、HTTP Range Headerを直接使ってS3以外でも使えるものを作った。

完成品

rangeio.rb

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として扱うことができるようになる。

プログラム

sample.rb

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
0
0
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?