セッション管理のメカニズムを理解するための実験

 


 


少々長いが、次に示すSimpleSessionTestというサーブレットをまず皆さんの開発環境に作成しよう。

package basic_servlets;

 

import java.io.*;

import java.lang.*;

import java.util.*;

import javax.servlet.*;

import javax.servlet.http.*;

/**

 * SimpleSessionTestサーブレット

 * セッションの実習教材その(セッションのメカニズムを知る)

 * 作成日 : (01/06/27 12:55:32)

 * @author: Terry

 */

//public class SimpleSessionTest extends HttpServlet implements SingleThreadModel {

public class SimpleSessionTest extends HttpServlet {

public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

    performTask(request, response);

}

public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

    performTask(request, response);

}

public String getServletInfo() {

    return "SimpleSessionTest, Version 1.0 by Terry";

}

/**

 * このサーブレットはセッション管理のメカニズムの実習に使われる

 * Cookieを受け付けるクライアントではこのページを繰り返し更新するか

 * 「URL再書き込みなし」のハイパーリンクを繰り返しクリックする

 * Cookieを受け付けないクライアントはこのセーブレットがつくるページの一番下の

 * 「URL再書き込み」のボタンやハイパーリンクを繰り返しクリックする

 * セッションを解除するときは「新規セッション」のハイパーリンクをクリックする

 */

public void performTask(HttpServletRequest request, HttpServletResponse response) {

    try

    {

 

        //新規セッション指定のときは存在しているセッションを無効にする

        HttpSession session = request.getSession();

        if ((session.isNew()==false)&&(request.getParameterValues("resetSession")!=null)){

            session.invalidate();

        }

        //到来要求が最初かどうか調べる

        session = request.getSession(true);

        session.setMaxInactiveInterval(30);

        int visitTimes = 0;

        if (session.isNew()){

            //初めて、セッションを作って初期画面をクライアントにかえす

            session.setAttribute("visitTimes", new Integer(1));

            visitTimes = 1;

        }

        else{

            //再度訪問者、訪問回数をインクリメント

            Integer number = (Integer)session.getAttribute("visitTimes");

            visitTimes = number.intValue();

            visitTimes++;

            session.setAttribute("visitTimes", new Integer(visitTimes));

        }

 

        //応答のページの送信(これはセッションの生成後に行わないとHTTP/1.1対応の

        //Tomcat4ではPrintWriterに1行書き出すとすぐコミットされるので例外がスローされる)

        response.setContentType("text/html; charset=Shift_JIS");

        //Tomcatの場合は、以下の1行のヘッダ行を追加することを推奨

        response.setHeader("Cache-Control", "no-cache=\"set-cookie,set-cookie2\"");

        PrintWriter out = response.getWriter();

        out.println("<HTML><HEAD><TITLE>セッション・テスト(SimpleSessionTest)</TITLE>");

//      out.println("<META HTTP-EQUIV=\"cache-control\" CONTENT=\"no-cache\">");    //HTMLからキャッシュ禁止するとき

        out.println("</HEAD><BODY><H1>セッション・テスト(SimpleSessionTest)</H1><PRE>");

        dumpRequest(request, out);                      //要求オブジェクトの内容出力

        out.println();

        out.println("  Your vist(s): " + visitTimes);   //訪問回数の出力

        out.println();

        dumpSession(request, session, out);             //セッションオブジェクトの内容の出力

        out.println("</PRE>");

 

        //フォームによるURL再書き込みのセッションIDの渡し

        out.println("<FORM ACTION=\"./SimpleSessionTest;jsessionid="+session.getId()+"\" METHOD=\"POST\">");

        out.println("<INPUT TYPE=\"hidden\" NAME=\"jsessionid\" VALUE=\"\">");

        out.println("<INPUT TYPE=\"submit\" VALUE=\"URL再書き込み\"></FORM>");

 

        //ハイパーリンクによるURL再書き込みのセッションIDの渡し

        String url = "http://" + request.getServerName() + ":"  + request.getServerPort() + request.getRequestURI();

//      String rewriteUrl = response.encodeURL(url);    //本来ならこれでエンジンがURL再書き込みするか判断する

        out.println("<A HREF=\"" + url + ";jsessionid="+session.getId() + "\">URL再書き込み</A><P>");

       

        //クッキーのみでのセッション維持の実験

        out.println("<A HREF=\"" + url + "\">URL再書き込みなし</A><P>");

 

        //セッションの開放の実験

        out.println("<A HREF=\"" + url + "?resetSession=true\">新規セッション</A><P>");

        out.println("</BODY></HTML>");

 

 

        //SingleThreadModelによる複数の本サーブレットのインスタンスにも対応可能なことの実験

//      Thread.sleep(10000L);

 

        //クライアントに送信(明示的な後始末)

        out.flush();

        out.close();

        response.flushBuffer();

    }

    catch(Throwable theException){

    }

}

 

/**

 * ダンプ処理の実体。

 * 作成日 : (01/05/14 15:35:19)

 * @param request javax.servlet.http.HttpServletRequest

 * @param out java.io.PrintWriter

 */

public void dumpRequest(HttpServletRequest request, PrintWriter out) {

  // HTMLヘッダの出力

  out.println("<HTML><HEAD><TITLE>");

  out.println("要求オブジェクトの情報");

  out.println("</TITLE></HEAD>");

 

  // HTMLボディの出力

  out.println("<BODY><H1>要求オブジェクトの情報</H1><PRE>");

 

  out.println("getAuthType: " + request.getAuthType());

  out.println("getCharacterEncoding: " + request.getCharacterEncoding());

  out.println("getContentLength: " + request.getContentLength());

  out.println("getContentType: " + request.getContentType());

  out.println("getMethod: " + request.getMethod());

  //out.println("getLocale: " + request.getLocale().toString()); //VisualAgeにはまだ組み込まれていない

  out.println("getPathInfo: " + request.getPathInfo());

  out.println("getPathTranslated: " + request.getPathTranslated());

  out.println("getProtocol: " + request.getProtocol());

  out.println("getQueryString: " + request.getQueryString());

  out.println("getRemoteAddr: " + request.getRemoteAddr());

  out.println("getRemoteHost: " + request.getRemoteHost());

  out.println("getRemoteUser: " + request.getRemoteUser());

  out.println("getRequestURI: " + request.getRequestURI());

  out.println("getRequestedSessionId: " + request.getRequestedSessionId());

  out.println("isRequestedSessionIdValid: " + (new Boolean(request.isRequestedSessionIdValid())).toString());

  out.println("isRequestedSessionIdFromCookie: " + (new Boolean(request.isRequestedSessionIdFromCookie())).toString());

  out.println("isRequestedSessionIdFromURL: " + (new Boolean(request.isRequestedSessionIdFromURL())).toString());

  out.println("getScheme: " + request.getScheme());

  //out.println("isSecure: " + (new Boolean(request.isSecure())).toString()); //VisualAgeにはまだ組み込まれていない

  out.println("getServerName: " + request.getServerName());

  out.println("getServerPort: " + request.getServerPort());

  ServletContext context = getServletContext();

  out.println("getRealPath: " + context.getRealPath(request.getRequestURI()));

  out.println("getServletPath: " + request.getServletPath());

  out.println("getContextPath: " + request.getContextPath()); //VisualAgeにはまだ組み込まれていない

 

  out.println();

  out.println("要求パラメタ:");

  Enumeration paramNames = request.getParameterNames();

  while (paramNames.hasMoreElements()) {

    String name = (String) paramNames.nextElement();

    String[] values = request.getParameterValues(name);

    out.println("    " + name + ":");

/*  Tomcatの場合は上の1行は以下の6行のように変更すること

    TomcatはURLエンコードされているパラメタのコードが標準のISO8859_1(Latin-1)であると仮定し

    それを単にStringに(つまりUnicodeで)格納して各サーブレットに引き渡している。

    したがってそのためパラメタ文字列を取得する際にはISO8859_1から自分が期待する

    エンコードへの変換をしなければならない。*/

/*  try{

        String correctName = new String(name.getBytes(8859_1), "Shift_JIS");

        out.println("      " + correctName + ":");

    }catch(UnsupportedEncodingException theException){

        out.println("UnsupportedExceptionの例外が発生");

    }

*/  

    for (int i = 0; i < values.length; i++) {

 

      out.println("      " + values[i]);

/*  同様に上の1行は以下の6行のように変更すること */

/*    try{

          String correctValue = new String(values[i].getBytes(8859_1), "Shift_JIS");

          out.println("      " + correctValue);

      }catch(UnsupportedEncodingException theException){

          out.println("UnsupportedExceptionの例外が発生");

      }

*/

    }

  }

 

  out.println();

  out.println("要求ヘッダのダンプ:");

  Enumeration headerNames = request.getHeaderNames();

 

  while (headerNames.hasMoreElements()) {

    String name = (String) headerNames.nextElement();

    String value = request.getHeader(name);

 

    out.println("  " + name + " : " + value);

//  この個所もTomcatの場合以下の6行のように適用される

//  try{

//    String correctValue = new String(value.getBytes("8859_1"), "Shift_JIS");

//    out.println("  " + name + " : " + correctValue);

//  }catch(UnsupportedEncodingException theException){

//    out.println("UnsupportedExceptionの例外が発生");

//  }

    }

 

 

  out.println();

  out.println("属性(Attributes):");

  Enumeration attributeNames = request.getAttributeNames();

  while (attributeNames.hasMoreElements()) {

    String name = (String) attributeNames.nextElement();

    Object value = request.getAttribute(name);

    out.println("  " + name + " : " + value.toString());

  }

 

  out.println();

  out.println("クッキー(Cookies):");

  Cookie[] cookies = request.getCookies();

  if (cookies.length > 0) {

    for (int i = 0; i < cookies.length; i++) {

        String name = cookies[i].getName();

        String value = cookies[i].getValue();

        out.println("  " + name + " : " + value);

    }

  }

  out.println();

  out.println("*** 以上 ***");

   

}

 

/**

 * HTTP要求のなかのセッションに関わる情報リストをクライアントにHTMLで返す。

 * 作成日 : (01/07/02 13:12:40)

 * @param request javax.servlet.http.HttpServletRequest

 * @param session javax.servlet.http.HttpSession

 * @param out java.io.PrintWriter

 */

public void dumpSession(HttpServletRequest request, HttpSession session, PrintWriter out) throws java.io.IOException  {

    out.println();

    out.println("セッション情報:");

    out.println("  isRequestedSessionIdValid: " + request.isRequestedSessionIdValid());

    out.println("  isRequestedSessionIdFromCookie: " + request.isRequestedSessionIdFromCookie());

    out.println("  isRequestedSessionIdFromURL: " + request.isRequestedSessionIdFromURL());

    out.println("  getCreationTime: " + (new Date(session.getCreationTime())).toString());

    out.println("  getId: " + session.getId());

    out.println("  getLastAccessedTime: " +  (new Date(session.getLastAccessedTime())).toString());

    out.println("  getMaxInactiveInterval: " + session.getMaxInactiveInterval());

    out.println("  属性(Attributes):");

    Enumeration sessionAttributeNames = session.getAttributeNames();

    while (sessionAttributeNames.hasMoreElements()) {

        String name = (String) sessionAttributeNames.nextElement();

        Object value = session.getAttribute(name);

        out.println("    " + name + " : " + value.toString());

    }

}

}

 

このサーブレットの詳細はあとで説明するとして、始めにこのサーブレットがどのような応答パケットをクライアントに返しているかTelnetで調べてみよう。

 

TelnetによるSimpleSessionTestサーブレットの応答パケット

GET /SimpleSessionTest HTTP/1.0

 

HTTP/1.0 200 ok

Content-Type: text/html; charset=Shift_JIS

Set-Cookie: sesessionid=LV150HYAAAABZQFITEHUD1Y;Path=/

Cache-Control: no-cache="set-cookie,set-cookie2"

Expires: Thu, 01 Dec 1994 16:00:00 GMT

Content-Language: ja

 

<HTML><HEAD><TITLE>セッション・テスト(SimpleSessionTest)

</TITLE></HEAD><BODY><H1>セッション・テスト(SimpleSessionTest)</H1><PRE>

要求情報:

  getAuthType: null

  getCharacterEncoding: iso-8859-1

 

以下省略

これから判るように、この応答パケットはクライアントにsessionidとそのPath情報をCookieとして蓄積し、次回そのパスにはこれを要求パケットにいれてサーバに返すよう要求している。つまりクライアントがCookiesを使ってくれることを期待している。その他有効期限やキャシュしないためのヘッダ行も追加されている。(WebSphereのサーブレット・エンジンはクッキーのパラメタ名をサーブレット仕様書2.2版に記されているような大文字を使ってはいない)

 

それではクライアントがCookieを受け付けないように設定されている場合はどうするのか疑問に思われるかもしれない。その場合はエンジンはsessionidURLにいれて戻されることを期待しているのである。これは例えば:

http://localhost:8080/SimpleSessionTest;jsessionid=1234

などのように返されてくることを期待している。そのような仕掛けを作るのはプログラマの責任である。

 

下図はIEでこのサーブレットを最初にアクセスした後は「更新」を3回クリックしたときの画面である。「更新」を繰り返すごとにYour visit(s):で示される訪問回数がインクリメントされるのが確認されよう。ユーザとの間にやりとりされるのはセッションIDだけである。このIDはサーブレット・エンジンが生成した唯一無二の値である。訪問回数はsessionオブジェクトの属性として蓄積されサーブレット・エンジンが管理する。

 


クライアントがセキュリティの理由でクッキーを受け付けない場合はどういうことになるのか試してみよう。IEでツール(T)−>インターネットオプション(O)−>セキュリティ−>レベルのカスタマイズの設定ウィンドウにあるCookieを無効にしてみよう。(NetScapeの場合は編集(E)−>設定(E)−>カテゴリ:詳細−>Cookieを無効にする(D)と選択する)そうするとブラウザからはCookieが戻されないのでこのサーブレット・エンジンは新規のクライアントであると判断する。従ってブラウザにはYour visit(s): 1と表示される。

 

この状態でブラウザに表示された画面の下にあるURL再書き込みのハイパーリンクされている行をクリックしてみよう。次のような画面が戻される筈である。

 


URL再書き込みをクリックすると、URLは:

http://localhost:8080/SimpleSessionTest;jsessionid=LWXVY4YAAAAB3QFITEHUD1Y

となっている。jsessionidという値はSimpleSessionTestサーブレットがsessionオブジェクトから取得し、これをHTMLページにはめ込んだものである。Netscapeのブラウザでも全く同じ結果が得られることを確認しよう。

 

ところでCookieURL再書き込み双方でjsessionidが返されたら、サーブレット・エンジンはどちらを採るか皆さんのエンジンで試されたい。WebSphereのサーブレット・エンジンはCookieを優先させる。URL再書き込みよりはCookieのほうが高信頼度とする考えは賛成である。

 

なおURL再書き込みではブラウザのアドレス(URL)表示にセッションIDが表示されてしまう。悪意を持った人がこのIDを使って別のブラウザからこのサイトをアクセスするなりすまし行為が出来ないことは無い。セッションに設定されたタイムアウト時間はその可能性を下げるのに有用である。

 

CookieURL再書き込みのメカニズムが理解されたところで、このサーブレットの動作を説明しよう。PerformTaskのメソッドを見ていただきたい。

 

if ((session.isNew()==false)&&(request.getParameterValues("resetSession")!=null)){ session.invalidate(); }は、既にこのクライアントとのセッションが存在し、かつそのクライアントが表示ページの一番下の「新規セッション」とハイパーリンクされた個所をクリックしたかを判定し、そうであればそのセッションを開放する。

 HttpSession session = request.getSession(true); は、到来要求にsessionIdがあり且つそのIDがサーブレット・エンジンが蓄積しているデータベースに存在すればそのHttpSessionオブジェクトを返す。そうでなければ、エンジンは新しいHttpSessionオブジェクトを生成し、これを返す。

 if (session.isNew) は、そのセッションが新規であるかの判定をしている。

 session.setAttribute("visitTimes", new Integer(1)); は、上記条件が満たされている場合、そのHttpSessionオブジェクトに訪問回数を初期値1で属性として与えている。属性の名前はvisitTimesであり、オブジェクトはInteger(1)である。

 Integer number = (Integer)session.getAttribute("visitTimes"); 

 visitTimes = number.intValue(); の2行は、そのHttpSessionオブジェクトに属性として付与されている訪問回数を読み出す。

 session.setAttribute("visitTimes", new Integer(visitTimes)); は、カウントアップされた訪問回数でHttpSessionオブジェクトのvisitTimesという名前のオブジェクトを書き換えている。

 out.println("<A HREF=" + '"' + "../SimpleSessionTest;jsessionid="+session.getId());

 out.println('"' + ">URL再書き込み</A><P>"); はURL再書き込み用のハイパーリンクされた「URL再書き込み」というHTML文章を、及び

 out.println("<A HREF=" + '"' + "../SimpleSessionTest?resetSession=true");

 out.println('"' + ">新規セッション</A><P>"); はresetSessionというパラメタをクエリ文字で入れたURLをハイパーリンクさせた「新規セッション」というHTML文章を作成している。

 

String urlEncode = response.encodeURL(url);はエンジンがcookieによるセッション維持が可能かどうか判断して、可能でなければセッションIDを与えられたURLに付加する。WTEのエンジンはこれを正しく実行しないので、本プログラムではこのメソッドを使用していない。

 

もうひとつThread.sleep(10000L); という行が存在する。これはスレッドに対し10秒間スリープさせるものであるが、これは後ほどの実験に使うものである。

 

 

前節     目次     次節