ΚΕΝΤΡΟ ΠΛΗ.ΝΕ.Τ. Ν. ΦΛΩΡΙΝΑΣ

Η Τεχνολογία JSP (JavaServer Pages)
(Μέρος 5 - Cookies και Sessions)

 

Παρακολούθηση της Συνόδου Χρήστη (User Session)

Πολλά Web sites στηρίζονται στην παρακολούθηση (καταγραφή) των ατομικών πληροφοριών και της κατάστασης των χρηστών. Το HTTP είναι το πρωτόκολλο που είναι υπεύθυνο για την αποστολή των ιστοσελίδων και τις αιτήσεις και αποκρίσεις ανάμεσα στους φυλλομετρητές των clients και τους servers. Το HTTP δεν είναι ρυθμισμένο για να διατηρεί τις πληροφορίες για την κατάσταση (state) των clients. Κάποιες λύσεις, όπως τα cookies και οι σύνοδοι χρήστη (user sessions), έχουν επινοηθεί για να μπορέσει να ξεπερασθεί αυτό το πρόβλημα.

Θα δούμε τρεις μηχανισμούς τους οποίους χρησιμοποιούν τα JSPs για να διατηρούν μια ισχυρή αλληλεπίδραση, που είναι η χρήση των cookies, των αντικειμένων συνόδου (session objects) και της κωδικοποίησης URL.

 

Το Πρωτόκολλο HTTP

Το WWW σχεδιάσθηκε γύρω από έναν απλό μηχανισμό αίτησης/απόκρισης (request/response). Οι φυλλομετρητές που εκτελούνται στα μηχανήματα των clients αιτούν τα HTML έγγραφα (ιστοσελίδες) και οι servers αποκρίνονται παραδίδοντας αυτά τα έγγραφα. Οι κανόνες και οι μηχανισμοί για τη μεταφορά των HTML εγγράφων αποκαλούνται το πρωτόκολλο HTTP (Hypertext Transfer Protocol). Οι servers συνήθως δέχονται αιτήσεις από πολλούς διαφορετικούς clients και ασχολούνται μ’ έναν συγκεκριμένο client μέχρις ότου ικανοποιήσουν μια αίτηση με μια απόκριση. Μετά απ’ αυτό, οι servers ξεχνούν αυτόν τον client και συνεχίζουν για να ικανοποιήσουν αποκρίσεις από άλλους clients.

Το HTTP δεν παρέχει μηχανισμούς για να μπορέσει να διακρίνει έναν client από τον επόμενο, έτσι ώστε αν δύο αιτήσεις συμβεί να προέλθουν από τον ίδιο client, ο server να μην είναι σε θέση να το καταλάβει. Μ’ αυτή την έννοια, το HTTP αναφέρεται σαν ένα stateless (άνευ υπηκοότητας) πρωτόκολλο. Αυτό το χαρακτηριστικό επέβαλε έναν περιορισμό στο τι μπορεί να κάνει ένα Web site. Οι servers δεν είναι σε θέση να θυμούνται στοιχεία πληροφορίας για έναν συγκεκριμένο χρήστη εφόσον δεν υπάρχει κάποιος τρόπος διαχωρισμού των χρηστών μεταξύ τους.

Για να μπορέσουμε να κατανοήσουμε αυτό το πρόβλημα, ας φανταστούμε ένα Web site που περιέχει έναν online κατάλογο απ’ όπου θα μπορεί ο χρήστης να επιλέξει πολλά προϊόντα και να τα τοποθετήσει σ’ ένα online καλάθι αγορών (shopping cart). Καθώς ο χρήστης κάνει κλικ σε διαφορετικούς υπερσυνδέσμους και πλήκτρα, δημιουργεί ξεχωριστές αιτήσεις (requests). Την ίδια στιγμή υπάρχουν κι άλλοι χρήστες που κάνουν αγορές από το ίδιο Web site.

Ο server θα πρέπει να μπορεί να παρακολουθεί ποιες αιτήσεις ανήκουν σε ποιον χρήστη έτσι ώστε τα προϊόντα να τοποθετούνται σε ξεχωριστά καλάθια αγορών. Ο server θα πρέπει να είναι σε θέση να διατηρεί την κατάσταση (state) του κάθε χρήστη αλλά το HTTP δεν το επιτρέπει αυτό. Για να μπορέσει να λυθεί αυτό το πρόβλημα, η Netscape επινόησε την ιδέα του cookie. Τα cookies είναι κομμάτια πληροφοριών και συσχετισμένων μηχανισμών που αποθηκεύουν πληροφορίες που έχουν σχέση με τον χρήστη στα μηχανήματα των clients εξ ονόματος του server. Οι πληροφορίες αυτές στέλνονται μετά στον server κάθε φορά που ο φυλλομετρητής κάνει μια αίτηση (request) στον ίδιο server.

Οι πληροφορίες σ’ ένα cookie μπορεί να περιέχουν ο,τιδήποτε και σε γενικές γραμμές χρησιμοποιούνται για να διακρίνουν τους διάφορους χρήστες που έχουν πρόσβαση στον ίδιο Web server. Ένα cookie μπορεί να περιέχει στοιχεία όπως το username, ένα ID, προσωπικές πληροφορίες ή ο,τιδήποτε άλλο που η εφαρμογή (application) του Web αποφασίσει να χρησιμοποιήσει για να μπορεί να παρακολουθεί συγκεκριμένους χρήστες.

Μια εφαρμογή (application) του Web ορίζει ένα cookie ζητώντας από τον φυλλομετρητή να δημιουργήσει ένα cookie στο μηχάνημα του client όπου εκτελείται ο φυλλομετρητής. Το πλήθος των πληροφοριών που περιέχει ένα cookie καθώς και ο αριθμός των cookies που μπορούν να γίνουν επιτρεπτά είναι συνήθως περιορισμένα επειδή ένας server δεν θα πρέπει να αφήνεται ελεύθερος να χρησιμοποιεί το μηχάνημα του client σαν απομακρυσμένη συσκευή αποθήκευσης.

Οι φυλλομετρητές μπορούν να ρυθμιστούν ώστε να μην δέχονται τα cookies και στην περίπτωση αυτή μερικές εφαρμογές (applications) του Web θα αρνηθούν κάποιες από τις υπηρεσίες τους οι οποίες θα πρέπει κατά τη λειτουργία τους να καταγράφουν τις κινήσεις των χρηστών. Τα JSPs παρέχουν τρεις τρόπους για να υπάρχει μια επικοινωνία ανάμεσα στον φυλλομετρητή στην πλευρά του πελάτη (client side) και την εφαρμογή του server : τα cookies, τις συνόδους (sessions) και την κωδικοποίηση των URLs.

 

Αποθήκευση και Ανάκτηση των Cookies

Τα cookies δημιουργούνται από την εφαρμογή στην πλευρά του server (server-side application), δηλ. το JSP, και αποθηκεύονται στο μηχάνημα του client εξ ονόματος του JSP. Τα JSPs μπορούν να προσθέσουν cookies στο μηχάνημα του client χρησιμοποιώντας τη μέθοδο (συνάρτηση) addCookie() του αντικειμένου response. Η μέθοδος addCookie() λαμβάνει ένα στιγμιότυπο (instance) της τάξης (class) Cookie, η οποία μπορεί να περιέχει πληροφορίες σχετικά με τον χρήστη.

Το μηχάνημα του client αποθηκεύει τα cookies στον τοπικό σκληρό δίσκο σ’ έναν φάκελο που ορίζεται από τον φυλλομετρητή που εκτελείται στο μηχάνημα του client. Τα cookies ανακτώνται από την JSP από το μηχάνημα του client όταν ο πελάτης (client) στείλει μια αίτηση (request) στο JSP. Τα JSPs έχουν πρόσβαση στα cookies χρησιμοποιώντας τη μέθοδο getCookies() του αντικειμένου request. Η μέθοδος getCookies() επιστρέφει έναν πίνακα (array) από αντικείμενα Cookie. Τα cookies παριστάνονται από την τάξη Cookie σαν αντικείμενα τάξης (class objects) στην πλευρά του server.

 

Μια Εφαρμογή με Cookies

Θα δούμε τη χρήση των cookies μ’ ένα παράδειγμα κώδικα JSP. Πρόκειται για ένα μικρό Web site που παρακολουθεί (καταγράφει) τις προτιμήσεις του χρήστη. Το site περιέχει ένα μικρό portal (πύλη) που παρέχει πληροφορίες για τους τέσσερις νομούς της περιφέρειας Δυτικής Μακεδονίας (Γρεβενών, Καστοριάς, Κοζάνης και Φλώρινας). Το portal δίνει τη δυνατότητα στους χρήστες (επισκέπτες) του να το προσαρμόσουν όπως θέλουν, παρέχοντας links για να ενεργοποιούν ή απενεργοποιούν όποιο νομό θέλουν. Όταν ο χρήστης κάνει κλικ στο link on/off του νομού που θέλει, το portal κάνει την αντίστοιχη ενέργεια.

Το portal χρησιμοποιεί cookies για να αποθηκεύσει τις προτιμήσεις των χρηστών στα μηχανήματά τους. Όταν ένας χρήστης επιστρέψει αργότερα στο portal, η εφαρμογή ανακτά τα cookies και εμφανίζει μια προσαρμοσμένη εμφάνισή του ανάλογα με τις προτιμήσεις που έχουν αποθηκευθεί στα cookies. Αυτό πρακτικά σημαίνει ότι διαφορετικοί χρήστες είναι πολύ πιθανό να δουν διαφορετικά πράγματα.

<html>

<head>

<title> Παράδειγμα Χρήσης των Cookies </title>

</head>

<body>

  <h1> Περιφέρεια Δυτικής Μακεδονίας </h1>

  <%  String[] cookieNames = {"grevena", "kastoria", "kozani", "florina"};

String cookieName = null;

String[] cookieValues = {"on", "on", "on", "on"};

String cookieValue = null;

Cookie[] cookies = request.getCookies();

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

            boolean cookieExists = false;

            for (k=0; k<cookieNames.length; k++) {

                        cookieName = cookies[j].getName();

                        cookieValue = cookies[j].getValue();

                        if (cookieName.equals(cookieNames[k])) {

                                    cookieExists = true;

                                    cookieValues[k] = cookieValue;

                        }

            }

            if (!cookieExists) {

                        response.addCookie(new Cookie(cookieName, "on"));

            }

}

            String Nomos = request.getParameter("Nomos");

            if (Nomos != null) {

              for (int j=0; j<cookieValues.length; j++) {

                if (Nomos.equals(cookieNames[j])) {

                  if (cookieValues[j].equals("on")) {

                        response.addCookie(new Cookie(Nomos, "off"));

                        cookieValues[j] = "off";

                   } else {

                        response.addCookie(new Cookie(Nomos, "on"));

                        cookieValues[j] = "on";

                   }

                }

            }

  } %>

<table><tr>

<% for (int j=0; j<cookieNames.length; j++) { %>

            <td>

<%      if (cookieValues[j].equals("on")) {

                        String pageName = cookieNames[j] + ".html"; %>

                        <jsp:include page="<%=pageName%>" flush="true" />

<%      } %>

  <a href=dytmaked.jsp?Nomos=<%=cookieNames[j]%>>on/off</a>

            </td>

<%      } %>

</tr></table>

</body>

</html>

Στις πρώτες γραμμές δηλώνονται τα ονόματα των cookies και οι προκαθορισμένες (default) τιμές τους σε αντίστοιχους πίνακες (arrays). Τα πραγματικά cookies ανακτώνται από το αντικείμενο request και τη μέθοδό του getCookies(). Ακολουθεί σε δύο φωλιασμένες εντολές for η σύγκριση όλων των cookies που είναι αποθηκευμένα στο τοπικό μηχάνημα με τα τέσσερα cookies που είναι αποθηκευμένα στον πίνακα.

Αν βρεθεί ισότητα, ανακτώνται οι τιμές τους και αποθηκεύονται στον πίνακα cookieValues[]. Αλλιώς, δημιουργούνται με μια αρχική τιμή on. Οι επόμενες εντολές χειρίζονται τα requests για να ενεργοποιηθούν ή απενεργοποιηθούν οι επιλογές για τους τέσσερις νομούς. Χρησιμοποιούμε την παράμετρο request με όνομα Nomos και είναι η τιμή της που καθορίζει ποιος νομός θα τεθεί on ή off. Η τιμή της παραμέτρου αυτής συγκρίνεται με τα ονόματα των cookies και ενημερώνεται το αντίστοιχο cookie στο τοπικό μηχάνημα με τη μέθοδο addCookie() αλλά και στον πίνακα cookieValues[].

Το τελευταίο κομμάτι του κώδικα δημιουργεί έναν πίνακα (table) με τους τέσσερις νομούς χρησιμοποιώντας το directive include της JSP. Ο κάθε νομός έχει έναν σύνδεσμο που δείχνει προς την ιστοσελίδα dytmaked.jsp και στην οποία περνάει την παράμετρο Nomos ώστε να αλλάζει η τιμή on ή off ανάλογα. Η χρήση των cookies είναι δύσκολη επειδή θα πρέπει να γράφουμε ειδικό κώδικα για να μπορέσουμε να τα επεξεργαστούμε και δεν διαθέτουν μια ευκίνητη δομή δεδομένων. Ένας καλύτερος τρόπος για να καταγράφουμε τα στοιχεία του χρήστη είναι να χρησιμοποιούμε τα αντικείμενα session στην πλευρά του server, τα οποία είναι μοναδικά για τον κάθε χρήστη.

 

Οι Σύνοδοι Χρήστη (User Sessions)

Το interface HttpSession δίνει στα JSPs τη δυνατότητα να αποθηκεύσουν και να μοιραστούν πληροφορίες για μια αλληλεπίδραση (interaction) μ’ έναν χρήστη που βρίσκεται σε εξέλιξη. Μπορούμε να έχουμε πρόσβαση στο interface HttpSession μέσω της μεθόδου getSession() του interface HttpServletRequest. Είδαμε κάποιες μεθόδους του interface HttpServletRequest, αλλά εδώ θα δούμε τις μεθόδους που έχουν σχέση με τη διαχείριση των συνόδων (sessions).

Μέσα σ’ ένα JSP το αντικείμενο session υλοποιεί ένα στιγμιότυπο (instance) του interface HttpSession. Το αντικείμενο session χρησιμοποιείται σαν ένα σχεδιάγραμμα για τα JSPs μιας εφαρμογής e-commerce ώστε να παρακολουθούν τα προσωρινά δεδομένα του χρήστη με τον οποίο αλληλεπιδρούν. Τα δεδομένα θα πρέπει να είναι μόνιμα αποθηκευμένα σε μια βάση δεδομένων. Θα δούμε αργότερα την αλληλεπίδραση με τις βάσεις δεδομένων με χρήση των JSPs και JDBC.

Ο κάθε μεμονωμένος χρήστης που ξεκινάει μια αλληλεπίδραση μ’ ένα οποιοδήποτε JSP μιας εφαρμογής συσχετίζεται μ’ ένα μοναδικό αντικείμενο session. Αυτό γίνεται δημιουργώντας ένα μοναδικό ID για τον κάθε χρήστη που αλληλεπιδρά με την εφαρμογή και αποθηκεύοντάς το σαν ένα cookie στο μηχάνημα του πελάτη (client). Σε κάθε αίτηση (request) που κάνει ο πελάτης προς την εφαρμογή του server, χρησιμοποιείται το cookie που περιέχει το ID του πελάτη ώστε να μπορέσει να ξεχωρίσει ανάμεσα από πολλούς άλλους χρήστες. Το ID χρησιμοποιείται για να μπορέσει να καθορίσει το σωστό στιγμιότυπο του session για τον αντίστοιχο χρήστη.

Αυτός ο μηχανισμός επιτρέπει στα JSPs να διατηρούν μια ιδιωτική συνομιλία με τον κάθε χρήστη καθώς αυτός πηγαίνει από σελίδα σε σελίδα. Τα JSPs έχουν τη δυνατότητα να αποθηκεύσουν και να ανακτήσουν τα προσωρινά δεδομένα ενός session χρησιμοποιώντας τις μεθόδους setAttribute() και getAttribute(). Αυτές οι μέθοδοι επιτρέπουν στους developers να συνδέσουν ένα αντικείμενο που περιέχει δεδομένα του χρήστη σ’ ένα string ώστε να μπορούν αυτά να ανακτηθούν με το όνομα.

 

Παράδειγμα με Αντικείμενο Συνόδου & Καλάθι Αγορών

Θα δούμε τώρα ένα παράδειγμα χρήσης ενός αντικειμένου session για να μπορούμε να παρακολουθούμε τα περιεχόμενα ενός καλαθιού αγορών (shopping cart). Η εφαρμογή αποτελείται από ένα JSP που είναι ένας online κατάλογος αγορών όπου οι χρήστες μπορούν να επιλέξουν προϊόντα (είδη) και να τα προσθέσουν σ’ ένα καλάθι αγορών. Τα συγκεκριμένα προϊόντα είναι διάφορα μουσικά CD. Θα μπορούμε να βλέπουμε συνέχεια τα περιεχόμενα του καλαθιού αγορών καθώς ο χρήστης θα μπορεί να προσθέτει και να αφαιρεί προϊόντα. Το αρχείο θα έχει το όνομα cds.jsp.

<html>

   <head><title>Ένα Παράδειγμα Χρήσης Cookies</title></head>

   <body>

            <h1> Online Κατάλογος CDs</h1>

            <table>

            <% String[] titles={"Ρεμπέτικα", "Νησιώτικα", "Κλασική Μουσική", "Παλιά Λαϊκά", "Νέο Κύμα"};

                        for (int j=0; j<titles.length; j++) { %>

                           <tr><td><%=titles[j]%></td>

                           <td><a href=cds.jsp?itemName=<%=titles[j]%>>Προσθήκη στο Καλάθι Αγορών</a></td>

                           </tr>

            <% } %></table>

<a href=cds.jsp?itemName=emptyCart>Άδειασμα του Καλαθιού Αγορών</a>

            <% String item=request.getParameter("itemName");

               if (item != null && item.equals("emptyCart")) {

                        Enumeration attributeNames=session.getAttributeNames();

                        while (attributeNames.hasMoreElements()) {

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

                           Session.removeAttribute(attributeName);

                        }

            } else if (item != null) {

                        String attributeName = item + "CD";

                        Session.setAttribute(attributeName, item);

            }

            %><hr><h1> Περιεχόμενα του Καλαθιού Αγορών </h1><ul>

<% Enumeration attributeNames=session.getAttributeNames();

               if (attributeNames != null)

                        while (attributeNames.hasMoreElements()) {

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

                           String attributeValue=(String)session.getAttribute(attributeName); %>

                           <li><%=attributeValue%>

            <% } %>

   </body>

</html>

Δημιουργούμε τον κατάλογο των CDs χρησιμοποιώντας έναν πίνακα με δύο στήλες, όπου η πρώτη στήλη εμφανίζει τους τίτλους των CDs και η άλλη στήλη περιέχει συνδέσμους (links) προς την ίδια σελίδα JSP για προσθήκη του τίτλου στο καλάθι αγορών (shopping cart). Ο σύνδεσμος μεταβιβάζει μ’ ένα query string ένα όρισμα (argument) με το όνομα μεταβλητής itemName για να μπορούμε να αναγνωρίσουμε το αντίστοιχο CD. Αφού τελειώσει η εμφάνιση του πίνακα, δημιουργούμε και έναν σύνδεσμο για να μπορέσουμε να αδειάσουμε το καλάθι αγορών, όπου στο όρισμα itemName δίνουμε την τιμή emptyCart.

Αποθηκεύουμε το όρισμα που μεταβιβάζεται στο JSP ως μια string μεταβλητή με όνομα item. Αν το item δεν είναι κενό (null) και είναι ίσο με την τιμή emptyCart, τότε μ’ έναν βρόχο while διασχίζουμε όλα τα attributes του session και τα διαγράφουμε όλα. Αλλιώς, αν το item είναι μόνο διάφορο του κενού, τότε θα περιέχει ένα τουλάχιστον όνομα CD, οπότε προσθέτουμε το item στο session. Ακολουθεί η εκτύπωση του περιεχομένου του καλαθιού αγορών μ’ έναν βρόχο while και με μια λίστα με κουκκίδες.

 

Κωδικοποίηση των URLs

Τα cookies και τα sessions δουλεύουν μόνο αν οι χρήστες έχουν ενεργοποιήσει τα cookies στους φυλλομετρητές τους. Πολλοί χρήστες απενεργοποιούν τα cookies για διάφορους λόγους, όπως αν δεν θέλουν να παρακολουθούνται ή αν δεν θέλουν να αφήνουν ίχνη στα μηχανήματά τους για το ποιες ιστοσελίδες έχουν επισκεφθεί. Αν και ο χρήστης έχει κάθε δικαίωμα να το κάνει αυτό, είναι γεγονός ότι μερικές εφαρμογές είναι αδύνατο να τρέξουν χωρίς να κρατάνε κάποιες πληροφορίες για τους χρήστες. Μερικά sites απαιτούν από τους χρήστες να έχουν ενεργοποιήσει τα cookies στους φυλλομετρητές τους ώστε να μπορέσουν να δουλέψουν κάποια στοιχεία του site.

Οι εφαρμογές του Web σε γενικές γραμμές δεν θα πρέπει να παίρνουν ως δεδομένο το ότι τα cookies είναι ενεργοποιημένα και θα πρέπει να χρησιμοποιούν την κωδικοποίηση URL (URL encoding) για να διατηρούν μια επικοινωνία με τις εφαρμογές του πελάτη (client). Για παράδειγμα, ας δούμε δύο ξεχωριστούς χρήστες που περιηγούνται σ’ έναν online κατάλογο, οι σελίδες του οποίου δημιουργούνται με JSPs. Τα URLs που βρίσκονται στους συνδέσμους των σελίδων του καταλόγου μπορούν να έχουν ειδικές παραμέτρους και τιμές στο query string. Οι παράμετροι και οι τιμές διαχωρίζουν τους χρήστες χωρίς να υπάρχει ανάγκη για χρήση των cookies.

Το ID του session μπορεί να αποθηκευθεί είτε σ’ ένα cookie ή στο κωδικοποιημένο URL. Το κωδικοποιημένο URL που θα προκύψει θα περιέχει ένα ID που χρησιμοποιείται για να συσχετίσει τον χρήστη μ’ ένα αντικείμενο HttpSession. Έτσι, ακόμη κι αν δεν είναι ενεργοποιημένα τα cookies, η εφαρμογή του Web μπορεί να κάνει χρήση ενός αντικειμένου session που συσχετίζεται με συγκεκριμένους χρήστες.

Η μέθοδος encodeURL() είναι σε θέση να αναγνωρίσει αν το URL θα πρέπει να κωδικοποιηθεί ή όχι. Αν τα cookies είναι ενεργοποιημένα, η μέθοδος encodeURL() δεν θα κωδικοποιήσει το URL και θα επιστρέψει το αρχικό URL. Διαφορετικά, θα κωδικοποιήσει (ενσωματώσει) ένα session ID μέσα στο URL. Το επόμενο παράδειγμα δείχνει τη χρήση των κωδικοποιημένων URLs. Ο JSP κώδικας δημιουργεί έναν σύνδεσμο και ένα πλήκτρο εντολής (button) με τη μέθοδο encodeURL(). Η κωδικοποίηση του URL γίνεται στην περίπτωση που ο χρήστης έχει απενεργοποιήσει τα cookies.

<html>

   <head> <title> Κωδικοποίηση URLs </title>

   </head>

   <body>

            <b> Παράδειγμα Κωδικοποιημένου Συνδέσμου </b>

            <%      String link = response.encodeURL("pageLink.html");

                        String button = response.encodeURL("pageButton.html");

                        String sessionID = request.getRequestedSessionId();

            %>

            <a href="<%=link%>"> Κάντε κλικ εδώ </a><hr>

            <form method=post action="<%=button%>">

                        <input type=submit value="Υποβολή">

            </form><hr>

            <b>Session ID</b>

            <%=sessionID%>

   </body>

</html>

 

back.gif (9867 bytes)

Επιστροφή