Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationEventAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
177
Help us understand the problem. What are the problem?

More than 1 year has passed since last update.

@furi

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

はじめに

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

終わりに

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

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

参考

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
177
Help us understand the problem. What are the problem?