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:
- her istemci için tek bir bağlantı kurulur
- 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:
, 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. | |
cari oturumun kimliği | |
cari oturumun oluşturulma tarihi (1 Ocak 1970, saat 00:00'dan bu yana geçen milisaniye sayısı). | |
Müşteri tarafından oturuma en son erişim tarihi | |
Bir oturumun etkin olmama süresinin saniye cinsinden maksimum süresi. Bu süre dolduğunda oturum geçersiz hale gelir. | |
bir oturumun etkin olmama süresinin maksimum süresini saniye cinsinden belirler. Bu süre dolduğunda oturum geçersiz hale gelir. | |
oturum yeni oluşturulmuşsa "true" | |
belirli bir oturumdaki bir parametreye bir değer atar. Bu mekanizma, oturum boyunca kullanılabilir kalacak bilgilerin saklanmasını sağlar. | |
oturum verilerinden parametre'i kaldırır. | |
oturumun paramètre parametresine atanan değer. null mevcut değilse, bu değeri döndürür. | |
geçerli oturumun tüm özniteliklerinin sıralı listesi | |
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:
Yukarıdaki docBase klasöründe şu öğeler bulunur:

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:

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:

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:
cari oturumun özelliklerini gösteren sayfanın URL'si | |
Mevcut oturumun geçersiz kılınmasından sonra görüntülenen sayfanın URL'si | |
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:
ana servlet - istemcinin isteğini analiz eder:
| |
| |
kullanıcı mevcut oturumu geçersiz kıldığında görüntülenir. Ardından yeni bir oturum oluşturmayı önerir. | |
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:
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:
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:

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

Ö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:

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:

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:

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

İ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:
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:

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

Ş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:

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

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
- 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:

Ad alanını dolduruyoruz:

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

Yaş alanını dolduruyoruz:

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

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

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

Uygulama, bir servlet ve dört sayfadan oluşur: JSP:
page0 sayfasını görüntüler | |
sayfa1'i görüntüler | |
sayfa 2'yi göster | |
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:
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ı mı?
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ı mı?
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ı mı?
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.
