ソースコードで学ぶWebプログラミング

Rubyで始めるCGIプログラム入門

このページでは、Ruby言語で作成したプログラムをWebプログラムとして動作させる方法を説明します。CGIとは、Webサーバと連携してプログラムを動作させる仕組みです。ブラウザからWebサイトにアクセスがあった場合、Webサーバはサーバ内にあるコンテンツを表示します。CGIプログラムでは、このコンテンツを動的に変化させて表示させることが出来ます。

RubyプログラムをCGIで動かす方法

Rubyは、スクリプト言語なので、プログラムソースは普通のテキストファイルになっています。これをプログラムとして動かすには、ソースコードを解析し、機械語に翻訳して実行するプログラムが必要になります。このプログラムをインタープリターと呼び、Ruby以外にもPerlやPythonがあります。

CGIでは、プログラムソースの一行目にインタープリターのパス(存在する場所)を記述するだけで、動作させることが出来ます。レンタルサーバなどでは、パスが公開されていますが、分からない場合は #!/usr/bin/env ruby とすることで、サーバ内のパス情報を元に起動させることが出来ます。

#!/usr/bin/ruby # 1行目にインタープリターのパスを記述 #!/usr/bin/env ruby # パスが分からない場合 1行目に

プログラムのコードは、テキストエディタで作成できます。CGIプログラムのファイル名は、name.cgiなど.cgiが付いた状態で保存します。ファイル名をindex.cgiにすると、通常http://example.com/のようなファイル名が不要なURLで表示することが出来ます。後はFTPソフトで、レンタルサーバなどへ設置します。またアップロードしたプログラムには、FTPソフトなどで実行権限(パーミッション)を与える必要があります。

RubyのCGIで文字を表示してみる

CGIプログラムでブラウザに文字を表示させてみます。CGIプログラムは、Webサーバにコンテンツを渡すことが出来ますが、テキストやHTML、画像などコンテンツには様々な種類があります。そのため、渡すコンテンツが何なのかを指定する必要があり、ヘッダー情報として渡します。

#!/usr/bin/ruby # ヘッダー情報、テキストファイルを指定 print "Content-Type: text/plain\n\n" # 改行ありで表示される puts 'Hello, World!' # 末尾の改行は表示されない puts "puts1\n" # putsで末尾の改行を2つにするには2つ付ける puts "puts2\n\n" # 開発時用の表示、表示対象を確認できる p "p\n"

printが表示させるための関数で、text/plainがコンテンツの内容になり、\nが改行になっています。表示させるコンテンツデータは、Hello, World!になります。コンテンツデータ部の前は改行で空ける事になっているため、\nが必要です。つまり改行を2つ付ける事になります。

Rubyのputsでは、改行付きで表示されますが、末尾の\nは表示されません。改行を2つ表示させたい場合は、2つ必要になるので、注意が必要です。また開発時は、pが利用できます。これは表示対象の内容確認用なので、通常ページでは使用しません。

また\nを有効とするには、""で括る必要があります。''で括った場合は改行の意味にはならず、\nの文字列になります。

続いて日本語を表示させてみます。日本語の文字は、同じ文字でもエンコーディングの種類により異なった文字コードになるため、エンコーディングを指定する必要があります。多くのサイト表示や開発環境は utf-8 になるため、プログラムソースはutf-8で保存して下さい。

Rubyのバージョン1.9以前で日本語を扱う場合は、内部エンコーディングを # coding: utf-8 のように指定する必要があります。

#!/usr/bin/ruby # coding: utf-8 # ヘッダー情報、テキストファイルを指定 print "Content-Type: text/plain; charset=utf-8\n\n" puts 'こんにちは、せかい!'

RubyのCGIで計算をしてみる

今回は、例題を解くかたちで、Ruby言語で四則演算をする方法を覚えることにします。Rubyの加減乗除は、順番に+ - * /になります。剰余(割った余り)は%で求めることが出来ます。

■例題
5人が集まって、1人が買い物に出かけました。購入金は5人で負担し、割り切れない金額は購入担当者が負担するものとします。所持金10,000円を持って、500円の物を7個、700円の物を4個、購入しました。1人当たりの購入金額と購入担当者の負担額を求めなさい。

#!/usr/bin/ruby print "Content-Type: text/plain; charset=utf-8\n\n" # 消費税 tax = 1.08 # 所持金 wallet = 10000 # 人数 num = 5 # 商品価格A priceA = 500 # 商品価格B priceB = 700 # 購入金額 total = (priceA * 7 + priceB * 4) * tax puts sprintf("購入金額:%d円", total); puts sprintf("残高:%d円", wallet - total) puts sprintf("1人当たり:%d円", total / num) puts sprintf("購入者負担:%d円", total % num)

今回は、変数を使用しています。Rubyでは、変数は英数字、アンダースコアで名前を付けることができます。ただし、先頭には数字は使えません。また数値を代入した変数を、決まった書式で文字列として表示する場合、sprintf()が使用できます。

人数を変更したり、購入商品を増やしたり、ポイント還元率を作り、取得ポイントを計算してみて下さい。実際に手を動かさないと覚えられないため、コードのカスタマイズをお勧めします。

RubyのCGIで日時を表示してみる

Rubyで日時を取得するには、Timeオブジェクトを利用します。Timeはクラスなので、Time.nowなどでオブジェクトを作成し、メソッドを実行して日時を取得します。

またTimeオブジェクトのto_iメソッドで、Unixタイムスタンプを取得できます。Unixタイムスタンプとは、1970年1月1日0時0分0秒からの秒数になります。1秒毎の連番として利用できるほか、指定時刻を求める際にも利用できます。

現在日時だけを表示してもなので、今回も例題を解くかたちで覚えます。例題の1万時間とは、プロレベルのスキルを習得するのに必要となる時間とされています。「1万時間の法則」と呼ばれ、イギリスのマルコム・グラドウェル氏が提唱しています。

■例題
1日6時間を訓練に当てたとして、累計1万時間に達するのはいつになるのかを求めよ。

#!/usr/bin/ruby print "Content-Type: text/plain; charset=utf-8\n\n" # 現在日時を表示 puts Time.now.strftime("%Y年%m月%d日 %H時%M分%S秒") # Unixタイムスタンプを取得 # 1日6時間の訓練を1万時間続けた場合の秒数を足す time = Time.now.to_i + 10000*3600 * (24/6) # Unixタイムスタンプから日時を文字列で表示 puts Time.at(time).strftime("%Y年%m月%d日 %H時%M分%S秒")

Timeオブジェクトのstrftime()メソッドで日時を文字列にしています。%Y%m%dがそれぞれ年月日、%H%M%Sがそれぞれ時分秒に置き換わった文字列を取得できるので、その文字列を表示しています。

1万時間には3600を掛けて秒単位にします。1日6時間の場合、1日は24時間なので全体としては4倍の時間が必要になります。1日2時間の場合、1日10時間の場合などを求めてみて下さい。

RubyのCGIで条件分岐を覚える

プログラムは、ある変数の状態で真か偽かを判定して、条件に合わせた処理を行わせる流れになります。Rubyでは、if else end でそれぞれの条件に合わせた処理をコーディングします。

また今回からCGIプログラムの処理として、ブラウザからの入力を受け取れるようにします。デコード用のライブラリ CgiDecode.rb を使うので、こちらからダウンロードして下さい。下記プログラムのURLへアクセスすると「入力がありません」が表示されると思います。続いてURLの末尾に、?word=文字列を入力後、アクセスしてみて下さい。

#!/usr/bin/ruby # デコード用のライブラリを読み込み require './CgiDecode.rb' # 変数をセット CgiDecode.new # $_GET,$_POST,$_COOKIE,$_FILES が使えるように print "Content-Type: text/plain; charset=utf-8\n\n" # word=の入力があった場合 if $_GET.key?('word') puts $_GET['word'] else puts '入力がありません' end

今度は文字列が表示されると思います。key?メソッドで変数(ハッシュ)の中にキーがあるかを判定し、あった場合にif以下のブロック内の処理を実行しています。この時、else以下のブロックは処理されないため、表示されません。

RubyのCGIで繰り返し処理を覚える

プログラムで同じ処理を何度も行なう場合は、for in が利用されます。Rubyでは、インクリメントやディクリメントが使えないため、ループする範囲を配列で指定して繰り返し処理を行います。または times、upto、downto なども利用できます。

# 範囲を指定してループする for i in 1..10 do puts i end # 範囲でループ (1..5).each do |i| puts i end # 回数を指定する 5.times do |i| puts i end # 方向を指定する 増加 1.upto(5) do |i| puts i end # 方向を指定する 減少 5.downto(1) do |i| puts i end

timesだけ範囲指定ではないので、0から始まります。for文の習得でよく使われる処理に、ピラミッド表示があります。ピラミッド表示とは、指定文字でのアスキーアートでピラミッド構造を構築する処理です。今回は*(アスタリスク)と「 」(スペース)で構築します。

■例題
*とスペースで、入力数に応じた段数のピラミッドを表示せよ。

初めから正解のかたちでコーディングは普通できないので、試行錯誤しながら作成していきます。まずは普通に段数ごとに*を増やす処理を実装します。

#!/usr/bin/ruby require './CgiDecode.rb' CgiDecode.new # $_GET,$_POST,$_COOKIE,$_FILES が使えるように print "Content-Type: text/plain; charset=utf-8\n\n" # 入力があり、数値の場合 if $_GET.key?('n') && $_GET['n'] !~ /[^\d]/ # 段数 for i in 1..$_GET['n'].to_i # 段の初めに初期化 t = '' # その段の*を作成 i.times do |j| t += '*' end puts t end else puts '入力がありません' end

for文が段数を決定し、timesがその段での*の数を決定することになります。また1つ目のループの変数は i で、2つ目のループでは j になっています。今回のケースでは同じ変数でも問題ありませんが、通常は下層ブロックになる場合は、別の変数にします。

確認時のURLは、?n=数値でアクセスしてみて下さい。GET送信からの入力 $_GET['n'] は文字列になっているため、to_iで数値化しています。ここまでで、表示はピラミッドの半分の状態になっていると思います。

# ?n=5 とした場合 * ** *** **** *****

この表示に、スペースを追加すればピラミッド構造を構築できますが、*の文字数から考えて、偶数の段と奇数の段ではうまく真ん中が揃いません。そのため奇数の段のみのピラミッドにするか、偶数の段をスペースで奇数にするかで解決する必要があります。今回は、奇数の段のみのピラミッドにしてみます。

# ?n=5 とした場合 スペースで調節 * ** *** **** *****

次にスペースを入れる方法を考えます。一番下の段を0とした場合、1段目に1個、2段目に2個とスペースを増やせば良さそうです。ただし、現在の処理では一番上の段から処理しているため、上からのアルゴリズムにする必要があります。一番上の段のスペースの数は、4個で、全体の段数5からその段数1を引いた数になっています。

#!/usr/bin/ruby require './CgiDecode.rb' CgiDecode.new # $_GET,$_POST,$_COOKIE,$_FILES が使えるように print "Content-Type: text/plain; charset=utf-8\n\n" # 入力があり、数値の場合 if $_GET.key?('n') && $_GET['n'] !~ /[^\d]/ # 段数 for i in 1..$_GET['n'].to_i # その段に含まれる*の数を奇数個に d = i*2 - 1 # 段の初めに初期化 t = '' # 全体の段数からその段の段数を引いた数のスペースを追加 ($_GET['n'].to_i - i).times do |j| t += ' ' end # その段の*を作成 d.times do |j| t += '*' end puts t end else puts '入力がありません' end

実行結果は下記になります。

# ?n=5 とした場合 * *** ***** ******* *********

偶数の段にスペースを入れて奇数にする方法も掲載しておきます。

#!/usr/bin/ruby require './CgiDecode.rb' CgiDecode.new # $_GET,$_POST,$_COOKIE,$_FILES が使えるように print "Content-Type: text/plain; charset=utf-8\n\n" # 入力があり、数値の場合 if $_GET.key?('n') && $_GET['n'] !~ /[^\d]/ # 段数 for i in 1..$_GET['n'].to_i # 段の初めに初期化 t = '' # 全体の段数からその段の段数を引いた数のスペースを追加 ($_GET['n'].to_i - i).times do |j| t += ' ' end # その段の*を作成 i.times do |j| # ここでスペースも追加 t += '* ' end # 末尾のスペースを除去 t.rstrip! puts t end else puts '入力がありません' end

実行結果は下記になります。

# ?n=5 とした場合 * * * * * * * * * * * * * * *

RubyのCGIでセキュリティ問題を確認する

CGIプログラムでは、ユーザーからの入力を受け取って処理を行なうため、入力データの内容により想定外の結果を引き起こすことがあります。主にブラウザにHTMLタグを表示する際に起こる問題(クロスサイトスクリプティング)と、DBなどに保存する際に起こる問題(SQLインジェクション)があります。

今回は、不正なタグを受け取った場合の問題について確認します。今まではテキストデータとして表示を行っていたため、問題は起こりませんでしたが、HTMLとして表示を行なう場合は、エスケープを行わないと、サイトの表示が崩れたり、javascriptを実行されたりします。

下記では、エスケープを行わず、入力を直接表示しています。?word=<h1>などでアクセスすると、タグの機能によりWebサイトのデザインが壊れることになります。

#!/usr/bin/ruby require './CgiDecode.rb' CgiDecode.new # $_GET,$_POST,$_COOKIE,$_FILES が使えるように print "Content-Type: text/html; charset=utf-8\n\n" # word=の入力があった場合 if $_GET.key?('word') puts $_GET['word'] else puts '入力がありません' end puts '<p>サイトのコンテンツ</p>'

Content-Typeの部分を、text/htmlにしています。以降の表示はHTMLとして扱われるため、タグで使われる文字はエスケープする必要があります。上記プログラムのご確認後は、サーバから削除して下さい。