From 04e0a65854e59af3e8e30d46229557cfd896af44 Mon Sep 17 00:00:00 2001 From: Morten Sorvig Date: Mon, 14 Sep 2009 15:30:50 +0200 Subject: Add support for session inactivity timeouts --- src/eventqueue.cpp | 2 +- src/eventqueue.h | 2 +- src/sessionserver.cpp | 5 +++- src/webclient.cpp | 5 ++++ src/webclient.h | 1 + src/webclientserver.cpp | 69 +++++++++++++++++++++++++++++++++++++++++----- src/webclientserver.h | 7 ++++- src/widgeteventhandler.cpp | 4 ++- 8 files changed, 83 insertions(+), 12 deletions(-) diff --git a/src/eventqueue.cpp b/src/eventqueue.cpp index bb4bc41..aeba7d4 100644 --- a/src/eventqueue.cpp +++ b/src/eventqueue.cpp @@ -346,7 +346,7 @@ json_object *EventQueue::jsonTextUpdateEvent(const EventEntry &event) const json_object *EventQueue::toJsonWidgetType(QWidget *widget) const { - if (!widget || m_session->m_server->shouldSkipUpdate(widget->metaObject()->className())) + if (!widget || m_session && m_session->m_server->shouldSkipUpdate(widget->metaObject()->className())) return json_object_new_string("skippedwidget"); if (qobject_cast(widget)) return json_object_new_string("lineedit"); diff --git a/src/eventqueue.h b/src/eventqueue.h index 7d93e1b..13dc055 100644 --- a/src/eventqueue.h +++ b/src/eventqueue.h @@ -55,7 +55,7 @@ public: bool isEmpty() { return events.isEmpty(); } static QByteArray pngCompress(const QImage &image); //private: - Session *m_session; + QPointer m_session; QQueue events; QHash images; QHash compressedImages; diff --git a/src/sessionserver.cpp b/src/sessionserver.cpp index 37ff160..77a189f 100644 --- a/src/sessionserver.cpp +++ b/src/sessionserver.cpp @@ -5,7 +5,6 @@ #include - QWidget *sharedRoot = 0; SessionServer::SessionServer(QWidget *widget, Session *session, Server *server) @@ -14,8 +13,11 @@ SessionServer::SessionServer(QWidget *widget, Session *session, Server *server) sharedRoot = new QWidget(); sharedRoot->resize(1000, 1000); sharedRoot->setAttribute(Qt::WA_DontShowOnScreen); + sharedRoot->setAttribute(Qt::WA_ForceUpdatesDisabled); + sharedRoot->setAttribute(Qt::WA_OpaquePaintEvent); sharedRoot->show(); } + widget->setParent(sharedRoot); widget->move(0,0); rootWidget = widget; @@ -23,6 +25,7 @@ SessionServer::SessionServer(QWidget *widget, Session *session, Server *server) widgetEventHandler = new WidgetEventHandler(widget, server); rootWidget->setAttribute(Qt::WA_DontShowOnScreen); + rootWidget->setAttribute(Qt::WA_OpaquePaintEvent); // try to avoid creating the backingstore rootWidget->setAttribute(Qt::WA_PaintOnScreen); diff --git a/src/webclient.cpp b/src/webclient.cpp index 2777e4f..8a507d8 100644 --- a/src/webclient.cpp +++ b/src/webclient.cpp @@ -44,6 +44,11 @@ void WebClient::setActiveSessionLimitHtml(const QString &html) this->server->activeSessionLimitHtml = html.toUtf8(); } +void WebClient::setInactiveSessionTimeout(int seconds) +{ + this->server->inactiveSessionTimeout = seconds; +} + void WebClient::newSession_internal(Session *session) { QWidget *rootWidget = 0; diff --git a/src/webclient.h b/src/webclient.h index ccbd6c5..5779a65 100644 --- a/src/webclient.h +++ b/src/webclient.h @@ -23,6 +23,7 @@ public: void setActiveSessionLimit(int sessionLimit); void setActiveSessionLimitHtml(const QString &html); + void setInactiveSessionTimeout(int seconds); enum WidgetHint { StaticWidget, NoDisplayWidget }; void setWidgetHint(QWidget *widget, WidgetHint hint); diff --git a/src/webclientserver.cpp b/src/webclientserver.cpp index f29abf0..762664b 100644 --- a/src/webclientserver.cpp +++ b/src/webclientserver.cpp @@ -49,11 +49,11 @@ void HttpRequest::parseText() if (line.startsWith("GET")) { m_path = QUrl::fromPercentEncoding(line.mid(4).split(' ').at(0)).toAscii(); // ### assumes well-formed string } else if (line.startsWith("POST")) { - m_path = QUrl::fromPercentEncoding(line.mid(5).split(' ').at(0)).toAscii(); // ### assumes well-formed string + m_path = QUrl::fromPercentEncoding(line.mid(5).split(' ').at(0)).toAscii(); // ### assumes well-formed string } else if (line.startsWith("Cookie:")) { -// qDebug() << "cookie line" << line.simplified(); +// DEBUG << "cookie line" << line.simplified(); m_cookies = line.mid(7).simplified(); // remove "Cookie:" -// qDebug() << "cookies text" << m_cookies; +// DEBUG << "cookies text" << m_cookies; foreach (const QByteArray cookieText, m_cookies.split(';')){ if (cookieText.contains('=')) { QList cookieParts = cookieText.split('='); @@ -159,7 +159,7 @@ QByteArray HttpResponse::toText() Session::Session(Server *server, int sessionId) :m_sessionId(sessionId), m_idleSocket(0), m_server(server) { - + lastActivityTime = QDateTime::currentDateTime(); } int Session::sessionId() @@ -198,7 +198,7 @@ Server::Server(quint16 port) this->port = port; connect(this, SIGNAL(newConnection()), SLOT(connectionAvailable())); listen(QHostAddress::Any, port); - qDebug() << QString("Server running on: http://" + QHostInfo::localHostName() + ":" + QString::number(port) + "/"); + DEBUG << QString("Server running on: http://" + QHostInfo::localHostName() + ":" + QString::number(port) + "/"); qsrand(QDateTime::currentDateTime().toTime_t()); nextCookieId = qrand(); dynamicBytesWritten = 0; @@ -208,7 +208,10 @@ Server::Server(quint16 port) totalSessions = 0; activeSessionLimit = INT_MAX; activeSessionLimitHtml = "Active session limit exceeded."; + inactiveSessionTimeout = 60 * 10; + connect(&purgeInactiveSessionsTimer, SIGNAL(timeout()), SLOT(purgeInactiveSessions())); + purgeInactiveSessionsTimer.start(1000 / 1); // This didn't work out. Disable for now. sendUpdatesForPlainQWidgets = true; // skipUpdatesClasses.insert("QWidget"); @@ -293,7 +296,7 @@ void Server::dataOnSocket() if (session == 0) { // ### accept unknown sessions for now, TODO do authentication here. - DEBUG << "new session for" << sessionId; + DEBUG << "create new session"; if (totalSessions >= activeSessionLimit) { dynamicBytesWritten += activeSessionLimitHtml.size(); @@ -320,6 +323,8 @@ void Server::dataOnSocket() // DEBUG << "found session for" << sessionId; } + session->lastActivityTime = QDateTime::currentDateTime(); + // Strip away the page ids: "-pageId=" /* int index = request.m_path.indexOf("-pageId="); @@ -372,12 +377,62 @@ void Server::dataOnSocket() // DEBUG << "socket write response done"; } +void Server::purgeInactiveSessions() +{ + DEBUG << "purgeInactiveSessions"; + QDateTime now = QDateTime::currentDateTime(); + + // find where we left off the last time, restart from + // the beginning if not found. + QHash::iterator it = activeSessions.find(lastSessionVisited); + if (it == activeSessions.end()) + it = activeSessions.begin(); + + const int maxSessonsToExamine = 50; // avoid pausing to long. + int i = 0; + while (it != activeSessions.end()) { + if (i > maxSessonsToExamine) { + lastSessionVisited = it.key(); + return; + } + + Session *session = it.value(); + DEBUG << "last act" << session << session->lastActivityTime; + int inactiveSeconds = now.toTime_t() - session->lastActivityTime.toTime_t(); + DEBUG << "inactive" << inactiveSeconds; + if (inactiveSeconds > inactiveSessionTimeout) { + QHash::iterator itToDelete = it; + + // get the next key so the search can continue after the + // erase. (all iterators are invalidated.) + ++it; + if (it == activeSessions.end()) { + delete itToDelete.value(); + activeSessions.erase(itToDelete); + it = activeSessions.end(); + } else { + int newKey = it.key(); + delete itToDelete.value(); + activeSessions.erase(itToDelete); + it = activeSessions.find(newKey); + } + continue; // it has been repositioned. (i has not been incremented - + // there is no limit to how many session we can delete in one go.) + } + ++it; + ++i; + } + + // reached the end, restart for next time. + lastSessionVisited = -1; //actually a valid session value, but most likely a miss. +} + QByteArray Server::createStatiticsPage() { const double ec2DataRate=0.02 / (1000 * 1000 * 1000) ; // cost per byte const double ec2InstanceRate = 0.1; // cost per hour const double upHours = (QDateTime::currentDateTime().toTime_t() - serverStart.toTime_t() / (60.0 * 60.0)); - qDebug() << "up" << upHours << QDateTime::currentDateTime().toTime_t(); + DEBUG << "up" << upHours << QDateTime::currentDateTime().toTime_t(); QByteArray stats; stats += " Statistics

"; stats += ""; diff --git a/src/webclientserver.h b/src/webclientserver.h index 032b101..4c45f31 100644 --- a/src/webclientserver.h +++ b/src/webclientserver.h @@ -74,7 +74,7 @@ public: HttpRequest m_idleRequest; HttpResponse m_idleResponse; Server *m_server; - + QDateTime lastActivityTime; }; class FileServer : public QObject @@ -107,9 +107,13 @@ public slots: private slots: void connectionAvailable(); void dataOnSocket(); +private slots: + void purgeInactiveSessions(); private: void printRequest(const QList &request); QHash activeSessions; + QTimer purgeInactiveSessionsTimer; // kills inactive sessions + int lastSessionVisited; int nextCookieId; quint16 port; FileServer fileServer; @@ -125,6 +129,7 @@ private: public: int activeSessionLimit; QByteArray activeSessionLimitHtml; + int inactiveSessionTimeout; bool sendUpdatesForPlainQWidgets; bool shouldSkipUpdate(const QByteArray &className); QSet skipUpdatesClasses; diff --git a/src/widgeteventhandler.cpp b/src/widgeteventhandler.cpp index 3568c43..b77dc6c 100644 --- a/src/widgeteventhandler.cpp +++ b/src/widgeteventhandler.cpp @@ -108,7 +108,9 @@ void WidgetEventHandler::addPendingUpdate(QWidget* widget, const QRect &rect) bool WidgetEventHandler::eventFilter(QObject *object, QEvent *event) { QWidget *widget = qobject_cast(object); - + extern QWidget *sharedRoot; + if (widget == sharedRoot) + return true; if (event->type() == QEvent::Paint) { if (server->shouldSkipUpdate(widget->metaObject()->className())) { // qDebug() << "skip plain widget" << widget; -- cgit v1.2.3