動機
外部ファイルをOpenURI#open_uri
で取得したオブジェクトがStringIO
で返ってきたりTempfile
で返ってきたりした。
リファレンス
開いたファイルオブジェクトは
StringIO
もしくはTempfile
ですがOpenURI::Meta
モジュールで拡張されていて、メタ情報を獲得する メソッドが使えます。
環境
- ruby 2.2.2p95 (2015-04-13 revision 50295) [x86_64-darwin14]
- Pry version 0.10.1 on Ruby 2.2.2
調査方法
-
pry-doc
でソースコードを読む
OpenURI.open_uri
> require 'open-uri'
> show-source OpenURI.open_uri
def OpenURI.open_uri(name, *rest) # :nodoc:
# URIのパース処理とか
io = open_loop(uri, options)
# 最終的にioが戻り値
end
URIの処理とかエンコーディングとかやってるけど最終的に返ってくるオブジェクトはopen_loop
内で作られてる
OpenURI.open_loop
> show-source OpenURI.open_loop
def OpenURI.open_loop(uri, options) # :nodoc:
# プロキシとかの処理
uri_set = {}
buf = nil
while true
redirect = catch(:open_uri_redirect) {
buf = Buffer.new
uri.buffer_open(buf, find_proxy.call(uri), options)
nil
}
# リダイレクトの例外をキャッチした場合の処理
end
io = buf.io
io.base_uri = uri
io
end
戻り値io
の中身がbuf.io
らしいのでBuffer
の中を見てみる。
OpenURI::Buffer
> show-source OpenURI::Buffer
class Buffer # :nodoc: all
def initialize
@io = StringIO.new
@size = 0
end
attr_reader :size
StringMax = 10240
def <<(str)
@io << str
@size += str.length
if StringIO === @io && StringMax < @size
require 'tempfile'
io = Tempfile.new('open-uri')
io.binmode
Meta.init io, @io if Meta === @io
io << @io.string
@io = io
end
end
def io
Meta.init @io unless Meta === @io
@io
end
end
お目当てのStringIO
とTempfile
を生成しているところを見つけました。
initialize
の中でまずStringIO
オブジェクトとして定義してます。
そして<<
メソッドでTempfile
オブジェクトとして再定義してます。
このメソッドが呼ばれる箇所を見てみます。
URI::HTTP#buffer_open
> show-source URI::HTTP#buffer_open
def buffer_open(buf, proxy, options) # :nodoc:
OpenURI.open_http(buf, self, proxy, options)
end
OpenURI.open_http
> show-source OpenURI.open_http
def OpenURI.open_http(buf, target, proxy, options) # :nodoc:
# プロキシとかの処理
http.start {
req = Net::HTTP::Get.new(request_uri, header)
if options.include? :http_basic_authentication
user, pass = options[:http_basic_authentication]
req.basic_auth user, pass
end
http.request(req) {|response|
resp = response
if options[:content_length_proc] && Net::HTTPSuccess === resp
if resp.key?('Content-Length')
options[:content_length_proc].call(resp['Content-Length'].to_i)
else
options[:content_length_proc].call(nil)
end
end
resp.read_body {|str|
buf << str
if options[:progress_proc] && Net::HTTPSuccess === resp
options[:progress_proc].call(buf.size)
end
}
}
}
io = buf.io
io.rewind
io.status = [resp.code, resp.message]
resp.each_name {|name| buf.io.meta_add_field2 name, resp.get_fields(name) }
# リダイレクトだった時の処理とか
end
buf << str
でBuffer
の<<
メソッドが呼ばれてました。
HTTP通信に成功したらそのファイルの文字列長によって返却されるオブジェクトが変わるんですね。
参考
Why does OpenURI treat files under 10kb in size as StringIO?