KDE BLOG

バイブス

【Node.js】POST後にページリロードすると再度POSTされる問題

2019年はプレイベートでサービスを作りたいので、Node.jsの勉強を始めました。
サーバーサイドプログラミングの経験はほとんどないので、まずは入門書から始めているのですが、そのサンプルアプリケーション(掲示板)の中で問題が発生しました。
その内容は、ググっていると全く同じことを書いていた記事がqiitaにありました(しかも同じ入門書)。

qiita.com

コメントをたどっていくと、どうやらPOST後はリダイレクトさせるPRGパターンというのが定石のようでした。
今回はステータスコード 303 See Other でリダイレクトさせるのが正しいことが分かりました。

HTTPの仕様を決めているRFC 2616を読むと,最も適しているのは303であることがわかります。このステータスコードは「情報は指定した別のURLにあるので,GETでアクセスしろ」というステータスで「POSTでのアクセスに対して別のURLをGETで読み込む」ために用意されたものです。ということで,まさにぴったりです。一方,301と302はというと,301は「恒久的にURLが変更されたこと」を,302は「一時的にURLが変更されたこと」を示すステータスコードですから,目的とはちょっと違っています。
(第28回 フォーム送信とブラウザ・ボタンと使い勝手(前編)~PRGパターンをご存じですか(2ページ目) | 日経 xTECH(クロステック) より)

コードを修正

修正前

// データ受信終了のイベント処理
request.on('end', () => {
  // 省略 
  response.writeHead(200, { 'Content-Type': 'text/html' });
  response.write(/* 省略 */); // indexページを表示させています
  response.end();
});

修正後

// データ受信終了のイベント処理
request.on('end', () => {
  // 省略 
  response.writeHead(303, { 'Location': '/' }); // indexページに303リダイレクト
  response.write(/* 省略 */);
  response.end();
});

これにより、無事リロードしても二重でPOSTされることはなくなりました。

今回は簡易的な掲示板だったのでこれで大丈夫かと思いますが、ECサイトなどで二重注文が発生しては絶対にいけないような場合は、ワンタイムトークンを発行したりセッションIDを使って厳密に判定を行う必要があるようです。

さらにリダイレクトさせる理由として大きなポイントは、

POSTメソッドによってアクセスされたページを再読込すると,ブラウザは「もう一度情報をPOSTしちゃうけどいい?」という以下のようなダイアログを表示してしまうからです」

ということが挙げられていました。
さらに以下のように続きます。

リダイレクトを挟むことで,再読込をしても,単にGETのアクセスが発生するだけの状況を作り,いちいちメッセージが表示されてしまうことを防ぐことができます。
...(略)
PRGパターンというのは,POSTの結果として表示したいページが再読込みされやすい場合に,特に有効だといえます。
...(略)
つまりPRGパターンを利用するかどうかは,そのページが再読み込みされるかどうかが判断ポイントとなりそうですね。

なるほど。。。
サーバーサイドではこのようなことも考える必要があるのですね。大変勉強になったお話でした。

参考