複数のデータを連結するIOもどきを修正。
昨日作成した、複数のデータをboundaryで連結するIOもどきをちょっと修正。
- サイズを得るAPIを追加。
- ただし、IOからはサイズがとれないので、IOが追加されている場合、エラーとなる。
- add_file()の時点でファイルを開くのはもったいないため、必要になった場合のみ、開くように修正。
- グローバル名前空間を汚さないため、モジュールで囲むようにした。
使い方。だいたい同じです。
data = nil begin data = MultipartFormData::Data.new "test" # 文字列データを追加。 data.add( "aaaa", { "content-disposition"=>'form-data; name="pics"; filename="file1.txt"', "Content-Type"=>"text/plain" } ) data.add( "xxxx", { "Content-Type"=>"text/plain" } ) # IOを追加。("./data.dat"の中身は「hogehoge」) data.add( File.open("./data.txt"), { "Content-Type"=>"application/octet-stream" } ) # ローカルファイルを追加。 data.add_file( "./data.txt", { "Content-Type"=>"application/octet-stream" } ) # サイズ取得 #puts data.size # 読み込み puts data.read #while ( str = data.read(10) ) # puts str #end ensure data.close if data!= nil # 必ず実行すること。 end
実装。
require "stringio" module MultipartFormData # multipart/form-dataの送信のため、複数のデータをつなぐIOもどき。 # read(length)のみ実装。 class Data def initialize ( boundary ) @boundary = boundary @datas = [] @index = -1 @io = nil end # IO or 文字列を追加する。 def add( data, headers ) if data.is_a? String src = StringSource.new data elsif data.is_a? IO src = IOSource.new data elsif data.is_a? Source src = data else raise ArgumentError.new end header = "--#{@boundary}\r\n" headers.each {|k,v| header << "#{k.to_s}: #{v.to_s}\r\n" } header << "\r\n" @datas << StringSource.new(header) @datas << src @datas << StringSource.new("\r\n") end # ファイルを追加する。 def add_file( file_path, headers ) add( FileSource.new(file_path), headers) end def read( length=nil ) buff = "" while ( length==nil || buff.length < length ) @io = next_io() if @io == nil return buff.length > 0 ? buff : nil if @io == nil data = @io.read( length==nil ? nil : length - buff.length ) if ( @io.eof? ) @io.close @io = nil end buff += data if data != nil end return buff end # サイズを得る。 def size total = 0 @datas.each { |data| total += data.size } total + @boundary.length + 6 end # リソースを破棄する。 # 不要になった際に、必ず実行すること。 def close begin if (@io != nil) @io.close if !@io.closed? @io = nil end ensure @datas.each { |data| if data.is_a? IO data.close if !data.closed? end } end end private def next_io @index+=1 if ( @index == 0) @datas << StringSource.new("--#{@boundary}--\r\n") end if ( @index < @datas.length ) return @datas[@index].get_io else return nil end end end class Source def get_io; end def size; end end class StringSource < Source def initialize ( data ) @data = data end def get_io StringIO.new(@data) end def size @data.length end end class FileSource < Source def initialize ( file_path ) @file_path = file_path end def get_io File.open(@file_path) end def size File.stat(@file_path).size end end class IOSource < Source def initialize ( data ) @data = data end def get_io @data end def size raise "unsupported operation." end end
修正 (2007-10-11)
サイズに最後の"--#{@boundary}--\r\n"の分が入っていなかった・・orz ので修正。