[Servlet]リダイレクト&ブラウザのリロード対策

2022/06/24に公開


◆はじめに

こういうホップアップが、
ページのリロード時に出てきたりしますよね。
今回は、こちらのホップアップが出ないようにしたいと思います。



リロード対策をしておくと、
フォームの再送信でおこる重複など防ぐことができるので、
実装しておくといいでしょう。


本記事では、「リダイレクト」と「リロード対策」の2つをご紹介します。
リロード対策を行うには、
まずは、リダイレクトについて知っておく必要があるためです。


◆リダイレクトとは

リダイレクトとは、
ページの切り替え方法の一つです。


ウェブブラウザに、移動先のURLをレスポンスで送ります。
すると、ウェブブラウザが自動的にURLの移動リクエストを返すので、
ページを移動します。


なので、例えば、GoogleのHPなど
別のサーバー上のページを指定することもできます。
【リダイレクト】
response.sendRedirect("移動先のURL");
リダイレクト以外にも、ページの切り替え方法があります。
それが、フォワードです。
こちらは、サーバー側で作成したページを返却します。
なので、同じサーバー上のページしか指定はできません。
【フォワード】
RequestDispatcher dispatcher = request.getRequestDispatcher("転送先のページ");
dispatcher.forward(req, resp);


◆リロード対策とは

リロード対策をしていない場合は、
ページを更新した時や戻るボタンを押した際にも、
下記のホップアップが表示されます。

これは、画面移動するたびに、Postが投げられていることで起こります。
そこで、「PRGパターン」を使用します。
PRGパターンとは、Post Redirect Getの頭文字を繋いだ言葉です。


処理の流れとしては、
①POSTメソッドでリクエストを受け取けとる
②のメソッドの処理の終わりにRedirectする
③処理後の結果はGetメソッドで呼び出せる画面に返す



また、このPRGパターンで実装することで、
例えば、オンライン注文のばあいなどで、
再度Postされて、2重注文が起こることを防ぐことができます。


◆前提/環境など

本記事は、以下の記事の「InputFunction」プロジェクトをベースに作成します。
Echo機能のServletの処理時間を計測するFilterを実装しています。
また、Sessionで結果を保持するようになっています。
以下の記事の内容を踏まえた前提で、記事は書いていますのでご了承ください。

https://zenn.dev/nakohama/articles/9f08b12905196e
https://zenn.dev/nakohama/articles/5ba42253eddef1
https://zenn.dev/nakohama/articles/40f4219a212e00


仕様は以下です。
<Echo機能> ・文字列を入力し、送信ボタンを押すと、送信した文字列が画面に表示される。
<Validation機能> ・送信された文字列が空欄の場合は,「この項目は必須入力です。」と文字列が表示される。
<TimeFiletr機能(Listener)> ・Servletの処理時間を計測し、コンソールに表示
<Session機能> ・処理結果をSessionがリセットされるまで保持する


※本記事では、 eclipseは以下のバージョンでの画像を使用しています。
日本語化はしていません。
他のversionでの動作は確認しておりません。


◆手順

それでは、実際に実装してみたいと思います。


リダイレクト先を、Postを受けたページのURLにします。
そして、処理の内容はSessionで保持しておけば、
元のページに戻った時に処理結果が表示されています。

①ソースコード(「EchoServlet.java」の変更点)

コードの変更点としては、
フォワード処理をリダイレクト処理に書き換えているだけです。


コードの全体は以下です。


EchoServlet.java
package input;

import java.io.IOException;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;

  //URLを定義するアノテーション
    @WebServlet(urlPatterns = {"/echo"})  
    
  //Validation機能を持たせたEchoサーブレットクラス
public class EchoServlet extends HttpServlet{
	private static final long serialVersionUID = 1L;
	
		
  //画面からPostされた場合に起動するメインとなるメソッド
	@Override
	protected void doPost(HttpServletRequest request , HttpServletResponse response)throws ServletException,
IOException {
		
	    //取得文字のエンコード設定(文字化け防止)
		request.setCharacterEncoding("UTF-8");
			
			
		//セッションの取得
		HttpSession session = request.getSession();  
				
	//Validation機能
        //テキストボックスのtextを保持したFormの作成
		TextForm SetForm = toSetForm(request);
		
		//toValidationFormをバリデーションに通す。
		//引っかかった場合はrequestにvalidationMessageをセットする
		setValidationAttribute(session,SetForm);
		
		
	//Echo機能
	    //Echoメッセージのセット
		if(session.getAttribute("validationMessage")  == null) {			
			
	        //バリデーションメッセージがrequestに付与されていない場合、
	        //Formに保持していたtextをrequestにEcho用に付与する
			String EchoMessage = SetForm.getTextMessage();
				
			//取得したセッションのmessageに対してセットする
			session.setAttribute("message",EchoMessage);
		}
		
		//Echo保持されている時の、バリデーションが働いた時,EchoMessageは初期化
	if(session.getAttribute("validationMessage")  != null) {			
			//取得したセッションのmessageに対してセットする
			session.setAttribute("message",null);
		}
	
	//セッションIDの取得
	 String session_id = session.getId();
			
		
		//コンソールに出力
	 System.out.println("セッションID:" + session_id );
	 System.out.println("validationMessage<session>):"+session.getAttribute("validationMessage"));
	 System.out.println("message<session>):"+session.getAttribute("message"));	
		
	//リダイレクト
     //コンテキストルート(InputFunction)の取得
		String contextPath = getServletContext().getContextPath();
		//リダイレクトpathを設定(表示させるページ→echo.jsp)
		String path = contextPath + "/echo.jsp";
		//実際にリダイレクトを行っている関数
		response.sendRedirect(path);
		
//		//RequestDispatcher
//		RequestDispatcher dispatcher = request.getRequestDispatcher("echo.jsp");
//		dispatcher.forward(request, response);	
	}
	
	
	//「セッションのリセット」のリクエストパラメータが渡された場合が押された場合、セッションを無効にする
	@Override
	protected void doGet(HttpServletRequest request , HttpServletResponse response) throws ServletException,IOException {
		Map<String , String[]> parameterMap = request.getParameterMap();
		if(parameterMap.containsKey("reset") == true) {
			//セッションの取得
			HttpSession session = request.getSession();
			//セッションを無効にする
			session.invalidate();
		}
			

	//リダイレクト
        //コンテキストルート(InputFunction)の取得
		String contextPath = getServletContext().getContextPath();
		//リダイレクトpathを設定(表示させるページ→echo.jsp)
		String path = contextPath + "/echo.jsp";
		//実際にリダイレクトを行っている関数
		response.sendRedirect(path);
		
     //PRGパターンの場合,RequestDispatcherは不要
//		RequestDispatcher dispatcher = request.getRequestDispatcher("echo.jsp");
//		dispatcher.forward(request,response);
	}
	
	
	
	//テキストを保持したFormを作成するメソッド
	private TextForm toSetForm(HttpServletRequest request) {
		//バリデーションに通すためのフォームのインスタンス作成
		TextForm SetForm = new TextForm();
		
		//テキストボックスの文字列を取得
		String text = request.getParameter("m");
		
		//テキストボックスが空欄ではない場合
		if(text.isEmpty() == false) {
			//テキストを保持したFormを返す(今回は実装してないがNull以外のバリデーション機能もあるため)
			SetForm.setTextMessage(text);
		}
		return SetForm;
	}
	

	
	//バリデーション用のメソッド
	private void setValidationAttribute(HttpSession session, TextForm toValidationForm) {
			
		//toValidationFormに対してバリデーションを実行
		Map<String,List<String>> validationMessage = validate(session,toValidationForm);
		
		//バリデーションメッセージがセットされている場合、requestにバリデーションメッセージを付与
		if(validationMessage.isEmpty() == false) {
			session.setAttribute("validationMessage", validationMessage);
			return;
		}   
		
		//Sessionで保持されていたバリデーションメッセージの初期化
		if(validationMessage.isEmpty() == true) {
			session.setAttribute("validationMessage", null);
			return;
		}    
	}
	
	
	//validate
	private Map<String , List<String>> validate(HttpSession session,TextForm toSetForm){
		
		ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory();
		Validator validator = validatorFactory.getValidator();
		Set<ConstraintViolation<TextForm>> validationResult = validator.validate(toSetForm);
		
		Map<String , List<String>> ret = new HashMap<String , List<String>>();
		
		for(ConstraintViolation<TextForm> violation : validationResult) {	
			String propertyPath = violation.getPropertyPath().toString();
			List<String> messages = ret.get(propertyPath);
			if(messages == null) {
				messages = new ArrayList<String>();
				ret.put(propertyPath, messages);
			}
			messages.add(violation.getMessage());
		}
		return ret;	
	}
}

②ソースコード(「echo.jsp」の変更点 )

<%-- Echoメッセージの出力--%>
<c:if test="${ not empty message }">
  入力された文字は、「${ message }」です。
</c:if>
以上の箇所で、「RequestScope.message」としてましたが、
Postではなく、リダイレクトにするとGetになるためか使用できなかったため、
「message」にしました。


コードの全体は以下です。
echo.jsp
<%@page language="java" contentType = "text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@page isELIgnored= "false" %>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">

<title>Echo機能+Validation機能+TimeFilter機能(Listener)+Session機能+リロード対策</title>

<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
</head>

<body>
	
 <h3>Echo機能+Validation機能+TimeFilter機能(Listener)+Session機能+リロード対策</h3>
 
   <%-- リセットのリクエストパラメータを渡す--%>
 <a href="echo?reset">セッションのリセット</a>

  <%-- 入力フォーム --%>
 <form action="echo" method="post">
  <br />入力してください : 
  
  <%-- テキストボックス(m) --%>
  <input type="text" name="m" />
  
   <%-- Validationメッセージの出力  --%>
   <%-- var="vm" → c:out で valueにセットする際に使う名称   --%>
   <%-- [' ']には、 MessageForm.javaのテキストボックスのメッセージ保持用のパラメータ名を入れる   --%>
 <c:forEach items="${validationMessage['textMessage']}" var="vm">
   <c:out value="${vm}"/>
 </c:forEach>
 
<br />
 
 <%-- 送信ボタン --%>
  <input type="submit" /> <br />

 </form>
 
 <br />
 
 
  <%-- Echoメッセージの出力--%>
 <c:if test="${ not empty message }">
  入力された文字は、「${ message }」です。
 </c:if>
  
</body>


③動作チェック

・Tomcatなどアプリケーションサーバーを起動
・実装後に以下のURLにアクセス

http://localhost:8080/InputFunction/echo.jsp

画面が表示されますので、
・テキストを打つ

・送信ボタンを押す

・更新ボタンを押す
すると、PRGパターンでリダイレクトされ、
再度、同じURLでページが開き、Sessionで保持された文字が表示されています。

先ほど更新ボタンを押した際に、
下記のホップアップが表示されていなkれば、
リロード対策が成功しています。


◆さいごに

以上です。いかがだったでしょうか。
Sessionの実装はできたでしょうか。
今回のポイントです。


①リロード対策は、PRGパターンを使用する

②フォワードではなく、リダイレクトを使用する

③値の保持が必要な場合は、Sessionを使用する


コードに関しての詳しい説明に関しては、
あまり書いていません。少し、コード内にコメントはつけてます。
あとは、他の参考に載せている書籍やサイトなど調べてみてください。


最後まで、ご覧いただきありがとうございました。


◆参考

本記事は、以下の書籍を大いに参考にしています。
より詳しく知りたい方は、どうぞ。

https://www.amazon.co.jp/dp/4797362596

・関連記事、その他

https://zenn.dev/imah/articles/3d186a6462ecc8

Discussion