1ページ1ファイル的なウェブページでフォームの送信を取り扱う時に知っておくと便利な手法の紹介です。
よくユーザーにフォームで何かを送信をしてもらい、送信された内容を保存するという機能があります。ブログ記事の投稿、コメントの投稿、アンケートの回答など様々です。この様なフォームの送信ですが、手なりに作るとブラウザで送信完了ページを更新した際に、再度フォームが同じ内容で送信されてしまうという問題があります。これは例えば次の様なつくりのプログラムで起きます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | <?php if ( $_SERVER [ 'REQUEST_METHOD' ] === 'GET' ) { // GET でアクセスされた時は送信用フォームを表示 echo <<<HTML <html lang= "ja" > <head> <meta charset= "utf-8" > <title>フォーム送信ページ</title> </head> <body> <form action= "" method= "post" > <label>名前:<input type= "text" name= "name" ></label> <button type= "submit" >送信</button> </form> </body> </html> HTML; } elseif ( $_SERVER [ 'REQUEST_METHOD' ] === 'POST' ) { // POST でアクセスされた時は送信完了メッセージを表示 // フォームで送られてきたデータを保存など何かしら処理 echo <<<HTML <html lang= "ja" > <head> <meta charset= "utf-8" > <title>完了ページ</title> </head> <body> データが保存されました。 </body> </html> HTML; } |
このプログラムを実行すると次の動画の様になります。フォームをPOSTで送信した後、完了ページでブラウザの更新ボタンを押して更新を実行するとデータが再びPOSTで送信されました。これはしばしば二重に〇〇されている、と報告をいただく不具合を引き起こします。
ブラウザの更新による二重送信の防止策はいくつかあります。例えば axios 等によりフォームのあるページ上の非同期処理として送信することでフォームの送信とブラウザの画面遷移を独立させる、フォームが開かれた時にトークンをブラウザに渡して送信時にトークンも送らせることで既に送られたことのあるフォームか否かをチェックする、などです。この記事ではその一つであるリダイレクトを用いた手法を紹介します。
やることはフォームが送られた時のリクエストの終了時にリダイレクトレスポンスを返すだけです。これをするとブラウザの更新はリダイレクト時の処理を再実行するため、リダイレクト前の処理であるフォームの送信を行いません。具体的には次の様にします。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | <?php if ( $_SERVER [ 'REQUEST_METHOD' ] === 'GET' && ! isset( $_GET [ 'complete' ])) { // GET で初期アクセスされた時は送信用フォームを表示 echo <<<HTML <html lang= "ja" > <head> <meta charset= "utf-8" > <title>フォーム送信ページ</title> </head> <body> <form action= "" method= "post" > <label>名前:<input type= "text" name= "name" ></label> <button type= "submit" >送信</button> </form> </body> </html> HTML; } elseif ( $_SERVER [ 'REQUEST_METHOD' ] === 'POST' ) { // フォームで送られてきたデータを保存など何かしら処理 // POST でアクセスされた時はリダイレクトレスポンスを返す header( 'Location: /tmp.php?complete' , true, 301); } elseif ( $_SERVER [ 'REQUEST_METHOD' ] === 'GET' && isset( $_GET [ 'complete' ])) { // POST でアクセスされた後のリダイレクト先。 // リダイレクト先の画面で完了ページを表示する echo <<<HTML <html lang= "ja" > <head> <meta charset= "utf-8" > <title>完了ページ</title> </head> <body> データが保存されました。 </body> </html> HTML; } |
フォームを受ける保存処理と表示処理を別のURLとメソッドの組み合わせに対応する様にし、保存処理の最後には表示処理にリダイレクトする様にします。こうすると次の様にブラウザの更新による再送信を防げます。
この方法は WordPress をはじめとした多くの場所で用いられている信頼性の比較的高い方法でもあります。