Skip to content

4. Oturum izleme

4.1. Sorun

Bir web uygulaması, sunucu ile istemci arasında birden fazla form alışverişinden oluşabilir. Bu durumda işleyiş şu şekildedir:

  • 1. adım
    • C1 istemcisi sunucuyla bir bağlantı kurar ve ilk isteğini gönderir.
    • Sunucu, F1 formunu C1 istemcisine gönderir ve 1. adımda açılan bağlantıyı kapatır.
  • 2. adım
    • C1 istemcisi formu doldurur ve sunucuya geri gönderir. Bunun için tarayıcı, sunucuyla yeni bir bağlantı kurar.
    • Sunucu, 1 numaralı formdaki verileri işler, bu verilerden I1 bilgilerini hesaplar, F2 formunu C1 istemcisine gönderir ve 3'te açık olan bağlantıyı kapatır.
  • 3. adım
    • 3. ve 4. adımların döngüsü, 5. ve 6. adımlarda tekrarlanır. 6. adımın sonunda, sunucu F1 ve F2 adlı iki formu almış olacak ve bunlardan I1 ve I2 bilgilerini hesaplamış olacaktır.

Buradaki sorun şudur: Sunucu, I1 ve I2 bilgilerini C1 istemcisiyle ilişkilendirerek nasıl saklar? Bu soruna, C1 istemcisinin oturum takibi denir. Sorunun kökenini anlamak için, aynı anda birden fazla istemciye hizmet veren TCP-IP sunucu uygulamasının şemasını inceleyelim:

Klasik bir TCP-IP istemci-sunucu uygulamasında:

  • istemci, sunucuyla bir bağlantı kurar
  • bu bağlantı üzerinden sunucuyla veri alışverişi yapar
  • bağlantı, iki taraftan biri tarafından kapatılır

Bu mekanizmanın iki önemli noktası şunlardır:

  1. her istemci için tek bir bağlantı kurulur
  2. bu bağlantı, sunucunun müşterisiyle olan iletişim süresince kullanılır

Sunucunun belirli bir anda hangi istemciyle çalıştığını bilmesini sağlayan şey, onu istemcisiyle bağlayan bağlantı, başka bir deyişle "kanal"dır. Bu kanal belirli bir istemciye ayrıldığından, bu kanaldan gelen her şey o istemciden gelir ve bu kanala gönderilen her şey istemciye ulaşır.

HTTP istemci-sunucu mekanizması, yukarıdaki şemayı tam olarak takip eder; ancak istemci-sunucu iletişiminin, istemci ile sunucu arasında tek bir veri alışverişiyle sınırlı olması bu mekanizmanın ayırt edici özelliğidir:

  • istemci sunucuya bir bağlantı açar ve isteğini iletir
  • sunucu yanıtını verir ve bağlantıyı kapatır

T1 zamanında, bir C istemcisi sunucuya bir istek gönderirse, tek seferlik istek-yanıt alışverişi için kullanılacak C1 bağlantısını elde eder. T2 zamanında, aynı istemci sunucuya ikinci bir istek gönderirse, C1 bağlantısından farklı olan C2 bağlantısını alır. Sunucu açısından, kullanıcı C'nin bu ikinci isteği ile ilk isteği arasında hiçbir fark yoktur: her iki durumda da sunucu, bu istemciyi yeni bir istemci olarak değerlendirir. Müşteri C’nin sunucuya yaptığı farklı bağlantılar arasında bir bağlantı kurulabilmesi için, müşteri C’nin sunucu tarafından bir “düzenli kullanıcı” olarak “tanınması” ve sunucunun bu düzenli kullanıcı hakkında sahip olduğu bilgileri alması gerekir.

Şu şekilde çalışan bir yönetim sistemi düşünelim:

  • Tek bir kuyruk vardır
  • Birden fazla gişe vardır. Dolayısıyla birden fazla müşteri aynı anda hizmet alabilir. Bir gişe boşaldığında, bir müşteri kuyruktan çıkarak o gişede hizmet alır
  • Müşteri ilk kez geliyorsa, gişe görevlisi ona üzerinde bir numara yazılı bir jeton verir. Müşteri yalnızca bir soru sorabilir. Cevabını aldıktan sonra gişeden ayrılmalı ve kuyruğun sonuna geçmelidir. Gişe görevlisi, bu müşterinin bilgilerini jeton numarasıyla eşleşen bir dosyaya kaydeder.
  • Sırası tekrar geldiğinde, müşteri önceki seferkinden farklı bir gişe görevlisi tarafından hizmet alabilir. Gişe görevlisi, müşteriden jetonunu ister ve jeton numarasını taşıyan dosyayı alır. Müşteri yine bir talepte bulunur, yanıt alır ve dosyaya yeni bilgiler eklenir.
  • ve böyle devam eder... Zamanla, müşteri tüm taleplerine yanıt alır. Farklı talepler arasındaki takip, jeton ve buna bağlı dosya sayesinde gerçekleştirilir.

Bir web istemci-sunucu uygulamasındaki oturum izleme mekanizması, yukarıdaki işleyişe benzerdir:

  • ilk isteğinde, bir müşteriye web sunucusu tarafından bir jeton verilir
  • kimliğini doğrulamak için sonraki her talebinde bu jetonu sunar

Jeton farklı şekillerde olabilir:

  • formda gizli bir alan
    • istemci ilk isteğini gönderir (sunucu, istemcinin jetonu olmadığını fark ederek onu tanır)
    • sunucu yanıtını (bir form) gönderir ve jetonu bu formun gizli bir alanına yerleştirir. Bu anda bağlantı kesilir (istemci jetonuyla birlikte sistemden ayrılır). Sunucu, gerekirse bu jetona bazı bilgiler eklemiştir.
    • istemci, formu geri göndererek ikinci isteğini yapar. Sunucu, formdan jetonu alır. Böylece, jeton sayesinde ilk istek sırasında hesaplanan bilgilere erişerek istemcinin ikinci isteğini işleyebilir. Jetona bağlı dosyaya yeni bilgiler eklenir, müşteriye ikinci bir yanıt verilir ve bağlantı ikinci kez kapatılır. Jeton, kullanıcının bir sonraki isteğinde sunabilmesi için yanıt formuna yeniden eklenir.
    • ve böyle devam eder...

Bu tekniğin en büyük dezavantajı, jetonun bir forma eklenmesi gerekliliğidir. Sunucunun yanıtı bir form değilse, gizli alan yöntemi artık kullanılamaz.

  • Çerez yöntemi
    • istemci ilk isteğini yapar (sunucu, istemcinin jetonu olmadığını fark eder)
    • sunucu, yanıtının HTTP başlıklarına bir çerez ekleyerek yanıt verir. Bu, HTTP Set-Cookie komutu kullanılarak yapılır:

Set-Cookie: param1=değer1;param2=değer2;....

burada parami parametre adları, valeursi ise bunların değerleridir. Parametreler arasında jeton da bulunur. Çoğu zaman çerezde sadece jeton bulunur; diğer bilgiler ise sunucu tarafından jetonla ilişkili klasöre kaydedilir. Çerezi alan tarayıcı, bunu diskteki bir dosyada saklar. Sunucudan yanıt alındıktan sonra bağlantı kapatılır (istemci, jetonuyla birlikte pencereyi kapatır).

  • (devam)
    • istemci, sunucuya ikinci isteğini gönderir. Sunucuya her istek gönderildiğinde, tarayıcı elindeki tüm çerezler arasında, istenen sunucudan gelen bir çerez olup olmadığına bakar. Varsa, bunu sunucuya her zaman HTTP komutu biçiminde gönderir; bu Cookie komutunun sözdizimi, sunucu tarafından kullanılan Set-Cookie komutunun sözdizimine benzerdir:

Cookie: param1=değer1;param2=değer2;....

Tarayıcı tarafından gönderilen parametreler arasında, sunucu, istemciyi tanımak ve ona ait bilgileri bulmak için gerekli olan jetonu tespit edecektir.

Bu, en yaygın olarak kullanılan jeton türüdür. Bunun bir dezavantajı vardır: Kullanıcı, tarayıcısını çerezleri kabul etmeyecek şekilde ayarlayabilir. Bu tür kullanıcılar, çerez kullanan web uygulamalarına erişemez.

  • URL'in yeniden yazımı
    • istemci ilk isteğini gönderir (sunucu, istemcinin çerezi olmadığı için onu tanır)
    • sunucu yanıtını gönderir. Bu yanıt, kullanıcının uygulamaya devam etmek için kullanması gereken bağlantılar içerir. Bu bağlantıların her birinin URL adresine, sunucu URL;token=değer biçiminde bir token ekler.
    • Kullanıcı uygulamaya devam etmek için bağlantılardan birine tıkladığında, tarayıcı web sunucusuna istekte bulunur ve başlıklarda HTTP, URL, URL;token=istenen değer bilgisini gönderir. Böylece sunucu, jetonu alabilir.

4.2. Oturum izleme için API Java

Şimdi, oturum takibi için yararlı olan başlıca yöntemleri sunuyoruz:

HttpSession [HttpServletRequest].getSession()
, mevcut isteğin ait olduğu Session nesnesini alır. Eğer istek henüz bir oturumun parçası değilse, bir oturum oluşturulur.
String [HttpSession].getId()
cari oturumun kimliği
long [HttpSession].getCreationTime()
cari oturumun oluşturulma tarihi (1 Ocak 1970, saat 00:00'dan bu yana geçen milisaniye sayısı).
long [HttpSession].getLastAccessedTime()
Müşteri tarafından oturuma en son erişim tarihi
long [HttpSession].getMaxInactiveInterval()
Bir oturumun etkin olmama süresinin saniye cinsinden maksimum süresi. Bu süre dolduğunda oturum geçersiz hale gelir.
[HttpSession].setMaxInactiveInterval(int durée)
bir oturumun etkin olmama süresinin maksimum süresini saniye cinsinden belirler. Bu süre dolduğunda oturum geçersiz hale gelir.
boolean [HttpSession].isNew()
oturum yeni oluşturulmuşsa "true"
[HttpSession].setAttribute(String paramètre, Object valeur)
belirli bir oturumdaki bir parametreye bir değer atar. Bu mekanizma, oturum boyunca kullanılabilir kalacak bilgilerin saklanmasını sağlar.
[HttpSession].removeAttribute(String paramètre)
oturum verilerinden parametre'i kaldırır.
Object [HttpSession].getAttribute(String paramètre)
oturumun paramètre parametresine atanan değer. null mevcut değilse, bu değeri döndürür.
Enumeration [HttpSession].getAttributeNames()
geçerli oturumun tüm özniteliklerinin sıralı listesi
[HttpSession].invalidate()
Geçerli oturumu sonlandırır. Oturumla ilişkili tüm bilgiler silinir.

4.3. Örnek 1

Wrox yayınevi tarafından basılan ve Eyrolles tarafından dağıtılan "J2EE ile Programlama" adlı mükemmel kitaptan bir örnek sunuyoruz. Bu kitap, Java ile web çözümleri geliştirenler için üst düzey bir bilgi hazinesidir. Kitapta tek bir Java servlet şeklinde sunulan uygulama, burada müşteriye çeşitli olası yanıtları görüntülemek için JSP sayfalarını kullanan bir ana servlet şeklinde yeniden ele alınmıştır.

Uygulamanın adı "sessions" olup, <tomcat>\conf\server.xml dosyasında şu şekilde yapılandırılmıştır:

                <Context path="/sessions" docBase="e:/data/serge/servlets/sessions" />

Yukarıdaki docBase klasöründe şu öğeler bulunur:

Image

erreur.jsp, invalide.jsp ve valide.jsp dosyalarının üçü de sessions uygulamasıyla ilişkilidir. Yukarıdaki WEB-INF klasöründe şunlar bulunur:

Image

Yukarıda, "sessions" uygulamasının yapılandırma dosyası olan web.xml dosyası görülmektedir. classes klasöründe ise servlet sınıf dosyası bulunmaktadır:

Image

Uygulamanın web.xml dosyası şöyledir:

<?xml version="1.0" encoding="ISO-8859-1"?>

<!DOCTYPE web-app
    PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
    "http://java.sun.com/dtd/web-app_2_3.dtd">

<web-app>
    <servlet>
      <servlet-name>cycledevie</servlet-name>
    <servlet-class>cycledevie</servlet-class>
    <init-param>
          <param-name>urlSessionValide</param-name>
        <param-value>/valide.jsp</param-value>
    </init-param>
    <init-param>
          <param-name>urlSessionInvalide</param-name>
        <param-value>/invalide.jsp</param-value>
    </init-param>
    <init-param>
          <param-name>urlErreur</param-name>
        <param-value>/erreur.jsp</param-value>
    </init-param>
  </servlet>
  <servlet-mapping>
      <servlet-name>cycledevie</servlet-name>
    <url-pattern>/cycledevie</url-pattern>
  </servlet-mapping>
</web-app>

Ana servlet'in adı cycledevie (servlet-name) olup, cycledevie.class (servlet-class) sınıf dosyasıyla ilişkilidir. Bu servletin /cycledevie (servlet-mapping) adlı bir takma adı vardır; bu sayede URL ve http://localhost:8080/sessions/cycledevie aracılığıyla çağrılabilir. Üç başlatma parametresi vardır:

urlSessionValide
cari oturumun özelliklerini gösteren sayfanın URL'si
urlSessionInvalide
Mevcut oturumun geçersiz kılınmasından sonra görüntülenen sayfanın URL'si
urlErreur
cycledevie ana servletinin başlatılmasında hata olması durumunda görüntülenen sayfanın URL'si

"sessions" uygulamasının bileşenleri şunlardır:

cycledevie
ana servlet - istemcinin isteğini analiz eder:
  • eğer istek bir oturumun parçasıysa, bu oturumun özelliklerini görüntüleyecek olan valide.jsp sayfasına yönlendirir. Bu sayfadan kullanıcı şunları yapabilir:
    • sayfayı yeniden yükleyebilir
    • sayfayı geçersiz kılmak
  • istek, mevcut oturumu geçersiz kılmayı talep ediyorsa, servlet, kullanıcıya yeni bir oturum oluşturmasını önerecek olan invalide.jsp sayfasına kontrolü devreder
  • servlet, başlatma sırasında hatalarla karşılaşırsa, kontrolü bir hata mesajı görüntüleyen erreur.jsp sayfasına devreder.
valide.jsp
  • , mevcut oturumun özelliklerini gösterir ve iki bağlantı sunar:
    • biri sayfayı yeniden yükleyerek mevcut oturuma son erişim parametresindeki değişiklikleri görmek için
    • diğeri ise mevcut oturumu geçersiz kılmak için
invalide.jsp
kullanıcı mevcut oturumu geçersiz kıldığında görüntülenir. Ardından yeni bir oturum oluşturmayı önerir.
erreur.jsp
Ana servlet'in başlatılması sırasında hata ile karşılaştığında görüntülenir.

Ana servlet cycledevie şu şekildedir:

import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;

public class cycledevie extends HttpServlet{

    // örnek değişkenleri
    String msgErreur=null;
    String urlSessionInvalide=null;
    String urlSessionValide=null;
    String urlErreur=null;

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

        // başlatma işlemi başarılı oldu mu?
        if(msgErreur!=null){
             // hata sayfasına yönlendiriliyor
            getServletContext().getRequestDispatcher(urlErreur).forward(request,response);
        }

         // mevcut oturum alınır
        HttpSession session=request.getSession();

         // yapılması gereken eylem analiz ediliyor
        String action=request.getParameter("action");
        // Mevcut oturumu geçersiz kılıyoruz
        if(action!=null && action.equals("invalider")){
            // geçerli oturumu geçersiz kılıyoruz
            session.invalidate();
             // kontrolü urlSessionInvalide URL'sine devrediliyor
            getServletContext().getRequestDispatcher(urlSessionInvalide).forward(request,response);
        }
         // diğer durumlar
         // kontrol urlSessionInvalide URL'sine devrediliyor
        getServletContext().getRequestDispatcher(urlSessionValide).forward(request,response);
    }

     //-------- POST
    public void doPost(HttpServletRequest request, HttpServletResponse response)
    throws IOException, ServletException{
        doGet(request,response);
    }

     //-------- INIT
    public void init(){
         // başlatma parametreleri alınır
        ServletConfig config=getServletConfig();
        urlSessionInvalide=config.getInitParameter("urlSessionInvalide");
        urlSessionValide=config.getInitParameter("urlSessionValide");
        urlErreur=config.getInitParameter("urlErreur");

        // parametreler uygun mu?
        if(urlSessionValide==null || urlSessionInvalide==null){
            msgErreur="Configuration incorrecte";
        }
    }
}

Aşağıdaki noktalara dikkat edilmelidir:

  • servlet, başlatma yönteminde üç parametresini alır
  • bir isteğin işlenmesi sırasında (doGet), servlet:
    • öncelikle başlatma sırasında herhangi bir hata olup olmadığını kontrol eder. Hata varsa, kontrolü erreur.jsp sayfasına devreder.
    • action parametresinin değerini kontrol eder. Bu parametrenin değeri "invalider" ise, servlet kontrolü invalide.jsp sayfasına devreder; aksi takdirde valide.jsp sayfasına devreder.

JSP valide.jsp sayfası, geçerli oturumun özelliklerini görüntüler:

<%@ page import="java.util.*" %>

<%
     // jspService
   // burada, devam eden oturumu tanımlamamız gereken durumdayız
  String etat= session.isNew() ? "Nouvelle session" : "Ancienne session";
%>
<!-- HTML sayfasının başlangıcı -->
  <html>
      <meta http-equiv="pragma" content="no-cache">
    <head>
        <title>Cycle de vie d'une session</title>
    </head>
    <body>
        <h3>Cycle de vie d'une session</h3>
        <hr>
        <br>Etat session : <%= etat %>
      <br>ID session : <%= session.getId() %>
      <br>Heure de création : <%= new Date(session.getCreationTime()) %>
      <br>Heure du dernier accès : <%= new Date(session.getLastAccessedTime()) %>
      <br>Intervalle maximum d'inactivité : <%= session.getMaxInactiveInterval() %>
      <br><a href="/sessions/cycledevie?action=invalider">Invalider la session</a>
      <br><a href="/sessions/cycledevie">Recharger la page</a>
    <body>
  </html>

Şu satırda dikkat edilmesi gereken nokta şudur:

  String etat= session.isNew() ? "Nouvelle session" : "Ancienne session";

satırında, nereden geldiği belli olmayan bir oturum nesnesi kullanıldığına dikkat edilmelidir. Aslında bu nesne, JSP sayfalarında kullanılabilen request, response, out, config (ServletConfig), context (ServletContext) gibi, daha önce karşılaştığımız nesneler gibi bu nesne de JSP sayfalarında kullanıma sunulan örtük nesnelerden biridir. Sayfadaki iki bağlantı, daha önce tanıtılan cycledevie servletine yönlendiriyor:

      <br><a href="/sessions/cycledevie?action=invalider">Invalider la session</a>
      <br><a href="/sessions/cycledevie">Recharger la page</a>

Oturumu geçersiz kılma bağlantısı, action=invalider parametresini içerir; bu parametre, cycledevie servletinin kullanıcının mevcut oturumu geçersiz kılmak istediğini tanımasını sağlar. Diğer bağlantı ise sayfanın yeniden yüklenmesini sağlar. Tarayıcının sayfayı önbellekten almaması için HTML yönergesi:

      <meta http-equiv="pragma" content="no-cache">

kullanılmıştır. Bu yönerge, tarayıcıya aldığı sayfa için önbelleği kullanmamasını belirtir.

invalide.jsp sayfası şöyledir:

<!-- HTML sayfasının başlangıcı -->
<html>
  <head>
      <title>Cycle de vie d'une session</title>
  </head>
  <body>
      <h3>Cycle de vie d'une session</h3>
      <hr>
    Votre session a été invalidée
    <a href="/sessions/cycledevie">Créer une nouvelle session</a>
  </body>
</html>

Bu sayfa, cycledevie servletine, action parametresi olmadan yönlendiren bir bağlantı sunar. Bu bağlantı, cycledevie servletinin yeni bir oturum açmasına neden olur.

erreur.jsp sayfası şöyledir:

<%
     // jspService
   // burada, devam eden oturumu tanımlamamız gereken durum söz konusudur
  String msgErreur= request.getAttribute("msgErreur");
  if(msgErreur==null) msgErreur="Erreur non identifiée)";
%>
<!-- sayfanın başlangıcı HTML -->
<html>
  <head>
      <title>Cycle de vie d'une session</title>
  </head>
  <body>
      <h3>Cycle de vie d'une session</h3>
      <hr>
    Application indisponible(<%= msgErreur %>)
  </body>
</html>

Bu servletin görevi, cycledevie servletinden kendisine iletilen hata mesajını görüntülemektir. Şimdi bazı çalışma örneklerine bakalım. Servlet ilk kez çağrıldığında:

Image

Yukarıdaki sayfa, yeni bir oturumda olduğumuzu gösterir. "Sayfayı Yenile" bağlantısı kullanılır:

Image

Önceki sonuç, önceki sayfadakiyle aynı oturumda olduğumuzu gösteriyor (aynı ID). Bu oturuma en son erişim saatinin değiştiği dikkatinizi çekecektir. Şimdi "Oturumu geçersiz kıl" bağlantısını kullanalım:

Image

Bu yeni sayfanın URL numarasının, action=invalider parametresiyle birlikte olduğu görülecektir. Yeni bir oturum oluşturmak için "Yeni oturum oluştur" bağlantısını kullanalım:

Image

Yeni bir oturumun başladığını görüyoruz. Önceki örneklerde oturum, çerez mekanizmasına dayanıyordu. Şimdi tarayıcımızda çerez kullanımını devre dışı bırakıp testleri tekrar yapalım. Aşağıdaki örnekler Netscape Communicator ile gerçekleştirilmiştir. Açıklanamayan bir nedenden ötürü, IE6 ile yapılan testler, çerezler devre dışı bırakılmış olmasına rağmen IE6'in çerezleri kullanmaya devam ediyormuş gibi beklenmedik sonuçlar vermiştir. cycledevie servleti ilk kez çağrılır:

Image

Şimdi "Sayfayı Yenile" bağlantısını kullanıyoruz:

Image

İki şey göze çarpıyor:

  • oturumun ID değeri değişti
  • servlet, oturumu yeni bir oturum olarak algılıyor

Tomcat sunucusu, tarayıcılarında çerez kullanımını engelleyen kullanıcıların sorununa bir çözüm sunuyor. Bu paragrafın başında bahsettiğimiz jetonu uygulamak için iki mekanizma kullanıyor: çerezler ve URL'in yeniden yazılması. Oturum çerezi mevcut değilse, istemci tarafından talep edilen URL'ten jetonu almaya çalışır. Bunun için, bu URL’nin jetonu içermesi gerekir. Genel olarak, bir HTML belgesinde web uygulamasına yönlendiren tüm bağlantıların, uygulamanın jetonunu içermesi gerekir. Bu, encodeURL yöntemi ile yapılabilir:

String [HttpResponse].encodeURL(String URL)
parametre olarak geçirilen URL'e, URL;jsessionid=xxxx biçiminde geçerli oturumun jetonunu ekler

Uygulamamızı şu şekilde değiştiriyoruz:

  • cycledevie.java servletinde URL'ler şu şekilde kodlanmıştır:
             // kontrolü hata sayfasına devrediyoruz
            getServletContext().getRequestDispatcher(response.encodeURL(urlErreur)).forward(request,response);
....
             // kontrol, urlSessionInvalide URL'sine devrediliyor
            getServletContext().getRequestDispatcher(response.encodeURL(urlSessionInvalide)).forward(request,response);
....
         // kontrol, urlSessionInvalide URL'sine devredilir
        getServletContext().getRequestDispatcher(response.encodeURL(urlSessionValide)).forward(request,response);
  • valide.jsp sayfasında URL kodları şu şekilde kodlanmıştır:
<%
     // jspService
   // burada, devam eden oturumu tanımlamamız gereken durum söz konusudur
  String etat= session.isNew() ? "Nouvelle session" : "Ancienne session";
   // kodlama URL cycledevie
  String URLcycledevie=response.encodeURL("/sessions/cycledevie");  
%>
............
      <br><a href="<%= URLcycledevie %>?action=invalider">Invalider la session</a>
      <br><a href="<%= URLcycledevie %>">Recharger la page</a>
  • invalide.jsp sayfasında URL kodları şu şekilde kodlanmıştır:
<%
     // jspservice - mevcut oturumu geçersiz kılıyoruz
  session.invalidate();
   // URL kodlaması, cycledevie
  String URLcycledevie=response.encodeURL("/sessions/cycledevie");
%>  
..........
    <a href="<%= URLcycledevie %>">Créer une nouvelle session</a>

Artık testlere hazırız. Netscape 4.5 kullanıyoruz ve çerezler devre dışı bırakıldı. İlk olarak cycledevie servletini çağırıyoruz:

Image

ve "Sayfayı Yenile" bağlantısıyla sayfayı yeniden yüklüyoruz:

Image

Şunu görebiliyoruz ki:

  • oturum değişmemiştir (aynı ID)
  • cycledevie servlet'indeki URL, yukarıdaki Adresse alanında gösterildiği gibi jetonu içeriyor
  • Tomcat sunucusu, talep edilen URL'te oturum jetonunu geri alıyor (geliştirici bunu kodlamaya özen göstermişse).

4.4. Örnek 2

Şimdi, bir müşterinin oturumunda bilgilerin nasıl depolanacağını gösteren bir örnek sunacağız. Burada tek bilgi, kullanıcı servletin URL'sini her çağırdığında artacak bir sayaç olacaktır. Bu ilk kez çağrıldığında, aşağıdaki sayfa görüntülenir:

Image

Yukarıdaki "Sayfayı Yenile" bağlantısına tıklandığında, aşağıdaki yeni sayfa görüntülenir:

Image

Uygulamanın üç bileşeni vardır:

  • istemcinin isteğini işleyen bir servlet
  • sayaç değerini görüntüleyen bir JSP sayfası
  • olası bir hatayı görüntüleyen bir JSP sayfası

Bu üç bileşen, halihazırda kullanılmakta olan "sessions" web uygulamasına yüklenmiştir. Yeni servletleri yapılandırmak için bu uygulamanın web.xml dosyası değiştirilmiştir:

<?xml version="1.0" encoding="ISO-8859-1"?>

<!DOCTYPE web-app
    PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
    "http://java.sun.com/dtd/web-app_2_3.dtd">

<web-app>
...
  <servlet>
      <servlet-name>compteur</servlet-name>
    <servlet-class>compteur</servlet-class>
    <init-param>
          <param-name>urlAffichageCompteur</param-name>
        <param-value>/compteur.jsp</param-value>
    </init-param>
    <init-param>
          <param-name>urlErreur</param-name>
        <param-value>/erreurcompteur.jsp</param-value>
    </init-param>
  </servlet>
...
  <servlet-mapping>
      <servlet-name>compteur</servlet-name>
    <url-pattern>/compteur</url-pattern>
  </servlet-mapping>
</web-app>
  • Servlet'in adı compteur (servlet-name) olup, compteur.class (servlet-class) sınıf dosyasına bağlıdır
  • İki başlatma parametresi vardır:
    • urlAffichageCompteur: Sayaç görüntüleme sayfası JSP'teki URL
    • urlErreur: Olası bir hatanın görüntülendiği JSP sayfasındaki URL
  • ve /compteur takma adı sayesinde URL http://localhost:8080/sessions/compteur üzerinden çağrılacaktır

compteur.java servleti şu şekildedir:

import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;

public class compteur extends HttpServlet{

     // örnek değişkenleri
    String msgErreur=null;
    String urlAffichageCompteur=null;
    String urlErreur=null;

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

        // başlatma işlemi başarılı oldu mu?
        if(msgErreur!=null){
             // hata sayfasına yönlendiriliyor
            getServletContext().getRequestDispatcher(urlErreur).forward(request,response);
        }

         // mevcut oturumu alınıyor
        HttpSession session=request.getSession();
         // ve sayaç
        String compteur=(String)session.getAttribute("compteur");
        if(compteur==null) compteur="0";
         // sayaç artırılıyor
        try{
            compteur=""+(Integer.parseInt(compteur)+1);
        }catch(Exception ex){}
         // sayaç oturumda kaydediliyor
        session.setAttribute("compteur",compteur);
         // ve isteğe
        request.setAttribute("compteur",compteur);

         // sayaç görüntüleme URL'sine kontrol devrediliyor
        getServletContext().getRequestDispatcher(urlAffichageCompteur).forward(request,response);
    }

     //-------- POST
    public void doPost(HttpServletRequest request, HttpServletResponse response)
    throws IOException, ServletException{
        doGet(request,response);
    }

     //-------- INIT
    public void init(){
         // başlatma parametreleri alınır
        ServletConfig config=getServletConfig();
        urlAffichageCompteur=config.getInitParameter("urlAffichageCompteur");
        urlErreur=config.getInitParameter("urlErreur");

         // parametreler uygun mu?
        if(urlAffichageCompteur==null){
            msgErreur="Configuration incorrecte";
        }
    }
}

Bu servlet, daha önce karşılaştığımız servletlerin yapısına sahiptir. Burada sadece sayaç yönetimi dikkat çekmektedir:

  • oturum, request.getSession() aracılığıyla alınır
  • sayaç, bu oturumda session.getAttribute("sayaç") aracılığıyla alınır
  • null değeri alınırsa, bu oturumun yeni başladığı anlamına gelir. Bu durumda sayaç 0'a sıfırlanır.
  • sayaç artırılır, oturuma geri yazılır (session.setAttribute("sayaç",sayaç)) ve görüntüleme servletine iletilecek isteğe eklenir (request.setAttribute("sayaç",sayaç)).

compteur.jsp görüntüleme sayfası şöyledir:

<%
     // jspService
   // sayaç alınmaktadır
  String compteur= (String) request.getAttribute("compteur");
  if(compteur==null) compteur="inconnu";
%>
<!-- sayfanın başlangıcı HTML -->
<html>
  <head>
      <title>Comptage au fil d'une session</title>
  </head>
  <body>
      <h3>Comptage au fil d'une session (nécessite l'activation des cookies)</h3>
      <hr>
    compteur = (<%= compteur %>)
    <br><a href="/sessions/compteur">Recharger la page</a>
  </body>
</html>

Yukarıdaki sayfa, ana servlet tarafından kendisine aktarılan compteur (request.getAttribute("sayaç")) özniteliğini alır ve görüntüler.

erreurcompteur.jsp hata sayfası şöyledir:

<%
     // jspService
   // bir hata oluştu
  String msgErreur= request.getAttribute("msgErreur");
  if(msgErreur==null) msgErreur="Erreur non identifiée";
%>
<!-- sayfanın başı HTML -->
<html>
  <head>
      <title>Comptage au fil d'une session</title>
  </head>
  <body>
      <h3>Comptage au fil d'une session (nécessite l'activation des cookies)</h3>
      <hr>
    Application indisponible(<%= msgErreur %>)
  </body>
</html>

4.5. Örnek 3

Önceki compteur uygulamasının istemcisi olacak bir Java uygulaması yazmayı planlıyoruz. Bu uygulama, N parametresiyle birlikte bu uygulamayı N kez arka arkaya çağıracaktır. Amacımız, programlanmış bir web istemcisini ve çerezlerin nasıl yönetileceğini göstermektir. Başlangıç noktamız, aynı yazarın Java ders notlarında sunulan genel bir web istemcisi olacaktır. Bu istemci şu şekilde çağrılır:

clientweb URL GET/HEAD

  • URL: istenen URL
  • GET/HEAD: GET, sayfanın HTML kodunu istemek için; HEAD, yalnızca başlıklarla sınırlandırmak için; HTTP

İşte URL ve http://localhost:8080/sessions/compteur ile ilgili bir örnek:


E:\data\serge\JAVA\SOCKETS\client web>java clientweb http://localhost:8080/sessions/compteur GET

HTTP/1.1 200 OK
Content-Type: text/html;charset=ISO-8859-1
Date: Thu, 08 Aug 2002 14:21:18 GMT
Connection: close
Server: Apache Tomcat/4.0.3 (HTTP/1.1 Connector)
Set-Cookie: JSESSIONID=B8A9076E552945009215C34A97A0EC5D;Path=/sessions


<!-- sayfanın başlangıcı HTML -->
<html>
  <head>
        <title>Comptage au fil d'une session</title>
  </head>
  <body>
        <h3>Comptage au fil d'une session (nécessite l'activation des cookies)</h3>
        <hr>
    compteur = (1)
    <br><a href="/sessions/compteur">Recharger la page</a>
  </body>
</html>

clientweb programı, sunucudan aldığı her şeyi görüntüler. Yukarıda, sunucunun istemcisine bir çerez gönderdiği HTTP Set-cookie komutu görülmektedir. Burada çerez iki bilgi içerir:

  • JSESSIONID, oturum jetonudur
  • Çerezin ait olduğu URL adresini tanımlayan Path. Path=/sessions, tarayıcıya, /sessions ile başlayan bir URL talep ettiği her seferinde çerezi sunucuya geri göndermesi gerektiğini belirtir. sessions uygulamasında, /sessions/cycledevie ve /sessions/compteur servletleri dahil olmak üzere çeşitli servletler kullandık. /sessions/cycledevie servlet'i çağrıldığında tarayıcı bir J jetonu alır. Aynı tarayıcıyla ardından /sessions/compteur servlet'i çağrılırsa, tarayıcı J jetonunu sunucuya geri gönderecektir; çünkü bu jeton, /sessions ile başlayan tüm URL serileri için geçerlidir. Örneğimizde, cycledevie ve compteur servlet'lerinin aynı oturum jetonunu paylaşması gerekmez. Dolayısıyla, bunlar aynı web uygulamasına yerleştirilmemeliydi. Unutulmaması gereken bir nokta şudur: Aynı uygulamadaki tüm servlet'ler aynı oturum jetonunu paylaşır.
  • Bir çerez ayrıca bir geçerlilik süresi de tanımlayabilir. Burada bu bilgi yoktur. Dolayısıyla çerez, tarayıcı kapatıldığında silinecektir. Bir çerezin geçerlilik süresi örneğin N gün olabilir. Geçerli olduğu sürece, tarayıcı, kendi etki alanındaki (Path) URL adreslerinden herhangi biri her ziyaret edildiğinde bu çerezi geri gönderecektir. CD adlı bir çevrimiçi satış sitesini ele alalım. Bu site, müşterisinin katalogdaki gezinme sürecini takip edebilir ve zamanla tercihlerini belirleyebilir: örneğin klasik müzik. Bu tercihler, 3 ay süreli bir çerezde saklanabilir. Aynı müşteri bir ay sonra siteye geri dönerse, tarayıcı çerezi sunucu uygulamasına geri gönderir. Sunucu uygulaması, çerezde bulunan bilgilere göre oluşturulan sayfaları müşterinin tercihlerine göre uyarlayabilir.

Web istemcisinin kodu aşağıdadır. Bu kod, ileride başka bir istemcinin geliştirilmesinde başlangıç noktası olacaktır.

// ithal edilen paketler
import java.io.*;
import java.net.*;

public class clientweb{

    // istek URL
     // içeriğini ekranda görüntüler

    public static void main(String[] args){
        // sözdizimi
        final String syntaxe="pg URI GET/HEAD";

        // argüman sayısı
        if(args.length != 2)
            erreur(syntaxe,1);

         // istenen URI not edilir
        String URLString=args[0];
        String commande=args[1].toUpperCase();

        // URI'in geçerliliğinin kontrolü
        URL url=null;
        try{
            url=new URL(URLString);
        }catch (Exception ex){
             // URI hatalı
            erreur("L'erreur suivante s'est produite : " + ex.getMessage(),2);
        }//yakalama
         // siparişin doğrulanması
        if(! commande.equals("GET") && ! commande.equals("HEAD")){
            // sipariş hatalı
            erreur("Le second paramètre doit être GET ou HEAD",3);
        }

         // URL'ten gerekli bilgiler çıkarılıyor
    String path=url.getPath();
    if(path.equals("")) path="/";
    String query=url.getQuery();
    if(query!=null) query="?"+query; else query="";
    String host=url.getHost();
    int port=url.getPort();
    if(port==-1) port=url.getDefaultPort();

         // işe devam edilebilir
        Socket  client=null;                        // müşteri
        BufferedReader IN=null;                    // müşterinin okuma akışı
        PrintWriter OUT=null;                        // müşterinin yazma akışı
        String réponse=null;                        // sunucunun yanıtı
        try{
             // sunucuya bağlanıyoruz
            client=new Socket(host,port);

            // istemcinin giriş-çıkış akışları oluşturuluyor TCP
            IN=new BufferedReader(new InputStreamReader(client.getInputStream()));
            OUT=new PrintWriter(client.getOutputStream(),true);

            // URL isteniyor - başlıkların gönderilmesi HTTP
            OUT.println(commande + " " + path + query + " HTTP/1.1");   
            OUT.println("Host: " + host + ":" + port);
            OUT.println("Connection: close");
            OUT.println();
             // yanıt okunur
            while((réponse=IN.readLine())!=null){
                 // yanıt işlenir
                System.out.println(réponse);
            }//while
             // işlem tamamlandı
            client.close();
        } catch(Exception e){
            // istisna işleniyor
            erreur(e.getMessage(),4);
        }//catch
    }//main

     // hataların görüntülenmesi
    public static void erreur(String msg, int exitCode){
         // hata görüntüleme
        System.err.println(msg);
         // hata ile sonlandırma
        System.exit(exitCode);
    }//hata
}//sınıf

Şimdi, şu şekilde çağrılan clientCompteur programını oluşturuyoruz:

clientCompteur URL N [JSESSIONID]

  • URL: sayaç servletinin URL'si
  • N: bu servlete yapılacak çağrı sayısı
  • JSESSIONID: isteğe bağlı parametre - oturum jetonu

Programın amacı, oturum çerezini yöneterek sayaç servletini N kez çağırmak ve her seferinde sunucu tarafından geri gönderilen sayaç değerini görüntülemektir. N çağrı tamamlandığında, sayaç değeri N olmalıdır. İşte ilk bir çalıştırma örneği:


E:\data\serge\Servlets\sessions\jb7>java.bat clientCompteur http://localhost:8080/sessions/sayaç 3
--> GET /sessions/compteur HTTP/1.1
--> Host: localhost:8080
--> Connection: close
-->

HTTP/1.1 200 OK
Content-Type: text/html;charset=ISO-8859-1
Date: Thu, 08 Aug 2002 18:25:00 GMT
Connection: close
Server: Apache Tomcat/4.0.3 (HTTP/1.1 Connector)
Set-Cookie: JSESSIONID=92DB3808CE8FCB47D47D997C8B52294A;Path=/sessions
cookie trouvÚ : 92DB3808CE8FCB47D47D997C8B52294A

compteur : 1

--> GET /sessions/compteur HTTP/1.1
--> Host: localhost:8080
--> Cookie: JSESSIONID=92DB3808CE8FCB47D47D997C8B52294A
--> Connection: close
-->

HTTP/1.1 200 OK
Content-Type: text/html;charset=ISO-8859-1
Date: Thu, 08 Aug 2002 18:25:00 GMT
Connection: close
Server: Apache Tomcat/4.0.3 (HTTP/1.1 Connector)

compteur : 2

--> GET /sessions/compteur HTTP/1.1
--> Host: localhost:8080
--> Cookie: JSESSIONID=92DB3808CE8FCB47D47D997C8B52294A
--> Connection: close
-->

HTTP/1.1 200 OK
Content-Type: text/html;charset=ISO-8859-1
Date: Thu, 08 Aug 2002 18:25:00 GMT
Connection: close
Server: Apache Tomcat/4.0.3 (HTTP/1.1 Connector)

compteur : 3

Program şunu görüntüler:

  • sunucuya gönderdiği HTTP başlıklarını şu biçimde gösterir -->
  • aldığı HTTP başlıklarını
  • her çağrıdan sonra sayaç değeri

Görüldüğü gibi, ilk çağrıda:

  • istemci çerez göndermiyor
  • sunucu bir çerez gönderir

Sonraki çağrılarda:

  • istemci, ilk çağrı sırasında sunucudan aldığı çerezi her seferinde geri gönderir. Bu sayede sunucu, istemciyi tanıyabilir ve sayacını artırabilir.
  • sunucu ise artık çerez göndermiyor

Yukarıdaki jetonu üçüncü parametre olarak geçirerek önceki programı yeniden çalıştırıyoruz:


E:\data\serge\Servlets\sessions\jb7>java.bat clientCompteur http://localhost:8080/sessions/sayaç 3 92DB3808CE8FCB47D47D997C8B52294A

--> GET /sessions/compteur HTTP/1.1
--> Host: localhost:8080
--> Cookie: JSESSIONID=92DB3808CE8FCB47D47D997C8B52294A
--> Connection: close
-->

HTTP/1.1 200 OK
Content-Type: text/html;charset=ISO-8859-1
Date: Thu, 08 Aug 2002 18:25:25 GMT
Connection: close
Server: Apache Tomcat/4.0.3 (HTTP/1.1 Connector)

compteur : 4

--> GET /sessions/compteur HTTP/1.1
--> Host: localhost:8080
--> Cookie: JSESSIONID=92DB3808CE8FCB47D47D997C8B52294A
--> Connection: close
-->

HTTP/1.1 200 OK
Content-Type: text/html;charset=ISO-8859-1
Date: Thu, 08 Aug 2002 18:25:25 GMT
Connection: close
Server: Apache Tomcat/4.0.3 (HTTP/1.1 Connector)

compteur : 5

--> GET /sessions/compteur HTTP/1.1
--> Host: localhost:8080
--> Cookie: JSESSIONID=92DB3808CE8FCB47D47D997C8B52294A
--> Connection: close
-->

HTTP/1.1 200 OK
Content-Type: text/html;charset=ISO-8859-1
Date: Thu, 08 Aug 2002 18:25:25 GMT
Connection: close
Server: Apache Tomcat/4.0.3 (HTTP/1.1 Connector)

compteur : 6

Burada görüldüğü gibi, müşterinin ilk çağrısından itibaren sunucu geçerli bir oturum çerezi alır. Tomcat için bir oturumun maksimum hareketsizlik süresinin varsayılan olarak 20 dakika olduğunu (aslında bu ayarlanabilir) bilmek gerekir. Programın ikinci çağrısı, ilk çağrıda alınan çerezi yeterince hızlı bir şekilde gönderirse, sunucu için bu aynı oturum olarak kabul edilir. Burada potansiyel bir güvenlik açığına dikkat çekiyoruz. Eğer ağ üzerinden bir oturum jetonunu ele geçirebilirsem, o oturumu başlatan kişi gibi davranabilirim. Örneğimizde, ilk çağrı oturumu başlatan kişiyi temsil eder (belki de kendisine bir jeton alma hakkı verecek bir kullanıcı adı ve şifre ile) ve ikinci çağrı, ilk çağrının oturum jetonunu "çalan" kişiyi temsil eder. Eğer söz konusu işlem bir banka işlemiyse, bu durum çok can sıkıcı hale gelebilir...

İstemci kodu şu şekildedir:

// ithal edilen paketler
import java.io.*;
import java.net.*;
import java.util.regex.*;

public class clientCompteur{

     // bir URL talep ediyor
     // içeriğini ekranda görüntüler

    public static void main(String[] args){
        // sözdizimi
        final String syntaxe="pg URL-COMPTEUR N [JSESSIONID]";

         // argüman sayısı
        if(args.length !=2 && args.length != 3)
            erreur(syntaxe,1);

         // istenen URL not edilir
        String URLString=args[0];

        // URL'in geçerliliğinin kontrolü
        URL url=null;
        try{
            url=new URL(URLString);
        }catch (Exception ex){
             // URI hatalı
            erreur("L'erreur suivante s'est produite : " + ex.getMessage(),2);
        }//yakalama
         // çağrı sayısı N'nin doğrulanması
        int N=0;
        try{
            N=Integer.parseInt(args[1]);
            if(N<=0) throw new Exception();
        }catch(Exception ex){
             // N argümanı hatalı
            erreur("Le nombre d'appels N doit être un entier >0",3);
        }
         // JSESSIONID jetonu parametre olarak aktarıldı mı?
        String JSESSIONID="";
        if (args.length==3) JSESSIONID=args[2];

        // URL'ten gerekli bilgiler çıkarılıyor
        String path=url.getPath();
        if(path.equals("")) path="/";
        String query=url.getQuery();
        if(query!=null) query="?"+query; else query="";
        String host=url.getHost();
        int port=url.getPort();
        if(port==-1) port=url.getDefaultPort();

         // çalışmaya devam edilebilir
        Socket  client=null;                        // müşteri
        BufferedReader IN=null;                    // müşterinin okuma akışı
        PrintWriter OUT=null;                        // müşterinin yazma akışı
        String réponse=null;                        // sunucunun yanıtı
         // başlıklarda aranan şablon HTTP
        Pattern modèleCookie=Pattern.compile("^Set-Cookie: JSESSIONID=(.*?);");
        // kodda aranan şablon HTML
        Pattern modèleCompteur=Pattern.compile("compteur = .*?(\\d+)");
         // şablonla karşılaştırma sonucu
        Matcher résultat=null;
         // sayaç aramasının sonucunu veren bir boole değeri
        boolean compteurTrouvé;

        try{
             // sunucuya N kez çağrı yapılır
            for(int i=0;i<N;i++){
                // sunucuya bağlanılır
                client=new Socket(host,port);

                // müşterinin giriş-çıkış akışları oluşturulur TCP
                IN=new BufferedReader(new InputStreamReader(client.getInputStream()));
                OUT=new PrintWriter(client.getOutputStream(),true);

                // URL talep ediliyor - başlıklar gönderiliyor HTTP
                envoie(OUT,"GET " + path + query + " HTTP/1.1");
                envoie(OUT,"Host: " + host + ":" + port);
                if(! JSESSIONID.equals("")){
                    envoie(OUT,"Cookie: JSESSIONID="+JSESSIONID);
                }
                envoie(OUT,"Connection: close");
                envoie(OUT,"");

                 // cevap, başlıkların sonuna kadar okunur ve olası bir çerez aranır
                while((réponse=IN.readLine())!=null){
                     // yanıtın takibi
                    System.out.println(réponse);
                     // satır boş mu?
                    if(réponse.equals("")) break;
                     // HTTP satırı boş değil
                     // oturum çerezi yoksa aranır
                    if (JSESSIONID.equals("")){
                        // HTTP satırını çerez şablonuyla karşılaştırılır
                        résultat=modèleCookie.matcher(réponse);
                        if(résultat.find()){
                            // çerez bulundu
                            JSESSIONID=résultat.group(1);
                        }
                    }
                }//while

                 // HTTP başlıkları için işlem tamamlandı - HTML koduna geçilir
                compteurTrouvé=false;
                while((réponse=IN.readLine())!=null){
                     // geçerli satırda sayaç var mı?
                    if (! compteurTrouvé){
                        résultat=modèleCompteur.matcher(réponse);
                        if(résultat.find()){
                            // sayaç bulundu - ekrana yazdırılıyor
                            System.out.println("compteur : " + résultat.group(1));
                            compteurTrouvé=true;
                        }
                    }
                }//while
                 // işlem tamamlandı
                client.close();
            }//for
        } catch(Exception e){
            // istisna işleniyor
            erreur(e.getMessage(),4);
        }//catch
    }//main

     // hataların görüntülenmesi
    public static void erreur(String msg, int exitCode){
         // hata görüntüleme
        System.err.println(msg);
         // hata ile sonlandırma
        System.exit(exitCode);
    }//hata

     // istemci-sunucu iletişiminin izlenmesi
    public static void envoie(PrintWriter OUT,String msg){
        // sunucuya mesaj gönderme
        OUT.println(msg);
         // ekran izleme
        System.out.println("--> "+msg);
    }//hata
}//sınıf

Bu programın önemli noktalarını inceleyelim:

  • N adet istemci-sunucu iletişimi gerçekleştirmemiz gerekiyor. Bu nedenle bunlar bir döngü içinde yer alıyor
            for(int i=0;i<N;i++){
  • Her iletişimde, istemci sunucuyla bir bağlantı kurar TCP-IP. Bağlantı kurulduktan sonra, isteğinin başlıklarını sunucuya gönderir HTTP:
                 // URL isteniyor - başlıkların gönderilmesi HTTP
                envoie(OUT,"GET " + path + query + " HTTP/1.1");
                envoie(OUT,"Host: " + host + ":" + port);
                if(! JSESSIONID.equals("")){
                    envoie(OUT,"Cookie: JSESSIONID="+JSESSIONID);
                }
                envoie(OUT,"Connection: close");
                envoie(OUT,"");

JSESSIONID jetonu mevcutsa, çerez olarak gönderilir; aksi takdirde gönderilmez.

  • İsteği gönderildikten sonra, istemci sunucudan yanıt bekler. Öncelikle, bu yanıtın HTTP başlıklarını inceleyerek olası bir çerez olup olmadığını araştırır. Çerezi bulmak için, aldığı satırları çerezin düzenli ifadesiyle karşılaştırır:
         // başlıklarda aranan şablon HTTP
        Pattern modèleCookie=Pattern.compile("^Set-Cookie: JSESSIONID=(.*?);");
...........................
                 // olası bir çerez aranarak yanıt başlıklarının sonuna kadar okunur
                while((réponse=IN.readLine())!=null){
                     // yanıtın takibi
                    System.out.println(réponse);
                     // satır boş mu?
                    if(réponse.equals("")) break;
                     // HTTP satırı boş değil
                     // oturum çerezi yoksa aranır
                    if (JSESSIONID.equals("")){
                        // HTTP satırını çerez şablonuyla karşılaştırılır
                        résultat=modèleCookie.matcher(réponse);
                        if(résultat.find()){
                            // çerez bulundu
                            JSESSIONID=résultat.group(1);
                        }
                    }
                }//while
  • Jeton ilk kez bulunduğunda, sunucuya yapılan sonraki çağrılarda artık aranmayacaktır. Yanıtın HTTP başlıkları işlendiğinde, aynı yanıtın HTML koduna geçilir. Bu kodda, sayaç değerini veren satır aranır. Bu arama da yine bir düzenli ifade kullanılarak yapılır:
         // HTML kodunda aranan sayaç şablonu
        Pattern modèleCompteur=Pattern.compile("compteur = .*?(\\d+)");
..................................
                 // başlıklar için işlem tamamlandı HTTP - HTML koduna geçiliyor
                compteurTrouvé=false;
                while((réponse=IN.readLine())!=null){
                     // geçerli satırda sayaç var mı?
                    if (! compteurTrouvé){
                        résultat=modèleCompteur.matcher(réponse);
                        if(résultat.find()){
                            // sayaç bulundu - görüntüleniyor
                            System.out.println("compteur : " + résultat.group(1));
                            compteurTrouvé=true;
                        }
                    }
                }//while

4.6. Örnek 4

Önceki örnekte, web istemcisi jetonu bir çerez olarak geri gönderir. Bunun yanı sıra, istenen URL içinde URL;jsessionid=xxx biçiminde de geri gönderebileceğini görmüştük. Bunu kontrol edelim. clientCompteur.java programı, clientCompteur2.java’e dönüştürülür ve şu şekilde değiştirilir:

....
                 // URL isteniyor - HTTP başlıkları gönderiliyor
                if(JSESSIONID.equals(""))
                    envoie(OUT,"GET " + path + query + " HTTP/1.1");
                else envoie(OUT,"GET " + path + query + ";jsessionid=" + JSESSIONID + " HTTP/1.1");
                envoie(OUT,"Host: " + host + ":" + port);
                envoie(OUT,"Connection: close");
                envoie(OUT,"");
....

Dolayısıyla müşteri, GET URL;jsessionid=xx HTTP/1.1 aracılığıyla sayacın URL değerini talep eder ve artık çerez göndermez. Tek değişiklik budur. İşte ilk çağrının sonuçları:


E:\data\serge\Servlets\sessions\jb7>java.bat clientCompteur2 http://localhost:8080/sessions/sayaç 2

--> GET /sessions/compteur HTTP/1.1
--> Host: localhost:8080
--> Connection: close
-->

HTTP/1.1 200 OK
Content-Type: text/html;charset=ISO-8859-1
Date: Thu, 08 Aug 2002 18:49:30 GMT
Connection: close
Server: Apache Tomcat/4.0.3 (HTTP/1.1 Connector)
Set-Cookie: JSESSIONID=48A6DBA8357D808EC012AAF3A2AFDA63;Path=/sessions
cookie trouvÚ : 48A6DBA8357D808EC012AAF3A2AFDA63

compteur : 1

--> GET /sessions/compteur;jsessionid=48A6DBA8357D808EC012AAF3A2AFDA63 HTTP/1.1
--> Host: localhost:8080
--> Connection: close
-->

HTTP/1.1 200 OK
Content-Type: text/html;charset=ISO-8859-1
Date: Thu, 08 Aug 2002 18:49:30 GMT
Connection: close
Server: Apache Tomcat/4.0.3 (HTTP/1.1 Connector)

compteur : 2

İlk çağrıda istemci, oturum jetonu olmadan URL'i ister. Sunucu, jetonu göndererek yanıt verir. Ardından istemci, aldığı jetonu ekleyerek aynı URL'i tekrar sorgular. Sayaçın artmış olduğu görülmektedir; bu da sunucunun bunun aynı oturum olduğunu doğru bir şekilde tanıdığının kanıtıdır.

4.7. Örnek 5

Bu örnek, page0, page1 ve page2 olarak adlandıracağımız üç sayfadan oluşan bir uygulamayı göstermektedir. Kullanıcı bu sayfaları şu sırayla almalıdır:

  • page0, bir bilgi isteyen bir formdur: bir isim
  • page1, page0 formunun gönderilmesine yanıt olarak alınan bir formdur. İkinci bir bilgi ister: yaş
  • page2, page0 aracılığıyla alınan adı ve page1 aracılığıyla alınan yaşı görüntüleyen HTML adlı bir belgedir.

Burada üç adet istemci-sunucu iletişimi vardır:

  • ilk iletişimde page0 formu istemci tarafından talep edilir ve sunucu tarafından gönderilir
  • ikinci iletişimde, page1 formu istemci tarafından talep edilir ve sunucu tarafından gönderilir. İstemci, adı sunucuya gönderir.
  • Üçüncü iletişimde, page3 belgesi istemci tarafından talep edilir ve sunucu tarafından gönderilir. İstemci, yaşı sunucuya gönderir. page3 belgesi, adı ve yaşı görüntülemelidir. Ad, ikinci iletişimde sunucu tarafından alınmış, ancak o andan itibaren "unutulmuştur". Adın üçüncü iletişimde kullanılabilmesi için ikinci iletişimde bir oturum kullanılarak ad kaydedilir.

İlk veri alışverişinde alınan page0 sayfası şöyledir:

Image

Ad alanını dolduruyoruz:

Image

Suite düğmesini kullanıyoruz ve ardından aşağıdaki page1 sayfasını elde ediyoruz:

Image

Yaş alanını dolduruyoruz:

Image

Suite düğmesini kullandığımızda, aşağıdaki page2 sayfası görüntülenir:

Image

page0 sayfasını sunucuya gönderdiğimizde, isim alanı boşsa sunucu bir hata kodu ile geri gönderebilir:

Image

page1 sayfasını sunucuya gönderdiğimizde, yaş bilgisi geçersizse sunucu bir hata kodu ile yanıt verebilir:

Image

Uygulama, bir servlet ve dört sayfadan oluşur: JSP:

page0.jsp
page0 sayfasını görüntüler
page1.jsp
sayfa1'i görüntüler
page2.jsp
sayfa 2'yi göster
erreur.jsp
hata sayfası göster

Web uygulamasının adı suitedepages'dir ve Tomcat'taki server.xml dosyasında şu şekilde yapılandırılmıştır:

                <Context path="/suitedepages" docBase="e:/data/serge/servlets/suitedepages" />

suitedepages uygulamasının web.xml yapılandırma dosyası şu şekildedir:

<?xml version="1.0" encoding="ISO-8859-1"?>

<!DOCTYPE web-app
    PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
    "http://java.sun.com/dtd/web-app_2_3.dtd">

<web-app>
    <servlet>
      <servlet-name>main</servlet-name>
    <servlet-class>main</servlet-class>
    <init-param>
          <param-name>urlPage0</param-name>
        <param-value>/page0.jsp</param-value>
    </init-param>
    <init-param>
          <param-name>urlPage1</param-name>
        <param-value>/page1.jsp</param-value>
    </init-param>
    <init-param>
          <param-name>urlPage2</param-name>
        <param-value>/page2.jsp</param-value>
    </init-param>
    <init-param>
          <param-name>urlErreur</param-name>
        <param-value>/erreur.jsp</param-value>
    </init-param>    
  </servlet>
  <servlet-mapping>
      <servlet-name>main</servlet-name>
    <url-pattern>/main</url-pattern>
  </servlet-mapping>
</web-app>

Ana servletin adı main'dir ve takma adı (servlet eşlemesi) sayesinde URL http://localhost:8080/suitedepages/main üzerinden erişilebilir. Bu servletin dört başlatma parametresi vardır; bunlar, farklı görüntülemeler için kullanılan dört sayfanın URL değerleridir. "main" servletinin kodu şöyledir:

import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
import java.util.*;
import java.util.regex.*;

public class main extends HttpServlet{

    // örnek değişkenleri
    String msgErreur=null;
    String urlPage0=null;
    String urlPage1=null;
    String urlPage2=null;
    String urlErreur=null;

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

        // başlatma işlemi başarılı oldu mu?
        if(msgErreur!=null){
             // hata sayfasına yönlendiriliyor
            getServletContext().getRequestDispatcher(urlErreur).forward(request,response);
        }
         // adım parametresini alıyoruz
        String étape=request.getParameter("etape");
         // cari oturumu alınıyor
        HttpSession session=request.getSession();
         // Current step işleniyor
        if(étape==null) étape0(request,response,session);
        if(étape.equals("1")) étape1(request,response,session);
        if(étape.equals("2")) étape2(request,response,session);
         // diğer durumlar geçersizdir
        étape0(request,response,session);
    }

     //-------- POST
    public void doPost(HttpServletRequest request, HttpServletResponse response)
    throws IOException, ServletException{
        doGet(request,response);
    }

     //-------- INIT
    public void init(){
         // başlatma parametreleri alınır
        ServletConfig config=getServletConfig();
        urlPage0=config.getInitParameter("urlPage0");
        urlPage1=config.getInitParameter("urlPage1");
        urlPage2=config.getInitParameter("urlPage2");
        urlErreur=config.getInitParameter("urlErreur");

         // ayarlar uygun mu?
        if(urlPage0==null || urlPage1==null || urlPage2==null){
            msgErreur="Configuration incorrecte";
        }
    }

     //-------- adım 0
    public void étape0(HttpServletRequest request, HttpServletResponse response, HttpSession session)
            throws IOException, ServletException{
        // bazı öznitelikler belirleniyor
        request.setAttribute("nom","");
         // 0. sayfayı gösteriyoruz
        request.getRequestDispatcher(urlPage0).forward(request,response);
    }

     //-------- adım 1
    public void étape1(HttpServletRequest request, HttpServletResponse response, HttpSession session)
            throws IOException, ServletException{
         // istekten adı alıyoruz
        String nom=request.getParameter("nom");
        // ad ayarlandı ?
        if(nom==null) étape0(request,response,session);
         // adındaki boşluklar varsa kaldırılıyor
        nom=nom.trim();
         // ad, sorgunun bir özniteliğine eklenir
        request.setAttribute("nom",nom);
         // Ad boş mu?
        if(nom.equals("")){
             // bu bir hatadır
            ArrayList erreurs=new ArrayList();
            erreurs.add("Nous n'avez pas indiqué de nom");
             // hataları sorguya eklenir
            request.setAttribute("erreurs",erreurs);
             // 0. sayfaya geri dön
            étape0(request,response,session);
        }
         // geçerli isim - mevcut oturumda saklanıyor
        session.setAttribute("nom",nom);
         // istekte age özniteliği ayarlanır
        request.setAttribute("age","");
         // 1. sayfa görüntülenir
        request.getRequestDispatcher(urlPage1).forward(request,response);
    }

     //-------- 2. adım
    public void étape2(HttpServletRequest request, HttpServletResponse response, HttpSession session)
            throws IOException, ServletException{
         // oturumdan isim alınır
        String nom=(String)session.getAttribute("nom");
         // ad ayarlandı ?
        if(nom==null) étape0(request,response,session);
         // sorgunun bir özniteliğine ekleniyor
        request.setAttribute("nom",nom);
         // yaşı sorgudan alıyoruz
        String age=request.getParameter("age");
        // yaş ayarlandı ?
        if(age==null){
            // 1. sayfaya geri dön
            request.setAttribute("age","");
            request.getRequestDispatcher(urlPage1).forward(request,response);
        }
         // yaş, sorguda saklanır
        age=age.trim();
        request.setAttribute("age",age);
        // yaş geçerli mi?
        if(! Pattern.matches("^\\s*\\d+\\s*$",age)){
            // bu bir hata
            ArrayList erreurs=new ArrayList();
            erreurs.add("Age invalide");
            // hataları sorguya ekleniyor
            request.setAttribute("erreurs",erreurs);
             // 1. sayfaya geri dön
            request.getRequestDispatcher(urlPage1).forward(request,response);
        }
         // yaş geçerli - 2. sayfa görüntüleniyor
        request.getRequestDispatcher(urlPage2).forward(request,response);
    }
}
  • init yöntemi, dört başlatma parametresini alır ve bunlardan biri eksikse bir hata mesajı görüntüler
  • İsteğin üç aşamadan oluştuğunu gördük. Bu aşamalarda hangi aşamada olduğumuzu anlamak için, page0 ve page1 formlarında, değeri 1 (page0) veya 2 (page1) değerlerinden birine sahiptir. Bu numarayı, görüntülenecek bir sonraki sayfa numarası olarak düşünebiliriz. doGet yönteminde, bu parametre istekten alınır ve değerine göre işlem üç farklı yönteme devredilir:
    • étape0, ilk isteği işler ve page0'i gönderir
    • étape1, page0 formunu işler ve page1'i gönderir; hata oluşması durumunda ise tekrar page0'i gönderir
    • 2. adım, page1 formunu işler ve bir hata oluşması durumunda page2’i veya yeniden page1’i gönderir
  • adım 0
    • page0'i boş bir adla görüntüler
  • adım1
    • page0 formundan nom parametresini alır.
    • Adın mevcut olup olmadığını (null olup olmadığını) kontrol eder. Değilse, ilk çağrıymış gibi page0'i tekrar görüntüler.
    • Adın boş olmadığını kontrol eder. Aksi takdirde, bir hata mesajıyla birlikte page0'i tekrar görüntüler.
    • Adı geçerli oturumda saklar ve ad geçerliyse page1'i görüntüler.
  • adım 2
    • Geçerli oturumdan nom parametresini alır.
    • Adın var olup olmadığını (null olup olmadığını) kontrol eder. Eğer değilse, ilk çağrıymış gibi tekrar page0'i görüntüler.
    • page1 tarafından gönderilen mevcut istekte age parametresini alır.
    • yaşın geçerli olup olmadığını kontrol eder. Geçerli değilse, bir hata mesajıyla birlikte page1'i tekrar görüntüler.
    • Adı ve yaşı istek öznitelikleri olarak kaydeder ve ad ile yaş geçerliyse page2 sayfasını görüntüler.

page0.jsp sayfası şöyledir:

<%@ page import="java.util.*" %>

<% // page0.jsp
     // isteğin öznitelikleri alınır
  String nom=(String)request.getAttribute("nom");
  ArrayList erreurs=(ArrayList)request.getAttribute("erreurs");
   // Özellikler geçerli mi?
  if(nom==null){
       // ana servlete geri dönülüyor
    request.getRequestDispatcher("/main").forward(request,response);
  }
%>  

<html>
  <head>
    <title>page 0</title>
  </head>
  <body>
    <h3>Page 0/2</h3>
    <form name="frmNom" method="POST" action="/suitedepages/main">
        <input type="hidden" name="etape" value="1">
      <table>
        <tr>
          <td>Votre nom</td>
          <td><input type="text" name="nom" value="<%= nom %>"></td>
        </tr>
      </table>
      <input type="submit" value="Suite">
    </form>
    <% // Hata var mı?
      if (erreurs!=null){
    %>
      <hr>
      <font color="red">
        Les erreurs suivantes se sont produites
        <ul>
        <% for(int i=0;i<erreurs.size();i++){ %>
            <li><%= erreurs.get(i) %>
        <% }//for %>
        </ul>
     <% }//if %>
  </body>
</html>
  • page0.jsp sayfası, iki durumda ana servlet tarafından çağrılabilir:
    • ilk istek sırasında
    • page0 formunun işlenmesinden sonra bir hata oluştuğunda
  • Görüntülenecek nom parametresi ve varsa hata listesi, ana servlet tarafından bu sayfaya verilir. Dolayısıyla page0.jsp servleti, önce bu iki bilgiyi alır.
  • Form, uygulamanın hangi aşamasında olduğunuzu belirten etape adlı gizli (hidden) alanla birlikte ana servlete "gönderilir".

page1.jsp sayfası şu şekildedir:

<%@ page import="java.util.*" %>

<% // page1.jsp
     // isteğin öznitelikleri alınır
  String nom=(String)request.getAttribute("nom");
  String age=(String)request.getAttribute("age");
  ArrayList erreurs=(ArrayList)request.getAttribute("erreurs");
  // Özellikler geçerli mi?
  if(nom==null || age==null){
      // ana servlete geri dönülür
    request.getRequestDispatcher("/main").forward(request,response);
  }
%>  

<html>
  <head>
    <title>page 1</title>
  </head>
  <body>
    <h3>Page 1/2</h3>
    <form name="frmAge" method="POST" action="/suitedepages/main">
        <input type="hidden" name="etape" value="2">    
      <table>
        <tr>
          <td>Nom</td>
          <td><font color="green"><%= nom %></font></td>
        </tr>
        <tr>
          <td>Votre âge</td>
          <td><input type="text" name="age" size="3" value="<%= age %>"></td>
        </tr>
      </table>
      <input type="submit" value="Suite">
    </form>
    <% // Hata var mı?
      if (erreurs!=null){
    %>
      <hr>
      <font color="red">
        Les erreurs suivantes se sont produites
        <ul>
        <% for(int i=0;i<erreurs.size();i++){ %>
            <li><%= erreurs.get(i) %>
        <% }//for %>
        </ul>
     <% }//if %>
  </body>
</html>

page1.jsp sayfası, page0.jsp sayfasına benzer bir yapıya sahiptir; tek farkı, artık ana servletten iki öznitelik almasıdır: nom ve age. Son olarak, page2.jsp sayfası şu şekildedir:

<% 
     // page2.jsp
     // isteğin öznitelikleri alınır
  String nom=(String)request.getAttribute("nom");
  String age=(String)request.getAttribute("age");
  // Özellikler geçerli mi?
  if(nom==null || age==null){
      // ana servlete geri dönülür
    request.getRequestDispatcher("/main").forward(request,response);
  }
%>  


<html>
  <head>
    <title>page 2</title>
  </head>
  <body>
    <h3>Page 2/2</h3>
      <table>
        <tr>
          <td>Nom</td>
          <td><font color="green"><%= nom %></font></td>
        </tr>
        <tr>
          <td>Votre âge</td>
          <td><font color="green"><%= age %></font></td>
        </tr>
      </table>
  </body>
</html>

page2.jsp sayfası da ana servletten nom ve age özniteliklerini alır. Bu sayfa, sadece bunları görüntüler. Son olarak, servletin yanlış başlatılması durumunda bir hata mesajı görüntülemekle görevli erreur.jsp sayfası şu şekildedir:

<%
     // jspService
   // bir hata oluştu
  String msgErreur= request.getAttribute("msgErreur");
  if(msgErreur==null) msgErreur="Erreur non identifiée";
%>
<!-- sayfanın başlangıcı HTML -->
<html>
  <head>
      <title>Suite de pages</title>
  </head>
  <body>
      <h3>Suite de pages</h3>
      <hr>
    Application indisponible(<%= msgErreur %>)
  </body>
</html>

Ana servlet tarafından kendisine aktarılan msgErreur özniteliğini görüntüler.

Sonuç olarak, uygulamanın üç aşaması boyunca tarayıcının her zaman ilk olarak ana servleti sorguladığı görülebilir. Ancak görüntülenecek yanıtı oluşturan ana servlet değil, dört sayfadan biri olan JSP'tir. Kullanıcı bu durumu fark etmez; çünkü tarayıcı, “Adres” alanında başlangıçta talep edilen, yani ana servletin adresi olan URL’i göstermeye devam eder.