PythonでWebプログラミングの基礎(その9)CGIでの例外処理

前回の続きです。前回はファイルの内容を画面にHTML形式で表示するCGIプログラムを作成しました。今回はこれに例外発生時(実行時エラー的な状況)の処理を追加します。

前回のCGIプログラムでは実行時に例外が発生した場合は、中途半端な状態で終了します。この場合はブラウザ画面ではどういう状態か分かりません。これにせめて例外が発生したことが分かるぐらいのメッセージとログ的な内容を保存するようにしてみます。

以下のような順番で書きます。
Webサーバの実行時の状態はどのように見えるか
例外処理をしないとどうなるか
例外処理の基本的な書き方
ログファイルとして例外内容を保存する方法
実際に例外処理を発生させてテストをする

例外、つまり、try except の正しい詳細な書き方というより、「流れ」について書きます。ここでの「流れ」とは処理中に問題(実行時例外)があっても、その状況が分かって、後で問題点が検証出来るようにするための「流れ」ということです。そういう「流れ」がCGIに限らずプログラムでは必要になりますということです。

Webサーバの実行状態
本格的なWebサーバですと、しっかりしたログの出力の仕組みがあって日別等のファイルで保存されます。今回の一連の記事で使用しているのはPythonの簡易的なWebサーバです。実行状態(処理の経過ログ)はそのまま画面に表示されます。正常に処理されている場合ですと例えばですが以下のように表示されます。
HTTPのリクエストとステータス(200とか404等の数字)が表示されています。
※これを見ていると何だろうというエラーが表示されることがあると思います。それについては最後の補足に書きました。

入力ファイルがない場合の状態
前回のCGIプログラムでは例外処理はありません。この状態であえて例外を発生させます。入力ファイルが存在しない状態、つまりファイル(member.txt)を削除してブラウザから実行します。

ブラウザ画面の表示

「ファイルの内容」とは表示されていますが、内容は表示されません。確かにファイルがないので表示されませんが、これではどういう状態が正確には分かりません。

実行画面は以下です。

FileNotFoundErrorと表示されていて、member.txtというファイルがないというメッセージが表示されています。さらにどこでこの例外が発生したか分かります。

これを捕捉して必要な処理をします。

例外処理その1
以下のように try except を使って FileNotFoundError を捕捉します。

#!/usr/bin/env python3

#HTMLテキストの上の部分
htmlText1st = '''Content-type: text/html; charset=UTF-8

<html>
<head>
  <title>ファイルの読み込みから表示</title>
  <link rel="stylesheet" href="../style.css">
</head>
<body>
<header class="header">
   <p>ファイルの内容</p>
</header>
<div class="content">
'''

#HTMLテキストの下の部分
htmlText2nd = '''

</div>
</body>
</html>
'''

#ここから処理
import cgi
import traceback

def main():
    try:
        print(htmlText1st)

        f = open('member.txt', 'r')
        line = f.readline()

        print('<table border="1">')
        print('<th>お名前</th><th>ログイン名</th><th>パスワード</th><th>メールアドレス</th>')

        while line:
            print('<tr>')

            items = line.split(',')

            for item in items:
                print('<td>')
                print(item)
                print('</td>')

            print('</tr>')

            line = f.readline()

        f.close()  

        print('</table>')
        print(htmlText2nd)

    except FileNotFoundError:
        exstr = traceback.format_exc()
        errfile = open('error.txt','a')
        errfile.write(exstr)
        errfile.close()

        print('<p>入力ファイルに異常があります。</p>'); 
        print('<p>システム管理者に連絡して下さい。</p>'); 

        print(htmlText2nd)

if __name__ == "__main__":
    main()

これで入力ファイルの member.txt がない状態で実行するとブラウザ画面には以下のように表示されます。

「システム管理者に連絡して下さい。」というのは実務のアプリケーションではこういったメッセージを表示してユーザに対応してもらうという例です。

また、error.txt に以下のように例外(エラー)の情報が保存されました。

実務のアプリケーションではもっと他に時刻等の情報を付加して後で問題点を明らかに出来るようにしますが、ここではこのような流れで例外は処理されますという例です。

例外処理その2
上記の例外処理その1では FileNotFoundError を捕捉しました。それでは、FileNotFoundError 以外ではどうなるかというと捕捉出来ないのでブラウザ画面には状況が分からない表示になります。そのためここでは FileNotFoundError 以外の場合も捕捉するようにしてみます。

FileNotFoundError 以外とは随分ざっくりとした処理ですが、実務のアプリケーションではもちろん想定出来れば細かく例外を捕捉します。以下はPython3.5での組み込み例外のマニュアルページです。
Python3.5 組み込み例外

FileNotFoundError 以外の場合も捕捉するようにしたCGIプログラムが以下です。

#!/usr/bin/env python3

#HTMLテキストの上の部分
htmlText1st = '''Content-type: text/html; charset=UTF-8

<html>
<head>
  <title>ファイルの読み込みから表示</title>
  <link rel="stylesheet" href="../style.css">
</head>
<body>
<header class="header">
   <p>ファイルの内容</p>
</header>
<div class="content">
'''

#HTMLテキストの下の部分
htmlText2nd = '''

</div>
</body>
</html>
'''

#ここから処理
import cgi
import traceback

def main():
    try:
        print(htmlText1st)

        f = open('member.txt', 'r')
        line = f.readline()

        print('<table border="1">')
        print('<th>お名前</th><th>ログイン名</th><th>パスワード</th><th>メールアドレス</th>')

        while line:
            print('<tr>')

            items = line.split(',')

            for item in items:
                print('<td>')
                print(item)
                print('</td>')

            print('</tr>')

            line = f.readline()

        f.close()  

        print('</table>')
        print(htmlText2nd)

    except FileNotFoundError:
        exstr = traceback.format_exc()
        errfile = open('error.txt','a')
        errfile.write(exstr)
        errfile.close()

        print('<p>入力ファイルに異常があります。</p>'); 
        print('<p>システム管理者に連絡して下さい。</p>'); 

        print(htmlText2nd)

    except:
        exstr = traceback.format_exc()
        errfile = open('error.txt','a')
        errfile.write(exstr)
        errfile.close()

        print('<p>システムエラーが発生しました。</p>');
        print('<p>システム管理者に連絡して下さい。</p>');

        print(htmlText2nd)

if __name__ == "__main__":
    main()

それでは実際にどういった状態で例外が発生するかですが、よくやるのが以下のように分母が0での割り算です。
x = 1
y = 0
z = x / y
これを例えば以下のように挿入します。

import cgi
import traceback

def main():
    try:
        print(htmlText1st)

        f = open('member.txt', 'r')
        line = f.readline()

        # 以下の3行
        x = 1
        y = 0                                                                                                                        
        z = x / y

        print('<table border="1">') 
                                                                                                    
#以下省略

以下が実行時のブラウザ画面です。

何となくそれらしい「システムエラーが発生しました。」という表示が出来ました。これでは毎回こうなりますので、正常な動作にするためには x,y,z の3行を削除します。もちろん入力ファイルのmember.txtも存在している状態にします。

error.txt に以下のように例外(エラー)の情報が保存されました。

FileNotFoundErrorの内容は例外処理その1の内容で、その続きで今回の例外の内容が保存されました。例外としては、ZeroDivisionError を捕捉して z = x / y に原因があったことが分かります。

このように例外処理で何か動作に問題があっても何かしらブラウザ画面に表示して後で調べられるような仕組みを用意しておきますということです。

(補足)Webサーバ自体の実行時例外
最初の方で書きましたが、PythonのWebサーバを起動しながら何回も再読み込みしたり、ブラウザの履歴を移動させると以下のような(まさに)実行時例外が表示されます。これはCGIプログラムで発生しているのではなくて、PythonのWebサーバ自体で発生しているようです。

BrokenPipeErrorというエラーです。どうもブラウザの再読み込みや履歴の移動で、HTTPの通信が完結しないような状態で出るようです。Webサーバとしては動作し続けているので特に問題はないということでいいと思います。

今回はここまでです。次回は関数分割についてです。
次回へ移動する