送受信_フォームデータの扱い

ブログや掲示板などによくあるような、ページの記入欄に書き込まれて送信されたデータを、受け取って処理する方法を紹介します。

フォームの例 ⇒ Google: :記入してキーボードのenterを押す

概観

GETメソッド

まず、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

name 値がダブる場合の対策

チェックボックスなど、同じ 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

POSTメソッド(文字のみ)

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

簡潔に.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] }}

正規表現でのパターンマッチを利用した例

#!/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] }}

POST と GET が混在している場合

たまにある質問として、下記のように混ぜたらどうなるか?というのがあります。

<!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

POSTメソッド(添付ファイルあり)

フォームにファイルの添付タグをつけた 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

★改良されたサンプル ⇒ [ ZIP 圧縮ファイル form_mp.cgi ]

★以上全部組み合わせて、全部の送信形式に対応させると ⇒ [ ZIP 圧縮ファイル form_all.cgi ]

以上です。

Last updated 28.Sep.2006 [ Home ] [ Up ] [ 質問メール ]
Copyright © 2005-2006 Shigeru Konno All Rights Reserved..