179
187

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

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

Last updated at Posted at 2014-12-09

はじめに

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

Post/Redirect/Get パターン

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

Post/Redirect/Get (PRG) とは

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

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

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

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

dialog-only.png

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

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

せっかくなので、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/

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

index.png
hello.png

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

dialog.png

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

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

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リクエストであることが確認できます。

終わりに

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

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

参考

179
187
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
179
187

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?