Dieser Artikel soll einen Überblick über das Session Objekt in Grails geben und Lösungsmöglichkeiten bei Anwendungsfehlern aufzeigen.
Die allgegenwärtige Session
In allen Controllern ist die Session bereits standardmäßig im Application-Scope und wartet gebrauchsfertig mit dem Namen "session" auf Verwendung. Das Objekt implementiert im wesentlichen die Standardfunktionalität der javax.servlet.HttpSession und hält ergänzend die Zugriffsmöglichkeiten einer GroovyMap bereit.
session.username = 'John Doe'
println session["username"] oder
println session.username
Das Objekt selbst wird im Hintergrund über request.getSession() initialisiert, daher hat man auch nach einem session.invalidate()
bzw. einem Timeout beim nächsten Actionaufruf (Achtung: Nicht im selben Controller!) eine neue Session. Am Beispiel einer Nutzer-Authentifizierung soll der Zugriff verdeutlich werden:
//Im Interceptor ist die Authentifizierung definiert, die vor jeder Action ausgeführt werden soll
def beforeInterceptor = [action:this.&auth,except:'login']//Falls kein Nutzer in der Session gefunden wird, erfolgt eine Weiterleitung zur Login-Action
def auth() {
if(!session.user) {
redirect(action:'login')
return false
}
}
def login = {
// zeige Login-Seite
}
Session Timeouts
Wie oben bereits angesprochen, muss man sich kein neues Session Objekt nach einem Timeout besorgen. Leider hat dieser Mechanismus aber den Nachteil, dass man dadurch nicht merkt ob eine neue Session durch den Timeout einer Vorhergehenden erzeugt wurde und Rückschlüsse darauf lassen nur eine neue ID und die fehlenden Session-Attribute zu.
Braucht ein Nutzer z.B. zu lange um eine Formular auszufüllen, bekommt er beim nächsten Klick den Zugriff verweigert oder der aktuelle Bearbeitungssatus wird zurückgesetzt. Sicher ist dies in den meisten Fällen eines Session-Timeouts der Fall, aber hier ist der unberechtigte Zugriff mit dem Timeout gleichgesetzt und der Nutzer kann schlecht explizit auf eine abgelaufene Session hingewiesen werden.
Eine Möglichkeit herauszufinden ob die aktuelle Session noch existiert und eine entsprechende Meldung auszugeben, wäre einen Parameter zu setzen und diesen vor einer Action abzufragen:
static beforeInterceptor = [action:this&checkSession, except:'login']
def checkSession = {
if(!session.timeout)
render("Session Timeout!")
}
AfterInterceptor Verwendung
Die Möglichkeiten eines Interceptors sind vielfältig, sollten aber gerade bei Ausführung nach Methodenaufrufen mit bedacht eingesetzt werden.
Beispiel: Man definiert in einer Basisklasse den Zugriff auf die Session per AfterInterceptor (z.B. für einen Logger) und leitet diese dann in einem Controller ab. In einer neuen Action wird nun die Session ungültig gemacht und als Ergebnis löst der Aufruf der Session-Variable im Interceptor automatisch eine IllegalStateException
aus.
Dieser Aufruf kann auch schon die blosse Ausgabe der Session sein, da das Objekt im Controller nicht mehr exisiert. Im nachfolgenden Codeauszug wird der eben beschriebene Fall veranschaulicht:
class BasisController {
def afterInterceptor = {
//wirft eine IllegalStateException
println session
}
…
}class UserInterface extends BasisController {
//Erzeugt nach dem render eine IllegalStateException
def logout = {
session.invalidate()
render(view:'logout')
}
}
Ein Workaround wäre, im Interceptor nicht die Session-Variable des Controller zu nutzen. Stattdessen sollte man die Session direkt aus dem Request holen:
class BasisController {
def afterInterceptor = {
//gibt die Session aus oder null, wenn diese ungültig wurde
println request.getSession(false)
}
…
}
Zugriffe ohne Session
Ein großer Nachteil der automatischen Sessionvergabe war bislang auch die Tatsache, dass bei einzelnen Controlleraufrufen (z.B. durch einen externen Service), ebenfalls eine Session erzeugt wurde, obwohl diese gar nicht benötigt wurde. So hatte man bei einem Aufruf durch einen Webservice nach jedem Request immer eine Session, welche unbenutzt auf den Timeout wartete.
Seit Grails 1.0.1 wird nur nach der expliziten Nutzung des Session Objektes im Controller eine neue Session angelegt. Siehe dazu http://jira.codehaus.org/browse/GRAILS-1238.