前回のおさらい
前回は、さくらのレンタルサーバーで最安プランであるライトを契約し、CGIしか使えない所をBottleという1ファイルで実装されているWebフレームワークを用いて、SQLiteと組み合わせて動的Webページを実現しました。
よくあるTODOアプリとか、個人の簡易ブログ程度には今ある手持ちの武器で出来そうですが、もう少し本格的なWebアプリになるように主にBottleの機能を試していきたいと思います。
これはBottleの機能をCGIのケースでも正常に使えるかどうかということの確認でもあります。
目次
基本的には公式ドキュメントに掲載されているサンプルを用いて確認していきます。結論から先に載せておきますと、本稿における機能は全てOKでした。
Cookieを操作してみる
cookieを操作できるようになると、リクエストを跨いて情報を連携することが可能になります。少量のデータであればcookie自体に含めてしまえば良いですが、大きなデータの場合はDBへ保管しておいて、それを引き出すキーをcookieに含めるとかが良くある手ですね。
main.cgi
#!/usr/local/bin/python # coding=utf-8 import sqlite3 from bottle import route, run, request, response # # init # f = open('test.log', "w") f.write("start") dbname = 'main.db' conn = sqlite3.connect(dbname) cur = conn.cursor() # # function # def select_db(): # データ検索 cur.execute('SELECT * FROM items') name = None # 取得したデータはカーソルの中に入る for row in cur: name = row[1] return name # # web # @route('/hello') def hello(): return "Hello World!" @route('/select') def select(): name = select_db() return name @route('/counter') def counter(): count = int( request.cookies.get('counter', '0') ) count += 1 response.set_cookie('counter', str(count)) return 'You visited this page %d times' % count # # server # # run(host='localhost', port=8080, reloader=True, debug=True) run(server="cgi") # # end # conn.close() # DBとの接続を閉じる(必須) f.write("end") f.close()
まず増えたのはimport文のrequest, responseです。そして、counter()を追加しました。行っていることは、request.cookies.get()で”counter”の値を取得していますが、値が取れなかったときには”0″となるようにしています。cookieに保管出来るのは文字列になるので、それをint()で整数に変換しています。
カウント値に+1した上で、response.set_cookie()でレスポンスのcookieに値をセットしています。画面には、このページを訪問した回数が表示されています。
何度かリロードした後なので答えが1ではありませんが、実行例は以下の通りです。
実行例
補足
掲載しているPythonプログラム上にtest.logなるテキストファイルへの出力が見られるが、これはレンタルサーバー上で問題が起こった場合にその問題判別用に出力できるように構えているものです。実際には、同時に複数リクエストが到来した場合の考慮がないので、単なるテスト用と捉えていただければと思います。
GETリクエストのクエリ文字列を取得する
main.cgi
#!/usr/local/bin/python # coding=utf-8 import sqlite3 from bottle import route, run, request, response, template # # init # f = open('test.log', "w") f.write("start") dbname = 'main.db' conn = sqlite3.connect(dbname) cur = conn.cursor() # # function # def select_db(): # データ検索 cur.execute('SELECT * FROM items') name = None # 取得したデータはカーソルの中に入る for row in cur: name = row[1] return name # # web # @route('/hello') def hello(): return "Hello World!" @route('/select') def select(): name = select_db() return name @route('/counter') def counter(): count = int( request.cookies.get('counter', '0') ) count += 1 response.set_cookie('counter', str(count)) return 'You visited this page %d times' % count @route('/forum') def display_forum(): forum_id = request.query.id page = request.query.page or '1' return template('Forum ID: {{id}} (page {{page}})', id=forum_id, page=page) # # server # # run(host='localhost', port=8080, reloader=True, debug=True) run(server="cgi") # # end # conn.close() # DBとの接続を閉じる(必須) f.write("end") f.close()
import文にtemplateが必要です。/forumに対してidとpageのクエリ文字列が想定されています。いずれもrequest.query.xxxという形で取得可能です。
実行例
HTTPヘッダ(リクエストヘッダ)を読み取る
main.cgi
#!/usr/local/bin/python # coding=utf-8 import sqlite3 from bottle import route, run, request, response # # init # f = open('test.log', "w") f.write("start") dbname = 'main.db' conn = sqlite3.connect(dbname) cur = conn.cursor() # # function # def select_db(): # データ検索 cur.execute('SELECT * FROM items') name = None # 取得したデータはカーソルの中に入る for row in cur: name = row[1] return name # # web # @route('/hello') def hello(): return "Hello World!" @route('/select') def select(): name = select_db() return name @route('/counter') def counter(): count = int( request.cookies.get('counter', '0') ) count += 1 response.set_cookie('counter', str(count)) return 'You visited this page %d times' % count @route('/forum') def display_forum(): forum_id = request.query.id page = request.query.page or '1' return template('Forum ID: {{id}} (page {{page}})', id=forum_id, page=page) @route('/get_header') def get_header(): headers = "" for header in request.headers: headers += header + ": " + request.get_header(header) + "<br>" return headers # # server # # run(host='localhost', port=8080, reloader=True, debug=True) run(server="cgi") # # end # conn.close() # DBとの接続を閉じる(必須) f.write("end") f.close()
get_header()において、request.headersに全てのヘッダーのキーが格納されているのでforループで回しつつ、request.get_header()によって値を取得し、結果に含めるという処理を行っています。
実行例
HTTPヘッダ(レスポンスヘッダ)を操作する
main.cgi
#!/usr/local/bin/python # coding=utf-8 import sqlite3 from bottle import route, run, request, response # # init # f = open('test.log', "w") f.write("start") dbname = 'main.db' conn = sqlite3.connect(dbname) cur = conn.cursor() # # function # def select_db(): # データ検索 cur.execute('SELECT * FROM items') name = None # 取得したデータはカーソルの中に入る for row in cur: name = row[1] return name # # web # @route('/hello') def hello(): return "Hello World!" @route('/select') def select(): name = select_db() return name @route('/counter') def counter(): count = int( request.cookies.get('counter', '0') ) count += 1 response.set_cookie('counter', str(count)) return 'You visited this page %d times' % count @route('/forum') def display_forum(): forum_id = request.query.id page = request.query.page or '1' return template('Forum ID: {{id}} (page {{page}})', id=forum_id, page=page) @route('/get_header') def get_header(): headers = "" for header in request.headers: headers += header + ": " + request.get_header(header) + "<br>" return headers @route('/set_header') def set_header(): lang = request.query.lang response.set_header('Content-Language', lang) return 'You specified lang: %s' % lang # # server # # run(host='localhost', port=8080, reloader=True, debug=True) run(server="cgi") # # end # conn.close() # DBとの接続を閉じる(必須) f.write("end") f.close()
set_header()ではクエリ文字列langで指定された文字列をContent-Languageで指定して返すという処理を行っています。
実行例
set_header()が動作していないケース
set_header()でenが指定されたケース
POSTリクエストのPOSTデータを取得する
main.cgi
#!/usr/local/bin/python # coding=utf-8 import sqlite3 from bottle import route, run, request, response # # init # f = open('test.log', "w") f.write("start") dbname = 'main.db' conn = sqlite3.connect(dbname) cur = conn.cursor() # # function # def select_db(): # データ検索 cur.execute('SELECT * FROM items') name = None # 取得したデータはカーソルの中に入る for row in cur: name = row[1] return name # # web # @route('/hello') def hello(): return "Hello World!" @route('/select') def select(): name = select_db() return name @route('/counter') def counter(): count = int( request.cookies.get('counter', '0') ) count += 1 response.set_cookie('counter', str(count)) return 'You visited this page %d times' % count @route('/forum') def display_forum(): forum_id = request.query.id page = request.query.page or '1' return template('Forum ID: {{id}} (page {{page}})', id=forum_id, page=page) @route('/get_header') def get_header(): headers = "" for header in request.headers: headers += header + ": " + request.get_header(header) + "<br>" return headers @route('/set_header') def set_header(): lang = request.query.lang response.set_header('Content-Language', lang) return 'You specified lang: %s' % lang @route('/login') def login(): return ''' <form action="/main.cgi/login" method="post"> Username: <input name="username" type="text" /> Password: <input name="password" type="password" /> <input value="Login" type="submit" /> </form> ''' @route('/login', method='POST') def do_login(): username = request.forms.get('username') password = request.forms.get('password') if username == password: return "<p>Your login information was correct.</p>" else: return "<p>Login failed.</p>" # # server # # run(host='localhost', port=8080, reloader=True, debug=True) run(server="cgi") # # end # conn.close() # DBとの接続を閉じる(必須) f.write("end") f.close()
login()はGETでdo_login()はPOSTで同じurlの/loginとなるようになっています。公式ドキュメントとの違いは、login()側のHTML内でaction="/login"
となっていたところをaction="/main.cgi/login"
としている点、それからdo_login()においてcheck_login()が登場するがその実装がないため、2つの値の比較にしている点の2点です。
本題は、request.forms.get()でPOSTデータの取り出しが可能となっています。
実行例
まとめ
以下の5点の確認が終えられ、全て正常に動作することが確認されました。
- Cookieを操作する
- GETリクエストのクエリ文字列を取得する
- HTTPヘッダ(リクエストヘッダ)を読み取る
- HTTPヘッダ(レスポンスヘッダ)を操作する
- POSTリクエストのPOSTデータを取得する