web-dev-qa-db-ja.com

ページの更新時にフォームの再送信を防ぐ方法(F5 / CTRL + R)

SQLテーブルにテキストを送信する簡単なフォームがあります。問題は、ユーザーがテキストを送信した後、ページを更新でき、フォームに再度入力せずにデータが再度送信されることです。テキストが送信された後、ユーザーを別のページにリダイレクトすることはできますが、ユーザーが同じページに留まるようにします。

各ユーザーに一意のセッションIDを与え、それを別の値と比較して、私が抱えている問題を解決したことを読んだことを覚えていますが、それがどこにあるか忘れていました。

102
user701510

Post/Redirect/Getパターンを使用します。 http://en.wikipedia.org/wiki/Post/Redirect/Get

私のWebサイトでは、メッセージをCookieまたはセッションに保存し、投稿後にリダイレクトし、Cookie /セッションを読み取り、そのセッションまたはCookie変数の値をクリアします。

77
Keverw

また、JavaScriptのアプローチwindow.history.replaceStateを使用して、更新および戻るボタンでの再送信を防ぐことができることを指摘したいと思います。

<script>
    if ( window.history.replaceState ) {
        window.history.replaceState( null, null, window.location.href );
    }
</script>

ここで概念実証: https://dtbaker.net/files/prevent-post-resubmit.php

Post/Redirect/Getアプローチを引き続きお勧めしますが、これは新しいJSソリューションです。

54
dtbaker

実際にこれを処理するには、リダイレクト後取得パターンを使用する必要がありますが、何らかの理由でPRGが実行できない位置になった場合(たとえば、フォーム自体がインクルード内にあり、リダイレクトを防止している場合)、リクエストパラメーターの一部をハッシュできますコンテンツに基づいて文字列を作成し、まだ送信していないことを確認します。

//create digest of the form submission:

    $messageIdent = md5($_POST['name'] . $_POST['email'] . $_POST['phone'] . $_POST['comment']);

//and check it against the stored value:

    $sessionMessageIdent = isset($_SESSION['messageIdent'])?$_SESSION['messageIdent']:'';

    if($messageIdent!=$sessionMessageIdent){//if its different:          
        //save the session var:
            $_SESSION['messageIdent'] = $messageIdent;
        //and...
            do_your_thang();
    } else {
        //you've sent this already!
    }
16
Moob

セッション変数を介してフォームの再送信を許可できます

まず、テキストボックスにRand()を設定し、フォームページに$ _SESSION ['Rand']を設定する必要があります

<form action="" method="post">
  <?php
   $Rand=rand();
   $_SESSION['Rand']=$Rand;
  ?>
 <input type="hidden" value="<?php echo $Rand; ?>" name="randcheck" />
   Your Form's Other Field 
 <input type="submit" name="submitbtn" value="submit" />
</form>

テキストボックス$ _POST ['randcheck']の値で$ _SESSION ['Rand']をチェックした後

         if(isset($_POST['submitbtn']) && $_POST['randcheck']==$_SESSION['Rand'])
          {
         //Your Code here
         }
13
Savoo

フォームが処理されると、別のページにリダイレクトされます。

... process complete....
header('Location: thankyou.php');

同じページにリダイレクトすることもできます。

コメントのようなことをしていて、ユーザーが同じページに留まるようにしたい場合は、Ajaxを使用してフォーム送信を処理できます。

12
Ibu
  1. ヘッダーを使用してページをリダイレクトします。

    header("Location:your_page.php");同じページまたは異なるページにリダイレクトできます。

  2. データベースに挿入した後、$ _ POSTの設定を解除します。

    unset($_POST);

11
Prasanth Bendra

このjavascript行を使用して、フォームが送信されると、更新時にフォームの再送信を要求するポップアップをブロックします。

if ( window.history.replaceState ) {
  window.history.replaceState( null, null, window.location.href );
}

この行をファイルのフッターに配置して、魔法を見てください

10
Mo'men Mohamed

次の回避策を見つけました。 POST オブジェクトを操作することにより、historyリクエストの処理後にリダイレクトをエスケープできます。

これでHTMLフォームができました:

<form method=POST action='/process.php'>
 <input type=submit value=OK>
</form>

サーバーでこのフォームを処理するとき、次のようにLocationヘッダーを設定することにより、ユーザーを/the/result/pageにリダイレクトする代わりに:

$cat process.php
<?php 
     process POST data here
     ... 
     header('Location: /the/result/page');
     exit();
?>

enter image description here

POSTedデータを処理した後、小さな<script>と結果/the/result/pageをレンダリングします

<?php 
     process POST data here
     render the <script>         // see below
     render `/the/result/page`   // OK
?>

レンダリングする必要のある<script>

<script>
    window.onload = function() {
        history.replaceState("", "", "/the/result/page");
    }
</script>

結果は次のとおりです。

enter image description here

ご覧のとおり、フォームデータはPOSTedからprocess.phpスクリプトです。
このスクリプトPOSTedデータを処理し、/the/result/pageを一度にレンダリングします

  1. リダイレクトなし
  2. ページを更新するときにrePOSTデータがありません(F5)
  3. ブラウザの履歴から前/次のページに移動すると、rePOSTはありません

UPD

別の解決策として、 機能リクエストMozilla FireFoxチームが、ユーザーがNextPageヘッダーのように機能するLocationヘッダーを設定できるようにしますpost/redirect/getパターンを廃止します。

要するに。サーバープロセスがPOSTデータを正常に形成すると、次のようになります。

  1. NextPageの代わりにLocationヘッダーをセットアップします
  2. post/redirect/getパターンのPOST要求に対してレンダリングするように、GETフォームデータを処理した結果をレンダリングします

ブラウザは、NextPageヘッダーを見ると順番に:

  1. NextPage値でwindow.locationを調整します
  2. ユーザーがページを更新すると、ブラウザはGETリクエストをreNextPageフォームデータの代わりにPOSTにネゴシエートします

実装すればこれは優れていると思いますか? =)

7
Eugen Konkov

非常に確実な方法は、一意のIDを投稿に実装し、

<input type='hidden' name='post_id' value='".createPassword(64)."'>

次に、コードでこれを行います:

if( ($_SESSION['post_id'] != $_POST['post_id']) )
{
    $_SESSION['post_id'] = $_POST['post_id'];
    //do post stuff
} else {
    //normal display
}

function createPassword($length)
{
    $chars = "abcdefghijkmnopqrstuvwxyz023456789";
    srand((double)microtime()*1000000);
    $i = 0;
    $pass = '' ;

    while ($i <= ($length - 1)) {
        $num = Rand() % 33;
        $tmp = substr($chars, $num, 1);
        $pass = $pass . $tmp;
        $i++;
    }
    return $pass;
}
7
Absolut

Moobの投稿の洗練されたバージョン。 POSTのハッシュを作成し、セッションCookieとして保存し、セッションごとにハッシュを比較します。

// Optionally Disable browser caching on "Back"
header( 'Cache-Control: no-store, no-cache, must-revalidate' );
header( 'Expires: Sun, 1 Jan 2000 12:00:00 GMT' );
header( 'Last-Modified: ' . gmdate('D, d M Y H:i:s') . 'GMT' );

$post_hash = md5( json_encode( $_POST ) );

if( session_start() )
{
    $post_resubmitted = isset( $_SESSION[ 'post_hash' ] ) && $_SESSION[ 'post_hash' ] == $post_hash;
    $_SESSION[ 'post_hash' ] = $post_hash;
    session_write_close();
}
else
{
    $post_resubmitted = false;
}

if ( $post_resubmitted ) {
  // POST was resubmitted
}
else
{
  // POST was submitted normally
}
3
skibulk

フォームデータを使用した後、同じページにリダイレクトするだけで機能します。私はそれを試しました。

header('location:yourpage.php');
3
krsoni

データベースに挿入した後、unset()メソッドを呼び出してデータをクリアします。

nset($ _ POST);

更新データの挿入を防ぐには、レコードの挿入後に同じページまたは別のページにページをリダイレクトします。

header( 'Location:'。$ _ SERVER ['PHP_SELF']);

2
Thinesh

リダイレクトなしでphpフォームの再送信を防ぐ方法。 (session_startの後の)$ _SESSIONと$ _POSTフォームを使用している場合、次のようなことができます。

if ( !empty($_SESSION['act']) && !empty($_POST['act']) && $_POST['act'] == $_SESSION['act'] ) {
  // do your stuff, save data into database, etc
}

あなたのhtmlフォームにこれを入れてください:

<input type="hidden" id="act" name="act" value="<?php echo ( empty($_POST['act']) || $_POST['act']==2 )? 1 : 2; ?>">
<?php
if ( $_POST['act'] == $_SESSION['act'] ){
    if ( empty( $_SESSION['act'] ) || $_SESSION['act'] == 2 ){
        $_SESSION['act'] = 1;
    } else {
        $_SESSION['act'] = 2;
    }
}
?>

そのため、フォームが送信されるたびに、新しいアクトが生成され、セッションに保存され、アクト後のアクトと比較されます。

Ps:Getフォームを使用している場合、すべてのPOSTをGETで簡単に変更できます。

2

基本的に、そのページからリダイレクトする必要がありますが、インターネットの速度が遅い場合でも問題が発生する可能性があります(サーバーサイドからヘッダーをリダイレクト)

基本的なシナリオの例:

送信ボタンを2回クリックします

解決する方法

  • クライアント側

    • クライアントがボタンをクリックしたら送信ボタンを無効にする
    • Jqueryを使用している場合: Jquery.one
    • PRGパターン
  • サーバ側

    • 差別化ベースのハッシュタイムスタンプ/リクエスト送信時のタイムスタンプの使用。
    • リクエストトークンを使用します。メインがロードされたら、一時的なリクエストtockenを割り当てます。
2
ZenithS

Keverw answerのPost/Redirect/Getパターンを使用するのは良い考えです。しかし、あなたはあなたのページにとどまることができません(そして、これはあなたが求めていたものだと思いますか?)さらに、時々 fail

サーバーの遅延のために最初の送信が完了する前にWebユーザーが更新され、特定のユーザーエージェントでHTTP POST要求が重複する場合。

別のオプションは、次のようにテキストをSQLデータベースに書き込む必要がある場合にセッションに保存することです。

if($_SERVER['REQUEST_METHOD'] != 'POST')
{
  $_SESSION['writeSQL'] = true;
}
else
{
  if(isset($_SESSION['writeSQL']) && $_SESSION['writeSQL'])
  {
    $_SESSION['writeSQL'] = false;

    /* save $_POST values into SQL */
  }
}
0
Adam

他の人が言ったように、post/redirect/getを使用することはできません。しかし同時に、サーバー側でやりたいことを行うのは非常に簡単です。

POSTページでは、単にユーザー入力を検証しますが、操作は行わず、代わりにSESSION配列にコピーします。次に、メインの送信ページに再度リダイレクトします。メインの送信ページは、使用しているSESSION配列が存在するかどうかを確認することから始まります。存在する場合は、ローカル配列にコピーして設定を解除します。そこから行動することができます。

このようにして、すべての主要な作業を一度だけ実行し、やりたいことを達成します。

0
Stu

その後、巨大なプロジェクトでの再提出を防ぐための解決策を探しました。コードは$ _GETと$ _POSTで非常に機能し、予期しないバグのリスクなしにフォーム要素の動作を変更することはできません。だから、ここに私のコードがあります:

<!-- language: lang-php -->
<?php

// Very top of your code:

// Start session:
session_start();

// If Post Form Data send and no File Upload
if ( empty( $_FILES ) && ! empty( $_POST ) ) {
    // Store Post Form Data in Session Variable
    $_SESSION["POST"] = $_POST;
    // Reload Page if there were no outputs
    if ( ! headers_sent() ) {
        // Build URL to reload with GET Parameters
        // Change https to http if your site has no ssl
        $location = "https://" . $_SERVER['HTTP_Host'] . $_SERVER['REQUEST_URI'];
        // Reload Page
        header( "location: " . $location, true, 303 );
        // Stop any further progress
        die();
    }
}

// Rebuilt POST Form Data from Session Variable
if ( isset( $_SESSION["POST"] ) ) {
    $_POST = $_SESSION["POST"];
    // Tell PHP that POST is sent
    $_SERVER['REQUEST_METHOD'] = 'POST';
}

// Your code:
?><html>
    <head>
        <title>GET/POST Resubmit</title>
    </head>
    <body>

    <h1>Forms:</h1>
    <h2>GET Form:</h2>
    <form action="index.php" method="get">
        <input type="text" id="text_get" value="test text get" name="text_get"/>
        <input type="submit" value="submit">
    </form>
    <h2>POST Form:</h2>
    <form action="index.php" method="post">
        <input type="text" id="text_post" value="test text post" name="text_post"/>
        <input type="submit" value="submit">
    </form>
    <h2>POST Form with GET action:</h2>
    <form action="index.php?text_get2=getwithpost" method="post">
        <input type="text" id="text_post2" value="test text get post" name="text_post2"/>
        <input type="submit" value="submit">
    </form>
    <h2>File Upload Form:</h2>
    <form action="index.php" method="post" enctype="multipart/form-data">
        <input type="file" id="file" name="file">
        <input type="submit" value="submit">
    </form>

    <h1>Results:</h1>
    <h2>GET Form Result:</h2>
    <p>text_get: <?php echo $_GET["text_get"]; ?></p>
    <h2>POST Form Result:</h2>
    <p>text_post: <?php echo $_POST["text_post"]; ?></p>
    <h2>POST Form with GET Result:</h2>
    <p>text_get2: <?php echo $_GET["text_get2"]; ?></p>
    <p>text_post2: <?php echo $_POST["text_post2"]; ?></p>
    <h2>File Upload:</h2>
    <p>file:
    <pre><?php if ( ! empty( $_FILES ) ) {
            echo print_r( $_FILES, true );
        } ?></pre>
    </p>
    <p></p>
    </body>
    </html><?php
// Very Bottom of your code:
// Kill Post Form Data Session Variable, so User can reload the Page without sending post data twice
unset( $_SESSION["POST"] );

$ _GETではなく、$ _ POSTの再送信を回避するためにのみ機能します。しかし、これは私が必要とする動作です。再送信の問題は、ファイルのアップロードでは機能しません!

0
Andreas Rex