このサイトについて

みんなのPython Webアプリ編 - リクエストとして送られる文字列

みんなのPython Webアプリ編 - リクエストとして送られる文字列

Python,プログラミングに興味のある方のためのFacebookグループ。Python関連グループとしてナンバーワン。

リクエストとして送られる文字列

WebブラウザにURLを入力すると、Webサーバに対してリクエストが送られます。URLは前半(スキームとホスト)と後半(パスとクエリ)に分かれます。前半をWebブラウザが知っているべき情報です。Webブラウザがスキームとホストを解釈し、リクエストを送信する方法と送信先がネットワークのどこにあるかを決めます。

図02 URLはいくつかの部分に分かれる

図02 URLはいくつかの部分に分かれる

URLの文字列のうち、Webサーバにリクエストとして送られるのは、基本的に後半のパスとクエリ部分のみです。たとえば「http://127.0.0.1:8000/cgi-bin/foo.py?bar=baz」というリクエストが送られるとします。127.0.0.1はループバックアドレスと呼ばれ自分自身を指すことはすでに解説しました。Webブラウザと同じマシンの8000番ポートに対して、以下のような文字列がリクエストとして送信されます。そう、リクエストも文字データなのです。

レスポンスの例:

:::linux
GET /cgi-bin/foo.py?bar=baz
HTTP/1.1 If-Modified-Since: Sun, 1 Jul 2007 11:31:06 GMT
User-Agent: ....

リクエストラインとヘッダ

リクエストのうち、1行目はリクエストラインと呼ばれています。リクエストラインには、リクエストの種類とパスやクエリなどの情報が記載されています。2行目以降はヘッダフィールドと呼ばれています。ヘッダフィールドには、主にブラウザが追加したいろいろな情報が記載されています。リクエストのヘッダフィールドと同じように、いろいろな付加情報が記載されています。

リクエストラインはURLとほぼ同じ内容です。それに対して、ヘッダフィールドは表面には見えません。リクエスト送信時に、Webブラウザがこっそりと埋め込んで送信する情報だからです。実際にWebブラウザから送信されるヘッダにはもっとたくさんの種類があります。

リクエストにあるヘッダの情報は、環境変数としてWebサーバ上で動くプログラムに受け渡されます。cgiモジュールには、この環境変数を整形して表示するための関数が用意されています。以下のようなプログラムをcgi-binフォルダに設置して、Webブラウザでアクセスしてみてください。環境変数として、たくさんの情報が表示されるはずです。

List01 environ.py

:::python
#!/usr/bin/env python
import cgi
print "Content-type: text/html¥n"
print cgi.print_environ()

図03 実行結果

図03 実行結果

環境変数の中には、Webサーバを実行している環境に設定された情報が含まれています。そのため、情報のすべてがリクエストに送られてくる情報ではありません。CONTENT_TYPEやHTTP_USER_AGENTなどの環境変数の多くは、リクエストのヘッダで送信された情報です。

HTTPメソッド

Webブラウザから送られるリクエストには何種類かあります。先ほどのリクエストでは、リクエストラインの先頭にGETという文字列が見えました。この部分はメソッド(method)と呼ばれていて、Webサーバに与えるリクエストの種類を示しています。Pythonで組み込み型やインスタンスオブジェクトに対して利用するメソッドと同じ言葉なので混乱するかもしれません。methodという英単語には「方法」「手法」という意味があります。ここでは、リクエストを送るための方法、という意味ととらえてください。なお、ここでいうメソッドは、<form>タグにアトリビュートとして埋め込んだmethodと同じことを指しています。

次に、クエリデータの送り方が異なる2つのメソッドについて解説したいと思います。

GETメソッド

Webサーバに対してレスポンスを要求するために、パスを指定したリクエストを発行するのに利用するのがGETメソッドです。HTMLファイルや画像ファイルのような静的ファイルをWebサーバに要求するときに主に利用します。

Webサーバ上で動くプログラムに値を渡すときには、パスの後ろにクエリを追加します。クエリは、Pythonの辞書型と同じような構造をしていて、キーと値のペアをイコールでつなげる形で記述します。複数のペアがあるときにはアンド(&)で区切ります。

フォームを使ってリクエストを送信するとき、GETメソッドを利用したい場合は<form>タグのmethodアトリビュートに「GET」を指定します。<form>タグにmethodアトリビュートを指定しない場合はGETメソッドとなります。

クエリとURLエンコード

GETメソッドのクエリの中で利用できる文字列には制限があります。たとえば、アンド(&)やイコール(=)のような文字列は、クエリの中で区切り文字として利用されます。クエリの値としてこのような文字があると、クエリ中の区切りが正しく認識できなくなってしまいます。

このようなことを避けるため、クエリでは特定の文字列をエスケープする、という決まりがあります。エスケープすべき文字列は%+16進の文字コードという形でエスケープします。

以下のような規則で、エスケープすべき文字とそうでない文字が決まっています。

エスケープしなくてよい文字列

英字(大文字、小文字とも)、数字、ハイフン(-)、アンダースコア(_)やピリオド(.)などの一部記号

エスケープする文字

アンド(&)やイコール(=)などURLで意味がある文字、空白文字や不等号(<>)などURLで使えない文字、日本語のようなマルチバイト文字列

フォームからGETメソッドでリクエストを送信する場合には、Webブラウザが自動的にURLエンコードを行います。また、一部のWebブラウザでは、URLの中にマルチバイトがあった場合に自動的にURLエンコードをするようです。

なお、プログラム側では、クエリの文字列を解析してキーと値に分割して処理をします。クエリの値はURLエンコードされているので、エンコードを解いて、元の文字列を取り出す処理をするわけです。

PythonでWebアプリケーションを作る場合、クエリの解析を行うことはほとんどありません。cgiモジュールのFieldStorage()関数を呼ぶときに、クエリの解析が自動的に行われるからです。クエリを解析した結果は、FieldStorage()関数の返すフィールドストレージオブジェクトに格納されます。

デコードとは逆に、クエリをエンコードするには、urllibモジュールのurlencode()関数を利用します。引数としてクエリを辞書にして渡すと、クエリをURLエンコードした文字列が返ってきます。以下に簡単なサンプルを示します。

urlencode()関数の使用例

:::python
import urllib
querystr=urllib.urlencode({'foo':'bar& baz'})
print querystr
foo=bar%26+baz

POSTメソッド

POSTというのは投稿をするという意味の英単語です。このことからも分かると思いますが、Webサーバに対してデータを送信するときに利用するのがPOSTメソッドです。

GETメソッドを使う場合、URLにクエリを渡さなければなりません。URLの長さにはたいてい制限がありますし、あまり長いURLは好ましいとは言えません。大きなサイズのデータを送るにはGETメソッドは不向きです。大きなサイズのデータを送るときにはPOSTメソッドを利用します。

フォームを使ってPOSTメソッドでデータを送るには、methodアトリビュートに「POST」という文字列を指定します。POSTリクエストの場合、クエリのデータはリクエストのヘッダの後に改行を挟んで埋め込まれます。埋め込まれるデータは、クエリのキーと値のペアをURLエンコードした文字列です。

以下は簡単なPOSTリクエストの例です。「bar」というキーに対応する値として「baz」という文字列を指定しています。GETリクエストでURLに埋め込まれていたクエリ文字列が、ヘッダの直後に来ているのが分かります。

POSTリクエスト時のデータ送信

:::linux
POST /cgi-bin/foo.py HTTP/1.1
If-Modified-Since: Sun 1 Jul 2007 11:31:06 GMT
User-Agent: ....

bar=baz

cgiモジュールのFiedlStorage()関数では、POSTメソッドのレスポンスを解析して、クエリをオブジェクトに変換します。変換は自動的に行われるので、プログラム側でPOSTメソッドが使われているかGETメソッドが使われているかを意識する必要はあまりありません。

GETメソッド、POSTメソッドの利点、欠点

前述のとおり、クエリを送るメソッドにはGETとPOST2つの種類があります。GETメソッドは、特定の性質を持ったデータを取得したいときに利用されます。POSTメソッドは、データの新規作成や更新のために利用されます。

GETとPOSTにはそれぞれ利点と欠点があります。2つのメソッドの最大の違いは、クエリがURLの一部として残るかどうか、ということです。どのような目的でリクエストを送るかによって、2つのメソッドを使い分けることになります。

GETメソッドではクエリの内容がURLに記載されます。このため、クエリの内容をURLの文字列として保存できます。

私たちが最もよく目にするGETメソッドのリクエストは、検索エンジンの検索結果です。以下はGoogleで「Python」という言葉を検索したときのURLです(一部のクエリを省略しています)。

:::url
https://www.google.co.jp/#q=Python

クエリとして検索語そのものが埋め込まれているのが分かります。リクエストに必要な情報がすべてURLに埋め込まれているので、この文字列をリンクとしてHTMLに埋め込んだり,メールなどで誰かに知らせたりできます。

検索結果がたくさんある場合に「次の結果を表示」というようなリンクが表示されます。このようなリンクをページネーションと呼びます。検索エンジンのページネーションでは、クエリがリンクに埋め込まれています。GETメソッドでクエリを発行して、同じ検索語の別の結果を表示するリンクを作っているわけです。また、この文字列をそのままメールに貼り付けることもできます。

一方、POSTメソッドではクエリがリクエストの中に見えないように埋め込まれてしまいます。このため、POSTリクエスト後のURLをページネーションのリンクとしてそのまま利用することはできませんし、メールに貼り付けても正しい結果が得られません。

GETリクエストにも欠点があります。URLには長さの制限があるため、大きなサイズのクエリが送信できないのです。URLのサイズ制限はWebサーバによって異なりますが、1キロバイト(1024)からせいぜい数キロバイトほどです。このため、数十キロバイトから数メガバイトもあるような大きなサイズのデータをGETメソッドで送信することはできません。

大きなサイズのデータを送りたいときはPOSTメソッドを使うことになります。POSTメソッドで送るデータのサイズには事実上制限がありません。POSTメソッドを使うと、長い文字列や文章だけでなく、画像、動画データをWebサーバやWebアプリケーションに送ることができます。

また、POSTメソッドは外部に見せたくないデータを隠す意味でも使われます。POSTメソッドではクエリのデータが表面上には見えないようにWeb サーバに送られます。パスワードのような文字列を、URLのように目に触れ る場所に出さずにWebサーバに送信する、という目的で利用されることがあ ります。

なお,GET,POSTの他にPUTやDELETEというメソッドもありますが,ここでは詳しく解説しません。

みんなのブロックチェーン

ブロックチェーンの入門書を書きました。暗号やハッシュなどを含め,基礎からブロックチェーンの仕組みを学べる書籍です。いろんな方に「新しい技術を学ぶことの楽しさ」を感じ取ってくれたら著者として嬉しいです:-)。お金は技術的にどのように定義されるのか。

2014-09-03 15:00