summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMorten Sorvig <msorvig@trolltech.com>2009-09-14 15:30:50 +0200
committerMorten Sorvig <msorvig@trolltech.com>2009-09-14 15:30:50 +0200
commit04e0a65854e59af3e8e30d46229557cfd896af44 (patch)
tree4490b3c182f6b9f95d0ccb5a8d3e58dc2ec0824d
parent71b49b98fb8f410af992f551beab66d7b21d699d (diff)
Add support for session inactivity timeouts
-rw-r--r--src/eventqueue.cpp2
-rw-r--r--src/eventqueue.h2
-rw-r--r--src/sessionserver.cpp5
-rw-r--r--src/webclient.cpp5
-rw-r--r--src/webclient.h1
-rw-r--r--src/webclientserver.cpp69
-rw-r--r--src/webclientserver.h7
-rw-r--r--src/widgeteventhandler.cpp4
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<QLineEdit *>(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<Session> m_session;
QQueue<EventEntry> events;
QHash<quintptr, QImage> images;
QHash<quintptr, QByteArray> 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 <json.h>
-
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<QByteArray> 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 = "<html><body>Active session limit exceeded.</body></html>";
+ 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<int, Session *>::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<int, Session *>::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 += "<b> Statistics </b><br><br>";
stats += "<table border=1>";
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<QByteArray> &request);
QHash<int, Session *> 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<QByteArray> 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<QWidget *>(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;