このサイトについて

みんなのPython Webアプリ編 - レスポンスの処理

みんなのPython Webアプリ編 - レスポンスの処理

レスポンスの処理

 Webアプリケーションが入力を受け取り、処理をした結果としてレスポンスを返します。Webアプリケーションの場合、レスポンスもほとんどの場合HTMLのような文字列となります。レスポンスとして返された文字列をWebブラウザのようなクライアントが受け取り、結果を表示します。

Webアプリケーションが返すレスポンスには、HTMLのような文字列の他にも多くの情報を埋め込めます。ヘッダと呼ばれている部分に、レスポンスを返した日付などの情報を埋め込むことができるのです。

これまでのサンプルでは、ヘッダを含んだ文字列を、Pythonのプログラムから出力していました。最初に必ず、Contet-typeから始まる行がありました。この部分がヘッダになっています。ヘッダの後、HTMLを送信することでレスポンスを出力しています。毎回、似たような内容の文字列を出力するわけです。この部分を含め、レスポンスをスマートに扱う方法について考えてみましょう。

レスポンスとして送るべきヘッダには、レスポンスの種類を示すContent-typeの他にもいくつかの種類があります。ヘッダの内容によって、レスポンスを受け取るクライアントの動きをコントロールすることもあります。ですので、必要に応じてヘッダを付け加えたり、ヘッダの内容を変更できるようになっているのが理想です。

まとめると、レスポンスには、ヘッダの内容、およびレスポンス本文となる文字列を保存する必要がある、ということになります。データを保存する必要があるので、レスポンスもクラスとして実装することにしましょう。クラス名は「Response」です。

Responseクラスを実装する

レスポンスとして返す文字列をスマートに扱うために、Responseクラスを実装してみましょう。レスポンスクラスのインスタンスには、ヘッダと、Webブラウザに表示するHTML文字列を保存できるようにします。

ヘッダは、名前と値という2種類の文字列のペアで表現します。このようなデータを扱うためには、辞書オブジェクトを使えばいいでしょう。HTML文字列は、文字列として保存しておけばよいでしょう。2つのデータはインスタンスオブジェクトのアトリビュートとして保存します。初期化メソッドでアトリビュートを初期化しておくわけです。

ヘッダには複数の種類があります。レスポンスとして送信すべきヘッダのうち何種類かは、デフォルトの状態を決めることができたり、プログラム側で作ることができます。そのようなヘッダは、クラスのメソッドで自動的に作るようにします。

プログラム側では、あらかじめResponseクラスのインスタンスオブジェクトを作っておきます。インスタンスに対して、ヘッダを追加したりレスポンス本文を登録したり、という処理をするわけです。最終的に、ヘッダと本文を含んだレスポンス文字列全体を得るためには、クラスのメソッドを利用します。

以下が、現時点でのResponseクラスの初期化メソッドです。ヘッダのうち、レスポンスの種類を指定するヘッダをあらかじめ定義しておきます。また、レスポンス本文となるアトリビュートを空の文字列として初期化しています。レスポンスの1行目として返すステータス行のために、ステータスコードとステータスメッセージをアトリビュートとして定義しています。

Responseクラスの初期化メソッド(httphandler.py)

:::python
class Response(object):
    """
    HTTPのレスポンスをハンドリングするクラス
    レスポンスを送る前にインスタンスを生成して利用する
    レスポンスやヘッダの内容の保持,ヘッダを含めたレスポンスの
    送信を行う
    """
    def __init__(self, charset='utf-8'):
        """
        インスタンスの初期化メソッド
        ヘッダ用の辞書,本文用の文字列などを初期化する
        """
        self.headers={'Content-type':'text/html;charset=%s' %
                      charset}
        self.body=""
        self.status=200
        self.status_message=''
        ...(続く)

ヘッダの追加用メソッド

次に、レスポンス・ヘッダを追加したり、取り出したりするメソッドを定義します。Webアプリケーション側では、必要に応じてこのメソッドを使い、ヘッダを登録します。

ヘッダの制御用メソッド(httphandler.py)

:::python
        ...(続き)
    def set_header(self, name, value):
        """
        レスポンスのヘッダを設定する
        """
        self.headers[name]=value
    def get_header(self, name):
        """
        設定済みのレスポンス用ヘッダを返す
        """
        return self.headers.get(name, None)
        ...(続く)

メソッドの内部では、アトリビュートの辞書を使って処理をしています。ヘッダを取得するget_header()メソッドでは、ヘッダが定義されていない場合は、Noneを返すようにしています。辞書の未定義のキーに対してself.header[name]のようにアクセスしようとすると、KeyErrorという例外が発生します。get()メソッドを使うことで、例外が発生しないように処理をしています。

次に、レスポンス本文を設定するメソッドと、レスポンス文字列全体を作るメソッドを見てみましょう。

本文の登録とレスポンス文字列の生成(httphandler.py)

:::python
        ...(続き)
    def set_body(self, bodystr):
        """
        レスポンスとして出力する本文の文字列を返す
        """
        self.body=bodystr
    def make_output(self, timestamp=None):
        """
        ヘッダと本文を含めたレスポンス文字列を作る
        """
        if timestamp is None:
            timestamp = time.time()
            year, month, day, hh, mm, ss, wd, y, z = time.gmtime( timestamp)
            dtstr="%s, %02d %3s %4d %02d:%02d:%02d GMT" % (
                         _weekdayname[wd], day,
                         _monthname[month], year,
                         hh, mm, ss)
            self.set_header("Last-Modified", dtstr)
            headers='¥n'.join(["%s: %s" % (k, v)
                               for k,v in self.headers.items()])
        return headers+'¥n¥n'+self.body
    def __str__(self):
        """
        リクエストを文字列に変換する
        """
        return self.make_output().encode('utf-8')
        ...(続く)

レスポンス文字列を設定するset_body()はとても簡単なメソッドです。引数として受け取ったオブジェクトを、インスタンスオブジェクトのアトリビュートに代入するだけのメソッドです。

make_output()は、ヘッダと本文を含めたレスポンス文字列全体を作るメソッドです。初期化メソッドではContent-Typeヘッダが追加されています。また、set_header()などを通じて追加されたヘッダの他に、別のヘッダを追加しています。

レスポンスを返す時間を表すLast-Modifiedヘッダは、決められたフォーマットに沿って文字列を作らなければなりません。ここでは、timeモジュールを使って現在日時を文字列に変換しています。

最後に、ヘッダ全体を文字列に変換し、改行を挟んでレスポンス本文を連結して1つの文字列を作っています。

最後に定義されている__str__()メソッドは特殊メソッドと呼ばれるメソッドです。インスタンスオブジェクトを文字列に変換しようとするときに呼ばれるメソッドです。Responseクラスでは、レスポンス文字列全体を返します。このメソッドを定義しておくと、インスタンスオブジェクトを文字列に変換するだけで、レスポンス文字列を得ることができるわけです。

そのほかの処理

 ここで定義したRequestクラスとResponseクラスを、「httphandler.py」という1つのファイルにまとめます。Pythonでは、スクリプトファイルがモジュールとなります。Webアプリケーションでは、このモジュールからクラスをインポートして、利用するわけです。

またhttphanderモジュールには、レスポンス本文としてよく使う<html>〜</html>のような文字列を返す関数を定義しておきます。HTMLの外枠となる定型文字列を返す関数を定義して、Webアプリケーションて利用するためです。この関数はget_htmltemplate()とします。

get_htmltemplate()関数(httphandler.py)

:::python
def get_htmltemplate():
    """
    レスポンスとして返すHTMLのうち,定型部分を返す
    """
    html_body = u"""
    <html>
      <head>
        <meta http-equiv="content-type"
              content="text/html;charset=utf-8" />
      </head>
      <body>
      %s
      </body>
    </html>"""
    return html_bod

そのほか、Last-Modifiedヘッダを作成するためにtimeモジュールの読み込みと、以下のような配列変数の定義も必要です。

:::python
import time
_weekdayname = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]
_monthname = [None,
              "Jan", "Feb", "Mar", "Apr", "May", "Jun",
              "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
2014-09-03 15:00