複数のデータを連結するIOもどき( multipart/form-dataの送信用 )
multipart/form-dataの送信のため、複数のデータ(ファイルパス、IO、文字列のいずれか)を指定したboundaryで連結するIOもどきを作った。IOのAPIを一式実装しようかと思ったけど多すぎるので速やかに挫折。Net::HTTPで使えればいいのでread(length)だけ実装した。
使い方
data = nil begin data = MultipartFormData.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.dat"), { "Content-Type"=>"application/octet-stream" } ) # ローカルファイルを追加。 data.add_file( "./data.dat", { "Content-Type"=>"application/octet-stream" } ) # 読み込み # 読み込んだ後、再度読むことはできないので注意 puts data.read #while ( str = data.read(100) ) # puts str #end ensure data.close if data!= nil # 必ず実行すること。 end
実行結果です。
--test Content-Type: text/plain content-disposition: form-data; name="pics"; filename="file1.txt" aaaa --test Content-Type: text/plain xxxx --test Content-Type: application/octet-stream hogehoge --test Content-Type: application/octet-stream hogehoge --test--
実装は以下。連結処理の内容は次の通りです。
- データの区切り(「"--" + boundary」)を出力
- 各データのヘッダを「\r\n」区切りで出力。
- データを出力
- データの分だけ1-3を繰り返す。
- 最後に「"--" + boundary + "--"」を付ける
require "stringio" # multipart/form-dataの送信のため、複数のデータをつなぐIOもどき。 # read(length)のみ実装。 class MultipartFormData def initialize ( boundary ) @boundary = boundary @datas = [] @index = -1 @io = nil end # IO or 文字列を追加する。 def add( data, headers ) raise ArgumentError.new if data == nil || ( !(data.is_a?(IO)) && !(data.is_a?(String)) ) @datas << "--#{@boundary}\r\n" headers.each {|k,v| @datas << "#{k.to_s}: #{v.to_s}\r\n" } @datas << "\r\n" @datas << data @datas << "\r\n" end # ファイルを追加する。 def add_file( file_path, headers ) add( File.open(file_path), headers) end def read( length=nil ) buff = "" while ( length==nil || buff.length < length ) if @io == nil @io = next_io() end 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 close if (@io != nil) @io.close if !@io.closed? @io = nil end @datas.each { |data| if data.is_a? IO data.close if !data.closed? end } end private def next_io @index+=1 if ( @index == 0) @datas << "--#{@boundary}--\r\n" end if ( @index < @datas.length ) obj = @datas[@index] return obj.is_a?(IO) ? obj : StringIO.new(obj) else return nil end end end