一、诡异的10kb 的问题
最近做项目的时候,修改了图片的上传方式。经过缜密的开发和测试,成功上线。
后台监控newrelic
提示
undefined method `path' for #<StringIO>
由于上传图片,是非常关键的步骤。大约有 1000rpm,但是为啥报错的仅仅只有那么几个例子呢?
经过详细的研究和实现。发现open-uri
有一个设置,对 <10kb
的文件会将其设置为StringIO
类,但是 >10kb
的文件会设置为 Tempfile
类。
其实他的设置还是比较聪明的,我猜想作者的目的是不要让大量的小文件存在。所以设置了这个阀值。
上例子。
其中
http://yannini.qiniudn.com/lessthanten.png 是 <10kb 的文件
http://yannini.qiniudn.com/1.png 是 >10kb 的文件
启动irb,
>> require "open-uri"
>> open("http://yannini.qiniudn.com/lessthanten.png")
=> #<StringIO:0x00000102b6e920 @base_uri=#<URI::HTTP:0x00000102b6eba0 URL:http://yannini.qiniudn.com/lessthanten.png>, @meta={"date"=>"Sat, 28 Mar 2015 13:43:04 GMT", "server"=>"nginx/1.4.4", "content-type"=>"image/jpeg", "content-length"=>"8157", "accept-ranges"=>"bytes", "access-control-allow-origin"=>"*", "access-control-max-age"=>"2592000", "cache-control"=>"public, max-age=31536000", "content-disposition"=>"inline; filename=\"lessthanten.png\"", "content-transfer-encoding"=>"binary", "etag"=>"\"Fg3HN9hK3Hy68nGT99MOSdtaafDx\"", "x-log"=>"mc.g;IO:1", "x-reqid"=>"BVsAAE70cUNUrc8T", "x-whom"=>"nb990", "x-qiniu-zone"=>"0", "age"=>"1", "x-via"=>"1.1 tzh55:8104 (Cdn Cache Server V2.0), 1.1 bjdxtck17:8 (Cdn Cache Server V2.0)", "connection"=>"keep-alive"}, @metas={"date"=>["Sat, 28 Mar 2015 13:43:04 GMT"], "server"=>["nginx/1.4.4"], "content-type"=>["image/jpeg"], "content-length"=>["8157"], "accept-ranges"=>["bytes"], "access-control-allow-origin"=>["*"], "access-control-max-age"=>["2592000"], "cache-control"=>["public, max-age=31536000"], "content-disposition"=>["inline; filename=\"lessthanten.png\""], "content-transfer-encoding"=>["binary"], "etag"=>["\"Fg3HN9hK3Hy68nGT99MOSdtaafDx\""], "x-log"=>["mc.g;IO:1"], "x-reqid"=>["BVsAAE70cUNUrc8T"], "x-whom"=>["nb990"], "x-qiniu-zone"=>["0"], "age"=>["1"], "x-via"=>["1.1 tzh55:8104 (Cdn Cache Server V2.0), 1.1 bjdxtck17:8 (Cdn Cache Server V2.0)"], "connection"=>["keep-alive"]}, @status=["200", "OK"]>
>> open("http://yannini.qiniudn.com/lessthanten.png").class
=> StringIO
>> open("http://yannini.qiniudn.com/1.png")
=> #<Tempfile:/var/folders/mj/q33ysmhn02v_xr430ftz6mnw0000gn/T/open-uri20150328-31267-1qyc4wr>
>> open("http://yannini.qiniudn.com/1.png").class
=> Tempfile
很明显,两个文件最终生成的类不一致。
接着往下看,上传图片时,需要使用图片的真实路径。所以需要调用file.path
方法,但是 StringIO
没有path
方法,导致报错。
>> open("http://yannini.qiniudn.com/1.png").path
=> "/var/folders/mj/q33ysmhn02v_xr430ftz6mnw0000gn/T/open-uri20150328-31267-oyxvdv"
>> open("http://yannini.qiniudn.com/lessthanten.png").path
NoMethodError: undefined method `path' for #<StringIO:0x00000101b9d168>
from (irb):16
from /Users/wanghao/.rvm/rubies/ruby-2.1.1/bin/irb:11:in `<main>'
>>
二、解决方式
在rails
script
目录下,新建一个 reset_open_uri.rb
if defined?(OpenURI) && OpenURI::Buffer.const_defined? (:StringMax)
OpenURI::Buffer.send('remove_const', :StringMax)
OpenURI::Buffer.send('const_set', :StringMax, 0)
end
然后在需要启动的开发或生产环境中,引入该配置即可
require(File.join(Rails.root, 'script/reset_open_uri.rb'))
这样,任何大小的文件都open时候最终都生成 Tempfile
三、清除大量的小图片
由于上述设置,大量的小图片生成。导致服务器的硬盘空间不足。
所以最后需要写一个定时任务,手动删除 cd /var/folders/mj/q33ysmhn02v_xr430ftz6mnw0000gn/T/
下的关联文件。
参考
Undefined Method 'path' For StringIO in Ruby
Why does Ruby open-uri's open return a StringIO in my unit test, but a FileIO in my controller?