Ruby ビギナーのための CGI 入門 【第 5 回】 文字コードと排他処理 1 ページ

目次へ 次のページへ

目次

はじめに

最近、朝晩が寒くなってきたため、手袋を使っています。 私は駅までバイクで通勤しているので、 手袋がないと手が痛くなって運転に支障があるからです。 皆さん防寒対策はどのようにされていますか?

前回 は bbs.rb が担当する表示部分を中心に改造していきました。 見た目は良くなって、だいぶ掲示板らしくなってきましたが、 それでも細かい改良がまだまだ必要です。 今号は実用に耐える掲示板を目指して update.rb が担当するフォームデータの処理や掲示板データへの書き込みの部分を改良します。 こられの処理は目に見えにくいので、つまらないかもしれませんが、非常に重要です。 しっかりと理解してください。 この連載の掲示板における bbs.rb や update.rb の役割を知りたい方は 14 号 を参照して下さい。

対象読者

この記事は以下のような人を対象としています。

  • 前回 の記事を読んだ人
  • HTML を書ける人
  • Windows 98/98SE/Me/2000/XP のいずれかを使っている人

この連載は 前回 までの記事を読んでいる方を対象として書かれています。 今号を読む前に前回までの内容を把握しておいて下さい。

準備

必要なものは下の 2 つです。

  • サーバー
  • Ruby

この他に RDE を使います。これらの準備の方法は 11号 で述べたので、 詳しくはそちらを参照して下さい。 今号で使うプログラムは zip ファイルにまとめてあります。 これまでと同じようにダウンロードして C:\ に展開して下さい。 rubima017-cgi.zip

ニュー Ruby に馴れよう

今回も例によって Ruby の機能の紹介から始めていきます。 新しい概念が多いので、とまどうかもしれませんが、 掲示板を完成させるためには必要なものばかりです。

例によって詳しい内容は後で説明しますので、 ここでは機能名を並べるだけにしておきます。

  • 定数
  • 文字コード
  • 排他処理

定数

定数というのは変数に似た機能を持っています。

以前、変数はデータ(オブジェクト)に付ける目印だと言いました。 変数を使うことで同じデータを使いまわしたり、 計算結果を記録しておいたりと 柔軟なプログラムを作ることが出来ます。

実はこうしたことの幾つかは定数でも可能です。 定数でも変数と同じようにオブジェクトに目印を付けることが出来るからです。 定数も変数もオブジェクトに目印を付けるという点では同じ機能なので、 すこし使っただけでは 2 つの違いがなかなか分かりません。 (特に小さな プログラム ではこの違いを実感しにくいと思います)。

そこで、まずは変数の機能を詳しく見直してみましょう。 そうすることで変数と定数との違いがはっきりしてきます。

変数と再代入

今までは一度オブジェクトに目印を付ける(変数へ代入する)と、 変数の目印はそのオブジェクトに付けたままでした。 でも、オブジェクトに付けた目印 (変数) は簡単に 別のオブジェクトに付け替えることが出来ます。

例えば、下のプログラムを考えてみましょう。

a = 1
puts a
a = 2
puts a

最初の 1 行目では変数 a は 1 というオブジェクトを指しているので、 2 行目では 1 が表示されます。 1 行目の処理のことを「変数 a に 1 を代入する」 というのは以前も説明しました。

このプログラムで説明したい点は次の 3 行目にあります。 3 行目では変数の目印を変更するために 「=」を使って目印を付け直しています。 こうすることで変数 a の目印は 2 に付け替えられます。 1・3行目の処理を図にすると、下のようになります。

1 行目

1  ←────  a

3 行目

1  ←  ─  ─  a  ────→  2

結果、4 行目では 2 が表示されます (実行して 4 行目で 1 が表示されていないことを確認してみて下さい)。 このような処理を__変数 a への再代入__といいます。 このプログラムの処理をまとめると、1 行目で変数 a に 1 が代入され、 2行目で変数 a の 1 が表示されます。 そして、3行目で変数 a に 2 が再代入され、4 行目で変数 a の 2 が表示されます。

Ruby で書かれたプログラムであれば、 再代入の際には同じ種類のオブジェクトを代入する必要はありません。 例えば、下のように別の種類のオブジェクトを再代入することが可能です。

a = 1
b = 3
a = "abc"
b = []

定数と変数の違い

ここまでは変数への再代入という点に注目してきました。 実は定数と変数の大きな違いは定数では再代入が許されていない (するべきではない)ということなのです。

プログラムを書いていると、 同じオブジェクトに目印を付けたいことがあります。 このような場合、プログラムが小さければ変数で済ませることも出来ますが、 プログラムが大きくなってくると間違って変数に再代入してしまうことがあります。 そうした際に利用するのが今回紹介する定数というわけです。

変数は小文字の a-z から始まりますが、変数は大文字の A-Z で始まります。 既に述べたように単純な例であれば、 定数と変数の使い方にそれほど大きな違いはありません。 例えば、下の 2 つのプログラムには変数と定数の違いがありますが、 実行結果は同じです。

変数を使用

a = 1
puts a

定数を使用

A = 1
puts A

では、次のようなプログラムではどうなるでしょうか? このプログラムは先ほどの再代入のプログラムを定数で書きなおしたものです。

A = 1
puts A
A = 2
puts A

まずはこのプログラムを実際に実行してみて下さい。 すると、1, 2 などの数字の他に下記のようなメッセージが表示されます。

warning: already initialized constant A

このメッセージは Ruby が間違った (注意の必要な) プログラムを実行した時に warning として表示されます。 このプログラムは変数 a を定数 A に変えただけですから、 上のメッセージは変数 a を定数 A に変更したことが原因です。

先ほども言ったように、定数は再代入できない変数のようなものです。 上記のプログラムでは 3 行目で定数 A に無理矢理再代入を行っており、 そのために Ruby が上のような warning を表示させます。 ちなみに warning の中の constant という言葉は定数という意味の英単語です。

このようなメッセージがでることから分かるように Ruby では定数への再代入をするべきではありません。

定数と変数の使い分け

上で見たように一度定数を使ってオブジェクトに目印を付けたら、 その目印を別のオブジェクトに付け替えるべきではありません。 プログラムを書く時はこの性質を踏まえて、変数と定数を使い分ける必要があります。 その使い分けは代入するオブジェクトがプログラムの中で どのような役割を果たすかによって変わってきます。

例として、消費税の金額を計算するプログラムを考えてみましょう。 消費税の税率はそれほど頻繁に変更されないことから、 消費税率は定数にすると良いと思われます。 そこで、消費税の税率を定数 TAX に割り当て、0.05 とします。 一方、購入金額には変数 a を使います。 購入金額は頻繁に変わるので、変数を使う方が適切です。

以上をふまえるとプログラムは下のようになります。 ここでは金額を1000円、消費税率を 0.05 としました。

TAX = 0.05
a = 1000
puts a * TAX

この程度のプログラムなら変数・定数のどちらか一方でまとめても良いでしょうが、 プログラムが大きくなってきたら 2 つを使い分ける方が望ましいです。 どのような場面で使い分けるべきなのかその判断には経験が必要です。 馴れないうちはすべて変数でも良いでしょうが、 大きなプログラムを作る時には定数を利用するようにしましょう。

また、すでに Ruby に備わっている機能を利用する時には 定数・変数を使い分ける必要があります。 このような場合はたいてい使用例があるので、 それを参考にプログラムを書くことになります。

文字コード

今まで Ruby のプログラムで何気なく日本語を使ってきました。 例えば、日本語の文字列を表示するなら、

print "日本語"

のように「”」の間に日本語を書くだけで 簡単に日本語を表示させることが出来ました。 ところが、CGI プログラムで日本語を扱うにあたってどうしても 避けて通れない問題があります。それが文字コードです。

文字コードとは

文字コードというのはコンピューターが簡単に文字を扱うことが 出来るようにするためのルールのようなものです。 通常、文字列はどれか 1 つの文字コードに則ったデータです。 例えば、

"1234"
"abcdef"
"こんにちは"

など、いずれも何か 1 つの文字コードが使われています。 文字コードというのは文字列を表すデータを コンピューターがどのように解釈するのか決めているのです。

日本語の文字コードの種類

普段、コンピューターで文章を扱う時は 文字コードのことを意識することはあまりありません。 それは OS や各種プログラムが適切に文字コードを処理してくれるからです。 しかし、CGI プログラムを作るとなると、 今度はその処理を自分で行わなくてはなりません。 特に日本語の文字コードには注意が必要です。 なぜなら、日本語にはよく使用される文字コードが複数あるからです。 CGI プログラムに関して言えば文字コードが問題になるのは ブラウザから送られてきたフォームデータです。 フォームデータの文字コードは一般的には自明ではないからです。

日本語では主に

  • Shift_JIS
  • EUC-JP
  • ISO-2022-JP
  • UTF-8

といった文字コードが使われています。

Shift_JIS は Windows で主に使われています。 この連載を読んでいる人のほとんどの方は日本語を使う際に この文字コードを利用しているはずです。 この連載でこれまで紹介してきたプログラムもすべて Shift_JIS を利用しています。

EUC-JP というのは Unix 系 OS でよく使われる文字コードです。 利用したことが無い人も多いと思います。

ISO-2022-JP というのは電子メールでしばしば使われます。 電子メール関係のプログラムはこの文字コードと相性が良かったので、 この文字コードが現在でも利用されています。

UTF-8 というのは最近使われてきている文字コードです。 複数の言葉の文字を一つの文字コードで統一的に扱うことを目標に作られた文字コードです。

文字コードと CGI プログラム

CGI プログラムで文字コードを扱う場面は幾つかありますが、 最も多いのはブラウザから送られてきたフォームデータを処理する時でしょう。 フォームデータの文字コードは事前に分からないことがほとんどです。 そのため送られてきたデータを調べて、どの文字コードなのかを決定し、 それにあわせた処理を行う必要があります。

例えば、Shift_JIS を前提に作られた CGI プログラムに EUC-JP のフォームデータが渡されると、 その CGI プログラムはフォームデータをきちんと処理出来なくなってしまいます。 前回までの掲示板はこうした問題を抱えています。 そこで、今号では Ruby にそなわっている文字コードの機能を使って、 この問題点を改良していきます。 ここではそのための準備をします。

Kconv - 文字コードの変換

通常、フォームデータの文字コードを扱う際には文字コードの変換が必要になります。 文字コードの変換というと難しそうに聞こえます。 確かに世の中のすべて文字コードに対応するのは非常に難しいと思いますが、 実用的に使われている文字コードに限って対応するのなら、 Ruby に用意されている Kconv という機能を使えば可能です。 例えば、フォームデータの文字コードが ISO-2022-JP の時に そのデータを EUC-JP に変換したい時などにこの Kconv が役立ちます。

実際に Kconv を使って文字コードを変換する練習をしてみましょう。 ここからは shift_jis.txt, euc_jp.txt, iso_2022_jp.txt というファイルを使います。 shift_jis.txt は Windows に付属しているメモ帳で見ることが出来ますが、 他の二つは見ることが出来ません。 shift_jis.txt は Shift_JIS の文字コードで書かれたデータで、 euc_jp.txt は EUC-JP で、iso_2022_jp.txt は ISO-2022-JP で書かれています。 メモ帳は EUC-JP や ISO-2022-JP に対応していないので、 iso_2022_jp.txt や euc_jp.txt を開くと文字化けします。

Shift_JIS memo_sjis.jpg

EUC-JP memo_euc.jpg

iso2022-jp memo_jis.jpg

では、EUC-JP や iso2022-jp のファイルを Windows のメモ帳で 読めるようにするプログラムを書いてみましょう。

下に日本語の文字コードを Shift_JIS に変換するプログラムを示します。 このプログラムは euc_jp.txt と iso_2022_jp.txt の内容を変更せずに 文字コードだけを Shift_JIS に変換します。

change_enc.rb

require "kconv"

f = open("euc_jp.txt")
euc = f.read
f.close

f = open("iso_2022_jp.txt")
iso = f.read
f.close

out = open("euc_sjis.txt", "w")
out.write(Kconv.tosjis(euc))
out.close

out2 = open("iso_sjis.txt", "w")
out2.write(Kconv.tosjis(iso))
out2.close


このプログラムの先頭の require “kconv” というのが Kconv を使うためのおまじないです。 require “cgi” と同じものと思って下さい。 これは Kconv を使う前に必ず必要となります。

最初の 3-9 行では euc_jp.txt, iso_2022_jp.txt に書かれた内容を 読み取っています。 その内容は変数の euc, iso に代入されます。 euc_jp.txt, iso_2022_jp.txt にはそれぞれ EUC-JP、ISO-2022-JP が使われているので、 変数 euc, iso の文字コードも EUC-JP, ISO-2022-JP になります。

このプログラムの最後の部分で Kconv.tosjis というメソッドが出てきます。 これが文字コードを変換している部分です。 Kconv.tosjis は引数の文字列を調べ、 対応可能であれば引数の文字列の文字コードを Shift_JIS に変換する機能があり、 変数 euc, iso は Kconv.tosjis によって文字コードが Shift_JIS に変更されます (文字コードが変わるだけで中身は変更されません)。

このプログラムは変換後のデータを euc_sjis.txt, euc_jis.txt に 保存するようになっています。 実際に実行して変換後の内容がメモ帳で見ることが出来るか試してみて下さい。

上で見たように Kconv には Kconv.tosjis というメソッドがあり、 文字コードを Shift_JIS に変えることが出来ます。 これ以外にも Kconv には文字コードに関わる色々な機能があります。 よく使いそうなメソッドは Kconv.toeuc, Kconv.tojis で、 それぞれ引数の文字列を EUC-JP に、 ISO-2022-JP に変えるといった機能があります。

次ページではこの機能を使って掲示板の文字コードを 1 つに統一します。 よく復習しておいて下さい。

排他処理

CGI プログラムは多くの人によって利用されるため、 時には同時に CGI プログラムへアクセスされることがあります。 仮に同時に掲示板に書き込みがあった場合、どのようになるのでしょう? 少し考えてみましょう。

同時に掲示板に書き込まれる

仮に A さん、B さんがほぼ同時に掲示板に書き込みをし、 わずかに A さんが早かったとします。

この連載の掲示板では update.rb が 投稿内容を受け取って、 bbs.dat にその内容を書き込むようになっています。 そのため A さんが書き込みをすると、 update.rb が bbs.dat を開いて A さんの投稿内容を書き込もうとします。

この時、わずかに遅れて B さんが掲示板に書き込みをすると、 A さんの時と同様に B さんの時にも update.rb が bbs.dat を開こうとします。 update.rb は同時には動作出来ないんじゃないの? と思う人がいるかもしれませんが、 CGI プログラム (この場合は update.rb) は実行されると、 コピー人間のように増えて同じ処理を行うプログラムが別々に動き出します。 このためほぼ同時に掲示板への書込みがあると、ほぼ同じタイミングで update.rb という 同じ CGI プログラムが 2 つ動作することになります。 conflict.jpg

2 つの update.rb がほぼ同時に動作し始めて、 bbs.datに書き込みをした場合、bbs.dat はどうなるのでしょうか。 その結果は様々ですが、 最悪の場合は bbs.dat に書かれた内容がすべて白紙に戻ることになります。 これは非常に大きな問題です。 掲示板の資産というものがあるとすると、その 1 つは過去の書き込みです。 bbs.dat が白紙になるということは過去の書き込みがなくなることを 意味するわけですから、大きな痛手です。

この問題は頻繁に起きるわけではありませんが、 過去のデータを守るためには対処しなければなりません。 この問題を防止するためにはいくつか方法がありますが、 要は同時にファイルを開くことが問題ですので、 それを防げば良いはずです。

何かを同時に利用できないようにすることを排他処理といい、 ファイルの場合ならファイルの排他処理と言います。 Ruby では File クラスのオブジェクトにファイルの排他処理の機能が備わっており、 今回はこれを利用します。 File クラスの排他処理を使うことで、 同時にファイルを開くことを防止することが出来ます。

ここから先は File のオブジェクトによる排他処理について簡単に説明していきます。 ここで排他処理に馴れてもらい、 次ページでは排他処理の機能を掲示板に追加していくことにします。

File オブジェクトによる排他処理

では、File オブジェクトによる排他処理を行うプログラムを紹介します。

exclusive.rb

f = File.open("tmp.txt", "a")

f.flock(File::LOCK_EX)
f.write("test\r\n")
f.close

このプログラムは tmp.txt に test という行を追加するプログラムです。 tmp.txt に同時に書き込みできないようにファイルを開く際に排他処理をしています。

すぐ目に付くと思いますが、排他処理は 3 行目で行われます。 3 行目の f.flock が排他処理を行うためのメソッドです。

f.flock(File::LOCK_EX)

flock メソッドの引数には幾つかの定数を指定することが出来ます。 今回は File::LOCK_EX という定数を指定しています (File::LOCK_EX というのは難しいかもしれませんが、File クラスの定数と思って下さい)。 これを指定すると、同時に同じファイルを開くことが出来ないようになります。

上記の例では 3 行目で変数 f は tmp.txt の File オブジェクトを指しており、 3行目が実行された後は tmp.txt は同時に一つしか開けません。

もう 1 つのポイントは 5 行目です。 この行は今までどおり close メソッドでファイルを閉じているだけですが、 flock を利用した排他処理では少し動作が異なってきます。 通常、書き込みが終わって排他処理が必要なくなれば、 そのことを OS に明示しなければなりません。 そうしなければ、他のプログラムがいつまでも tmp.txt を使うことが出来ないからです。 File の close メソッドはファイルを閉じるだけでなく、 排他処理が必要なくなったことを OS に連絡する役目も果たしてくれます。 ファイルを閉じたり、排他処理が終わった時には必ず close メソッドで ファイルを閉じるようにしましょう。

このプログラムでは排他処理は flock で File::LOCK_EX を指定した部分から、 close でファイルを閉じるまでの間行われています。 この間は他のプログラムは tmp.txt を開くことが出来ません。 こうすることで同時書き込みの問題をかなり抑えることが出来ます。

この機能を掲示板に追加すれば、 掲示板をより安全に使うことが出来るようになります。

まとめ

今回は定数、文字コード、排他処理について紹介しました。 次ページからは掲示板プログラムの 文字コードや排他処理の対応を進めていきます。

目次へ 次のページへ