Ruby で CGI > 送受信_フォームデータの扱い
ブログや掲示板などによくあるような、ページの記入欄に書き込まれて送信されたデータを、受け取って処理する方法を紹介します。
<html lang="ja"><body> <form method="get" action="http://www.google.com/search"> <input type="text" name="q" value=""> </form> </body></html>
#!/usr/local/bin/ruby -Ks を #!ruby -Ksに書き直して使ってください 
	
まず、GET メソッドでのフォームが入った html ファイルを作成してください。 使用する文字コードは、Shift_JIS とします。 データの送り先は、form.cgiとしてください。 よくわからない人は、下記サンプルをそのままコピーして、保存文字コードShift_JIS にて、ファイル名、form.html で保存してください。 ここでは、この html から送信されたデータを受け止めて一覧する CGI を紹介します。
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html lang="ja"> <head> <meta http-equiv="Content-Type" content="text/html; charset=Shift_JIS"> <title>フォーム送信テスト</title> </head> <body> <h1>フォーム送信テスト</h1> <form action="form.cgi" method="GET"> <p>文字列: <input type="text" name="messe" value="ここに記入" tabindex="1" accesskey="1" size="36" maxlength="72"></p> <p>チェックボックス: <input type="checkbox" name="chk1" value="rabbit" tabindex="2" accesskey="2">うさぎ、 <input type="checkbox" name="chk2" value="panda" tabindex="3" accesskey="3">パンダ</p> <p>ラジオボタン: <input type="radio" name="seibetu" value="otoko" tabindex="4" accesskey="4">男、 <input type="radio" name="seibetu" value="onna" tabindex="5" accesskey="5">女、 <input type="radio" name="seibetu" value="non" tabindex="6" accesskey="6" checked>ノーコメント</p> <p>ボタン: <button type="submit" name="btn" value="Push1" tabindex="7" accesskey="7">送信1</button> <button type="submit" name="btn" value="Push2" tabindex="8" accesskey="8">送信2</button></p> </form> </body> </html>

以下をファイル名 form.cgi で、上記htmlと同じディレクトリ内に置きます
#!/usr/local/bin/ruby -Ks
@in = Hash.new               # フォームデータをこのハッシュに格納します
for q in ENV['QUERY_STRING'].to_s.split(/[;&]/) do
  key,val = q.split(/=/,2)
  @in[key] = val.gsub(/\+/," ").gsub(/%[a-fA-F\d]{2}/){ $&[1,2].hex.chr } if val
end
# 出力します↓
print <<KEYWORD
Content-Disposition: filename="get.txt"
Content-type: text/plain;charset=Shift_JIS
ENV['QUERY_STRING'] = #{ ENV['QUERY_STRING'] }
name値      =>  value値
----------      ---------------------------------------
KEYWORD
@in.each do |key,val|
  puts "%10s  =>  %s" % [key,val]
end
@in[name値] = value値 という形に整理しますENV['QUERY_STRING']で受け取りますENV['QUERY_STRING']が空っぽ(nil)だった場合に備えて、.to_sで文字列化しておきます.split(/[;&]/)は、;か又は、&を区切りとして配列に分解します。
	⇒ 参照:Ampersands in URI attribute valuesq をさらに、最初の = を区切りにして、name値とvalue値にに分解します は+に直されるので、まず.gsub(/\+/," ")で+を空白 に戻します/%[a-fA-F\d]{2}/を抜き出して、通常の文字に置き換えますval.gsub(/正規表現/){置換文字列} は、val の中で正規表現にマッチした部分を置換文字列に逐次置き換えます$&は、/正規表現/にマッチした部分を表します"usagi"[1,2]は"sa"です。0番目から数えて、1番目の文字から2文字抜き出しています。要するに、"%ab"[1,2]は"ab"。.hexは16進数を書き表している文字列を、数に直します。すなわち、"ab".hexは、10進数で表すなら定数 10*16+11 = 171.chrは定数を、その番号が割り振られた文字に直しますチェックボックスなど、同じ name 値で複数の value 値を持たせたい場合があります。
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html lang="ja"> <head> <meta http-equiv="Content-Type" content="text/html; charset=Shift_JIS"> <title>フォーム送信テスト</title> </head> <body> <h1>フォーム送信テスト</h1> <form action="form.cgi" method="GET"> <p>文字列: <input type="text" name="messe" value="ここに記入" tabindex="1" accesskey="1" size="36" maxlength="72"></p> <p>チェックボックス: <input type="checkbox" name="chk" value="rabbit" tabindex="2" accesskey="2">うさぎ、 <input type="checkbox" name="chk" value="panda" tabindex="3" accesskey="3">パンダ</p> <p>ラジオボタン: <input type="radio" name="danjo" value="otoko" tabindex="4" accesskey="4">男、 <input type="radio" name="danjo" value="onna" tabindex="5" accesskey="5">女、 <input type="radio" name="danjo" value="non" tabindex="6" accesskey="6" checked>ノーコメント</p> <p>ボタン: <button type="submit" name="btn" value="Push1" tabindex="7" accesskey="7">送信1</button> <button type="submit" name="btn" value="Push2" tabindex="8" accesskey="8">送信2</button></p> </form> </body> </html>
そういうときは、値を配列に入れてみましょう。
#!/usr/local/bin/ruby -Ks
@in = Hash.new
for q in ENV['QUERY_STRING'].to_s.split(/[;&]/) do
  key, val  = q.split(/=/,2)
  @in[key]  = [ ] unless @in[key]
  @in[key] += [ val.gsub(/\+/," ").gsub(/%[a-fA-F\d]{2}/){ $&[1,2].hex.chr } ] if val
end
print <<KEYWORD
Content-Disposition: filename="get2.txt"
Content-type: text/plain;charset=Shift_JIS
ENV['QUERY_STRING'] = #{ ENV['QUERY_STRING'] }
name値      =>  value値
----------      ---------------------------------------
KEYWORD
@in.each do |key,val|
  for v in val do
    puts "%10s  =>  %s" % [key,v]
  end
end
@in[name値] = [ value値1, value値2, ... ] という形に整理しますhtml に関しては、method="GET" を method="POST" に書き直しておいてください
form.cgi は、GET メソッドの場合のENV['QUERY_STRING']をSTDIN.readと取り替えれば、POST メソッドでもそのまま使えます
#!/usr/local/bin/ruby -Ks
@in = Hash.new
query = STDIN.read(102400).to_s # 標準入力を読み込みます(数 102400 は、読み込みの上限とするバイト数)
for q in query.split(/&/) do
  key, val  = q.split(/=/,2)
  @in[key]  = [ ] unless @in[key]
  @in[key] += [ val.gsub(/\+/," ").gsub(/%[a-fA-F\d]{2}/){ $&[1,2].hex.chr } ] if val
end
print <<KEYWORD
Content-Disposition: filename="post.txt"
Content-type: text/plain;charset=Shift_JIS
STDIN.read = #{ query }
name値      =>  value値
----------      ---------------------------------------
KEYWORD
@in.each do |key,val|
  for v in val do
    puts "%10s  =>  %s" % [key,v]
  end
end
.read(102400)読み込むデータのサイズに上限を設けた。この場合、100kb が上限。簡潔に.getsを使って逐次読み込む方法もあります
#!/usr/local/bin/ruby -Ks
@in = Hash.new
while q = STDIN.gets("&") do
  key, val  = q.chomp("&").chomp.split(/=/,2)
  @in[key]  = [ ] unless @in[key]
  @in[key] += [ val.gsub(/\+/," ").gsub(/%[a-fA-F\d]{2}/){ $&[1,2].hex.chr } ] if val
end
print <<KEYWORD
Content-Disposition: filename="post.txt"
Content-type: text/plain;charset=Shift_JIS
name値      =>  value値
----------      ---------------------------------------
KEYWORD
@in.each{|key,val| val.each{|v| puts "%10s  =>  %s" % [key,v] }}
.chomp:区切りの文字が末尾にくっついてくるので削除正規表現でのパターンマッチを利用した例
#!/usr/local/bin/ruby -Ks
@in = Hash.new
while /^([A-Za-z_\d\-\.]+)=([^&]+)&?$/ =~ STDIN.gets("&").to_s[0,20480] do
  key = $1 ; val = $2
  @in[key]  = [ ] unless @in[key]
  @in[key] += [ val.gsub(/\+/," ").gsub(/%[a-fA-F\d]{2}/){ $&[1,2].hex.chr } ] if val
end
print <<KEYWORD
Content-Disposition: filename="post.txt"
Content-type: text/plain;charset=Shift_JIS
name値      =>  value値
----------      ---------------------------------------
KEYWORD
@in.each{|key,val| val.each{|v| puts "%10s  =>  %s" % [key,v] }}
たまにある質問として、下記のように混ぜたらどうなるか?というのがあります。
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html lang="ja"> <head> <meta http-equiv="Content-Type" content="text/html; charset=Shift_JIS"> <title>フォーム送信テスト</title> </head> <body> <h1>フォーム送信テスト</h1> <form action="form.cgi?hoge=HogeHoge;hello=World" method="POST"> <p>文字列: <input type="text" name="messe" value="ここに記入" tabindex="1" accesskey="1" size="36" maxlength="72"></p> <p>チェックボックス: <input type="checkbox" name="chk1" value="rabbit" tabindex="2" accesskey="2">うさぎ、 <input type="checkbox" name="chk2" value="panda" tabindex="3" accesskey="3">パンダ</p> <p>ラジオボタン: <input type="radio" name="seibetu" value="otoko" tabindex="4" accesskey="4">男、 <input type="radio" name="seibetu" value="onna" tabindex="5" accesskey="5">女、 <input type="radio" name="seibetu" value="non" tabindex="6" accesskey="6" checked>ノーコメント</p> <p>ボタン: <button type="submit" name="btn" value="Push1" tabindex="7" accesskey="7">送信1</button> <button type="submit" name="btn" value="Push2" tabindex="8" accesskey="8">送信2</button></p> </form> </body> </html>
データの受け取りは、単純に2段重ねするだけです。これで POST(添付書類なし) にも GET にも、両方対応できます。
#!/usr/local/bin/ruby -Ks
@in = Hash.new
query = ENV['QUERY_STRING'].to_s            # GET からのクエリー
for q in query.split(/[;&]/) do
  key, val  = q.split(/=/,2)
  @in[key]  = [ ] unless @in[key]
  @in[key] += [ val.gsub(/\+/," ").gsub(/%[a-fA-F\d]{2}/){ $&[1,2].hex.chr } ] if val
end
query = STDIN.read(102400).to_s             # POST からのクエリー
for q in query.split(/&/) do
  key, val  = q.split(/=/,2)
  @in[key]  = [ ] unless @in[key]
  @in[key] += [ val.gsub(/\+/," ").gsub(/%[a-fA-F\d]{2}/){ $&[1,2].hex.chr } ] if val
end
print <<KEYWORD
Content-Disposition: filename="get_and_post.txt"
Content-type:   text/plain;charset=Shift_JIS
ENV['CONTENT_TYPE']   = #{ ENV['CONTENT_TYPE'] }
ENV['REQUEST_METHOD'] = #{ ENV['REQUEST_METHOD'] }
ENV['QUERY_STRING']   = #{ ENV['QUERY_STRING'] }
STDIN.read            = #{ query }
name値      =>  value値
----------      ---------------------------------------
KEYWORD
@in.each do |key,val|
  for v in val do
    puts "%10s  =>  %s" % [key,v]
  end
end
同じことの2度繰り返しになるので、予めやることを定義しておいた方がすっきりするかもしれません
#!/usr/local/bin/ruby -Ks
def q2h(query="")
  for q in query.to_s.split(/[;&]/) do
    key, val  = q.split(/=/,2)
    @in[key]  = [ ] unless @in[key]
    @in[key] += [ val.gsub(/\+/," ").gsub(/%[a-fA-F\d]{2}/){ $&[1,2].hex.chr } ] if val
  end
end
@in = Hash.new
q2h( ENV['QUERY_STRING'] )  # GET  からのクエリー処理
q2h( STDIN.read(102400)  )  # POST からのクエリー処理
print <<KEYWORD
Content-Disposition: filename="get_and_post.txt"
Content-type:   text/plain;charset=Shift_JIS
ENV['CONTENT_TYPE']   = #{ ENV['CONTENT_TYPE'] }
ENV['REQUEST_METHOD'] = #{ ENV['REQUEST_METHOD'] }
name値      =>  value値
----------      ---------------------------------------
KEYWORD
@in.each do |key,val|
  for v in val do
    puts "%10s  =>  %s" % [key,v]
  end
end
フォームにファイルの添付タグをつけた html ファイル
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html lang="ja"> <head> <meta http-equiv="Content-Type" content="text/html; charset=Shift_JIS"> <title>フォーム送信テスト</title> </head> <body> <h1>フォーム送信テスト</h1> <form action="form.cgi" method="POST" enctype="multipart/form-data"> <p>文字列: <input type="text" name="messe" value="ここに記入" tabindex="1" accesskey="1" size="36" maxlength="72"></p> <p>添付書類: <input type="file" name="tenpu" value="" tabindex="2" accesskey="2" size="36"></p> <p>チェックボックス: <input type="checkbox" name="chk" value="rabbit" tabindex="3" accesskey="3">うさぎ、 <input type="checkbox" name="chk" value="panda" tabindex="4" accesskey="4">パンダ</p> <p>ラジオボタン: <input type="radio" name="danjo" value="otoko" tabindex="5" accesskey="5">男、 <input type="radio" name="danjo" value="onna" tabindex="6" accesskey="6">女、 <input type="radio" name="danjo" value="non" tabindex="7" accesskey="7" checked>ノーコメント</p> <p>ボタン: <button type="submit" name="btn" value="Push1" tabindex="8" accesskey="8">送信1</button> <button type="submit" name="btn" value="Push2" tabindex="9" accesskey="9">送信2</button></p> </form> </body> </html>

form.cgi :たぶん、一番簡単な例
#!/usr/local/bin/ruby -Ks
@in = Hash.new
ct  = ENV['CONTENT_TYPE'].to_s 
boundary = nil                                      # 境界の目印
boundary = $1 if /multipart/i =~ ct && /boundary=([^\r\n]+)\r?$/i =~ ct
STDIN.read(102400).to_s.each( boundary ) do |q|
  q.sub!(/\A\r?\n[\r\n]*/,'')                       # 先頭の空行を削除
  q.sub!(/(\r?\n)\-\-#{ boundary }\Z/,'')           # 後端の余分を削除
  head,q = q.split(/\r?\n\r?\n/,2)                  # 最初の空行でヘッダとボディに分離
  cd = nil                                          # Content-Disposition
  cd = $1 if /^Content\-Disposition:\s*([^\n]+)\r?$/i =~ head.to_s
  next unless cd                                    # データ形式不備のものをスキップ(初端終端のゴミも排除)
  key = nil                                         # name 値
  key = $1 if /\bname="([^"]+)"/ =~ cd
  next unless key                                   # name 値が拾えなかった場合はスキップ
  @in[key] = Array.new unless @in[key]              # 配列を準備
  filename = nil                                    # 添付されたファイルのファイル名
  filename = $1 if /\bfilename="([^"]*)"/ =~ cd
  next if filename && q.to_s.size == 0              # 添付ファイルの空振りはスキップ
  ct = nil                                          # 添付されたファイルのタイプ(送信者のブラウザが拡張子から判断)
  ct = $1 if /^Content\-Type:\s*([^\n]+)\r?$/i =~ head
  if ct then                                        # 添付ファイルの場合
    @in[key] += [{"filename",filename, "type",ct, "size",q.size , "body",q }]
  else                                              # 添付ファイルではない場合
    @in[key] += [ q ]
  end
end if boundary
print <<KEYWORD
Content-Disposition: filename="multipart.txt"
Content-type:   text/plain;charset=Shift_JIS
ENV['CONTENT_TYPE']   = #{ ENV['CONTENT_TYPE'] }
ENV['REQUEST_METHOD'] = #{ ENV['REQUEST_METHOD'] }
name値      =>  value値
----------      ---------------------------------------
KEYWORD
@in.each do |key,val|
  for v in val do
    puts "%10s  =>  %s" % [key,v] if v.class == String
    puts "%10s  =>  filename=%s, type=%s, size=%d" % [key,v["filename"],v["type"],v["size"]] if v.class == Hash
  end 
end
STDIN.read で読み込んだデータを、ENV['CONTENT_TYPE']から読み取った境界の目印で切り分け、それをさらにヘッダ部分(head)と内容(body = value値)に切り分けますENV['CONTENT_TYPE']から境界のキーワードを抜き出します。
	STDIN.read で読み込んだデータを、境界のキーワードで切り分けて、逐次処理します。
	.to_sで文字列化してあります.each( boundary ) do |q|を適用すると、boundaryを終端とする文字列に区切って、変数qに逐次写し取ります.class:添付されたファイルに関しては、内容を表示することができないので、ファイル名などを表示することにします。内容が文字列(String)になっているか、ハッシュ(Hash)になっているかどうかで、添付ファイルの情報かどうかを区別しています.readで一度に読み込むことはせずに、.getsで逐次読み込み、ファイルは一時的に保存してしまうのが良いでしょう。★改良されたサンプル ⇒ [ ZIP 圧縮ファイル form_mp.cgi ]
★以上全部組み合わせて、全部の送信形式に対応させると ⇒ [ ZIP 圧縮ファイル form_all.cgi ]
以上です。