Online Status eines Benutzers abrufen

beta20

Top Contributor
Hallo,

ich möchte gerne in meine Applikation den Online Status eines Benutzers abrufen.
In der DB zu speichern macht nur bedingt Sinn, da:

a) User kann Browser schließen, ohne sich auszuloggen -> wie bekommt meine Applikation nun mit, dass der User offline ist?

Wie kann ich das realisieren?
 

mrBrown

Super-Moderator
Mitarbeiter
wer 'a' sagt, muss auch 'b' sagen ;)


Regelmäßig den Client an den Server melden lassen, dass er noch online ist, zB mit Websockets oder regelmäßigen normalen Http-Requests.
Wenn er sich meldet ist er online, sonst offline
 

mihe7

Top Contributor
User schließt den Browser. Wie bekomme ich das dann mit?
Das Prinzip ist: wenn sich der User eine Zeit lang nicht meldet, bekommt der Server das mit. Du kannst natürlich clientseitig auch noch versuchen, über ein JS-Event einen entsprechenden Logout-Request abzusetzen. Verlassen würde ich mich darauf aber nicht.
 

beta20

Top Contributor
Ja, solang der User den Browser auf hat, ist das kein Problem.

Bzgl. dem Session Listener: wie mache ich hier eine Verbindung zum User Objekt selbst?
Klar könnte ich die Session ID dem User Objekt hinzufügen und sobald der Session Listener in die expired Methode geht, sucht er die Session Id bei einem User...

Aber:
- Session ID ist nicht aufsteigend, sodass theoretisch mehrere User die gleiche Session haben können
- User könnte sich mehrmals gleichzeitig in verschiedenen Browser einloggen und ich hätte kein 1:1 Mapping zum User mehr...

irgendwelche Ideen?
 

mihe7

Top Contributor
Ja, solang der User den Browser auf hat, ist das kein Problem.
Und wenn der User den Browser nicht auf hat, ist das auch kein Problem. Dann nämlich tut er sich schwer, sich beim Server zu melden.

Klar könnte ich die Session ID dem User Objekt hinzufügen
Wozu?!?

In dem Beispiel unter http://programmersought.com/article/724060146/ wird einfach der Benutzername als Attribut der Session gespeichert und einer Liste (im application scope) hinzugefügt bzw. von dieser entfernt.

Wenn Du mehrere Sessions parallel verwalten willst, brauchst Du lediglich statt einer Liste z. B. eine Map<String,Integer> zu verwenden, die die Anzahl Sessions mitzählt. Beim Anmelden wird der Eintrag mit einem Zähler von 1 eingerichtet bzw. der Zähler erhöht, beim Abmelden wird der Zähler verringert und, wenn er 0 erreicht hat, der Eintrag entfernt.
 

beta20

Top Contributor
Ok, danke....

Aber wo und wann setze ich das:
Code:
String username = (String) session.getAttribute("username");// Remove the username from the online list
List onlineUserList = (List) application.getAttribut("onlineUserList");
 

beta20

Top Contributor
Habe den Code mal testweise bei mir probiert.... Bekomme aber einen Compiler Fehler:
Warum? Wie kann ich das fixen?

Müsste das nicht:
List onlineUserList = (List) application.getAttribute("onlineUserList");

heißen?

12754
 

beta20

Top Contributor
Habe nochmal ein paar Fragen:
1)
- Wie kann ich den Status erst setzen (sessionCreated Methode) ausführen, wenn der User sich einloggt?
-> Derzeit wird sessionCreated ja bereits ausgeführt, sobald der User die Webpage öffnet...

2) Ebenfalls möchte ich die destroy Methode ausführen, sobald der User sich ausloggt...
 

beta20

Top Contributor
1) Genau - eine SessionBean habe ich bereits.... Das heißt aber, dass ich den Status in meiner Datenbank setze, sodass auch andere Clients übergreifend den Status sehen können, korrekt? Wenn ich den Status nur in der SessionBean setze, dann sieht ja nur der eingeloggte User den Status?
 

mihe7

Top Contributor
Was willst Du denn ständig mit Deiner DB? Ob wer online ist oder nicht, ist doch kein persistenter Zustand.

In Deiner SessionBean setzt Du einfach das Attribut username:
Java:
ExternalContext ec = FacesContext.getCurrentInstace().getExternalContext();
Principal principal = ec.getUserPrincipal();
if (principal != null) {
    HttpSession session = (HttpSession) ec.getSession(false);
    if (session != null) {
        session.setAttribute("username", principal.getName());
    }
}

Der HttpSessionBindingListener wird über diese Änderung informiert, holt sich über das Session-Attribut username den Benutzernamen und legt diesen in die anwendungsweit gültige Liste der Online-Benutzer (bzw. die Map mit Zähler).

Die sessionDestroyed-Methode des HttpSessionListeners wird beim Abmelden (oder Timeout) automatisch aufgerufen.
 

beta20

Top Contributor
ok, danke...
Aber brauche ich zwingend Principal?

In:
session.setAttribute("username", MYVALUE);

-> In MYVALUE könnte ja auch einfach die ID von meinem User - Objekt dann stehen, oder?
 

beta20

Top Contributor
danke....

Habe nochmal eine weitere Frage:
Nun würde ich den Online Status gerne in meiner JSF Seite abrufen....
Wie kann ich das machen?

1) Frage
Stelle mir sowas vor:
-> Wie fülle ich aber "event"
Java:
    public boolean checkOnlineStatus(ChatUser chatUser) {

        HttpSession session = event.getSession();

        // Get the login username
        ServletContext application = session.getServletContext();

        List onlineUserList = (List) application.getAttribute("onlineUserList");

        if (onlineUserList.contains(chatUser.getId()))
            return true;

        else

            return false;
    }

2) Frage
Ich will nicht nur true oder false zurückgeben. Ich möchte mind.
- Online
- Abwesend
- Offline

Wie kann ich das machen?
Könnte mir dann sowas vorstellen:
Java:
    public String checkOnlineStatus(ChatUser chatUser) {

        HttpSession session = event.getSession();

        // Get the login username
        ServletContext application = session.getServletContext();

        List onlineUserList = (List) application.getAttribute("onlineUserList");

        if (onlineUserList.contains(chatUser.getId())){

// Prüfen wann Client zuletzt aktiv war? Wie kann ich das prüfen?
// Wenn kleiner 5 min
 return "ONLINE",
else 
return "AWAY";
}
       
        else
return "OFFLINE";

            return false;
    }

Irgendeine Idee?
 

mihe7

Top Contributor
Wie fülle ich aber "event"
Gar nicht. Du kommst an die Liste über den FacesContext (bzw. ExternalContext) ran:
Java:
ExternalContext ec = FacesContext.getCurrentInstace().getExternalContext();
List onlineUserList = (List) ec.getApplicationMap().get("onlineUserList");

Wie kann ich das machen?
Erstens kannst Du in den Application Scope nicht nur Strings sondern auch andere (serialisierbare) Objekte ablegen. Im einfachsten Fall könntest Du statt einer List eine Map<String, Instant> verwenden, die einen Benutzernamen auf einen Zeitstempel abbildet, der den Zeipunkt der letzten Aktivität angibt. Bei jeder (relevanten) Aktivität musst Du dann den Zeitstempel aktualisieren. Damit kannst Du feststellen, wie lange die letzte Aktivität her war.
 

beta20

Top Contributor
Danke!
Nun... "onlineUserList" ist mir noch nicht wirklich klar... Wann und wo passiert das?
Im Moment verwende ich das nur bei sessionDestroyed aber irgendwann muss diese Liste doch auch erstellt werden?


Java:
public class ChatOnlineListener implements HttpSessionListener {

    @Inject
    private LoginChatController loginChatController;

    private final Logger LOGGER = LoggerFactory.getLogger(ChatOnlineListener.class);

    /**
     * Session created
     */
    public void sessionCreated(HttpSessionEvent event) {

        LOGGER.info("START sessionCreated");

        try {
            final HttpSession session = event.getSession();
            InetAddress addr = InetAddress.getLocalHost();

            String ip = addr.getHostAddress().toString();
            session.setAttribute("ip", ip);
            
            HashMap<Long, Date> map = new HashMap<Long, Date>();
            map.put(loginChatController.getChatUser().getId(), new Date());
            session.setAttribute("username", map);

            LOGGER.info("Session created");
        } catch (Exception e) {
            LOGGER.error("Error");
        }
    }

    /**
     * Destroyed
     */
    public void sessionDestroyed(HttpSessionEvent event) {

        LOGGER.info("START sessionDestroyed");

        HttpSession session = event.getSession();

        // Get the login username
        ServletContext application = session.getServletContext();

        // Remove the username from the online list
        String username = (String) session.getAttribute("username");
        List onlineUserList = (List) application.getAttribute("onlineUserList");
        onlineUserList.remove(username);

        LOGGER.info(username + "timeout.");

        LOGGER.info("END sessionDestroyed");
    }
}
 

beta20

Top Contributor
Also wenn ich eine LoginBean habe (SessionScoped), dann sollte das so aussehen?
-> In der Init Methode....

Java:
@PostConstruct
    [B]public[/B] [B]void[/B] init() {

// mein ChatUser Objekt holen aus DB...
....
               
HashMap<Long, Date> map = new HashMap<Long, Date>();
                    map.put(chatUser.getId(), new Date());
                    FacesContext facesContext = FacesContext.getCurrentInstance();
                    HttpSession session = (HttpSession) facesContext.getExternalContext().getSession(true);
                    session.setAttribute("username", map);
                   ServletContext application = session.getServletContext();
                    List<HashMap<Long, Date>> onlineUserList = (List<HashMap<Long, Date>>) application  .getAttribute("onlineUserList");

                    if (onlineUserList == null || onlineUserList.isEmpty()) {
                        List<HashMap<Long, Date>> list = new ArrayList<HashMap<Long, Date>>();
                        list.add(map);
                        session.setAttribute("onlineUserList", list);

                    }


Hier mal meine Online Check Methode (Rückgabe ist gerade noch "test", da onlineUserList immer NULL ist ? Warum?

Java:
@ViewScoped
@Named
public class ChatOnlineCheck implements Serializable {

    private static final long serialVersionUID = -6695529018699874351L;
    private final Logger LOGGER = LoggerFactory.getLogger(ChatOnlineCheck.class);
    
    @Inject
    private LoginChatController loginChatController;
    
    @PostConstruct
    public void init() {
        checkOnlineStatus(loginChatController.getChatUser());
    }
    
    /**
     * Online Status prüfen
     *
     * @param chatUser
     * @return
     */
    public String checkOnlineStatus(ChatUser chatUser) {

        LOGGER.info("START checkOnlineStatus");
        
        FacesContext facesContext = FacesContext.getCurrentInstance();
        HttpSession session = (HttpSession) facesContext.getExternalContext().getSession(true);
        
        ServletContext application = session.getServletContext();
        
        @SuppressWarnings("unchecked")
        List<HashMap<Long, Date>> onlineUserList = (List<HashMap<Long, Date>>) application
                .getAttribute("onlineUserList");
        
        if (onlineUserList != null && !onlineUserList.isEmpty()) {
            
            for(HashMap<Long, Date> map : onlineUserList) {
                map.containsKey(chatUser.getId());
            }           
        }

        LOGGER.info("END checkOnlineStatus");
        return "test";
    }
}

Was ist denn hier falsch?
 

mihe7

Top Contributor
Rückgabe ist gerade noch "test", da onlineUserList immer NULL ist ? Warum?
Die Rückgabe ist immer "test", auch wenn onlineUserList nicht null ist.

Außerdem:
session.setAttribute("onlineUserList", list);
Da müsste schon application statt session stehen.

Außerdem:
List<HashMap<Long, Date>> onlineUserList
Wozu brauchst Du eine Liste von HashMaps?!?
 

beta20

Top Contributor
OK, habe es geändert zu:
Java:
ServletContext application = session.getServletContext();
application.setAttribute("onlineUserList", list);

Was soll ich sonst als HashMaps verwenden? Ich will doch Username und Datum speichern?

Wie sollte das dann aussehen?
Java:
                ServletContext application = session.getServletContext();
                    List<HashMap<Long, Date>> onlineUserList = (List<HashMap<Long, Date>>) application
                            .getAttribute("onlineUserList");
                    if (onlineUserList == null || onlineUserList.isEmpty()) {
                        List<HashMap<Long, Date>> list = new ArrayList<HashMap<Long, Date>>();
                        list.add(map);
                        application.setAttribute("onlineUserList", list);
                    }


Ebenfalls dann in der Session Destroyed Methode:
Java:
    /**
     * Destroyed
     */
    public void sessionDestroyed(HttpSessionEvent event) {
....
        List onlineUserList = (List) application.getAttribute("onlineUserList");
        onlineUserList.remove(username);

    }

Und in meiner checkOnline Methode:
Java:
/**
     * Online Status prüfen
     *
     * @param chatUser
     * @return
     */
    public String checkOnlineStatus(ChatUser chatUser) {

        LOGGER.info("START checkOnlineStatus");
        
        FacesContext facesContext = FacesContext.getCurrentInstance();
        HttpSession session = (HttpSession) facesContext.getExternalContext().getSession(true);
        
        ServletContext application = session.getServletContext();
        
        @SuppressWarnings("unchecked")
        List<HashMap<Long, Date>> onlineUserList = (List<HashMap<Long, Date>>) application
                .getAttribute("onlineUserList");
        
        if (onlineUserList != null && !onlineUserList.isEmpty()) {
            
            for(HashMap<Long, Date> map : onlineUserList) {
                map.containsKey(chatUser.getId());
            }           
        }

        LOGGER.info("END checkOnlineStatus");
        return "test";
    }


Warum ist return immer "test" ? Ich will dochl ausgeben:
a) ob User ONLINE, AWAY, OFFLINE
b) Wenn AWAY, dann ebenfalls die Zeit speichern
-> Also könnte mir vorstellen eine Klasse mit zwei Attributen zu erstellen (status, timeAway) zu erstellen und das Objekt der Klasse dann zurückgeben?
 

mihe7

Top Contributor
Ich will doch Username und Datum speichern?
Map<String, Date> würde Benutzernamen auf Daten (pl. von Datum) abbilden.

Java:
Map<String, Date> onlineUserList = (Map<String, Date>) application
                .getAttribute("onlineUserList");

onlineUserList.put(username, new Date()); // letzten Zugriff des Benutzers speichern

Warum ist return immer "test" ? Ich will dochl ausgeben:
Naja, wenn Du das willst, musst Du das dem Rechner auch sagen. Aktuell iterierst Du über einfach über eine Liste und gibst dann "test" zurück.

Also könnte mir vorstellen eine Klasse mit zwei Attributen zu erstellen (status, timeAway) zu erstellen und das Objekt der Klasse dann zurückgeben?
Ja, eine Klasse ist eine gute Idee. Wenn Du noch den Benutzernamen mit aufnimmst, dann würde das Objekt alle Informationen enthalten, die Du anzeigen kannst.
 

beta20

Top Contributor
Könnte ich dann auch anstatt String meine Klasse (MyClass) verwenden, wo ich dann den User speichere?

Map<MyClass, Date> onlineUserList = (Map<MyClass, Date>) application
.getAttribute("onlineUserList");
 

mihe7

Top Contributor
Könnte ich dann auch anstatt String meine Klasse (MyClass) verwenden, wo ich dann den User speichere?
Könntest Du (wenn Du equals und hashCode implementierst und die dort verwendeten Attribute unveränderlich sind), allerdings: warum sollte ein Objekt dieser Klasse ein Schlüssel sein?

Mach es doch einfach andersrum:
Java:
Map<String, MyClass> onineUserList;
Der Schlüssel wäre dann immer noch der Benutzername.
 
Ähnliche Java Themen
  Titel Forum Antworten Datum
F Architekturvorschläge für Online-Wörterbuch? Allgemeines EE 5
M Online Formular Allgemeines EE 7
M Online-Fragebogen mit JSP oder Servlet? Allgemeines EE 2
W JSF Validator Problem HTTP Status 500 Allgemeines EE 2
Antoras Status code explizit von der Webapp aus senden Allgemeines EE 2
byte Tomcat Deployment Problem (HTTP Status 503) Allgemeines EE 8
K Status des Sevrers via Email? Allgemeines EE 10
WetWer Aufbau eines JSP EE Projekts Allgemeines EE 4
O JSF Login mit Hilfe eines Sharepoint 2013 Users Allgemeines EE 4
R Mehrere Bilder gleichzeitig bzw. dynamisch eines Objektes speichern Allgemeines EE 2
R JPA Problem beim Speichern eines Users Allgemeines EE 2
S Aufruf eines EJBs aus einer nativen Java-Applikation Allgemeines EE 1
O JBoss und die Einbindung eines externen JAR Allgemeines EE 10
DStrohma Innerhalb eines Webservices die reine SOAP Nachricht ausgaben Allgemeines EE 2
A Probleme bei der Einbindung eines Liferay Portalserver (Glassfish) Allgemeines EE 7
S Validierung eines Datums Allgemeines EE 3
TheDarkRose Überlegungen zur Modularität eines Projekts und Pluginartige Programmierung Allgemeines EE 2
M Frage zum Einsatz eines loggers Allgemeines EE 2
G Rollen eines Benutzers ermitteln Allgemeines EE 16
M Wie zeige ich Attribute eines Objekts innerhalb einer JSP an Allgemeines EE 2
isowiz Positionierung innerhalb eines <h:commandLink> Allgemeines EE 7
D Controller-Klassen eines Servlets testen mit JUnit Allgemeines EE 3
S Struts - Direktaufruf eines URL verhindern Allgemeines EE 11
J init-Methode eines Servlet ausführen ohne vorherigen request Allgemeines EE 2
G Servlet beim Absenden eines Formulars aufrufen Allgemeines EE 11
M Builden eines Web Service Projekts scheitert Allgemeines EE 6
B Ursprung eines Requests Allgemeines EE 5
F Aufbau eines Content managment systems Allgemeines EE 8
M Pfad eines Bildes angeben? Allgemeines EE 1

Ähnliche Java Themen

Neue Themen


Oben