Posted at

Post/Redirect/Get (PRG) パターン

More than 3 years have passed since last update.


はじめに

最近、Flaskで個人用のWebアプリを開発していて、

Post/Redirect/Get パターン

というものを知ったのでメモ。


Post/Redirect/Get (PRG) とは

一言で言うと、フォームデータの二重送信を防止する手法の一つです。

フォームデータをPOSTした後、リダイレクトせずに画面を表示し、ブラウザを再読み込みすると、もう一度POSTしようとしてしまいます。

しかし、POST後にリダイレクトして画面を表示するようにすれば、ブラウザを再読み込みしても送信されるのはGETリクエストなので、フォームデータの二重送信が防げるというものです。

もちろん、リダイレクトしなくてもこのような二重送信を防ぐ方法はありますが、その場合、ブラウザの再読み込み時に以下のようなダイアログが表示されてしまいます。

リダイレクトすればこのようなダイアログは表示されないため、ユーザビリティも良くなるわけですね。


サンプルコード(リダイレクトなし)

せっかくなので、Flaskで簡単なサンプルを書いてみます。

フォームで name を送信し、次の画面で表示するというものです。

まずは、リダイレクトしない場合です。


app.py

from flask import Flask, render_template, request

app = Flask(__name__)

@app.route('/')
def index():
return render_template('index.html')

@app.route('/post', methods=['POST'])
def post():
name = request.form['name']
return render_template('hello.html', name=name)

if __name__ == '__main__':
app.run()



templates/index.html

<!DOCTYPE html>

<html lang="ja">
<head>
<meta charset="UTF-8">
<title>PRG Sample</title>
</head>
<body>
<h1>What's your name?</h1>
<form action="{{ url_for('post') }}" method="post">
<input type="text" name='name' placeholder="name"/>
<button type="submit">Submit</button>
</form>
</body>
</html>


templates/hello.html

<!DOCTYPE html>

<html lang="ja">
<head>
<meta charset="UTF-8">
<title>PRG Sample</title>
</head>
<body>
<h1>Hello, {{ name }} !</h1>
</body>
</html>

実行してみます。

$ python app.py

* Running on http://127.0.0.1:5000/

ブラウザでアクセスし、名前を入力して画面遷移します。



ここまでは問題ありませんが、ブラウザの再読み込みを行うと、フォームを再送信してもよいかどうかの確認ダイアログが表示されてしまいました。


サンプルコード(リダイレクトあり)

次に、リダイレクトを行う場合です。


app.py

from flask import Flask, render_template, request, session, redirect, url_for

app = Flask(__name__)
app.secret_key = 'seacret_key'

@app.route('/')
def index():
return render_template('index.html')

@app.route('/post', methods=['POST'])
def post():
session['name'] = request.form['name']
return redirect(url_for('hello'))

@app.route('/hello')
def hello():
if 'name' not in session:
return redirect(url_for('index'))
return render_template('hello.html', name=session['name'])

if __name__ == '__main__':
app.run()


templates/index.htmltemplates/hello.html は同じです。

これでフォームを送信後、ブラウザの再読み込みをしても、ダイアログは表示されなくなります。

リクエスト内容を確認しても、GETリクエストであることが確認できます。


終わりに

結構昔からある手法のようで、私が知ったのが今更といった感じですね…。

とはいえ、現在私が利用している某サービスで、ログイン後にブラウザの再読み込みをしてみたら、フォームを再送信しようとしているものがあったので、知っておいたほうが良い手法だと思いました。


参考