├── .gitignore ├── QtWebApp ├── Doxyfile ├── QtWebApp.pro ├── doc │ ├── copyright.txt │ ├── lgpl-3.0.txt │ └── releasenotes.txt ├── httpserver │ ├── httpconnectionhandler.cpp │ ├── httpconnectionhandler.h │ ├── httpconnectionhandlerpool.cpp │ ├── httpconnectionhandlerpool.h │ ├── httpcookie.cpp │ ├── httpcookie.h │ ├── httpglobal.cpp │ ├── httpglobal.h │ ├── httplistener.cpp │ ├── httplistener.h │ ├── httprequest.cpp │ ├── httprequest.h │ ├── httprequesthandler.cpp │ ├── httprequesthandler.h │ ├── httpresponse.cpp │ ├── httpresponse.h │ ├── httpserver.pri │ ├── httpsession.cpp │ ├── httpsession.h │ ├── httpsessionstore.cpp │ ├── httpsessionstore.h │ ├── staticfilecontroller.cpp │ └── staticfilecontroller.h ├── logging │ ├── dualfilelogger.cpp │ ├── dualfilelogger.h │ ├── filelogger.cpp │ ├── filelogger.h │ ├── logger.cpp │ ├── logger.h │ ├── logging.pri │ ├── logglobal.h │ ├── logmessage.cpp │ └── logmessage.h ├── mainpage.dox ├── qtservice │ ├── qtservice.cpp │ ├── qtservice.h │ ├── qtservice.pri │ ├── qtservice_p.h │ ├── qtservice_unix.cpp │ ├── qtservice_win.cpp │ ├── qtunixserversocket.cpp │ ├── qtunixserversocket.h │ ├── qtunixsocket.cpp │ └── qtunixsocket.h └── templateengine │ ├── template.cpp │ ├── template.h │ ├── templatecache.cpp │ ├── templatecache.h │ ├── templateengine.pri │ ├── templateglobal.h │ ├── templateloader.cpp │ └── templateloader.h ├── QtWebAppExample.pro ├── README.md ├── common ├── common.pro ├── html-static │ ├── favicon-32x32.png │ ├── favicon.png │ └── style.css ├── html │ ├── common.htm │ ├── first.htm │ ├── index.htm │ └── second.htm ├── httpsettings.hpp ├── main.cpp ├── resources.qrc ├── webconfigurator.cpp ├── webconfigurator.h ├── webconfiguratorpage.cpp └── webconfiguratorpage.h └── license /.gitignore: -------------------------------------------------------------------------------- 1 | # C++ objects and libs 2 | 3 | *.slo 4 | *.lo 5 | *.o 6 | *.a 7 | *.la 8 | *.lai 9 | *.so 10 | *.dll 11 | *.dylib 12 | 13 | # Qt-es 14 | 15 | /.qmake.cache 16 | /.qmake.stash 17 | *.pro.user 18 | *.pro.user.* 19 | *.qbs.user 20 | *.qbs.user.* 21 | *.moc 22 | moc_*.cpp 23 | qrc_*.cpp 24 | ui_*.h 25 | Makefile* 26 | *build-* 27 | 28 | # QtCreator 29 | 30 | *.autosave 31 | 32 | #QtCtreator Qml 33 | *.qmlproject.user 34 | *.qmlproject.user.* 35 | -------------------------------------------------------------------------------- /QtWebApp/Doxyfile: -------------------------------------------------------------------------------- 1 | PROJECT_NAME = QtWebApp 2 | OUTPUT_DIRECTORY = doc 3 | JAVADOC_AUTOBRIEF = YES 4 | QUIET = YES 5 | INPUT = templateengine logging httpserver qtservice mainpage.dox 6 | INPUT_ENCODING = UTF-8 7 | RECURSIVE = YES 8 | SOURCE_BROWSER = YES 9 | GENERATE_TREEVIEW = YES 10 | GENERATE_LATEX = NO 11 | -------------------------------------------------------------------------------- /QtWebApp/QtWebApp.pro: -------------------------------------------------------------------------------- 1 | # Build this project to generate a shared library (*.dll or *.so). 2 | 3 | TARGET = QtWebApp 4 | TEMPLATE = lib 5 | QT -= gui 6 | CONFIG += staticlib 7 | VERSION = 1.6.4 8 | 9 | mac { 10 | QMAKE_MAC_SDK = macosx10.10 11 | QMAKE_CXXFLAGS += -std=c++11 12 | CONFIG += c++11 13 | QMAKE_LFLAGS_SONAME = -Wl,-install_name,/usr/local/lib/ 14 | } 15 | 16 | win32 { 17 | DEFINES += QTWEBAPPLIB_EXPORT 18 | } 19 | 20 | # Windows and Unix get the suffix "d" to indicate a debug version of the library. 21 | # Mac OS gets the suffix "_debug". 22 | CONFIG(debug, debug|release) { 23 | win32: TARGET = $$join(TARGET,,,d) 24 | mac: TARGET = $$join(TARGET,,,_debug) 25 | unix:!mac: TARGET = $$join(TARGET,,,d) 26 | } 27 | 28 | DISTFILES += doc/* mainpage.dox Doxyfile 29 | OTHER_FILES += ../readme.txt 30 | 31 | include(qtservice/qtservice.pri) 32 | include(logging/logging.pri) 33 | include(httpserver/httpserver.pri) 34 | include(templateengine/templateengine.pri) 35 | -------------------------------------------------------------------------------- /QtWebApp/doc/copyright.txt: -------------------------------------------------------------------------------- 1 | This program and its source is distributed under the Lesser General Public License. 2 | http://www.gnu.org/licenses/lgpl.html 3 | 4 | When you modify the library, you have to publish your modified version for free under LGPL license. 5 | 6 | Author and Copyright owner: Stefan Frings 7 | stefan@stefanfrings.de 8 | http://www.stefanfrings.de 9 | 10 | The qtservice module had been published by Trolltech/Nokia under the LGPL license. 11 | https://qt.gitorious.org/qt-solutions/qt-solutions/source/qtservice 12 | -------------------------------------------------------------------------------- /QtWebApp/doc/lgpl-3.0.txt: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | 9 | This version of the GNU Lesser General Public License incorporates 10 | the terms and conditions of version 3 of the GNU General Public 11 | License, supplemented by the additional permissions listed below. 12 | 13 | 0. Additional Definitions. 14 | 15 | As used herein, "this License" refers to version 3 of the GNU Lesser 16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 17 | General Public License. 18 | 19 | "The Library" refers to a covered work governed by this License, 20 | other than an Application or a Combined Work as defined below. 21 | 22 | An "Application" is any work that makes use of an interface provided 23 | by the Library, but which is not otherwise based on the Library. 24 | Defining a subclass of a class defined by the Library is deemed a mode 25 | of using an interface provided by the Library. 26 | 27 | A "Combined Work" is a work produced by combining or linking an 28 | Application with the Library. The particular version of the Library 29 | with which the Combined Work was made is also called the "Linked 30 | Version". 31 | 32 | The "Minimal Corresponding Source" for a Combined Work means the 33 | Corresponding Source for the Combined Work, excluding any source code 34 | for portions of the Combined Work that, considered in isolation, are 35 | based on the Application, and not on the Linked Version. 36 | 37 | The "Corresponding Application Code" for a Combined Work means the 38 | object code and/or source code for the Application, including any data 39 | and utility programs needed for reproducing the Combined Work from the 40 | Application, but excluding the System Libraries of the Combined Work. 41 | 42 | 1. Exception to Section 3 of the GNU GPL. 43 | 44 | You may convey a covered work under sections 3 and 4 of this License 45 | without being bound by section 3 of the GNU GPL. 46 | 47 | 2. Conveying Modified Versions. 48 | 49 | If you modify a copy of the Library, and, in your modifications, a 50 | facility refers to a function or data to be supplied by an Application 51 | that uses the facility (other than as an argument passed when the 52 | facility is invoked), then you may convey a copy of the modified 53 | version: 54 | 55 | a) under this License, provided that you make a good faith effort to 56 | ensure that, in the event an Application does not supply the 57 | function or data, the facility still operates, and performs 58 | whatever part of its purpose remains meaningful, or 59 | 60 | b) under the GNU GPL, with none of the additional permissions of 61 | this License applicable to that copy. 62 | 63 | 3. Object Code Incorporating Material from Library Header Files. 64 | 65 | The object code form of an Application may incorporate material from 66 | a header file that is part of the Library. You may convey such object 67 | code under terms of your choice, provided that, if the incorporated 68 | material is not limited to numerical parameters, data structure 69 | layouts and accessors, or small macros, inline functions and templates 70 | (ten or fewer lines in length), you do both of the following: 71 | 72 | a) Give prominent notice with each copy of the object code that the 73 | Library is used in it and that the Library and its use are 74 | covered by this License. 75 | 76 | b) Accompany the object code with a copy of the GNU GPL and this license 77 | document. 78 | 79 | 4. Combined Works. 80 | 81 | You may convey a Combined Work under terms of your choice that, 82 | taken together, effectively do not restrict modification of the 83 | portions of the Library contained in the Combined Work and reverse 84 | engineering for debugging such modifications, if you also do each of 85 | the following: 86 | 87 | a) Give prominent notice with each copy of the Combined Work that 88 | the Library is used in it and that the Library and its use are 89 | covered by this License. 90 | 91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 92 | document. 93 | 94 | c) For a Combined Work that displays copyright notices during 95 | execution, include the copyright notice for the Library among 96 | these notices, as well as a reference directing the user to the 97 | copies of the GNU GPL and this license document. 98 | 99 | d) Do one of the following: 100 | 101 | 0) Convey the Minimal Corresponding Source under the terms of this 102 | License, and the Corresponding Application Code in a form 103 | suitable for, and under terms that permit, the user to 104 | recombine or relink the Application with a modified version of 105 | the Linked Version to produce a modified Combined Work, in the 106 | manner specified by section 6 of the GNU GPL for conveying 107 | Corresponding Source. 108 | 109 | 1) Use a suitable shared library mechanism for linking with the 110 | Library. A suitable mechanism is one that (a) uses at run time 111 | a copy of the Library already present on the user's computer 112 | system, and (b) will operate properly with a modified version 113 | of the Library that is interface-compatible with the Linked 114 | Version. 115 | 116 | e) Provide Installation Information, but only if you would otherwise 117 | be required to provide such information under section 6 of the 118 | GNU GPL, and only to the extent that such information is 119 | necessary to install and execute a modified version of the 120 | Combined Work produced by recombining or relinking the 121 | Application with a modified version of the Linked Version. (If 122 | you use option 4d0, the Installation Information must accompany 123 | the Minimal Corresponding Source and Corresponding Application 124 | Code. If you use option 4d1, you must provide the Installation 125 | Information in the manner specified by section 6 of the GNU GPL 126 | for conveying Corresponding Source.) 127 | 128 | 5. Combined Libraries. 129 | 130 | You may place library facilities that are a work based on the 131 | Library side by side in a single library together with other library 132 | facilities that are not Applications and are not covered by this 133 | License, and convey such a combined library under terms of your 134 | choice, if you do both of the following: 135 | 136 | a) Accompany the combined library with a copy of the same work based 137 | on the Library, uncombined with any other library facilities, 138 | conveyed under the terms of this License. 139 | 140 | b) Give prominent notice with the combined library that part of it 141 | is a work based on the Library, and explaining where to find the 142 | accompanying uncombined form of the same work. 143 | 144 | 6. Revised Versions of the GNU Lesser General Public License. 145 | 146 | The Free Software Foundation may publish revised and/or new versions 147 | of the GNU Lesser General Public License from time to time. Such new 148 | versions will be similar in spirit to the present version, but may 149 | differ in detail to address new problems or concerns. 150 | 151 | Each version is given a distinguishing version number. If the 152 | Library as you received it specifies that a certain numbered version 153 | of the GNU Lesser General Public License "or any later version" 154 | applies to it, you have the option of following the terms and 155 | conditions either of that published version or of any later version 156 | published by the Free Software Foundation. If the Library as you 157 | received it does not specify a version number of the GNU Lesser 158 | General Public License, you may choose any version of the GNU Lesser 159 | General Public License ever published by the Free Software Foundation. 160 | 161 | If the Library as you received it specifies that a proxy can decide 162 | whether future versions of the GNU Lesser General Public License shall 163 | apply, that proxy's public statement of acceptance of any version is 164 | permanent authorization for you to choose that version for the 165 | Library. 166 | -------------------------------------------------------------------------------- /QtWebApp/doc/releasenotes.txt: -------------------------------------------------------------------------------- 1 | Dont forget to update the release number also in 2 | QtWebApp.pro and httpserver/httpglobal.cpp. 3 | 4 | 1.6.4 5 | 27.03.2016 6 | Fixed constructor of Template class did not load the source file properly. 7 | Template loader and cache were not affected. 8 | 9 | 1.6.3 10 | 11.03.2016 11 | Fixed compilation error. 12 | Added missing implementation of HttpRequest::getPeerAddress(). 13 | 14 | 1.6.2 15 | 06.03.2016 16 | Added mime types for some file extensions. 17 | 18 | 1.6.1 19 | 25.01.2016 20 | Fixed parser of boundary value in multi-part request, which caused that 21 | QHttpMultipart did not work on client side. 22 | 23 | 1.6.0 24 | 29.12.2015 25 | Much better output buffering, reduces the number of small IP packages. 26 | 27 | 1.5.13 28 | 29.12.2015 29 | Improved performance a little. 30 | Add support for old HTTP 1.0 clients. 31 | Add HttpResposne::flush() and HttpResponse::isConnected() which are helpful to support 32 | SSE from HTML 5 specification. 33 | 34 | 1.5.12 35 | 11.12.2015 36 | Fix program crash when using SSL with a variable sized thread pool on Windows. 37 | Changed name of HttpSessionStore::timerEvent() to fix compiler warnings since Qt 5.0. 38 | Add HttpRequest::getRawPath(). 39 | HttpSessionStore::sessions is now protected. 40 | 41 | 1.5.11 42 | 21.11.2015 43 | Fix project file for Mac OS. 44 | Add HttpRequest::getPeerAddress() and HttpResponse::getStatusCode(). 45 | 46 | 1.5.10 47 | 01.09.2015 48 | Modified StaticFileController to support ressource files (path starting with ":/" or "qrc://"). 49 | 50 | 1.5.9 51 | 06.08.2015 52 | New HttpListener::listen() method, to restart listening after close. 53 | Add missing include for QObject in logger.h. 54 | Add a call to flush() before closing connections, which solves an issue with nginx. 55 | 56 | 1.5.8 57 | 26.07.2015 58 | Fixed segmentation fault error when closing the application while a HTTP request is in progress. 59 | New HttpListener::close() method to simplifly proper shutdown. 60 | 61 | 1.5.7 62 | 20.07.2015 63 | Fix Qt 5.5 compatibility issue. 64 | 65 | 1.5.6 66 | 22.06.2015 67 | Fixed compilation failes if QT does not support SSL. 68 | 69 | 1.5.5 70 | 16.06.2015 71 | Improved performance of SSL connections. 72 | 73 | 1.5.4 74 | 15.06.2015 75 | Support for Qt versions without OpenSsl. 76 | 77 | 1.5.3 78 | 22.05.2015 79 | Fixed Windows issue: QsslSocket cannot be closed from other threads than it was created in. 80 | 81 | 1.5.2 82 | 12.05.2015 83 | Fixed Windows issue: QSslSocket cannot send signals to another thread than it was created in. 84 | 85 | 1.5.1 86 | 14.04.2015 87 | Add support for pipelining. 88 | 89 | 1.5.0 90 | 03.04.2015 91 | Add support for HTTPS. 92 | 93 | 1.4.2 94 | 03.04.2015 95 | Fixed HTTP request did not work if it was split into multipe IP packages. 96 | 97 | 1.4.1 98 | 20.03.2015 99 | Fixed session cookie expires while the user is active, expiration time was not prolonged on each request. 100 | 101 | 1.4.0 102 | 14.03.2015 103 | This release has a new directory structure and new project files to support the creation of a shared library (*.dll or *.so). 104 | 105 | 1.3.8 106 | 12.03.2015 107 | Improved shutdown procedure. 108 | New config setting "host" which binds the listener to a specific network interface. 109 | 110 | 1.3.7 111 | 14.01.2015 112 | Fixed setting maxMultiPartSize worked only with file-uploads but not with form-data. 113 | 114 | 1.3.6 115 | 16.09.2014 116 | Fixed DualFileLogger produces no output. 117 | 118 | 1.3.5 119 | 11.06.2014 120 | Fixed a multi-threading issue with race condition in StaticFileController. 121 | 122 | 1.3.4 123 | 04.06.2014 124 | Fixed wrong content type when the StaticFileController returns a cached index.html. 125 | 126 | 1.3.3 127 | 17.03.2014 128 | Improved security of StaticFileController by denying "/.." in any position of the request path. 129 | Improved performance of StaticFileController a little. 130 | New convenience method HttpResponse::redirect(url). 131 | Fixed a missing return statement in StaticFileController. 132 | 133 | 1.3.2 134 | 08.01.2014 135 | Fixed HTTP Server ignoring URL parameters when the request contains POST parameters. 136 | 137 | 1.3.1 138 | 15.08.2013 139 | Fixed HTTP server not accepting connections on 64bit OS with QT 5. 140 | 141 | 1.3.0 142 | 20.04.2013 143 | Updated for compatibility QT 5. You may still use QT 4.7 or 4.8, if you like. 144 | Also added support for logging source file name, line number and function name. 145 | 146 | 1.2.13 147 | 03.03.2013 148 | Fixed Logger writing wrong timestamp for buffered messages. 149 | Improved shutdown procedure. The webserver now processes all final signals before the destructor finishes. 150 | 151 | 1.2.12 152 | 01.03.2013 153 | Fixed HttpResponse sending first part of data repeatedly when the amount of data is larger than the available memory for I/O buffer. 154 | 155 | 1.2.11 156 | 06.01.2013 157 | Added clearing the write buffer when accepting a new connection, so that it does not send remaining data from an aborted previous connection (which is possibly a bug in QT). 158 | 159 | 1.2.10 160 | 18.12.2012 161 | Reduced memory usage of HttpResponse in case of large response. 162 | 163 | 1.2.9 164 | 29.07.2012 165 | Added a mutex to HttpConnectionHandlerPool to fix a concurrency issue when a pooled object gets taken from the cache while it times out. 166 | Modified HttpConnectionHandler so that it does not throw an exception anymore when a connection gets closed by the peer in the middle of a read. 167 | 168 | 1.2.8 169 | 22.07.2012 170 | Fixed a possible concurrency issue when the file cache is so small that it stores less files than the number of threads. 171 | 172 | 1.2.7 173 | 18.07.2012 174 | Fixed HttpRequest ignores additional URL parameters of POST requests. 175 | Fixed HttpRequest ignores POST parameters of body if there is no Content-Type header. 176 | Removed unused tempdir variable from HttpRequest. 177 | Added mutex to cache of StaticFileController to prevent concurrency problems. 178 | Removed HTTP response with status 408 after read timeout. Connection gets simply closed now. 179 | 180 | 1.2.6 181 | 29.06.2012 182 | Fixed a compilation error on 64 bit if super verbose debugging is enabled. 183 | Fixed a typo in static file controller related to the document type header. 184 | 185 | 1.2.5 186 | 27.06.2012 187 | Fixed error message "QThread: Destroyed while thread is still running" during program termination. 188 | 189 | 1.2.4 190 | 02.06.2012 191 | Fixed template engine skipping variable tokens when a value is shorter than the token. 192 | 193 | 1.2.3 194 | 26.12.2011 195 | Fixed null pointer error when the HTTP server aborts a request that is too large. 196 | 197 | 1.2.2 198 | 06.11.2011 199 | Fixed compilation error on 64 bit platforms. 200 | 201 | 1.2.1 202 | 22.10.2011 203 | Fixed a multi-threading bug in HttpConnectionHandler. 204 | 205 | 1.2.0 206 | 05.12.2010 207 | Added a controller that serves static files, with cacheing. 208 | 209 | 210 | 1.1.0 211 | 19.10.2010 212 | Added support for sessions. 213 | Separated the base classes into individual libraries. 214 | 215 | 1.0.0 216 | 17.10.2010 217 | First release 218 | -------------------------------------------------------------------------------- /QtWebApp/httpserver/httpconnectionhandler.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | @file 3 | @author Stefan Frings 4 | */ 5 | 6 | #include "httpconnectionhandler.h" 7 | #include "httpresponse.h" 8 | 9 | HttpConnectionHandler::HttpConnectionHandler(QSettings* settings, HttpRequestHandler* requestHandler, QSslConfiguration* sslConfiguration) 10 | : QThread() 11 | { 12 | Q_ASSERT(settings!=0); 13 | Q_ASSERT(requestHandler!=0); 14 | this->settings=settings; 15 | this->requestHandler=requestHandler; 16 | this->sslConfiguration=sslConfiguration; 17 | currentRequest=0; 18 | busy=false; 19 | 20 | // Create TCP or SSL socket 21 | createSocket(); 22 | 23 | // execute signals in my own thread 24 | moveToThread(this); 25 | socket->moveToThread(this); 26 | readTimer.moveToThread(this); 27 | 28 | // Connect signals 29 | connect(socket, SIGNAL(readyRead()), SLOT(read())); 30 | connect(socket, SIGNAL(disconnected()), SLOT(disconnected())); 31 | connect(&readTimer, SIGNAL(timeout()), SLOT(readTimeout())); 32 | readTimer.setSingleShot(true); 33 | 34 | qDebug("HttpConnectionHandler (%p): constructed", this); 35 | this->start(); 36 | } 37 | 38 | 39 | HttpConnectionHandler::~HttpConnectionHandler() 40 | { 41 | quit(); 42 | wait(); 43 | qDebug("HttpConnectionHandler (%p): destroyed", this); 44 | } 45 | 46 | 47 | void HttpConnectionHandler::createSocket() 48 | { 49 | // If SSL is supported and configured, then create an instance of QSslSocket 50 | #ifndef QT_NO_OPENSSL 51 | if (sslConfiguration) 52 | { 53 | QSslSocket* sslSocket=new QSslSocket(); 54 | sslSocket->setSslConfiguration(*sslConfiguration); 55 | socket=sslSocket; 56 | qDebug("HttpConnectionHandler (%p): SSL is enabled", this); 57 | return; 58 | } 59 | #endif 60 | // else create an instance of QTcpSocket 61 | socket=new QTcpSocket(); 62 | } 63 | 64 | 65 | void HttpConnectionHandler::run() 66 | { 67 | qDebug("HttpConnectionHandler (%p): thread started", this); 68 | try 69 | { 70 | exec(); 71 | } 72 | catch (...) 73 | { 74 | qCritical("HttpConnectionHandler (%p): an uncatched exception occured in the thread",this); 75 | } 76 | socket->close(); 77 | delete socket; 78 | readTimer.stop(); 79 | qDebug("HttpConnectionHandler (%p): thread stopped", this); 80 | } 81 | 82 | 83 | void HttpConnectionHandler::handleConnection(tSocketDescriptor socketDescriptor) 84 | { 85 | qDebug("HttpConnectionHandler (%p): handle new connection", this); 86 | busy = true; 87 | Q_ASSERT(socket->isOpen()==false); // if not, then the handler is already busy 88 | 89 | //UGLY workaround - we need to clear writebuffer before reusing this socket 90 | //https://bugreports.qt-project.org/browse/QTBUG-28914 91 | socket->connectToHost("",0); 92 | socket->abort(); 93 | 94 | if (!socket->setSocketDescriptor(socketDescriptor)) 95 | { 96 | qCritical("HttpConnectionHandler (%p): cannot initialize socket: %s", this,qPrintable(socket->errorString())); 97 | return; 98 | } 99 | 100 | #ifndef QT_NO_OPENSSL 101 | // Switch on encryption, if SSL is configured 102 | if (sslConfiguration) 103 | { 104 | qDebug("HttpConnectionHandler (%p): Starting encryption", this); 105 | ((QSslSocket*)socket)->startServerEncryption(); 106 | } 107 | #endif 108 | 109 | // Start timer for read timeout 110 | int readTimeout=settings->value("readTimeout",10000).toInt(); 111 | readTimer.start(readTimeout); 112 | // delete previous request 113 | delete currentRequest; 114 | currentRequest=0; 115 | } 116 | 117 | 118 | bool HttpConnectionHandler::isBusy() 119 | { 120 | return busy; 121 | } 122 | 123 | void HttpConnectionHandler::setBusy() 124 | { 125 | this->busy = true; 126 | } 127 | 128 | 129 | void HttpConnectionHandler::readTimeout() 130 | { 131 | qDebug("HttpConnectionHandler (%p): read timeout occured",this); 132 | 133 | //Commented out because QWebView cannot handle this. 134 | //socket->write("HTTP/1.1 408 request timeout\r\nConnection: close\r\n\r\n408 request timeout\r\n"); 135 | 136 | socket->flush(); 137 | socket->disconnectFromHost(); 138 | delete currentRequest; 139 | currentRequest=0; 140 | } 141 | 142 | 143 | void HttpConnectionHandler::disconnected() 144 | { 145 | qDebug("HttpConnectionHandler (%p): disconnected", this); 146 | socket->close(); 147 | readTimer.stop(); 148 | busy = false; 149 | } 150 | 151 | void HttpConnectionHandler::read() 152 | { 153 | // The loop adds support for HTTP pipelinig 154 | while (socket->bytesAvailable()) 155 | { 156 | #ifdef SUPERVERBOSE 157 | qDebug("HttpConnectionHandler (%p): read input",this); 158 | #endif 159 | 160 | // Create new HttpRequest object if necessary 161 | if (!currentRequest) 162 | { 163 | currentRequest=new HttpRequest(settings); 164 | } 165 | 166 | // Collect data for the request object 167 | while (socket->bytesAvailable() && currentRequest->getStatus()!=HttpRequest::complete && currentRequest->getStatus()!=HttpRequest::abort) 168 | { 169 | currentRequest->readFromSocket(socket); 170 | if (currentRequest->getStatus()==HttpRequest::waitForBody) 171 | { 172 | // Restart timer for read timeout, otherwise it would 173 | // expire during large file uploads. 174 | int readTimeout=settings->value("readTimeout",10000).toInt(); 175 | readTimer.start(readTimeout); 176 | } 177 | } 178 | 179 | // If the request is aborted, return error message and close the connection 180 | if (currentRequest->getStatus()==HttpRequest::abort) 181 | { 182 | socket->write("HTTP/1.1 413 entity too large\r\nConnection: close\r\n\r\n413 Entity too large\r\n"); 183 | socket->flush(); 184 | socket->disconnectFromHost(); 185 | delete currentRequest; 186 | currentRequest=0; 187 | return; 188 | } 189 | 190 | // If the request is complete, let the request mapper dispatch it 191 | if (currentRequest->getStatus()==HttpRequest::complete) 192 | { 193 | readTimer.stop(); 194 | qDebug("HttpConnectionHandler (%p): received request",this); 195 | 196 | // Copy the Connection:close header to the response 197 | HttpResponse response(socket); 198 | bool closeConnection=QString::compare(currentRequest->getHeader("Connection"),"close",Qt::CaseInsensitive)==0; 199 | if (closeConnection) 200 | { 201 | response.setHeader("Connection","close"); 202 | } 203 | 204 | // In case of HTTP 1.0 protocol add the Connection:close header. 205 | // This ensures that the HttpResponse does not activate chunked mode, which is not spported by HTTP 1.0. 206 | else 207 | { 208 | bool http1_0=QString::compare(currentRequest->getVersion(),"HTTP/1.0",Qt::CaseInsensitive)==0; 209 | if (http1_0) 210 | { 211 | closeConnection=true; 212 | response.setHeader("Connection","close"); 213 | } 214 | } 215 | 216 | // Call the request mapper 217 | try 218 | { 219 | requestHandler->service(*currentRequest, response); 220 | } 221 | catch (...) 222 | { 223 | qCritical("HttpConnectionHandler (%p): An uncatched exception occured in the request handler",this); 224 | } 225 | 226 | // Finalize sending the response if not already done 227 | if (!response.hasSentLastPart()) 228 | { 229 | response.write(QByteArray(),true); 230 | } 231 | 232 | qDebug("HttpConnectionHandler (%p): finished request",this); 233 | 234 | // Find out whether the connection must be closed 235 | if (!closeConnection) 236 | { 237 | // Maybe the request handler or mapper added a Connection:close header in the meantime 238 | bool closeResponse=QString::compare(response.getHeaders().value("Connection"),"close",Qt::CaseInsensitive)==0; 239 | if (closeResponse==true) 240 | { 241 | closeConnection=true; 242 | } 243 | else 244 | { 245 | // If we have no Content-Length header and did not use chunked mode, then we have to close the 246 | // connection to tell the HTTP client that the end of the response has been reached. 247 | bool hasContentLength=response.getHeaders().contains("Content-Length"); 248 | if (!hasContentLength) 249 | { 250 | bool hasChunkedMode=QString::compare(response.getHeaders().value("Transfer-Encoding"),"chunked",Qt::CaseInsensitive)==0; 251 | if (!hasChunkedMode) 252 | { 253 | closeConnection=true; 254 | } 255 | } 256 | } 257 | } 258 | 259 | // Close the connection or prepare for the next request on the same connection. 260 | if (closeConnection) 261 | { 262 | socket->flush(); 263 | socket->disconnectFromHost(); 264 | } 265 | else 266 | { 267 | // Start timer for next request 268 | int readTimeout=settings->value("readTimeout",10000).toInt(); 269 | readTimer.start(readTimeout); 270 | } 271 | delete currentRequest; 272 | currentRequest=0; 273 | } 274 | } 275 | } 276 | -------------------------------------------------------------------------------- /QtWebApp/httpserver/httpconnectionhandler.h: -------------------------------------------------------------------------------- 1 | /** 2 | @file 3 | @author Stefan Frings 4 | */ 5 | 6 | #ifndef HTTPCONNECTIONHANDLER_H 7 | #define HTTPCONNECTIONHANDLER_H 8 | 9 | #ifndef QT_NO_OPENSSL 10 | #include 11 | #endif 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include "httpglobal.h" 17 | #include "httprequest.h" 18 | #include "httprequesthandler.h" 19 | 20 | /** Alias type definition, for compatibility to different Qt versions */ 21 | #if QT_VERSION >= 0x050000 22 | typedef qintptr tSocketDescriptor; 23 | #else 24 | typedef int tSocketDescriptor; 25 | #endif 26 | 27 | /** Alias for QSslConfiguration if OpenSSL is not supported */ 28 | #ifdef QT_NO_OPENSSL 29 | #define QSslConfiguration QObject 30 | #endif 31 | 32 | /** 33 | The connection handler accepts incoming connections and dispatches incoming requests to to a 34 | request mapper. Since HTTP clients can send multiple requests before waiting for the response, 35 | the incoming requests are queued and processed one after the other. 36 |

37 | Example for the required configuration settings: 38 |

 39 |   readTimeout=60000
 40 |   maxRequestSize=16000
 41 |   maxMultiPartSize=1000000
 42 |   
43 |

44 | The readTimeout value defines the maximum time to wait for a complete HTTP request. 45 | @see HttpRequest for description of config settings maxRequestSize and maxMultiPartSize. 46 | */ 47 | class DECLSPEC HttpConnectionHandler : public QThread { 48 | Q_OBJECT 49 | Q_DISABLE_COPY(HttpConnectionHandler) 50 | 51 | public: 52 | 53 | /** 54 | Constructor. 55 | @param settings Configuration settings of the HTTP webserver 56 | @param requestHandler Handler that will process each incoming HTTP request 57 | @param sslConfiguration SSL (HTTPS) will be used if not NULL 58 | */ 59 | HttpConnectionHandler(QSettings* settings, HttpRequestHandler* requestHandler, QSslConfiguration* sslConfiguration=NULL); 60 | 61 | /** Destructor */ 62 | virtual ~HttpConnectionHandler(); 63 | 64 | /** Returns true, if this handler is in use. */ 65 | bool isBusy(); 66 | 67 | /** Mark this handler as busy */ 68 | void setBusy(); 69 | 70 | private: 71 | 72 | /** Configuration settings */ 73 | QSettings* settings; 74 | 75 | /** TCP socket of the current connection */ 76 | QTcpSocket* socket; 77 | 78 | /** Time for read timeout detection */ 79 | QTimer readTimer; 80 | 81 | /** Storage for the current incoming HTTP request */ 82 | HttpRequest* currentRequest; 83 | 84 | /** Dispatches received requests to services */ 85 | HttpRequestHandler* requestHandler; 86 | 87 | /** This shows the busy-state from a very early time */ 88 | bool busy; 89 | 90 | /** Configuration for SSL */ 91 | QSslConfiguration* sslConfiguration; 92 | 93 | /** Executes the threads own event loop */ 94 | void run(); 95 | 96 | /** Create SSL or TCP socket */ 97 | void createSocket(); 98 | 99 | public slots: 100 | 101 | /** 102 | Received from from the listener, when the handler shall start processing a new connection. 103 | @param socketDescriptor references the accepted connection. 104 | */ 105 | void handleConnection(tSocketDescriptor socketDescriptor); 106 | 107 | private slots: 108 | 109 | /** Received from the socket when a read-timeout occured */ 110 | void readTimeout(); 111 | 112 | /** Received from the socket when incoming data can be read */ 113 | void read(); 114 | 115 | /** Received from the socket when a connection has been closed */ 116 | void disconnected(); 117 | 118 | }; 119 | 120 | #endif // HTTPCONNECTIONHANDLER_H 121 | -------------------------------------------------------------------------------- /QtWebApp/httpserver/httpconnectionhandlerpool.cpp: -------------------------------------------------------------------------------- 1 | #ifndef QT_NO_OPENSSL 2 | #include 3 | #include 4 | #include 5 | #include 6 | #endif 7 | #include 8 | #include "httpconnectionhandlerpool.h" 9 | 10 | HttpConnectionHandlerPool::HttpConnectionHandlerPool(QSettings* settings, HttpRequestHandler* requestHandler) 11 | : QObject() 12 | { 13 | Q_ASSERT(settings!=0); 14 | this->settings=settings; 15 | this->requestHandler=requestHandler; 16 | this->sslConfiguration=NULL; 17 | loadSslConfig(); 18 | cleanupTimer.start(settings->value("cleanupInterval",1000).toInt()); 19 | connect(&cleanupTimer, SIGNAL(timeout()), SLOT(cleanup())); 20 | } 21 | 22 | 23 | HttpConnectionHandlerPool::~HttpConnectionHandlerPool() 24 | { 25 | // delete all connection handlers and wait until their threads are closed 26 | foreach(HttpConnectionHandler* handler, pool) 27 | { 28 | delete handler; 29 | } 30 | delete sslConfiguration; 31 | qDebug("HttpConnectionHandlerPool (%p): destroyed", this); 32 | } 33 | 34 | 35 | HttpConnectionHandler* HttpConnectionHandlerPool::getConnectionHandler() 36 | { 37 | HttpConnectionHandler* freeHandler=0; 38 | mutex.lock(); 39 | // find a free handler in pool 40 | foreach(HttpConnectionHandler* handler, pool) 41 | { 42 | if (!handler->isBusy()) 43 | { 44 | freeHandler=handler; 45 | freeHandler->setBusy(); 46 | break; 47 | } 48 | } 49 | // create a new handler, if necessary 50 | if (!freeHandler) 51 | { 52 | int maxConnectionHandlers=settings->value("maxThreads",100).toInt(); 53 | if (pool.count()setBusy(); 57 | pool.append(freeHandler); 58 | } 59 | } 60 | mutex.unlock(); 61 | return freeHandler; 62 | } 63 | 64 | 65 | void HttpConnectionHandlerPool::cleanup() 66 | { 67 | int maxIdleHandlers=settings->value("minThreads",1).toInt(); 68 | int idleCounter=0; 69 | mutex.lock(); 70 | foreach(HttpConnectionHandler* handler, pool) 71 | { 72 | if (!handler->isBusy()) 73 | { 74 | if (++idleCounter > maxIdleHandlers) 75 | { 76 | delete handler; 77 | pool.removeOne(handler); 78 | qDebug("HttpConnectionHandlerPool: Removed connection handler (%p), pool size is now %i",handler,pool.size()); 79 | break; // remove only one handler in each interval 80 | } 81 | } 82 | } 83 | mutex.unlock(); 84 | } 85 | 86 | 87 | void HttpConnectionHandlerPool::loadSslConfig() 88 | { 89 | // If certificate and key files are configured, then load them 90 | QString sslKeyFileName=settings->value("sslKeyFile","").toString(); 91 | QString sslCertFileName=settings->value("sslCertFile","").toString(); 92 | if (!sslKeyFileName.isEmpty() && !sslCertFileName.isEmpty()) 93 | { 94 | #ifdef QT_NO_OPENSSL 95 | qWarning("HttpConnectionHandlerPool: SSL is not supported"); 96 | #else 97 | // Convert relative fileNames to absolute, based on the directory of the config file. 98 | QFileInfo configFile(settings->fileName()); 99 | #ifdef Q_OS_WIN32 100 | if (QDir::isRelativePath(sslKeyFileName) && settings->format()!=QSettings::NativeFormat) 101 | #else 102 | if (QDir::isRelativePath(sslKeyFileName)) 103 | #endif 104 | { 105 | sslKeyFileName=QFileInfo(configFile.absolutePath(),sslKeyFileName).absoluteFilePath(); 106 | } 107 | #ifdef Q_OS_WIN32 108 | if (QDir::isRelativePath(sslCertFileName) && settings->format()!=QSettings::NativeFormat) 109 | #else 110 | if (QDir::isRelativePath(sslCertFileName)) 111 | #endif 112 | { 113 | sslCertFileName=QFileInfo(configFile.absolutePath(),sslCertFileName).absoluteFilePath(); 114 | } 115 | 116 | // Load the SSL certificate 117 | QFile certFile(sslCertFileName); 118 | if (!certFile.open(QIODevice::ReadOnly)) 119 | { 120 | qCritical("HttpConnectionHandlerPool: cannot open sslCertFile %s", qPrintable(sslCertFileName)); 121 | return; 122 | } 123 | QSslCertificate certificate(&certFile, QSsl::Pem); 124 | certFile.close(); 125 | 126 | // Load the key file 127 | QFile keyFile(sslKeyFileName); 128 | if (!keyFile.open(QIODevice::ReadOnly)) 129 | { 130 | qCritical("HttpConnectionHandlerPool: cannot open sslKeyFile %s", qPrintable(sslKeyFileName)); 131 | return; 132 | } 133 | QSslKey sslKey(&keyFile, QSsl::Rsa, QSsl::Pem); 134 | keyFile.close(); 135 | 136 | // Create the SSL configuration 137 | sslConfiguration=new QSslConfiguration(); 138 | sslConfiguration->setLocalCertificate(certificate); 139 | sslConfiguration->setPrivateKey(sslKey); 140 | sslConfiguration->setPeerVerifyMode(QSslSocket::VerifyNone); 141 | sslConfiguration->setProtocol(QSsl::TlsV1SslV3); 142 | 143 | qDebug("HttpConnectionHandlerPool: SSL settings loaded"); 144 | #endif 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /QtWebApp/httpserver/httpconnectionhandlerpool.h: -------------------------------------------------------------------------------- 1 | #ifndef HTTPCONNECTIONHANDLERPOOL_H 2 | #define HTTPCONNECTIONHANDLERPOOL_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include "httpglobal.h" 9 | #include "httpconnectionhandler.h" 10 | 11 | /** 12 | Pool of http connection handlers. The size of the pool grows and 13 | shrinks on demand. 14 |

15 | Example for the required configuration settings: 16 |

17 |   minThreads=4
18 |   maxThreads=100
19 |   cleanupInterval=60000
20 |   readTimeout=60000
21 |   ;sslKeyFile=ssl/my.key
22 |   ;sslCertFile=ssl/my.cert
23 |   maxRequestSize=16000
24 |   maxMultiPartSize=1000000
25 |   
26 | After server start, the size of the thread pool is always 0. Threads 27 | are started on demand when requests come in. The cleanup timer reduces 28 | the number of idle threads slowly by closing one thread in each interval. 29 | But the configured minimum number of threads are kept running. 30 |

31 | For SSL support, you need an OpenSSL certificate file and a key file. 32 | Both can be created with the command 33 |

34 |       openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout my.key -out my.cert
35 |   
36 |

37 | Visit http://slproweb.com/products/Win32OpenSSL.html to download the Light version of OpenSSL for Windows. 38 |

39 | Please note that a listener with SSL settings can only handle HTTPS protocol. To 40 | support both HTTP and HTTPS simultaneously, you need to start two listeners on different ports - 41 | one with SLL and one without SSL. 42 | @see HttpConnectionHandler for description of the readTimeout 43 | @see HttpRequest for description of config settings maxRequestSize and maxMultiPartSize 44 | */ 45 | 46 | class DECLSPEC HttpConnectionHandlerPool : public QObject { 47 | Q_OBJECT 48 | Q_DISABLE_COPY(HttpConnectionHandlerPool) 49 | public: 50 | 51 | /** 52 | Constructor. 53 | @param settings Configuration settings for the HTTP server. Must not be 0. 54 | @param requestHandler The handler that will process each received HTTP request. 55 | @warning The requestMapper gets deleted by the destructor of this pool 56 | */ 57 | HttpConnectionHandlerPool(QSettings* settings, HttpRequestHandler* requestHandler); 58 | 59 | /** Destructor */ 60 | virtual ~HttpConnectionHandlerPool(); 61 | 62 | /** Get a free connection handler, or 0 if not available. */ 63 | HttpConnectionHandler* getConnectionHandler(); 64 | 65 | private: 66 | 67 | /** Settings for this pool */ 68 | QSettings* settings; 69 | 70 | /** Will be assigned to each Connectionhandler during their creation */ 71 | HttpRequestHandler* requestHandler; 72 | 73 | /** Pool of connection handlers */ 74 | QList pool; 75 | 76 | /** Timer to clean-up unused connection handler */ 77 | QTimer cleanupTimer; 78 | 79 | /** Used to synchronize threads */ 80 | QMutex mutex; 81 | 82 | /** The SSL configuration (certificate, key and other settings) */ 83 | QSslConfiguration* sslConfiguration; 84 | 85 | /** Load SSL configuration */ 86 | void loadSslConfig(); 87 | 88 | private slots: 89 | 90 | /** Received from the clean-up timer. */ 91 | void cleanup(); 92 | 93 | }; 94 | 95 | #endif // HTTPCONNECTIONHANDLERPOOL_H 96 | -------------------------------------------------------------------------------- /QtWebApp/httpserver/httpcookie.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | @file 3 | @author Stefan Frings 4 | */ 5 | 6 | #include "httpcookie.h" 7 | 8 | HttpCookie::HttpCookie() 9 | { 10 | version=1; 11 | maxAge=0; 12 | secure=false; 13 | } 14 | 15 | HttpCookie::HttpCookie(const QByteArray name, const QByteArray value, const int maxAge, const QByteArray path, const QByteArray comment, const QByteArray domain, const bool secure) 16 | { 17 | this->name=name; 18 | this->value=value; 19 | this->maxAge=maxAge; 20 | this->path=path; 21 | this->comment=comment; 22 | this->domain=domain; 23 | this->secure=secure; 24 | this->version=1; 25 | } 26 | 27 | HttpCookie::HttpCookie(const QByteArray source) 28 | { 29 | version=1; 30 | maxAge=0; 31 | secure=false; 32 | QList list=splitCSV(source); 33 | foreach(QByteArray part, list) 34 | { 35 | 36 | // Split the part into name and value 37 | QByteArray name; 38 | QByteArray value; 39 | int posi=part.indexOf('='); 40 | if (posi) 41 | { 42 | name=part.left(posi).trimmed(); 43 | value=part.mid(posi+1).trimmed(); 44 | } 45 | else 46 | { 47 | name=part.trimmed(); 48 | value=""; 49 | } 50 | 51 | // Set fields 52 | if (name=="Comment") 53 | { 54 | comment=value; 55 | } 56 | else if (name=="Domain") 57 | { 58 | domain=value; 59 | } 60 | else if (name=="Max-Age") 61 | { 62 | maxAge=value.toInt(); 63 | } 64 | else if (name=="Path") 65 | { 66 | path=value; 67 | } 68 | else if (name=="Secure") 69 | { 70 | secure=true; 71 | } 72 | else if (name=="Version") 73 | { 74 | version=value.toInt(); 75 | } 76 | else { 77 | if (this->name.isEmpty()) 78 | { 79 | this->name=name; 80 | this->value=value; 81 | } 82 | else 83 | { 84 | qWarning("HttpCookie: Ignoring unknown %s=%s",name.data(),value.data()); 85 | } 86 | } 87 | } 88 | } 89 | 90 | QByteArray HttpCookie::toByteArray() const 91 | { 92 | QByteArray buffer(name); 93 | buffer.append('='); 94 | buffer.append(value); 95 | if (!comment.isEmpty()) 96 | { 97 | buffer.append("; Comment="); 98 | buffer.append(comment); 99 | } 100 | if (!domain.isEmpty()) 101 | { 102 | buffer.append("; Domain="); 103 | buffer.append(domain); 104 | } 105 | if (maxAge!=0) 106 | { 107 | buffer.append("; Max-Age="); 108 | buffer.append(QByteArray::number(maxAge)); 109 | } 110 | if (!path.isEmpty()) 111 | { 112 | buffer.append("; Path="); 113 | buffer.append(path); 114 | } 115 | if (secure) { 116 | buffer.append("; Secure"); 117 | } 118 | buffer.append("; Version="); 119 | buffer.append(QByteArray::number(version)); 120 | return buffer; 121 | } 122 | 123 | void HttpCookie::setName(const QByteArray name) 124 | { 125 | this->name=name; 126 | } 127 | 128 | void HttpCookie::setValue(const QByteArray value) 129 | { 130 | this->value=value; 131 | } 132 | 133 | void HttpCookie::setComment(const QByteArray comment) 134 | { 135 | this->comment=comment; 136 | } 137 | 138 | void HttpCookie::setDomain(const QByteArray domain) 139 | { 140 | this->domain=domain; 141 | } 142 | 143 | void HttpCookie::setMaxAge(const int maxAge) 144 | { 145 | this->maxAge=maxAge; 146 | } 147 | 148 | void HttpCookie::setPath(const QByteArray path) 149 | { 150 | this->path=path; 151 | } 152 | 153 | void HttpCookie::setSecure(const bool secure) 154 | { 155 | this->secure=secure; 156 | } 157 | 158 | QByteArray HttpCookie::getName() const 159 | { 160 | return name; 161 | } 162 | 163 | QByteArray HttpCookie::getValue() const 164 | { 165 | return value; 166 | } 167 | 168 | QByteArray HttpCookie::getComment() const 169 | { 170 | return comment; 171 | } 172 | 173 | QByteArray HttpCookie::getDomain() const 174 | { 175 | return domain; 176 | } 177 | 178 | int HttpCookie::getMaxAge() const 179 | { 180 | return maxAge; 181 | } 182 | 183 | QByteArray HttpCookie::getPath() const 184 | { 185 | return path; 186 | } 187 | 188 | bool HttpCookie::getSecure() const 189 | { 190 | return secure; 191 | } 192 | 193 | int HttpCookie::getVersion() const 194 | { 195 | return version; 196 | } 197 | 198 | QList HttpCookie::splitCSV(const QByteArray source) 199 | { 200 | bool inString=false; 201 | QList list; 202 | QByteArray buffer; 203 | for (int i=0; i 10 | #include 11 | #include "httpglobal.h" 12 | 13 | /** 14 | HTTP cookie as defined in RFC 2109. This class can also parse 15 | RFC 2965 cookies, but skips fields that are not defined in RFC 16 | 2109. 17 | */ 18 | 19 | class DECLSPEC HttpCookie 20 | { 21 | public: 22 | 23 | /** Creates an empty cookie */ 24 | HttpCookie(); 25 | 26 | /** 27 | Create a cookie and set name/value pair. 28 | @param name name of the cookie 29 | @param value value of the cookie 30 | @param maxAge maximum age of the cookie in seconds. 0=discard immediately 31 | @param path Path for that the cookie will be sent, default="/" which means the whole domain 32 | @param comment Optional comment, may be displayed by the web browser somewhere 33 | @param domain Optional domain for that the cookie will be sent. Defaults to the current domain 34 | @param secure If true, the cookie will only be sent on secure connections 35 | */ 36 | HttpCookie(const QByteArray name, const QByteArray value, const int maxAge, const QByteArray path="/", const QByteArray comment=QByteArray(), const QByteArray domain=QByteArray(), const bool secure=false); 37 | 38 | /** 39 | Create a cookie from a string. 40 | @param source String as received in a HTTP Cookie2 header. 41 | */ 42 | HttpCookie(const QByteArray source); 43 | 44 | /** Convert this cookie to a string that may be used in a Set-Cookie header. */ 45 | QByteArray toByteArray() const ; 46 | 47 | /** 48 | Split a string list into parts, where each part is delimited by semicolon. 49 | Semicolons within double quotes are skipped. Double quotes are removed. 50 | */ 51 | static QList splitCSV(const QByteArray source); 52 | 53 | /** Set the name of this cookie */ 54 | void setName(const QByteArray name); 55 | 56 | /** Set the value of this cookie */ 57 | void setValue(const QByteArray value); 58 | 59 | /** Set the comment of this cookie */ 60 | void setComment(const QByteArray comment); 61 | 62 | /** Set the domain of this cookie */ 63 | void setDomain(const QByteArray domain); 64 | 65 | /** Set the maximum age of this cookie in seconds. 0=discard immediately */ 66 | void setMaxAge(const int maxAge); 67 | 68 | /** Set the path for that the cookie will be sent, default="/" which means the whole domain */ 69 | void setPath(const QByteArray path); 70 | 71 | /** Set secure mode, so that the cookie will only be sent on secure connections */ 72 | void setSecure(const bool secure); 73 | 74 | /** Get the name of this cookie */ 75 | QByteArray getName() const; 76 | 77 | /** Get the value of this cookie */ 78 | QByteArray getValue() const; 79 | 80 | /** Get the comment of this cookie */ 81 | QByteArray getComment() const; 82 | 83 | /** Get the domain of this cookie */ 84 | QByteArray getDomain() const; 85 | 86 | /** Set the maximum age of this cookie in seconds. */ 87 | int getMaxAge() const; 88 | 89 | /** Set the path of this cookie */ 90 | QByteArray getPath() const; 91 | 92 | /** Get the secure flag of this cookie */ 93 | bool getSecure() const; 94 | 95 | /** Returns always 1 */ 96 | int getVersion() const; 97 | 98 | private: 99 | 100 | QByteArray name; 101 | QByteArray value; 102 | QByteArray comment; 103 | QByteArray domain; 104 | int maxAge; 105 | QByteArray path; 106 | bool secure; 107 | int version; 108 | 109 | }; 110 | 111 | #endif // HTTPCOOKIE_H 112 | -------------------------------------------------------------------------------- /QtWebApp/httpserver/httpglobal.cpp: -------------------------------------------------------------------------------- 1 | #include "httpglobal.h" 2 | 3 | const char* getQtWebAppLibVersion() 4 | { 5 | return "1.6.4"; 6 | } 7 | 8 | -------------------------------------------------------------------------------- /QtWebApp/httpserver/httpglobal.h: -------------------------------------------------------------------------------- 1 | /** 2 | @file 3 | @author Stefan Frings 4 | */ 5 | 6 | #ifndef HTTPGLOBAL_H 7 | #define HTTPGLOBAL_H 8 | 9 | #include 10 | 11 | // This is specific to Windows dll's 12 | #if defined(Q_OS_WIN) 13 | #if defined(QTWEBAPPLIB_EXPORT) 14 | #define DECLSPEC Q_DECL_EXPORT 15 | #elif defined(QTWEBAPPLIB_IMPORT) 16 | #define DECLSPEC Q_DECL_IMPORT 17 | #endif 18 | #endif 19 | #if !defined(DECLSPEC) 20 | #define DECLSPEC 21 | #endif 22 | 23 | /** Get the library version number */ 24 | DECLSPEC const char* getQtWebAppLibVersion(); 25 | 26 | #endif // HTTPGLOBAL_H 27 | 28 | -------------------------------------------------------------------------------- /QtWebApp/httpserver/httplistener.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | @file 3 | @author Stefan Frings 4 | */ 5 | 6 | #include "httplistener.h" 7 | #include "httpconnectionhandler.h" 8 | #include "httpconnectionhandlerpool.h" 9 | #include 10 | 11 | HttpListener::HttpListener(QSettings* settings, HttpRequestHandler* requestHandler, QObject *parent) 12 | : QTcpServer(parent) 13 | { 14 | Q_ASSERT(settings!=0); 15 | Q_ASSERT(requestHandler!=0); 16 | pool=NULL; 17 | this->settings=settings; 18 | this->requestHandler=requestHandler; 19 | // Reqister type of socketDescriptor for signal/slot handling 20 | qRegisterMetaType("tSocketDescriptor"); 21 | // Start listening 22 | listen(); 23 | } 24 | 25 | 26 | HttpListener::~HttpListener() 27 | { 28 | close(); 29 | qDebug("HttpListener: destroyed"); 30 | } 31 | 32 | 33 | void HttpListener::listen() 34 | { 35 | if (!pool) 36 | { 37 | pool=new HttpConnectionHandlerPool(settings,requestHandler); 38 | } 39 | QString host = settings->value("host").toString(); 40 | int port=settings->value("port").toInt(); 41 | QTcpServer::listen(host.isEmpty() ? QHostAddress::Any : QHostAddress(host), port); 42 | if (!isListening()) 43 | { 44 | qCritical("HttpListener: Cannot bind on port %i: %s",port,qPrintable(errorString())); 45 | } 46 | else { 47 | qDebug("HttpListener: Listening on port %i",port); 48 | } 49 | } 50 | 51 | 52 | void HttpListener::close() { 53 | QTcpServer::close(); 54 | qDebug("HttpListener: closed"); 55 | if (pool) { 56 | delete pool; 57 | pool=NULL; 58 | } 59 | } 60 | 61 | void HttpListener::incomingConnection(tSocketDescriptor socketDescriptor) { 62 | #ifdef SUPERVERBOSE 63 | qDebug("HttpListener: New connection"); 64 | #endif 65 | 66 | HttpConnectionHandler* freeHandler=NULL; 67 | if (pool) 68 | { 69 | freeHandler=pool->getConnectionHandler(); 70 | } 71 | 72 | // Let the handler process the new connection. 73 | if (freeHandler) 74 | { 75 | // The descriptor is passed via signal/slot because the handler lives in another 76 | // thread and cannot open the socket when directly called by another thread. 77 | connect(this,SIGNAL(handleConnection(tSocketDescriptor)),freeHandler,SLOT(handleConnection(tSocketDescriptor))); 78 | emit handleConnection(socketDescriptor); 79 | disconnect(this,SIGNAL(handleConnection(tSocketDescriptor)),freeHandler,SLOT(handleConnection(tSocketDescriptor))); 80 | } 81 | else 82 | { 83 | // Reject the connection 84 | qDebug("HttpListener: Too many incoming connections"); 85 | QTcpSocket* socket=new QTcpSocket(this); 86 | socket->setSocketDescriptor(socketDescriptor); 87 | connect(socket, SIGNAL(disconnected()), socket, SLOT(deleteLater())); 88 | socket->write("HTTP/1.1 503 too many connections\r\nConnection: close\r\n\r\nToo many connections\r\n"); 89 | socket->disconnectFromHost(); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /QtWebApp/httpserver/httplistener.h: -------------------------------------------------------------------------------- 1 | /** 2 | @file 3 | @author Stefan Frings 4 | */ 5 | 6 | #ifndef HTTPLISTENER_H 7 | #define HTTPLISTENER_H 8 | 9 | #include 10 | #include 11 | #include 12 | #include "httpglobal.h" 13 | #include "httpconnectionhandler.h" 14 | #include "httpconnectionhandlerpool.h" 15 | #include "httprequesthandler.h" 16 | 17 | /** 18 | Listens for incoming TCP connections and and passes all incoming HTTP requests to your implementation of HttpRequestHandler, 19 | which processes the request and generates the response (usually a HTML document). 20 |

21 | Example for the required settings in the config file: 22 |

23 |   ;host=192.168.0.100
24 |   port=8080
25 |   minThreads=1
26 |   maxThreads=10
27 |   cleanupInterval=1000
28 |   readTimeout=60000
29 |   ;sslKeyFile=ssl/my.key
30 |   ;sslCertFile=ssl/my.cert
31 |   maxRequestSize=16000
32 |   maxMultiPartSize=1000000
33 |   
34 | The optional host parameter binds the listener to one network interface. 35 | The listener handles all network interfaces if no host is configured. 36 | The port number specifies the incoming TCP port that this listener listens to. 37 | @see HttpConnectionHandlerPool for description of config settings minThreads, maxThreads, cleanupInterval and ssl settings 38 | @see HttpConnectionHandler for description of the readTimeout 39 | @see HttpRequest for description of config settings maxRequestSize and maxMultiPartSize 40 | */ 41 | 42 | class DECLSPEC HttpListener : public QTcpServer { 43 | Q_OBJECT 44 | Q_DISABLE_COPY(HttpListener) 45 | public: 46 | 47 | /** 48 | Constructor. 49 | Creates a connection pool and starts listening on the configured host and port. 50 | @param settings Configuration settings for the HTTP server. Must not be 0. 51 | @param requestHandler Processes each received HTTP request, usually by dispatching to controller classes. 52 | @param parent Parent object. 53 | @warning Ensure to close or delete the listener before deleting the request handler. 54 | */ 55 | HttpListener(QSettings* settings, HttpRequestHandler* requestHandler, QObject* parent = NULL); 56 | 57 | /** Destructor */ 58 | virtual ~HttpListener(); 59 | 60 | /** 61 | Restart listeing after close(). 62 | */ 63 | void listen(); 64 | 65 | /** 66 | Closes the listener, waits until all pending requests are processed, 67 | then closes the connection pool. 68 | */ 69 | void close(); 70 | 71 | protected: 72 | 73 | /** Serves new incoming connection requests */ 74 | void incomingConnection(tSocketDescriptor socketDescriptor); 75 | 76 | private: 77 | 78 | /** Configuration settings for the HTTP server */ 79 | QSettings* settings; 80 | 81 | /** Point to the reuqest handler which processes all HTTP requests */ 82 | HttpRequestHandler* requestHandler; 83 | 84 | /** Pool of connection handlers */ 85 | HttpConnectionHandlerPool* pool; 86 | 87 | signals: 88 | 89 | /** 90 | Sent to the connection handler to process a new incoming connection. 91 | @param socketDescriptor references the accepted connection. 92 | */ 93 | 94 | void handleConnection(tSocketDescriptor socketDescriptor); 95 | 96 | }; 97 | 98 | #endif // HTTPLISTENER_H 99 | -------------------------------------------------------------------------------- /QtWebApp/httpserver/httprequest.h: -------------------------------------------------------------------------------- 1 | /** 2 | @file 3 | @author Stefan Frings 4 | */ 5 | 6 | #ifndef HTTPREQUEST_H 7 | #define HTTPREQUEST_H 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include "httpglobal.h" 18 | 19 | /** 20 | This object represents a single HTTP request. It reads the request 21 | from a TCP socket and provides getters for the individual parts 22 | of the request. 23 |

24 | The follwing config settings are required: 25 |

 26 |   maxRequestSize=16000
 27 |   maxMultiPartSize=1000000
 28 |   
29 |

30 | MaxRequestSize is the maximum size of a HTTP request. In case of 31 | multipart/form-data requests (also known as file-upload), the maximum 32 | size of the body must not exceed maxMultiPartSize. 33 | The body is always a little larger than the file itself. 34 | */ 35 | 36 | class DECLSPEC HttpRequest { 37 | Q_DISABLE_COPY(HttpRequest) 38 | friend class HttpSessionStore; 39 | 40 | public: 41 | 42 | /** Values for getStatus() */ 43 | enum RequestStatus {waitForRequest, waitForHeader, waitForBody, complete, abort}; 44 | 45 | /** 46 | Constructor. 47 | @param settings Configuration settings 48 | */ 49 | HttpRequest(QSettings* settings); 50 | 51 | /** 52 | Destructor. 53 | */ 54 | virtual ~HttpRequest(); 55 | 56 | /** 57 | Read the HTTP request from a socket. 58 | This method is called by the connection handler repeatedly 59 | until the status is RequestStatus::complete or RequestStatus::abort. 60 | @param socket Source of the data 61 | */ 62 | void readFromSocket(QTcpSocket* socket); 63 | 64 | /** 65 | Get the status of this reqeust. 66 | @see RequestStatus 67 | */ 68 | RequestStatus getStatus() const; 69 | 70 | /** Get the method of the HTTP request (e.g. "GET") */ 71 | QByteArray getMethod() const; 72 | 73 | /** Get the decoded path of the HTPP request (e.g. "/index.html") */ 74 | QByteArray getPath() const; 75 | 76 | /** Get the raw path of the HTTP request (e.g. "/file%20with%20spaces.html") */ 77 | const QByteArray& getRawPath() const; 78 | 79 | /** Get the version of the HTPP request (e.g. "HTTP/1.1") */ 80 | QByteArray getVersion() const; 81 | 82 | /** 83 | Get the value of a HTTP request header. 84 | @param name Name of the header 85 | @return If the header occurs multiple times, only the last 86 | one is returned. 87 | */ 88 | QByteArray getHeader(const QByteArray& name) const; 89 | 90 | /** 91 | Get the values of a HTTP request header. 92 | @param name Name of the header 93 | */ 94 | QList getHeaders(const QByteArray& name) const; 95 | 96 | /** Get all HTTP request headers */ 97 | QMultiMap getHeaderMap() const; 98 | 99 | /** 100 | Get the value of a HTTP request parameter. 101 | @param name Name of the parameter 102 | @return If the parameter occurs multiple times, only the last 103 | one is returned. 104 | */ 105 | QByteArray getParameter(const QByteArray& name) const; 106 | 107 | /** 108 | Get the values of a HTTP request parameter. 109 | @param name Name of the parameter 110 | */ 111 | QList getParameters(const QByteArray& name) const; 112 | 113 | /** Get all HTTP request parameters. */ 114 | QMultiMap getParameterMap() const; 115 | 116 | /** Get the HTTP request body. */ 117 | QByteArray getBody() const; 118 | 119 | /** 120 | Decode an URL parameter. 121 | E.g. replace "%23" by '#' and replace '+' by ' '. 122 | @param source The url encoded strings 123 | @see QUrl::toPercentEncoding for the reverse direction 124 | */ 125 | static QByteArray urlDecode(const QByteArray source); 126 | 127 | /** 128 | Get an uploaded file. The file is already open. It will 129 | be closed and deleted by the destructor of this HttpRequest 130 | object (after processing the request). 131 |

132 | For uploaded files, the method getParameters() returns 133 | the original fileName as provided by the calling web browser. 134 | */ 135 | QTemporaryFile* getUploadedFile(const QByteArray fieldName) const; 136 | 137 | /** 138 | Get the value of a cookie. 139 | @param name Name of the cookie 140 | */ 141 | QByteArray getCookie(const QByteArray& name) const; 142 | 143 | /** Get all cookies. */ 144 | QMap& getCookieMap(); 145 | 146 | /** 147 | Get the address of the connected client. 148 | Note that multiple clients may have the same IP address, if they 149 | share an internet connection (which is very common). 150 | */ 151 | QHostAddress getPeerAddress() const; 152 | 153 | private: 154 | 155 | /** Request headers */ 156 | QMultiMap headers; 157 | 158 | /** Parameters of the request */ 159 | QMultiMap parameters; 160 | 161 | /** Uploaded files of the request, key is the field name. */ 162 | QMap uploadedFiles; 163 | 164 | /** Received cookies */ 165 | QMap cookies; 166 | 167 | /** Storage for raw body data */ 168 | QByteArray bodyData; 169 | 170 | /** Request method */ 171 | QByteArray method; 172 | 173 | /** Request path (in raw encoded format) */ 174 | QByteArray path; 175 | 176 | /** Request protocol version */ 177 | QByteArray version; 178 | 179 | /** 180 | Status of this request. For the state engine. 181 | @see RequestStatus 182 | */ 183 | RequestStatus status; 184 | 185 | /** Address of the connected peer. */ 186 | QHostAddress peerAddress; 187 | 188 | /** Maximum size of requests in bytes. */ 189 | int maxSize; 190 | 191 | /** Maximum allowed size of multipart forms in bytes. */ 192 | int maxMultiPartSize; 193 | 194 | /** Current size */ 195 | int currentSize; 196 | 197 | /** Expected size of body */ 198 | int expectedBodySize; 199 | 200 | /** Name of the current header, or empty if no header is being processed */ 201 | QByteArray currentHeader; 202 | 203 | /** Boundary of multipart/form-data body. Empty if there is no such header */ 204 | QByteArray boundary; 205 | 206 | /** Temp file, that is used to store the multipart/form-data body */ 207 | QTemporaryFile tempFile; 208 | 209 | /** Parset he multipart body, that has been stored in the temp file. */ 210 | void parseMultiPartFile(); 211 | 212 | /** Sub-procedure of readFromSocket(), read the first line of a request. */ 213 | void readRequest(QTcpSocket* socket); 214 | 215 | /** Sub-procedure of readFromSocket(), read header lines. */ 216 | void readHeader(QTcpSocket* socket); 217 | 218 | /** Sub-procedure of readFromSocket(), read the request body. */ 219 | void readBody(QTcpSocket* socket); 220 | 221 | /** Sub-procedure of readFromSocket(), extract and decode request parameters. */ 222 | void decodeRequestParams(); 223 | 224 | /** Sub-procedure of readFromSocket(), extract cookies from headers */ 225 | void extractCookies(); 226 | 227 | /** Buffer for collecting characters of request and header lines */ 228 | QByteArray lineBuffer; 229 | 230 | }; 231 | 232 | #endif // HTTPREQUEST_H 233 | -------------------------------------------------------------------------------- /QtWebApp/httpserver/httprequesthandler.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | @file 3 | @author Stefan Frings 4 | */ 5 | 6 | #include "httprequesthandler.h" 7 | 8 | HttpRequestHandler::HttpRequestHandler(QObject* parent) 9 | : QObject(parent) 10 | {} 11 | 12 | HttpRequestHandler::~HttpRequestHandler() 13 | {} 14 | 15 | void HttpRequestHandler::service(HttpRequest& request, HttpResponse& response) 16 | { 17 | qCritical("HttpRequestHandler: you need to override the service() function"); 18 | qDebug("HttpRequestHandler: request=%s %s %s",request.getMethod().data(),request.getPath().data(),request.getVersion().data()); 19 | response.setStatus(501,"not implemented"); 20 | response.write("501 not implemented",true); 21 | } 22 | -------------------------------------------------------------------------------- /QtWebApp/httpserver/httprequesthandler.h: -------------------------------------------------------------------------------- 1 | /** 2 | @file 3 | @author Stefan Frings 4 | */ 5 | 6 | #ifndef HTTPREQUESTHANDLER_H 7 | #define HTTPREQUESTHANDLER_H 8 | 9 | #include "httpglobal.h" 10 | #include "httprequest.h" 11 | #include "httpresponse.h" 12 | 13 | /** 14 | The request handler generates a response for each HTTP request. Web Applications 15 | usually have one central request handler that maps incoming requests to several 16 | controllers (servlets) based on the requested path. 17 |

18 | You need to override the service() method or you will always get an HTTP error 501. 19 |

20 | @warning Be aware that the main request handler instance must be created on the heap and 21 | that it is used by multiple threads simultaneously. 22 | @see StaticFileController which delivers static local files. 23 | */ 24 | 25 | class DECLSPEC HttpRequestHandler : public QObject { 26 | Q_OBJECT 27 | Q_DISABLE_COPY(HttpRequestHandler) 28 | public: 29 | 30 | /** 31 | * Constructor. 32 | * @param parent Parent object. 33 | */ 34 | HttpRequestHandler(QObject* parent=NULL); 35 | 36 | /** Destructor */ 37 | virtual ~HttpRequestHandler(); 38 | 39 | /** 40 | Generate a response for an incoming HTTP request. 41 | @param request The received HTTP request 42 | @param response Must be used to return the response 43 | @warning This method must be thread safe 44 | */ 45 | virtual void service(HttpRequest& request, HttpResponse& response); 46 | 47 | }; 48 | 49 | #endif // HTTPREQUESTHANDLER_H 50 | -------------------------------------------------------------------------------- /QtWebApp/httpserver/httpresponse.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | @file 3 | @author Stefan Frings 4 | */ 5 | 6 | #include "httpresponse.h" 7 | 8 | HttpResponse::HttpResponse(QTcpSocket* socket) 9 | { 10 | this->socket=socket; 11 | statusCode=200; 12 | statusText="OK"; 13 | sentHeaders=false; 14 | sentLastPart=false; 15 | chunkedMode=false; 16 | } 17 | 18 | void HttpResponse::setHeader(QByteArray name, QByteArray value) 19 | { 20 | Q_ASSERT(sentHeaders==false); 21 | headers.insert(name,value); 22 | } 23 | 24 | void HttpResponse::setHeader(QByteArray name, int value) 25 | { 26 | Q_ASSERT(sentHeaders==false); 27 | headers.insert(name,QByteArray::number(value)); 28 | } 29 | 30 | QMap& HttpResponse::getHeaders() 31 | { 32 | return headers; 33 | } 34 | 35 | void HttpResponse::setStatus(int statusCode, QByteArray description) 36 | { 37 | this->statusCode=statusCode; 38 | statusText=description; 39 | } 40 | 41 | int HttpResponse::getStatusCode() const 42 | { 43 | return this->statusCode; 44 | } 45 | 46 | void HttpResponse::writeHeaders() 47 | { 48 | Q_ASSERT(sentHeaders==false); 49 | QByteArray buffer; 50 | buffer.append("HTTP/1.1 "); 51 | buffer.append(QByteArray::number(statusCode)); 52 | buffer.append(' '); 53 | buffer.append(statusText); 54 | buffer.append("\r\n"); 55 | foreach(QByteArray name, headers.keys()) 56 | { 57 | buffer.append(name); 58 | buffer.append(": "); 59 | buffer.append(headers.value(name)); 60 | buffer.append("\r\n"); 61 | } 62 | foreach(HttpCookie cookie,cookies.values()) 63 | { 64 | buffer.append("Set-Cookie: "); 65 | buffer.append(cookie.toByteArray()); 66 | buffer.append("\r\n"); 67 | } 68 | buffer.append("\r\n"); 69 | writeToSocket(buffer); 70 | sentHeaders=true; 71 | } 72 | 73 | bool HttpResponse::writeToSocket(QByteArray data) 74 | { 75 | int remaining=data.size(); 76 | char* ptr=data.data(); 77 | while (socket->isOpen() && remaining>0) 78 | { 79 | // If the output buffer has become large, then wait until it has been sent. 80 | if (socket->bytesToWrite()>16384) 81 | { 82 | socket->waitForBytesWritten(-1); 83 | } 84 | 85 | int written=socket->write(ptr,remaining); 86 | if (written==-1) 87 | { 88 | return false; 89 | } 90 | ptr+=written; 91 | remaining-=written; 92 | } 93 | return true; 94 | } 95 | 96 | void HttpResponse::write(QByteArray data, bool lastPart) 97 | { 98 | Q_ASSERT(sentLastPart==false); 99 | 100 | // Send HTTP headers, if not already done (that happens only on the first call to write()) 101 | if (sentHeaders==false) 102 | { 103 | // If the whole response is generated with a single call to write(), then we know the total 104 | // size of the response and therefore can set the Content-Length header automatically. 105 | if (lastPart) 106 | { 107 | // Automatically set the Content-Length header 108 | headers.insert("Content-Length",QByteArray::number(data.size())); 109 | } 110 | 111 | // else if we will not close the connection at the end, them we must use the chunked mode. 112 | else 113 | { 114 | bool connectionClose=QString::compare(headers.value("Connection"),"close",Qt::CaseInsensitive)==0; 115 | if (!connectionClose) 116 | { 117 | headers.insert("Transfer-Encoding","chunked"); 118 | chunkedMode=true; 119 | } 120 | } 121 | 122 | writeHeaders(); 123 | } 124 | 125 | // Send data 126 | if (data.size()>0) 127 | { 128 | if (chunkedMode) 129 | { 130 | if (data.size()>0) 131 | { 132 | QByteArray size=QByteArray::number(data.size(),16); 133 | writeToSocket(size); 134 | writeToSocket("\r\n"); 135 | writeToSocket(data); 136 | writeToSocket("\r\n"); 137 | } 138 | } 139 | else 140 | { 141 | writeToSocket(data); 142 | } 143 | } 144 | 145 | // Only for the last chunk, send the terminating marker and flush the buffer. 146 | if (lastPart) 147 | { 148 | if (chunkedMode) 149 | { 150 | writeToSocket("0\r\n\r\n"); 151 | } 152 | socket->flush(); 153 | sentLastPart=true; 154 | } 155 | } 156 | 157 | 158 | bool HttpResponse::hasSentLastPart() const 159 | { 160 | return sentLastPart; 161 | } 162 | 163 | 164 | void HttpResponse::setCookie(const HttpCookie& cookie) 165 | { 166 | Q_ASSERT(sentHeaders==false); 167 | if (!cookie.getName().isEmpty()) 168 | { 169 | cookies.insert(cookie.getName(),cookie); 170 | } 171 | } 172 | 173 | 174 | QMap& HttpResponse::getCookies() 175 | { 176 | return cookies; 177 | } 178 | 179 | 180 | void HttpResponse::redirect(const QByteArray& url) 181 | { 182 | setStatus(303,"See Other"); 183 | setHeader("Location",url); 184 | write("Redirect",true); 185 | } 186 | 187 | 188 | void HttpResponse::flush() 189 | { 190 | socket->flush(); 191 | } 192 | 193 | 194 | bool HttpResponse::isConnected() const 195 | { 196 | return socket->isOpen(); 197 | } 198 | -------------------------------------------------------------------------------- /QtWebApp/httpserver/httpresponse.h: -------------------------------------------------------------------------------- 1 | /** 2 | @file 3 | @author Stefan Frings 4 | */ 5 | 6 | #ifndef HTTPRESPONSE_H 7 | #define HTTPRESPONSE_H 8 | 9 | #include 10 | #include 11 | #include 12 | #include "httpglobal.h" 13 | #include "httpcookie.h" 14 | 15 | /** 16 | This object represents a HTTP response, used to return something to the web client. 17 |

18 |

 19 |     response.setStatus(200,"OK"); // optional, because this is the default
 20 |     response.writeBody("Hello");
 21 |     response.writeBody("World!",true);
 22 |   
23 |

24 | Example how to return an error: 25 |

 26 |     response.setStatus(500,"server error");
 27 |     response.write("The request cannot be processed because the servers is broken",true);
 28 |   
29 |

30 | In case of large responses (e.g. file downloads), a Content-Length header should be set 31 | before calling write(). Web Browsers use that information to display a progress bar. 32 | */ 33 | 34 | class DECLSPEC HttpResponse { 35 | Q_DISABLE_COPY(HttpResponse) 36 | public: 37 | 38 | /** 39 | Constructor. 40 | @param socket used to write the response 41 | */ 42 | HttpResponse(QTcpSocket* socket); 43 | 44 | /** 45 | Set a HTTP response header. 46 | You must call this method before the first write(). 47 | @param name name of the header 48 | @param value value of the header 49 | */ 50 | void setHeader(QByteArray name, QByteArray value); 51 | 52 | /** 53 | Set a HTTP response header. 54 | You must call this method before the first write(). 55 | @param name name of the header 56 | @param value value of the header 57 | */ 58 | void setHeader(QByteArray name, int value); 59 | 60 | /** Get the map of HTTP response headers */ 61 | QMap& getHeaders(); 62 | 63 | /** Get the map of cookies */ 64 | QMap& getCookies(); 65 | 66 | /** 67 | Set status code and description. The default is 200,OK. 68 | You must call this method before the first write(). 69 | */ 70 | void setStatus(int statusCode, QByteArray description=QByteArray()); 71 | 72 | /** Return the status code. */ 73 | int getStatusCode() const; 74 | 75 | /** 76 | Write body data to the socket. 77 |

78 | The HTTP status line, headers and cookies are sent automatically before the body. 79 |

80 | If the response contains only a single chunk (indicated by lastPart=true), 81 | then a Content-Length header is automatically set. 82 |

83 | Chunked mode is automatically selected if there is no Content-Length header 84 | and also no Connection:close header. 85 | @param data Data bytes of the body 86 | @param lastPart Indicates that this is the last chunk of data and flushes the output buffer. 87 | */ 88 | void write(QByteArray data, bool lastPart=false); 89 | 90 | /** 91 | Indicates whether the body has been sent completely (write() has been called with lastPart=true). 92 | */ 93 | bool hasSentLastPart() const; 94 | 95 | /** 96 | Set a cookie. 97 | You must call this method before the first write(). 98 | */ 99 | void setCookie(const HttpCookie& cookie); 100 | 101 | /** 102 | Send a redirect response to the browser. 103 | Cannot be combined with write(). 104 | @param url Destination URL 105 | */ 106 | void redirect(const QByteArray& url); 107 | 108 | /** 109 | * Flush the output buffer (of the underlying socket). 110 | * You normally don't need to call this method because flush is 111 | * automatically called after HttpRequestHandler::service() returns. 112 | */ 113 | void flush(); 114 | 115 | /** 116 | * May be used to check whether the connection to the web client has been lost. 117 | * This might be useful to cancel the generation of large or slow responses. 118 | */ 119 | bool isConnected() const; 120 | 121 | private: 122 | 123 | /** Request headers */ 124 | QMap headers; 125 | 126 | /** Socket for writing output */ 127 | QTcpSocket* socket; 128 | 129 | /** HTTP status code*/ 130 | int statusCode; 131 | 132 | /** HTTP status code description */ 133 | QByteArray statusText; 134 | 135 | /** Indicator whether headers have been sent */ 136 | bool sentHeaders; 137 | 138 | /** Indicator whether the body has been sent completely */ 139 | bool sentLastPart; 140 | 141 | /** Whether the response is sent in chunked mode */ 142 | bool chunkedMode; 143 | 144 | /** Cookies */ 145 | QMap cookies; 146 | 147 | /** Write raw data to the socket. This method blocks until all bytes have been passed to the TCP buffer */ 148 | bool writeToSocket(QByteArray data); 149 | 150 | /** 151 | Write the response HTTP status and headers to the socket. 152 | Calling this method is optional, because writeBody() calls 153 | it automatically when required. 154 | */ 155 | void writeHeaders(); 156 | 157 | }; 158 | 159 | #endif // HTTPRESPONSE_H 160 | -------------------------------------------------------------------------------- /QtWebApp/httpserver/httpserver.pri: -------------------------------------------------------------------------------- 1 | INCLUDEPATH += $$PWD 2 | DEPENDPATH += $$PWD 3 | 4 | QT += network 5 | 6 | # Enable very detailed debug messages when compiling the debug version 7 | CONFIG(debug, debug|release) { 8 | DEFINES += SUPERVERBOSE 9 | } 10 | 11 | HEADERS += $$PWD/httpglobal.h \ 12 | $$PWD/httplistener.h \ 13 | $$PWD/httpconnectionhandler.h \ 14 | $$PWD/httpconnectionhandlerpool.h \ 15 | $$PWD/httprequest.h \ 16 | $$PWD/httpresponse.h \ 17 | $$PWD/httpcookie.h \ 18 | $$PWD/httprequesthandler.h \ 19 | $$PWD/httpsession.h \ 20 | $$PWD/httpsessionstore.h \ 21 | $$PWD/staticfilecontroller.h 22 | 23 | SOURCES += $$PWD/httpglobal.cpp \ 24 | $$PWD/httplistener.cpp \ 25 | $$PWD/httpconnectionhandler.cpp \ 26 | $$PWD/httpconnectionhandlerpool.cpp \ 27 | $$PWD/httprequest.cpp \ 28 | $$PWD/httpresponse.cpp \ 29 | $$PWD/httpcookie.cpp \ 30 | $$PWD/httprequesthandler.cpp \ 31 | $$PWD/httpsession.cpp \ 32 | $$PWD/httpsessionstore.cpp \ 33 | $$PWD/staticfilecontroller.cpp 34 | -------------------------------------------------------------------------------- /QtWebApp/httpserver/httpsession.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | @file 3 | @author Stefan Frings 4 | */ 5 | 6 | #include "httpsession.h" 7 | #include 8 | #include 9 | 10 | 11 | HttpSession::HttpSession(bool canStore) 12 | { 13 | if (canStore) 14 | { 15 | dataPtr=new HttpSessionData(); 16 | dataPtr->refCount=1; 17 | dataPtr->lastAccess=QDateTime::currentMSecsSinceEpoch(); 18 | dataPtr->id=QUuid::createUuid().toString().toLocal8Bit(); 19 | #ifdef SUPERVERBOSE 20 | qDebug("HttpSession: created new session data with id %s",dataPtr->id.data()); 21 | #endif 22 | } 23 | else 24 | { 25 | dataPtr=0; 26 | } 27 | } 28 | 29 | HttpSession::HttpSession(const HttpSession& other) 30 | { 31 | dataPtr=other.dataPtr; 32 | if (dataPtr) 33 | { 34 | dataPtr->lock.lockForWrite(); 35 | dataPtr->refCount++; 36 | #ifdef SUPERVERBOSE 37 | qDebug("HttpSession: refCount of %s is %i",dataPtr->id.data(),dataPtr->refCount); 38 | #endif 39 | dataPtr->lock.unlock(); 40 | } 41 | } 42 | 43 | HttpSession& HttpSession::operator= (const HttpSession& other) 44 | { 45 | HttpSessionData* oldPtr=dataPtr; 46 | dataPtr=other.dataPtr; 47 | if (dataPtr) 48 | { 49 | dataPtr->lock.lockForWrite(); 50 | dataPtr->refCount++; 51 | #ifdef SUPERVERBOSE 52 | qDebug("HttpSession: refCount of %s is %i",dataPtr->id.data(),dataPtr->refCount); 53 | #endif 54 | dataPtr->lastAccess=QDateTime::currentMSecsSinceEpoch(); 55 | dataPtr->lock.unlock(); 56 | } 57 | if (oldPtr) 58 | { 59 | int refCount; 60 | oldPtr->lock.lockForRead(); 61 | refCount=oldPtr->refCount--; 62 | #ifdef SUPERVERBOSE 63 | qDebug("HttpSession: refCount of %s is %i",oldPtr->id.data(),oldPtr->refCount); 64 | #endif 65 | oldPtr->lock.unlock(); 66 | if (refCount==0) 67 | { 68 | delete oldPtr; 69 | } 70 | } 71 | return *this; 72 | } 73 | 74 | HttpSession::~HttpSession() 75 | { 76 | if (dataPtr) { 77 | int refCount; 78 | dataPtr->lock.lockForRead(); 79 | refCount=--dataPtr->refCount; 80 | #ifdef SUPERVERBOSE 81 | qDebug("HttpSession: refCount of %s is %i",dataPtr->id.data(),dataPtr->refCount); 82 | #endif 83 | dataPtr->lock.unlock(); 84 | if (refCount==0) 85 | { 86 | qDebug("HttpSession: deleting data"); 87 | delete dataPtr; 88 | } 89 | } 90 | } 91 | 92 | 93 | QByteArray HttpSession::getId() const 94 | { 95 | if (dataPtr) 96 | { 97 | return dataPtr->id; 98 | } 99 | else 100 | { 101 | return QByteArray(); 102 | } 103 | } 104 | 105 | bool HttpSession::isNull() const { 106 | return dataPtr==0; 107 | } 108 | 109 | void HttpSession::set(const QByteArray& key, const QVariant& value) 110 | { 111 | if (dataPtr) 112 | { 113 | dataPtr->lock.lockForWrite(); 114 | dataPtr->values.insert(key,value); 115 | dataPtr->lock.unlock(); 116 | } 117 | } 118 | 119 | void HttpSession::remove(const QByteArray& key) 120 | { 121 | if (dataPtr) 122 | { 123 | dataPtr->lock.lockForWrite(); 124 | dataPtr->values.remove(key); 125 | dataPtr->lock.unlock(); 126 | } 127 | } 128 | 129 | QVariant HttpSession::get(const QByteArray& key) const 130 | { 131 | QVariant value; 132 | if (dataPtr) 133 | { 134 | dataPtr->lock.lockForRead(); 135 | value=dataPtr->values.value(key); 136 | dataPtr->lock.unlock(); 137 | } 138 | return value; 139 | } 140 | 141 | bool HttpSession::contains(const QByteArray& key) const 142 | { 143 | bool found=false; 144 | if (dataPtr) 145 | { 146 | dataPtr->lock.lockForRead(); 147 | found=dataPtr->values.contains(key); 148 | dataPtr->lock.unlock(); 149 | } 150 | return found; 151 | } 152 | 153 | QMap HttpSession::getAll() const 154 | { 155 | QMap values; 156 | if (dataPtr) 157 | { 158 | dataPtr->lock.lockForRead(); 159 | values=dataPtr->values; 160 | dataPtr->lock.unlock(); 161 | } 162 | return values; 163 | } 164 | 165 | qint64 HttpSession::getLastAccess() const 166 | { 167 | qint64 value=0; 168 | if (dataPtr) 169 | { 170 | dataPtr->lock.lockForRead(); 171 | value=dataPtr->lastAccess; 172 | dataPtr->lock.unlock(); 173 | } 174 | return value; 175 | } 176 | 177 | 178 | void HttpSession::setLastAccess() 179 | { 180 | if (dataPtr) 181 | { 182 | dataPtr->lock.lockForRead(); 183 | dataPtr->lastAccess=QDateTime::currentMSecsSinceEpoch(); 184 | dataPtr->lock.unlock(); 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /QtWebApp/httpserver/httpsession.h: -------------------------------------------------------------------------------- 1 | /** 2 | @file 3 | @author Stefan Frings 4 | */ 5 | 6 | #ifndef HTTPSESSION_H 7 | #define HTTPSESSION_H 8 | 9 | #include 10 | #include 11 | #include 12 | #include "httpglobal.h" 13 | 14 | /** 15 | This class stores data for a single HTTP session. 16 | A session can store any number of key/value pairs. This class uses implicit 17 | sharing for read and write access. This class is thread safe. 18 | @see HttpSessionStore should be used to create and get instances of this class. 19 | */ 20 | 21 | class DECLSPEC HttpSession { 22 | 23 | public: 24 | 25 | /** 26 | Constructor. 27 | @param canStore The session can store data, if this parameter is true. 28 | Otherwise all calls to set() and remove() do not have any effect. 29 | */ 30 | HttpSession(bool canStore=false); 31 | 32 | /** 33 | Copy constructor. Creates another HttpSession object that shares the 34 | data of the other object. 35 | */ 36 | HttpSession(const HttpSession& other); 37 | 38 | /** 39 | Copy operator. Detaches from the current shared data and attaches to 40 | the data of the other object. 41 | */ 42 | HttpSession& operator= (const HttpSession& other); 43 | 44 | 45 | /** 46 | Destructor. Detaches from the shared data. 47 | */ 48 | virtual ~HttpSession(); 49 | 50 | /** Get the unique ID of this session. This method is thread safe. */ 51 | QByteArray getId() const; 52 | 53 | /** 54 | Null sessions cannot store data. All calls to set() and remove() 55 | do not have any effect.This method is thread safe. 56 | */ 57 | bool isNull() const; 58 | 59 | /** Set a value. This method is thread safe. */ 60 | void set(const QByteArray& key, const QVariant& value); 61 | 62 | /** Remove a value. This method is thread safe. */ 63 | void remove(const QByteArray& key); 64 | 65 | /** Get a value. This method is thread safe. */ 66 | QVariant get(const QByteArray& key) const; 67 | 68 | /** Check if a key exists. This method is thread safe. */ 69 | bool contains(const QByteArray& key) const; 70 | 71 | /** 72 | Get a copy of all data stored in this session. 73 | Changes to the session do not affect the copy and vice versa. 74 | This method is thread safe. 75 | */ 76 | QMap getAll() const; 77 | 78 | /** 79 | Get the timestamp of last access. That is the time when the last 80 | HttpSessionStore::getSession() has been called. 81 | This method is thread safe. 82 | */ 83 | qint64 getLastAccess() const; 84 | 85 | /** 86 | Set the timestamp of last access, to renew the timeout period. 87 | Called by HttpSessionStore::getSession(). 88 | This method is thread safe. 89 | */ 90 | void setLastAccess(); 91 | 92 | private: 93 | 94 | struct HttpSessionData { 95 | 96 | /** Unique ID */ 97 | QByteArray id; 98 | 99 | /** Timestamp of last access, set by the HttpSessionStore */ 100 | qint64 lastAccess; 101 | 102 | /** Reference counter */ 103 | int refCount; 104 | 105 | /** Used to synchronize threads */ 106 | QReadWriteLock lock; 107 | 108 | /** Storage for the key/value pairs; */ 109 | QMap values; 110 | 111 | }; 112 | 113 | /** Pointer to the shared data. */ 114 | HttpSessionData* dataPtr; 115 | 116 | }; 117 | 118 | #endif // HTTPSESSION_H 119 | -------------------------------------------------------------------------------- /QtWebApp/httpserver/httpsessionstore.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | @file 3 | @author Stefan Frings 4 | */ 5 | 6 | #include "httpsessionstore.h" 7 | #include 8 | #include 9 | 10 | HttpSessionStore::HttpSessionStore(QSettings* settings, QObject* parent) 11 | :QObject(parent) 12 | { 13 | this->settings=settings; 14 | connect(&cleanupTimer,SIGNAL(timeout()),this,SLOT(sessionTimerEvent())); 15 | cleanupTimer.start(60000); 16 | cookieName=settings->value("cookieName","sessionid").toByteArray(); 17 | expirationTime=settings->value("expirationTime",3600000).toInt(); 18 | qDebug("HttpSessionStore: Sessions expire after %i milliseconds",expirationTime); 19 | } 20 | 21 | HttpSessionStore::~HttpSessionStore() 22 | { 23 | cleanupTimer.stop(); 24 | } 25 | 26 | QByteArray HttpSessionStore::getSessionId(HttpRequest& request, HttpResponse& response) 27 | { 28 | // The session ID in the response has priority because this one will be used in the next request. 29 | mutex.lock(); 30 | // Get the session ID from the response cookie 31 | QByteArray sessionId=response.getCookies().value(cookieName).getValue(); 32 | if (sessionId.isEmpty()) 33 | { 34 | // Get the session ID from the request cookie 35 | sessionId=request.getCookie(cookieName); 36 | } 37 | // Clear the session ID if there is no such session in the storage. 38 | if (!sessionId.isEmpty()) 39 | { 40 | if (!sessions.contains(sessionId)) 41 | { 42 | qDebug("HttpSessionStore: received invalid session cookie with ID %s",sessionId.data()); 43 | sessionId.clear(); 44 | } 45 | } 46 | mutex.unlock(); 47 | return sessionId; 48 | } 49 | 50 | HttpSession HttpSessionStore::getSession(HttpRequest& request, HttpResponse& response, bool allowCreate) 51 | { 52 | QByteArray sessionId=getSessionId(request,response); 53 | mutex.lock(); 54 | if (!sessionId.isEmpty()) 55 | { 56 | HttpSession session=sessions.value(sessionId); 57 | if (!session.isNull()) 58 | { 59 | mutex.unlock(); 60 | // Refresh the session cookie 61 | QByteArray cookieName=settings->value("cookieName","sessionid").toByteArray(); 62 | QByteArray cookiePath=settings->value("cookiePath").toByteArray(); 63 | QByteArray cookieComment=settings->value("cookieComment").toByteArray(); 64 | QByteArray cookieDomain=settings->value("cookieDomain").toByteArray(); 65 | response.setCookie(HttpCookie(cookieName,session.getId(),expirationTime/1000,cookiePath,cookieComment,cookieDomain)); 66 | session.setLastAccess(); 67 | return session; 68 | } 69 | } 70 | // Need to create a new session 71 | if (allowCreate) 72 | { 73 | QByteArray cookieName=settings->value("cookieName","sessionid").toByteArray(); 74 | QByteArray cookiePath=settings->value("cookiePath").toByteArray(); 75 | QByteArray cookieComment=settings->value("cookieComment").toByteArray(); 76 | QByteArray cookieDomain=settings->value("cookieDomain").toByteArray(); 77 | HttpSession session(true); 78 | qDebug("HttpSessionStore: create new session with ID %s",session.getId().data()); 79 | sessions.insert(session.getId(),session); 80 | response.setCookie(HttpCookie(cookieName,session.getId(),expirationTime/1000,cookiePath,cookieComment,cookieDomain)); 81 | mutex.unlock(); 82 | return session; 83 | } 84 | // Return a null session 85 | mutex.unlock(); 86 | return HttpSession(); 87 | } 88 | 89 | HttpSession HttpSessionStore::getSession(const QByteArray id) 90 | { 91 | mutex.lock(); 92 | HttpSession session=sessions.value(id); 93 | mutex.unlock(); 94 | session.setLastAccess(); 95 | return session; 96 | } 97 | 98 | void HttpSessionStore::sessionTimerEvent() 99 | { 100 | mutex.lock(); 101 | qint64 now=QDateTime::currentMSecsSinceEpoch(); 102 | QMap::iterator i = sessions.begin(); 103 | while (i != sessions.end()) 104 | { 105 | QMap::iterator prev = i; 106 | ++i; 107 | HttpSession session=prev.value(); 108 | qint64 lastAccess=session.getLastAccess(); 109 | if (now-lastAccess>expirationTime) 110 | { 111 | qDebug("HttpSessionStore: session %s expired",session.getId().data()); 112 | sessions.erase(prev); 113 | } 114 | } 115 | mutex.unlock(); 116 | } 117 | 118 | 119 | /** Delete a session */ 120 | void HttpSessionStore::removeSession(HttpSession session) 121 | { 122 | mutex.lock(); 123 | sessions.remove(session.getId()); 124 | mutex.unlock(); 125 | } 126 | -------------------------------------------------------------------------------- /QtWebApp/httpserver/httpsessionstore.h: -------------------------------------------------------------------------------- 1 | /** 2 | @file 3 | @author Stefan Frings 4 | */ 5 | 6 | #ifndef HTTPSESSIONSTORE_H 7 | #define HTTPSESSIONSTORE_H 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include "httpglobal.h" 14 | #include "httpsession.h" 15 | #include "httpresponse.h" 16 | #include "httprequest.h" 17 | 18 | /** 19 | Stores HTTP sessions and deletes them when they have expired. 20 | The following configuration settings are required in the config file: 21 |

 22 |   expirationTime=3600000
 23 |   cookieName=sessionid
 24 |   
25 | The following additional configurations settings are optionally: 26 |
 27 |   cookiePath=/
 28 |   cookieComment=Session ID
 29 |   ;cookieDomain=stefanfrings.de
 30 |   
31 | */ 32 | 33 | class DECLSPEC HttpSessionStore : public QObject { 34 | Q_OBJECT 35 | Q_DISABLE_COPY(HttpSessionStore) 36 | public: 37 | 38 | /** Constructor. */ 39 | HttpSessionStore(QSettings* settings, QObject* parent=NULL); 40 | 41 | /** Destructor */ 42 | virtual ~HttpSessionStore(); 43 | 44 | /** 45 | Get the ID of the current HTTP session, if it is valid. 46 | This method is thread safe. 47 | @warning Sessions may expire at any time, so subsequent calls of 48 | getSession() might return a new session with a different ID. 49 | @param request Used to get the session cookie 50 | @param response Used to get and set the new session cookie 51 | @return Empty string, if there is no valid session. 52 | */ 53 | QByteArray getSessionId(HttpRequest& request, HttpResponse& response); 54 | 55 | /** 56 | Get the session of a HTTP request, eventually create a new one. 57 | This method is thread safe. New sessions can only be created before 58 | the first byte has been written to the HTTP response. 59 | @param request Used to get the session cookie 60 | @param response Used to get and set the new session cookie 61 | @param allowCreate can be set to false, to disable the automatic creation of a new session. 62 | @return If autoCreate is disabled, the function returns a null session if there is no session. 63 | @see HttpSession::isNull() 64 | */ 65 | HttpSession getSession(HttpRequest& request, HttpResponse& response, bool allowCreate=true); 66 | 67 | /** 68 | Get a HTTP session by it's ID number. 69 | This method is thread safe. 70 | @return If there is no such session, the function returns a null session. 71 | @param id ID number of the session 72 | @see HttpSession::isNull() 73 | */ 74 | HttpSession getSession(const QByteArray id); 75 | 76 | /** Delete a session */ 77 | void removeSession(HttpSession session); 78 | 79 | protected: 80 | /** Storage for the sessions */ 81 | QMap sessions; 82 | 83 | private: 84 | 85 | /** Configuration settings */ 86 | QSettings* settings; 87 | 88 | /** Timer to remove expired sessions */ 89 | QTimer cleanupTimer; 90 | 91 | /** Name of the session cookie */ 92 | QByteArray cookieName; 93 | 94 | /** Time when sessions expire (in ms)*/ 95 | int expirationTime; 96 | 97 | /** Used to synchronize threads */ 98 | QMutex mutex; 99 | 100 | private slots: 101 | 102 | /** Called every minute to cleanup expired sessions. */ 103 | void sessionTimerEvent(); 104 | }; 105 | 106 | #endif // HTTPSESSIONSTORE_H 107 | -------------------------------------------------------------------------------- /QtWebApp/httpserver/staticfilecontroller.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | @file 3 | @author Stefan Frings 4 | */ 5 | 6 | #include "staticfilecontroller.h" 7 | #include 8 | #include 9 | #include 10 | 11 | StaticFileController::StaticFileController(QSettings* settings, QObject* parent) 12 | :HttpRequestHandler(parent) 13 | { 14 | maxAge=settings->value("maxAge","60000").toInt(); 15 | encoding=settings->value("encoding","UTF-8").toString(); 16 | docroot=settings->value("path",".").toString(); 17 | if(!(docroot.startsWith(":/") || docroot.startsWith("qrc://"))) 18 | { 19 | // Convert relative path to absolute, based on the directory of the config file. 20 | #ifdef Q_OS_WIN32 21 | if (QDir::isRelativePath(docroot) && settings->format()!=QSettings::NativeFormat) 22 | #else 23 | if (QDir::isRelativePath(docroot)) 24 | #endif 25 | { 26 | QFileInfo configFile(settings->fileName()); 27 | docroot=QFileInfo(configFile.absolutePath(),docroot).absoluteFilePath(); 28 | } 29 | } 30 | qDebug("StaticFileController: docroot=%s, encoding=%s, maxAge=%i",qPrintable(docroot),qPrintable(encoding),maxAge); 31 | maxCachedFileSize=settings->value("maxCachedFileSize","65536").toInt(); 32 | cache.setMaxCost(settings->value("cacheSize","1000000").toInt()); 33 | cacheTimeout=settings->value("cacheTime","60000").toInt(); 34 | qDebug("StaticFileController: cache timeout=%i, size=%i",cacheTimeout,cache.maxCost()); 35 | } 36 | 37 | 38 | void StaticFileController::service(HttpRequest& request, HttpResponse& response) 39 | { 40 | QByteArray path=request.getPath(); 41 | // Check if we have the file in cache 42 | qint64 now=QDateTime::currentMSecsSinceEpoch(); 43 | mutex.lock(); 44 | CacheEntry* entry=cache.object(path); 45 | if (entry && (cacheTimeout==0 || entry->created>now-cacheTimeout)) 46 | { 47 | QByteArray document=entry->document; //copy the cached document, because other threads may destroy the cached entry immediately after mutex unlock. 48 | QByteArray filename=entry->filename; 49 | mutex.unlock(); 50 | qDebug("StaticFileController: Cache hit for %s",path.data()); 51 | setContentType(filename,response); 52 | response.setHeader("Cache-Control","max-age="+QByteArray::number(maxAge/1000)); 53 | response.write(document); 54 | } 55 | else 56 | { 57 | mutex.unlock(); 58 | // The file is not in cache. 59 | qDebug("StaticFileController: Cache miss for %s",path.data()); 60 | // Forbid access to files outside the docroot directory 61 | if (path.contains("/..")) 62 | { 63 | qWarning("StaticFileController: detected forbidden characters in path %s",path.data()); 64 | response.setStatus(403,"forbidden"); 65 | response.write("403 forbidden",true); 66 | return; 67 | } 68 | // If the filename is a directory, append index.html. 69 | if (QFileInfo(docroot+path).isDir()) 70 | { 71 | path+="/index.html"; 72 | } 73 | // Try to open the file 74 | QFile file(docroot+path); 75 | qDebug("StaticFileController: Open file %s",qPrintable(file.fileName())); 76 | if (file.open(QIODevice::ReadOnly)) 77 | { 78 | setContentType(path,response); 79 | response.setHeader("Cache-Control","max-age="+QByteArray::number(maxAge/1000)); 80 | if (file.size()<=maxCachedFileSize) 81 | { 82 | // Return the file content and store it also in the cache 83 | entry=new CacheEntry(); 84 | while (!file.atEnd() && !file.error()) 85 | { 86 | QByteArray buffer=file.read(65536); 87 | response.write(buffer); 88 | entry->document.append(buffer); 89 | } 90 | entry->created=now; 91 | entry->filename=path; 92 | mutex.lock(); 93 | cache.insert(request.getPath(),entry,entry->document.size()); 94 | mutex.unlock(); 95 | } 96 | else 97 | { 98 | // Return the file content, do not store in cache 99 | while (!file.atEnd() && !file.error()) 100 | { 101 | response.write(file.read(65536)); 102 | } 103 | } 104 | file.close(); 105 | } 106 | else { 107 | if (file.exists()) 108 | { 109 | qWarning("StaticFileController: Cannot open existing file %s for reading",qPrintable(file.fileName())); 110 | response.setStatus(403,"forbidden"); 111 | response.write("403 forbidden",true); 112 | } 113 | else 114 | { 115 | response.setStatus(404,"not found"); 116 | response.write("404 not found",true); 117 | } 118 | } 119 | } 120 | } 121 | 122 | void StaticFileController::setContentType(QString fileName, HttpResponse& response) const 123 | { 124 | if (fileName.endsWith(".png")) 125 | { 126 | response.setHeader("Content-Type", "image/png"); 127 | } 128 | else if (fileName.endsWith(".jpg")) 129 | { 130 | response.setHeader("Content-Type", "image/jpeg"); 131 | } 132 | else if (fileName.endsWith(".gif")) 133 | { 134 | response.setHeader("Content-Type", "image/gif"); 135 | } 136 | else if (fileName.endsWith(".pdf")) 137 | { 138 | response.setHeader("Content-Type", "application/pdf"); 139 | } 140 | else if (fileName.endsWith(".txt")) 141 | { 142 | response.setHeader("Content-Type", qPrintable("text/plain; charset="+encoding)); 143 | } 144 | else if (fileName.endsWith(".html") || fileName.endsWith(".htm")) 145 | { 146 | response.setHeader("Content-Type", qPrintable("text/html; charset="+encoding)); 147 | } 148 | else if (fileName.endsWith(".css")) 149 | { 150 | response.setHeader("Content-Type", "text/css"); 151 | } 152 | else if (fileName.endsWith(".js")) 153 | { 154 | response.setHeader("Content-Type", "text/javascript"); 155 | } 156 | else if (fileName.endsWith(".svg")) 157 | { 158 | response.setHeader("Content-Type", "image/svg+xml"); 159 | } 160 | else if (fileName.endsWith(".woff")) 161 | { 162 | response.setHeader("Content-Type", "font/woff"); 163 | } 164 | else if (fileName.endsWith(".woff2")) 165 | { 166 | response.setHeader("Content-Type", "font/woff2"); 167 | } 168 | else if (fileName.endsWith(".ttf")) 169 | { 170 | response.setHeader("Content-Type", "application/x-font-ttf"); 171 | } 172 | else if (fileName.endsWith(".eot")) 173 | { 174 | response.setHeader("Content-Type", "application/vnd.ms-fontobject"); 175 | } 176 | else if (fileName.endsWith(".otf")) 177 | { 178 | response.setHeader("Content-Type", "application/font-otf"); 179 | } 180 | // Todo: add all of your content types 181 | else 182 | { 183 | qDebug("StaticFileController: unknown MIME type for filename '%s'", qPrintable(fileName)); 184 | } 185 | } 186 | -------------------------------------------------------------------------------- /QtWebApp/httpserver/staticfilecontroller.h: -------------------------------------------------------------------------------- 1 | /** 2 | @file 3 | @author Stefan Frings 4 | */ 5 | 6 | #ifndef STATICFILECONTROLLER_H 7 | #define STATICFILECONTROLLER_H 8 | 9 | #include 10 | #include 11 | #include "httpglobal.h" 12 | #include "httprequest.h" 13 | #include "httpresponse.h" 14 | #include "httprequesthandler.h" 15 | 16 | /** 17 | Delivers static files. It is usually called by the applications main request handler when 18 | the caller requests a path that is mapped to static files. 19 |

20 | The following settings are required in the config file: 21 |

22 |   path=../docroot
23 |   encoding=UTF-8
24 |   maxAge=60000
25 |   cacheTime=60000
26 |   cacheSize=1000000
27 |   maxCachedFileSize=65536
28 |   
29 | The path is relative to the directory of the config file. In case of windows, if the 30 | settings are in the registry, the path is relative to the current working directory. 31 |

32 | The encoding is sent to the web browser in case of text and html files. 33 |

34 | The cache improves performance of small files when loaded from a network 35 | drive. Large files are not cached. Files are cached as long as possible, 36 | when cacheTime=0. The maxAge value (in msec!) controls the remote browsers cache. 37 |

38 | Do not instantiate this class in each request, because this would make the file cache 39 | useless. Better create one instance during start-up and call it when the application 40 | received a related HTTP request. 41 | */ 42 | 43 | class DECLSPEC StaticFileController : public HttpRequestHandler { 44 | Q_OBJECT 45 | Q_DISABLE_COPY(StaticFileController) 46 | public: 47 | 48 | /** Constructor */ 49 | StaticFileController(QSettings* settings, QObject* parent = NULL); 50 | 51 | /** Generates the response */ 52 | void service(HttpRequest& request, HttpResponse& response); 53 | 54 | private: 55 | 56 | /** Encoding of text files */ 57 | QString encoding; 58 | 59 | /** Root directory of documents */ 60 | QString docroot; 61 | 62 | /** Maximum age of files in the browser cache */ 63 | int maxAge; 64 | 65 | struct CacheEntry { 66 | QByteArray document; 67 | qint64 created; 68 | QByteArray filename; 69 | }; 70 | 71 | /** Timeout for each cached file */ 72 | int cacheTimeout; 73 | 74 | /** Maximum size of files in cache, larger files are not cached */ 75 | int maxCachedFileSize; 76 | 77 | /** Cache storage */ 78 | QCache cache; 79 | 80 | /** Used to synchronize cache access for threads */ 81 | QMutex mutex; 82 | 83 | /** Set a content-type header in the response depending on the ending of the filename */ 84 | void setContentType(QString file, HttpResponse& response) const; 85 | }; 86 | 87 | #endif // STATICFILECONTROLLER_H 88 | -------------------------------------------------------------------------------- /QtWebApp/logging/dualfilelogger.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | @file 3 | @author Stefan Frings 4 | */ 5 | 6 | #include "dualfilelogger.h" 7 | 8 | 9 | DualFileLogger::DualFileLogger(QSettings* firstSettings, QSettings* secondSettings, const int refreshInterval, QObject* parent) 10 | :Logger(parent) 11 | { 12 | firstLogger=new FileLogger(firstSettings, refreshInterval, this); 13 | secondLogger=new FileLogger(secondSettings, refreshInterval, this); 14 | } 15 | 16 | void DualFileLogger::log(const QtMsgType type, const QString& message, const QString &file, const QString &function, const int line) 17 | { 18 | firstLogger->log(type,message,file,function,line); 19 | secondLogger->log(type,message,file,function,line); 20 | } 21 | 22 | void DualFileLogger::clear(const bool buffer, const bool variables) 23 | { 24 | firstLogger->clear(buffer,variables); 25 | secondLogger->clear(buffer,variables); 26 | } 27 | -------------------------------------------------------------------------------- /QtWebApp/logging/dualfilelogger.h: -------------------------------------------------------------------------------- 1 | /** 2 | @file 3 | @author Stefan Frings 4 | */ 5 | 6 | #ifndef DUALFILELOGGER_H 7 | #define DUALFILELOGGER_H 8 | 9 | #include 10 | #include 11 | #include 12 | #include "logglobal.h" 13 | #include "logger.h" 14 | #include "filelogger.h" 15 | 16 | /** 17 | Logs messages into two log files simultaneously. 18 | May be used to create two logfiles with different configuration settings. 19 | @see FileLogger for a description of the two underlying loggers. 20 | */ 21 | 22 | class DECLSPEC DualFileLogger : public Logger { 23 | Q_OBJECT 24 | Q_DISABLE_COPY(DualFileLogger) 25 | public: 26 | 27 | /** 28 | Constructor. 29 | @param firstSettings Configuration settings for the first log file, usually stored in an INI file. 30 | Must not be 0. 31 | Settings are read from the current group, so the caller must have called settings->beginGroup(). 32 | Because the group must not change during runtime, it is recommended to provide a 33 | separate QSettings instance to the logger that is not used by other parts of the program. 34 | @param secondSettings Same as firstSettings, but for the second log file. 35 | @param refreshInterval Interval of checking for changed config settings in msec, or 0=disabled 36 | @param parent Parent object. 37 | */ 38 | DualFileLogger(QSettings* firstSettings, QSettings* secondSettings, const int refreshInterval=10000, QObject *parent = 0); 39 | 40 | /** 41 | Decorate and log the message, if type>=minLevel. 42 | This method is thread safe. 43 | @param type Message type (level) 44 | @param message Message text 45 | @param file Name of the source file where the message was generated (usually filled with the macro __FILE__) 46 | @param function Name of the function where the message was generated (usually filled with the macro __LINE__) 47 | @param line Line Number of the source file, where the message was generated (usually filles with the macro __func__ or __FUNCTION__) 48 | @see LogMessage for a description of the message decoration. 49 | */ 50 | virtual void log(const QtMsgType type, const QString& message, const QString &file="", const QString &function="", const int line=0); 51 | 52 | /** 53 | Clear the thread-local data of the current thread. 54 | This method is thread safe. 55 | @param buffer Whether to clear the backtrace buffer 56 | @param variables Whether to clear the log variables 57 | */ 58 | virtual void clear(const bool buffer=true, const bool variables=true); 59 | 60 | private: 61 | 62 | /** First logger */ 63 | FileLogger* firstLogger; 64 | 65 | /** Second logger */ 66 | FileLogger* secondLogger; 67 | 68 | }; 69 | 70 | #endif // DUALFILELOGGER_H 71 | -------------------------------------------------------------------------------- /QtWebApp/logging/filelogger.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | @file 3 | @author Stefan Frings 4 | */ 5 | 6 | #include "filelogger.h" 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | void FileLogger::refreshSettings() 18 | { 19 | mutex.lock(); 20 | // Save old file name for later comparision with new settings 21 | QString oldFileName=fileName; 22 | 23 | // Load new config settings 24 | settings->sync(); 25 | fileName=settings->value("fileName").toString(); 26 | // Convert relative fileName to absolute, based on the directory of the config file. 27 | #ifdef Q_OS_WIN32 28 | if (QDir::isRelativePath(fileName) && settings->format()!=QSettings::NativeFormat) 29 | #else 30 | if (QDir::isRelativePath(fileName)) 31 | #endif 32 | { 33 | QFileInfo configFile(settings->fileName()); 34 | fileName=QFileInfo(configFile.absolutePath(),fileName).absoluteFilePath(); 35 | } 36 | maxSize=settings->value("maxSize",0).toLongLong(); 37 | maxBackups=settings->value("maxBackups",0).toInt(); 38 | msgFormat=settings->value("msgFormat","{timestamp} {type} {msg}").toString(); 39 | timestampFormat=settings->value("timestampFormat","yyyy-MM-dd hh:mm:ss.zzz").toString(); 40 | minLevel=static_cast(settings->value("minLevel",0).toInt()); 41 | bufferSize=settings->value("bufferSize",0).toInt(); 42 | 43 | // Create new file if the filename has been changed 44 | if (oldFileName!=fileName) 45 | { 46 | fprintf(stderr,"Logging to %s\n",qPrintable(fileName)); 47 | close(); 48 | open(); 49 | } 50 | mutex.unlock(); 51 | } 52 | 53 | 54 | FileLogger::FileLogger(QSettings* settings, const int refreshInterval, QObject* parent) 55 | : Logger(parent) 56 | { 57 | Q_ASSERT(settings!=0); 58 | Q_ASSERT(refreshInterval>=0); 59 | this->settings=settings; 60 | file=0; 61 | if (refreshInterval>0) 62 | { 63 | refreshTimer.start(refreshInterval,this); 64 | } 65 | flushTimer.start(1000,this); 66 | refreshSettings(); 67 | } 68 | 69 | 70 | FileLogger::~FileLogger() 71 | { 72 | close(); 73 | } 74 | 75 | 76 | void FileLogger::write(const LogMessage* logMessage) 77 | { 78 | // Try to write to the file 79 | if (file) 80 | { 81 | 82 | // Write the message 83 | file->write(qPrintable(logMessage->toString(msgFormat,timestampFormat))); 84 | 85 | // Flush error messages immediately, to ensure that no important message 86 | // gets lost when the program terinates abnormally. 87 | if (logMessage->getType()>=QtCriticalMsg) 88 | { 89 | file->flush(); 90 | } 91 | 92 | // Check for success 93 | if (file->error()) 94 | { 95 | close(); 96 | qWarning("Cannot write to log file %s: %s",qPrintable(fileName),qPrintable(file->errorString())); 97 | } 98 | 99 | } 100 | 101 | // Fall-back to the super class method, if writing failed 102 | if (!file) 103 | { 104 | Logger::write(logMessage); 105 | } 106 | 107 | } 108 | 109 | void FileLogger::open() 110 | { 111 | if (fileName.isEmpty()) 112 | { 113 | qWarning("Name of logFile is empty"); 114 | } 115 | else { 116 | file=new QFile(fileName); 117 | if (!file->open(QIODevice::WriteOnly | QIODevice::Append | QIODevice::Text)) 118 | { 119 | qWarning("Cannot open log file %s: %s",qPrintable(fileName),qPrintable(file->errorString())); 120 | file=0; 121 | } 122 | } 123 | } 124 | 125 | 126 | void FileLogger::close() 127 | { 128 | if (file) 129 | { 130 | file->close(); 131 | delete file; 132 | file=0; 133 | } 134 | } 135 | 136 | void FileLogger::rotate() { 137 | // count current number of existing backup files 138 | int count=0; 139 | forever 140 | { 141 | QFile bakFile(QString("%1.%2").arg(fileName).arg(count+1)); 142 | if (bakFile.exists()) 143 | { 144 | ++count; 145 | } 146 | else 147 | { 148 | break; 149 | } 150 | } 151 | 152 | // Remove all old backup files that exceed the maximum number 153 | while (maxBackups>0 && count>=maxBackups) 154 | { 155 | QFile::remove(QString("%1.%2").arg(fileName).arg(count)); 156 | --count; 157 | } 158 | 159 | // Rotate backup files 160 | for (int i=count; i>0; --i) { 161 | QFile::rename(QString("%1.%2").arg(fileName).arg(i),QString("%1.%2").arg(fileName).arg(i+1)); 162 | } 163 | 164 | // Backup the current logfile 165 | QFile::rename(fileName,fileName+".1"); 166 | } 167 | 168 | 169 | void FileLogger::timerEvent(QTimerEvent* event) 170 | { 171 | if (!event) 172 | { 173 | return; 174 | } 175 | else if (event->timerId()==refreshTimer.timerId()) 176 | { 177 | refreshSettings(); 178 | } 179 | else if (event->timerId()==flushTimer.timerId() && file) 180 | { 181 | mutex.lock(); 182 | 183 | // Flush the I/O buffer 184 | file->flush(); 185 | 186 | // Rotate the file if it is too large 187 | if (maxSize>0 && file->size()>=maxSize) 188 | { 189 | close(); 190 | rotate(); 191 | open(); 192 | } 193 | 194 | mutex.unlock(); 195 | } 196 | } 197 | -------------------------------------------------------------------------------- /QtWebApp/logging/filelogger.h: -------------------------------------------------------------------------------- 1 | /** 2 | @file 3 | @author Stefan Frings 4 | */ 5 | 6 | #ifndef FILELOGGER_H 7 | #define FILELOGGER_H 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include "logglobal.h" 15 | #include "logger.h" 16 | 17 | /** 18 | Logger that uses a text file for output. Settings are read from a 19 | config file using a QSettings object. Config settings can be changed at runtime. 20 |

21 | Example for the configuration settings: 22 |

 23 |   fileName=logs/QtWebApp.log
 24 |   maxSize=1000000
 25 |   maxBackups=2
 26 |   minLevel=0
 27 |   msgformat={timestamp} {typeNr} {type} thread={thread}: {msg}
 28 |   timestampFormat=dd.MM.yyyy hh:mm:ss.zzz
 29 |   bufferSize=0
 30 |   
31 | 32 | - fileName is the name of the log file, relative to the directory of the settings file. 33 | In case of windows, if the settings are in the registry, the path is relative to the current 34 | working directory. 35 | - maxSize is the maximum size of that file in bytes. The file will be backed up and 36 | replaced by a new file if it becomes larger than this limit. Please note that 37 | the actual file size may become a little bit larger than this limit. Default is 0=unlimited. 38 | - maxBackups defines the number of backup files to keep. Default is 0=unlimited. 39 | - minLevel defines the minimum type of messages that are written (together with buffered messages) into the file. Defaults is 0=debug. 40 | - msgFormat defines the decoration of log messages, see LogMessage class. Default is "{timestamp} {type} {msg}". 41 | - timestampFormat defines the format of timestamps, see QDateTime::toString(). Default is "yyyy-MM-dd hh:mm:ss.zzz". 42 | - bufferSize defines the size of the buffer. Default is 0=disabled. 43 | 44 | @see set() describes how to set logger variables 45 | @see LogMessage for a description of the message decoration. 46 | @see Logger for a descrition of the buffer. 47 | */ 48 | 49 | class DECLSPEC FileLogger : public Logger { 50 | Q_OBJECT 51 | Q_DISABLE_COPY(FileLogger) 52 | public: 53 | 54 | /** 55 | Constructor. 56 | @param settings Configuration settings, usually stored in an INI file. Must not be 0. 57 | Settings are read from the current group, so the caller must have called settings->beginGroup(). 58 | Because the group must not change during runtime, it is recommended to provide a 59 | separate QSettings instance to the logger that is not used by other parts of the program. 60 | @param refreshInterval Interval of checking for changed config settings in msec, or 0=disabled 61 | @param parent Parent object 62 | */ 63 | FileLogger(QSettings* settings, const int refreshInterval=10000, QObject* parent = 0); 64 | 65 | /** 66 | Destructor. Closes the file. 67 | */ 68 | virtual ~FileLogger(); 69 | 70 | /** Write a message to the log file */ 71 | virtual void write(const LogMessage* logMessage); 72 | 73 | protected: 74 | 75 | /** 76 | Handler for timer events. 77 | Refreshes config settings or synchronizes I/O buffer, depending on the event. 78 | This method is thread-safe. 79 | @param event used to distinguish between the two timers. 80 | */ 81 | void timerEvent(QTimerEvent* event); 82 | 83 | private: 84 | 85 | /** Configured name of the log file */ 86 | QString fileName; 87 | 88 | /** Configured maximum size of the file in bytes, or 0=unlimited */ 89 | long maxSize; 90 | 91 | /** Configured maximum number of backup files, or 0=unlimited */ 92 | int maxBackups; 93 | 94 | /** Pointer to the configuration settings */ 95 | QSettings* settings; 96 | 97 | /** Output file, or 0=disabled */ 98 | QFile* file; 99 | 100 | /** Timer for refreshing configuration settings */ 101 | QBasicTimer refreshTimer; 102 | 103 | /** Timer for flushing the file I/O buffer */ 104 | QBasicTimer flushTimer; 105 | 106 | /** Open the output file */ 107 | void open(); 108 | 109 | /** Close the output file */ 110 | void close(); 111 | 112 | /** Rotate files and delete some backups if there are too many */ 113 | void rotate(); 114 | 115 | /** 116 | Refreshes the configuration settings. 117 | This method is thread-safe. 118 | */ 119 | void refreshSettings(); 120 | 121 | }; 122 | 123 | #endif // FILELOGGER_H 124 | -------------------------------------------------------------------------------- /QtWebApp/logging/logger.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | @file 3 | @author Stefan Frings 4 | */ 5 | 6 | #include "logger.h" 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | Logger* Logger::defaultLogger=0; 15 | 16 | 17 | QThreadStorage*> Logger::logVars; 18 | 19 | 20 | QMutex Logger::mutex; 21 | 22 | 23 | Logger::Logger(QObject* parent) 24 | : QObject(parent), 25 | msgFormat("{timestamp} {type} {msg}"), 26 | timestampFormat("dd.MM.yyyy hh:mm:ss.zzz"), 27 | minLevel(QtDebugMsg), 28 | bufferSize(0) 29 | {} 30 | 31 | 32 | Logger::Logger(const QString msgFormat, const QString timestampFormat, const QtMsgType minLevel, const int bufferSize, QObject* parent) 33 | :QObject(parent) 34 | { 35 | this->msgFormat=msgFormat; 36 | this->timestampFormat=timestampFormat; 37 | this->minLevel=minLevel; 38 | this->bufferSize=bufferSize; 39 | } 40 | 41 | 42 | void Logger::msgHandler(const QtMsgType type, const QString &message, const QString &file, const QString &function, const int line) 43 | { 44 | static QMutex recursiveMutex(QMutex::Recursive); 45 | static QMutex nonRecursiveMutex(QMutex::NonRecursive); 46 | 47 | // Prevent multiple threads from calling this method simultaneoulsy. 48 | // But allow recursive calls, which is required to prevent a deadlock 49 | // if the logger itself produces an error message. 50 | recursiveMutex.lock(); 51 | 52 | // Fall back to stderr when this method has been called recursively. 53 | if (defaultLogger && nonRecursiveMutex.tryLock()) 54 | { 55 | defaultLogger->log(type, message, file, function, line); 56 | nonRecursiveMutex.unlock(); 57 | } 58 | else 59 | { 60 | fputs(qPrintable(message),stderr); 61 | fflush(stderr); 62 | } 63 | 64 | // Abort the program after logging a fatal message 65 | if (type>=QtFatalMsg) 66 | { 67 | abort(); 68 | } 69 | 70 | recursiveMutex.unlock(); 71 | } 72 | 73 | 74 | #if QT_VERSION >= 0x050000 75 | void Logger::msgHandler5(const QtMsgType type, const QMessageLogContext &context, const QString &message) 76 | { 77 | (void)(context); // suppress "unused parameter" warning 78 | msgHandler(type,message,context.file,context.function,context.line); 79 | } 80 | #else 81 | void Logger::msgHandler4(const QtMsgType type, const char* message) 82 | { 83 | msgHandler(type,message); 84 | } 85 | #endif 86 | 87 | 88 | Logger::~Logger() 89 | { 90 | if (defaultLogger==this) 91 | { 92 | #if QT_VERSION >= 0x050000 93 | qInstallMessageHandler(0); 94 | #else 95 | qInstallMsgHandler(0); 96 | #endif 97 | defaultLogger=0; 98 | } 99 | } 100 | 101 | 102 | void Logger::write(const LogMessage* logMessage) 103 | { 104 | fputs(qPrintable(logMessage->toString(msgFormat,timestampFormat)),stderr); 105 | fflush(stderr); 106 | } 107 | 108 | 109 | void Logger::installMsgHandler() 110 | { 111 | defaultLogger=this; 112 | #if QT_VERSION >= 0x050000 113 | qInstallMessageHandler(msgHandler5); 114 | #else 115 | qInstallMsgHandler(msgHandler4); 116 | #endif 117 | } 118 | 119 | 120 | void Logger::set(const QString& name, const QString& value) 121 | { 122 | mutex.lock(); 123 | if (!logVars.hasLocalData()) 124 | { 125 | logVars.setLocalData(new QHash); 126 | } 127 | logVars.localData()->insert(name,value); 128 | mutex.unlock(); 129 | } 130 | 131 | 132 | void Logger::clear(const bool buffer, const bool variables) 133 | { 134 | mutex.lock(); 135 | if (buffer && buffers.hasLocalData()) 136 | { 137 | QList* buffer=buffers.localData(); 138 | while (buffer && !buffer->isEmpty()) { 139 | LogMessage* logMessage=buffer->takeLast(); 140 | delete logMessage; 141 | } 142 | } 143 | if (variables && logVars.hasLocalData()) 144 | { 145 | logVars.localData()->clear(); 146 | } 147 | mutex.unlock(); 148 | } 149 | 150 | 151 | void Logger::log(const QtMsgType type, const QString& message, const QString &file, const QString &function, const int line) 152 | { 153 | mutex.lock(); 154 | 155 | // If the buffer is enabled, write the message into it 156 | if (bufferSize>0) { 157 | // Create new thread local buffer, if necessary 158 | if (!buffers.hasLocalData()) { 159 | buffers.setLocalData(new QList()); 160 | } 161 | QList* buffer=buffers.localData(); 162 | // Append the decorated log message 163 | LogMessage* logMessage=new LogMessage(type,message,logVars.localData(),file,function,line); 164 | buffer->append(logMessage); 165 | // Delete oldest message if the buffer became too large 166 | if (buffer->size()>bufferSize) 167 | { 168 | delete buffer->takeFirst(); 169 | } 170 | // If the type of the message is high enough, print the whole buffer 171 | if (type>=minLevel) { 172 | while (!buffer->isEmpty()) 173 | { 174 | LogMessage* logMessage=buffer->takeFirst(); 175 | write(logMessage); 176 | delete logMessage; 177 | } 178 | } 179 | } 180 | 181 | // Buffer is disabled, print the message if the type is high enough 182 | else { 183 | if (type>=minLevel) 184 | { 185 | LogMessage logMessage(type,message,logVars.localData(),file,function,line); 186 | write(&logMessage); 187 | } 188 | } 189 | mutex.unlock(); 190 | } 191 | -------------------------------------------------------------------------------- /QtWebApp/logging/logger.h: -------------------------------------------------------------------------------- 1 | /** 2 | @file 3 | @author Stefan Frings 4 | */ 5 | 6 | #ifndef LOGGER_H 7 | #define LOGGER_H 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include "logglobal.h" 16 | #include "logmessage.h" 17 | 18 | /** 19 | Decorates and writes log messages to the console, stderr. 20 |

21 | The decorator uses a predefined msgFormat string to enrich log messages 22 | with additional information (e.g. timestamp). 23 |

24 | The msgFormat string and also the message text may contain additional 25 | variable names in the form {name} that are filled by values 26 | taken from a static thread local dictionary. 27 |

28 | The logger keeps a configurable number of messages in a ring-buffer. 29 | A log message with a severity >= minLevel flushes the buffer, 30 | so the stored messages get written out. If the buffer is disabled, then 31 | only messages with severity >= minLevel are written out. 32 |

33 | If you enable the buffer and use minLevel=2, then the application logs 34 | only errors together with some buffered debug messages. But as long no 35 | error occurs, nothing gets written out. 36 |

37 | Each thread has it's own buffer. 38 |

39 | The logger can be registered to handle messages from 40 | the static global functions qDebug(), qWarning(), qCritical() and qFatal(). 41 | 42 | @see set() describes how to set logger variables 43 | @see LogMessage for a description of the message decoration. 44 | @warning You should prefer a derived class, for example FileLogger, 45 | because logging to the console is less useful. 46 | */ 47 | 48 | class DECLSPEC Logger : public QObject { 49 | Q_OBJECT 50 | Q_DISABLE_COPY(Logger) 51 | public: 52 | 53 | /** 54 | Constructor. 55 | Uses the same defaults as the other constructor. 56 | @param parent Parent object 57 | */ 58 | Logger(QObject* parent); 59 | 60 | 61 | /** 62 | Constructor. 63 | @param msgFormat Format of the decoration, e.g. "{timestamp} {type} thread={thread}: {msg}" 64 | @param timestampFormat Format of timestamp, e.g. "dd.MM.yyyy hh:mm:ss.zzz" 65 | @param minLevel Minimum severity that genertes an output (0=debug, 1=warning, 2=critical, 3=fatal). 66 | @param bufferSize Size of the backtrace buffer, number of messages per thread. 0=disabled. 67 | @param parent Parent object 68 | @see LogMessage for a description of the message decoration. 69 | */ 70 | Logger(const QString msgFormat="{timestamp} {type} {msg}", const QString timestampFormat="dd.MM.yyyy hh:mm:ss.zzz", const QtMsgType minLevel=QtDebugMsg, const int bufferSize=0, QObject* parent = 0); 71 | 72 | /** Destructor */ 73 | virtual ~Logger(); 74 | 75 | /** 76 | Decorate and log the message, if type>=minLevel. 77 | This method is thread safe. 78 | @param type Message type (level) 79 | @param message Message text 80 | @param file Name of the source file where the message was generated (usually filled with the macro __FILE__) 81 | @param function Name of the function where the message was generated (usually filled with the macro __LINE__) 82 | @param line Line Number of the source file, where the message was generated (usually filles with the macro __func__ or __FUNCTION__) 83 | @see LogMessage for a description of the message decoration. 84 | */ 85 | virtual void log(const QtMsgType type, const QString& message, const QString &file="", const QString &function="", const int line=0); 86 | 87 | /** 88 | Installs this logger as the default message handler, so it 89 | can be used through the global static logging functions (e.g. qDebug()). 90 | */ 91 | void installMsgHandler(); 92 | 93 | /** 94 | Sets a thread-local variable that may be used to decorate log messages. 95 | This method is thread safe. 96 | @param name Name of the variable 97 | @param value Value of the variable 98 | */ 99 | static void set(const QString& name, const QString& value); 100 | 101 | /** 102 | Clear the thread-local data of the current thread. 103 | This method is thread safe. 104 | @param buffer Whether to clear the backtrace buffer 105 | @param variables Whether to clear the log variables 106 | */ 107 | virtual void clear(const bool buffer=true, const bool variables=true); 108 | 109 | protected: 110 | 111 | /** Format string for message decoration */ 112 | QString msgFormat; 113 | 114 | /** Format string of timestamps */ 115 | QString timestampFormat; 116 | 117 | /** Minimum level of message types that are written out */ 118 | QtMsgType minLevel; 119 | 120 | /** Size of backtrace buffer, number of messages per thread. 0=disabled */ 121 | int bufferSize; 122 | 123 | /** Used to synchronize access of concurrent threads */ 124 | static QMutex mutex; 125 | 126 | /** 127 | Decorate and write a log message to stderr. Override this method 128 | to provide a different output medium. 129 | */ 130 | virtual void write(const LogMessage* logMessage); 131 | 132 | private: 133 | 134 | /** Pointer to the default logger, used by msgHandler() */ 135 | static Logger* defaultLogger; 136 | 137 | /** 138 | Message Handler for the global static logging functions (e.g. qDebug()). 139 | Forward calls to the default logger. 140 |

141 | In case of a fatal message, the program will abort. 142 | Variables in the in the message are replaced by their values. 143 | This method is thread safe. 144 | @param type Message type (level) 145 | @param message Message text 146 | @param file Name of the source file where the message was generated (usually filled with the macro __FILE__) 147 | @param function Name of the function where the message was generated (usually filled with the macro __LINE__) 148 | @param line Line Number of the source file, where the message was generated (usually filles with the macro __func__ or __FUNCTION__) 149 | */ 150 | static void msgHandler(const QtMsgType type, const QString &message, const QString &file="", const QString &function="", const int line=0); 151 | 152 | 153 | #if QT_VERSION >= 0x050000 154 | 155 | /** 156 | Wrapper for QT version 5. 157 | @param type Message type (level) 158 | @param context Message context 159 | @param message Message text 160 | @see msgHandler() 161 | */ 162 | static void msgHandler5(const QtMsgType type, const QMessageLogContext& context, const QString &message); 163 | 164 | #else 165 | 166 | /** 167 | Wrapper for QT version 4. 168 | @param type Message type (level) 169 | @param message Message text 170 | @see msgHandler() 171 | */ 172 | static void msgHandler4(const QtMsgType type, const char * message); 173 | 174 | #endif 175 | 176 | /** Thread local variables to be used in log messages */ 177 | static QThreadStorage*> logVars; 178 | 179 | /** Thread local backtrace buffers */ 180 | QThreadStorage*> buffers; 181 | 182 | }; 183 | 184 | #endif // LOGGER_H 185 | -------------------------------------------------------------------------------- /QtWebApp/logging/logging.pri: -------------------------------------------------------------------------------- 1 | INCLUDEPATH += $$PWD 2 | DEPENDPATH += $$PWD 3 | 4 | HEADERS += $$PWD/logglobal.h $$PWD/logmessage.h $$PWD/logger.h $$PWD/filelogger.h $$PWD/dualfilelogger.h 5 | 6 | SOURCES += $$PWD/logmessage.cpp $$PWD/logger.cpp $$PWD/filelogger.cpp $$PWD/dualfilelogger.cpp 7 | -------------------------------------------------------------------------------- /QtWebApp/logging/logglobal.h: -------------------------------------------------------------------------------- 1 | /** 2 | @file 3 | @author Stefan Frings 4 | */ 5 | 6 | #ifndef LOGGLOBAL_H 7 | #define LOGGLOBAL_H 8 | 9 | #include 10 | 11 | // This is specific to Windows dll's 12 | #if defined(Q_OS_WIN) 13 | #if defined(QTWEBAPPLIB_EXPORT) 14 | #define DECLSPEC Q_DECL_EXPORT 15 | #elif defined(QTWEBAPPLIB_IMPORT) 16 | #define DECLSPEC Q_DECL_IMPORT 17 | #endif 18 | #endif 19 | #if !defined(DECLSPEC) 20 | #define DECLSPEC 21 | #endif 22 | 23 | #endif // LOGGLOBAL_H 24 | 25 | -------------------------------------------------------------------------------- /QtWebApp/logging/logmessage.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | @file 3 | @author Stefan Frings 4 | */ 5 | 6 | #include "logmessage.h" 7 | #include 8 | 9 | LogMessage::LogMessage(const QtMsgType type, const QString& message, QHash* logVars, const QString &file, const QString &function, const int line) 10 | { 11 | this->type=type; 12 | this->message=message; 13 | this->file=file; 14 | this->function=function; 15 | this->line=line; 16 | timestamp=QDateTime::currentDateTime(); 17 | threadId=QThread::currentThreadId(); 18 | 19 | // Copy the logVars if not null, 20 | // so that later changes in the original do not affect the copy 21 | if (logVars) 22 | { 23 | this->logVars=*logVars; 24 | } 25 | } 26 | 27 | QString LogMessage::toString(const QString& msgFormat, const QString& timestampFormat) const 28 | { 29 | QString decorated=msgFormat+"\n"; 30 | decorated.replace("{msg}",message); 31 | 32 | if (decorated.contains("{timestamp}")) 33 | { 34 | decorated.replace("{timestamp}",timestamp.toString(timestampFormat)); 35 | } 36 | 37 | QString typeNr; 38 | typeNr.setNum(type); 39 | decorated.replace("{typeNr}",typeNr); 40 | 41 | switch (type) 42 | { 43 | case QtDebugMsg: 44 | decorated.replace("{type}","DEBUG"); 45 | break; 46 | case QtWarningMsg: 47 | decorated.replace("{type}","WARNING"); 48 | break; 49 | case QtCriticalMsg: 50 | decorated.replace("{type}","CRITICAL"); 51 | break; 52 | case QtFatalMsg: 53 | decorated.replace("{type}","FATAL"); 54 | break; 55 | default: 56 | decorated.replace("{type}",typeNr); 57 | } 58 | 59 | decorated.replace("{file}",file); 60 | decorated.replace("{function}",function); 61 | decorated.replace("{line}",QString::number(line)); 62 | 63 | QString threadId; 64 | threadId.setNum((unsigned long)QThread::currentThreadId()); 65 | decorated.replace("{thread}",threadId); 66 | 67 | // Fill in variables 68 | if (decorated.contains("{") && !logVars.isEmpty()) 69 | { 70 | QList keys=logVars.keys(); 71 | foreach (QString key, keys) 72 | { 73 | decorated.replace("{"+key+"}",logVars.value(key)); 74 | } 75 | } 76 | 77 | return decorated; 78 | } 79 | 80 | QtMsgType LogMessage::getType() const 81 | { 82 | return type; 83 | } 84 | -------------------------------------------------------------------------------- /QtWebApp/logging/logmessage.h: -------------------------------------------------------------------------------- 1 | /** 2 | @file 3 | @author Stefan Frings 4 | */ 5 | 6 | #ifndef LOGMESSAGE_H 7 | #define LOGMESSAGE_H 8 | 9 | #include 10 | #include 11 | #include 12 | #include "logglobal.h" 13 | 14 | /** 15 | Represents a single log message together with some data 16 | that are used to decorate the log message. 17 | 18 | The following variables may be used in the message and in msgFormat: 19 | 20 | - {timestamp} Date and time of creation 21 | - {typeNr} Type of the message in numeric format (0-3) 22 | - {type} Type of the message in string format (DEBUG, WARNING, CRITICAL, FATAL) 23 | - {thread} ID number of the thread 24 | - {msg} Message text 25 | - {xxx} For any user-defined logger variable 26 | 27 | Plus some new variables since QT 5.0, only filled when compiled in debug mode: 28 | 29 | - {file} Filename where the message was generated 30 | - {function} Function where the message was generated 31 | - {line} Line number where the message was generated 32 | */ 33 | 34 | class DECLSPEC LogMessage 35 | { 36 | Q_DISABLE_COPY(LogMessage) 37 | public: 38 | 39 | /** 40 | Constructor. All parameters are copied, so that later changes to them do not 41 | affect this object. 42 | @param type Type of the message 43 | @param message Message text 44 | @param logVars Logger variables, 0 is allowed 45 | @param file Name of the source file where the message was generated 46 | @param function Name of the function where the message was generated 47 | @param line Line Number of the source file, where the message was generated 48 | */ 49 | LogMessage(const QtMsgType type, const QString& message, QHash* logVars, const QString &file, const QString &function, const int line); 50 | 51 | /** 52 | Returns the log message as decorated string. 53 | @param msgFormat Format of the decoration. May contain variables and static text, 54 | e.g. "{timestamp} {type} thread={thread}: {msg}". 55 | @param timestampFormat Format of timestamp, e.g. "dd.MM.yyyy hh:mm:ss.zzz", see QDateTime::toString(). 56 | @see QDatetime for a description of the timestamp format pattern 57 | */ 58 | QString toString(const QString& msgFormat, const QString& timestampFormat) const; 59 | 60 | /** 61 | Get the message type. 62 | */ 63 | QtMsgType getType() const; 64 | 65 | private: 66 | 67 | /** Logger variables */ 68 | QHash logVars; 69 | 70 | /** Date and time of creation */ 71 | QDateTime timestamp; 72 | 73 | /** Type of the message */ 74 | QtMsgType type; 75 | 76 | /** ID number of the thread */ 77 | Qt::HANDLE threadId; 78 | 79 | /** Message text */ 80 | QString message; 81 | 82 | /** Filename where the message was generated */ 83 | QString file; 84 | 85 | /** Function name where the message was generated */ 86 | QString function; 87 | 88 | /** Line number where the message was generated */ 89 | int line; 90 | 91 | }; 92 | 93 | #endif // LOGMESSAGE_H 94 | -------------------------------------------------------------------------------- /QtWebApp/mainpage.dox: -------------------------------------------------------------------------------- 1 | /** 2 | @mainpage 3 | @author Stefan Frings 4 | QtWebApp supports you in writing server-side web application in C++ 5 | based on the Qt toolkit. It is a light-weight implementation inspired 6 | by Java Servlets. 7 |

8 | Features: 9 | 10 | - HttpListener is a HTTP 1.1 web server with support for 11 | - HTTPS 12 | - IP v4 and IP v6 13 | - persistent connections 14 | - pipelining 15 | - file uploads 16 | - cookies 17 | - dynamic thread pool 18 | - optional file cache in StaticFileController 19 | - optional sessions via HttpSessionStore 20 | - The Template engine supports 21 | - multi languages via TemplateLoader 22 | - optional file cache via TemplateCache 23 | - for all text based file formats 24 | - The Logger classes provide 25 | - automatic backups and file rotation 26 | - configurable message format, see LogMessage 27 | - messages may contain thread-local info variables 28 | - optional ring-buffer for writing debug messages in case of errors 29 | - they are configurable at runtime without program restart 30 | - The QtService class 31 | - Runs the application as a Windows service or Unix daemon 32 | 33 | If you write a real application based on this source, take a look into the Demo 34 | applications. They set up a single listener on port 8080, however multiple 35 | listeners with individual configurations are also possible. 36 |

37 | To set up a HA system with loadsharing and/or failover, I recommend an Apache HTTP 38 | server with mod_proxy and sticky sessions 39 | */ 40 | 41 | -------------------------------------------------------------------------------- /QtWebApp/qtservice/qtservice.h: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | ** 3 | ** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). 4 | ** Contact: http://www.qt-project.org/legal 5 | ** 6 | ** This file is part of the Qt Solutions component. 7 | ** 8 | ** $QT_BEGIN_LICENSE:BSD$ 9 | ** You may use this file under the terms of the BSD license as follows: 10 | ** 11 | ** "Redistribution and use in source and binary forms, with or without 12 | ** modification, are permitted provided that the following conditions are 13 | ** met: 14 | ** * Redistributions of source code must retain the above copyright 15 | ** notice, this list of conditions and the following disclaimer. 16 | ** * Redistributions in binary form must reproduce the above copyright 17 | ** notice, this list of conditions and the following disclaimer in 18 | ** the documentation and/or other materials provided with the 19 | ** distribution. 20 | ** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names 21 | ** of its contributors may be used to endorse or promote products derived 22 | ** from this software without specific prior written permission. 23 | ** 24 | ** 25 | ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 26 | ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 27 | ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 28 | ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 29 | ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 30 | ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 31 | ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 32 | ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 33 | ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 34 | ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 35 | ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." 36 | ** 37 | ** $QT_END_LICENSE$ 38 | ** 39 | ****************************************************************************/ 40 | /** 41 | @file 42 | @author Tolltech 43 | */ 44 | 45 | #ifndef QTSERVICE_H 46 | #define QTSERVICE_H 47 | 48 | #include 49 | #include 50 | 51 | // This is specific to Windows dll's 52 | #if defined(Q_OS_WIN) 53 | #if defined(QTWEBAPPLIB_EXPORT) 54 | #define DECLSPEC Q_DECL_EXPORT 55 | #elif defined(QTWEBAPPLIB_IMPORT) 56 | #define DECLSPEC Q_DECL_IMPORT 57 | #endif 58 | #endif 59 | #if !defined(DECLSPEC) 60 | #define DECLSPEC 61 | #endif 62 | 63 | class QStringList; 64 | class QtServiceControllerPrivate; 65 | 66 | class DECLSPEC QtServiceController 67 | { 68 | Q_DECLARE_PRIVATE(QtServiceController) 69 | public: 70 | enum StartupType 71 | { 72 | AutoStartup = 0, ManualStartup 73 | }; 74 | 75 | QtServiceController(const QString &name); 76 | virtual ~QtServiceController(); 77 | 78 | bool isInstalled() const; 79 | bool isRunning() const; 80 | 81 | QString serviceName() const; 82 | QString serviceDescription() const; 83 | StartupType startupType() const; 84 | QString serviceFilePath() const; 85 | 86 | static bool install(const QString &serviceFilePath, const QString &account = QString(), 87 | const QString &password = QString()); 88 | bool uninstall(); 89 | 90 | bool start(const QStringList &arguments); 91 | bool start(); 92 | bool stop(); 93 | bool pause(); 94 | bool resume(); 95 | bool sendCommand(int code); 96 | 97 | private: 98 | QtServiceControllerPrivate *d_ptr; 99 | }; 100 | 101 | class QtServiceBasePrivate; 102 | 103 | class DECLSPEC QtServiceBase 104 | { 105 | Q_DECLARE_PRIVATE(QtServiceBase) 106 | public: 107 | 108 | enum MessageType 109 | { 110 | Success = 0, Error, Warning, Information 111 | }; 112 | 113 | enum ServiceFlag 114 | { 115 | Default = 0x00, 116 | CanBeSuspended = 0x01, 117 | CannotBeStopped = 0x02, 118 | NeedsStopOnShutdown = 0x04 119 | }; 120 | 121 | Q_DECLARE_FLAGS(ServiceFlags, ServiceFlag) 122 | 123 | QtServiceBase(int argc, char **argv, const QString &name); 124 | virtual ~QtServiceBase(); 125 | 126 | QString serviceName() const; 127 | 128 | QString serviceDescription() const; 129 | void setServiceDescription(const QString &description); 130 | 131 | QtServiceController::StartupType startupType() const; 132 | void setStartupType(QtServiceController::StartupType startupType); 133 | 134 | ServiceFlags serviceFlags() const; 135 | void setServiceFlags(ServiceFlags flags); 136 | 137 | int exec(); 138 | 139 | void logMessage(const QString &message, MessageType type = Success, 140 | int id = 0, uint category = 0, const QByteArray &data = QByteArray()); 141 | 142 | static QtServiceBase *instance(); 143 | 144 | protected: 145 | 146 | virtual void start() = 0; 147 | virtual void stop(); 148 | virtual void pause(); 149 | virtual void resume(); 150 | virtual void processCommand(int code); 151 | 152 | virtual void createApplication(int &argc, char **argv) = 0; 153 | 154 | virtual int executeApplication() = 0; 155 | 156 | private: 157 | 158 | friend class QtServiceSysPrivate; 159 | QtServiceBasePrivate *d_ptr; 160 | }; 161 | 162 | template 163 | class DECLSPEC QtService : public QtServiceBase 164 | { 165 | public: 166 | QtService(int argc, char **argv, const QString &name) 167 | : QtServiceBase(argc, argv, name), app(0) 168 | { } 169 | ~QtService() 170 | { 171 | } 172 | 173 | protected: 174 | Application *application() const 175 | { return app; } 176 | 177 | virtual void createApplication(int &argc, char **argv) 178 | { 179 | app = new Application(argc, argv); 180 | QCoreApplication *a = app; 181 | Q_UNUSED(a); 182 | } 183 | 184 | virtual int executeApplication() 185 | { return Application::exec(); } 186 | 187 | private: 188 | Application *app; 189 | }; 190 | 191 | Q_DECLARE_OPERATORS_FOR_FLAGS(QtServiceBase::ServiceFlags) 192 | 193 | #endif // QTSERVICE_H 194 | -------------------------------------------------------------------------------- /QtWebApp/qtservice/qtservice.pri: -------------------------------------------------------------------------------- 1 | INCLUDEPATH += $$PWD 2 | DEPENDPATH += $$PWD 3 | 4 | !win32:QT += network 5 | win32:LIBS += -luser32 6 | 7 | HEADERS += $$PWD/qtservice.h $$PWD/qtservice_p.h 8 | unix:HEADERS += $$PWD/qtunixsocket.h $$PWD/qtunixserversocket.h 9 | 10 | SOURCES += $$PWD/qtservice.cpp 11 | win32:SOURCES += $$PWD/qtservice_win.cpp 12 | unix:SOURCES += $$PWD/qtservice_unix.cpp $$PWD/qtunixsocket.cpp $$PWD/qtunixserversocket.cpp 13 | 14 | -------------------------------------------------------------------------------- /QtWebApp/qtservice/qtservice_p.h: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | ** 3 | ** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). 4 | ** Contact: http://www.qt-project.org/legal 5 | ** 6 | ** This file is part of the Qt Solutions component. 7 | ** 8 | ** $QT_BEGIN_LICENSE:BSD$ 9 | ** You may use this file under the terms of the BSD license as follows: 10 | ** 11 | ** "Redistribution and use in source and binary forms, with or without 12 | ** modification, are permitted provided that the following conditions are 13 | ** met: 14 | ** * Redistributions of source code must retain the above copyright 15 | ** notice, this list of conditions and the following disclaimer. 16 | ** * Redistributions in binary form must reproduce the above copyright 17 | ** notice, this list of conditions and the following disclaimer in 18 | ** the documentation and/or other materials provided with the 19 | ** distribution. 20 | ** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names 21 | ** of its contributors may be used to endorse or promote products derived 22 | ** from this software without specific prior written permission. 23 | ** 24 | ** 25 | ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 26 | ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 27 | ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 28 | ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 29 | ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 30 | ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 31 | ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 32 | ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 33 | ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 34 | ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 35 | ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." 36 | ** 37 | ** $QT_END_LICENSE$ 38 | ** 39 | ****************************************************************************/ 40 | 41 | #ifndef QTSERVICE_P_H 42 | #define QTSERVICE_P_H 43 | 44 | #include 45 | #include "qtservice.h" 46 | 47 | class QtServiceControllerPrivate 48 | { 49 | Q_DECLARE_PUBLIC(QtServiceController) 50 | public: 51 | QString serviceName; 52 | QtServiceController *q_ptr; 53 | }; 54 | 55 | class QtServiceBasePrivate 56 | { 57 | Q_DECLARE_PUBLIC(QtServiceBase) 58 | public: 59 | 60 | QtServiceBasePrivate(const QString &name); 61 | ~QtServiceBasePrivate(); 62 | 63 | QtServiceBase *q_ptr; 64 | 65 | QString serviceDescription; 66 | QtServiceController::StartupType startupType; 67 | QtServiceBase::ServiceFlags serviceFlags; 68 | QStringList args; 69 | 70 | static class QtServiceBase *instance; 71 | 72 | QtServiceController controller; 73 | 74 | void startService(); 75 | int run(bool asService, const QStringList &argList); 76 | bool install(const QString &account, const QString &password); 77 | 78 | bool start(); 79 | 80 | QString filePath() const; 81 | bool sysInit(); 82 | void sysSetPath(); 83 | void sysCleanup(); 84 | class QtServiceSysPrivate *sysd; 85 | }; 86 | 87 | #endif 88 | -------------------------------------------------------------------------------- /QtWebApp/qtservice/qtservice_unix.cpp: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | ** 3 | ** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). 4 | ** Contact: http://www.qt-project.org/legal 5 | ** 6 | ** This file is part of the Qt Solutions component. 7 | ** 8 | ** $QT_BEGIN_LICENSE:BSD$ 9 | ** You may use this file under the terms of the BSD license as follows: 10 | ** 11 | ** "Redistribution and use in source and binary forms, with or without 12 | ** modification, are permitted provided that the following conditions are 13 | ** met: 14 | ** * Redistributions of source code must retain the above copyright 15 | ** notice, this list of conditions and the following disclaimer. 16 | ** * Redistributions in binary form must reproduce the above copyright 17 | ** notice, this list of conditions and the following disclaimer in 18 | ** the documentation and/or other materials provided with the 19 | ** distribution. 20 | ** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names 21 | ** of its contributors may be used to endorse or promote products derived 22 | ** from this software without specific prior written permission. 23 | ** 24 | ** 25 | ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 26 | ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 27 | ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 28 | ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 29 | ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 30 | ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 31 | ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 32 | ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 33 | ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 34 | ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 35 | ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." 36 | ** 37 | ** $QT_END_LICENSE$ 38 | ** 39 | ****************************************************************************/ 40 | 41 | #include "qtservice.h" 42 | #include "qtservice_p.h" 43 | #include "qtunixsocket.h" 44 | #include "qtunixserversocket.h" 45 | #include 46 | #include 47 | #include 48 | #include 49 | #include 50 | #include 51 | #include 52 | #include 53 | #include 54 | #include 55 | #include 56 | #include 57 | #include 58 | #include 59 | #include 60 | #include 61 | 62 | static QString encodeName(const QString &name, bool allowUpper = false) 63 | { 64 | QString n = name.toLower(); 65 | QString legal = QLatin1String("abcdefghijklmnopqrstuvwxyz1234567890"); 66 | if (allowUpper) 67 | legal += QLatin1String("ABCDEFGHIJKLMNOPQRSTUVWXYZ"); 68 | int pos = 0; 69 | while (pos < n.size()) { 70 | if (legal.indexOf(n[pos]) == -1) 71 | n.remove(pos, 1); 72 | else 73 | ++pos; 74 | } 75 | return n; 76 | } 77 | 78 | static QString login() 79 | { 80 | QString l; 81 | uid_t uid = getuid(); 82 | passwd *pw = getpwuid(uid); 83 | if (pw) 84 | l = QString(pw->pw_name); 85 | return l; 86 | } 87 | 88 | static QString socketPath(const QString &serviceName) 89 | { 90 | QString sn = encodeName(serviceName); 91 | return QString(QLatin1String("/var/tmp/") + sn + QLatin1String(".") + login()); 92 | } 93 | 94 | static bool sendCmd(const QString &serviceName, const QString &cmd) 95 | { 96 | bool retValue = false; 97 | QtUnixSocket sock; 98 | if (sock.connectTo(socketPath(serviceName))) { 99 | sock.write(QString(cmd+"\r\n").toLatin1().constData()); 100 | sock.flush(); 101 | sock.waitForReadyRead(-1); 102 | QString reply = sock.readAll(); 103 | if (reply == QLatin1String("true")) 104 | retValue = true; 105 | sock.close(); 106 | } 107 | return retValue; 108 | } 109 | 110 | static QString absPath(const QString &path) 111 | { 112 | QString ret; 113 | if (path[0] != QChar('/')) { // Not an absolute path 114 | int slashpos; 115 | if ((slashpos = path.lastIndexOf('/')) != -1) { // Relative path 116 | QDir dir = QDir::current(); 117 | dir.cd(path.left(slashpos)); 118 | ret = dir.absolutePath(); 119 | } else { // Need to search $PATH 120 | char *envPath = ::getenv("PATH"); 121 | if (envPath) { 122 | QStringList envPaths = QString::fromLocal8Bit(envPath).split(':'); 123 | for (int i = 0; i < envPaths.size(); ++i) { 124 | if (QFile::exists(envPaths.at(i) + QLatin1String("/") + QString(path))) { 125 | QDir dir(envPaths.at(i)); 126 | ret = dir.absolutePath(); 127 | break; 128 | } 129 | } 130 | } 131 | } 132 | } else { 133 | QFileInfo fi(path); 134 | ret = fi.absolutePath(); 135 | } 136 | return ret; 137 | } 138 | 139 | QString QtServiceBasePrivate::filePath() const 140 | { 141 | QString ret; 142 | if (args.isEmpty()) 143 | return ret; 144 | QFileInfo fi(args[0]); 145 | QDir dir(absPath(args[0])); 146 | return dir.absoluteFilePath(fi.fileName()); 147 | } 148 | 149 | 150 | QString QtServiceController::serviceDescription() const 151 | { 152 | QSettings settings(QSettings::SystemScope, "QtSoftware"); 153 | settings.beginGroup("services"); 154 | settings.beginGroup(serviceName()); 155 | 156 | QString desc = settings.value("description").toString(); 157 | 158 | settings.endGroup(); 159 | settings.endGroup(); 160 | 161 | return desc; 162 | } 163 | 164 | QtServiceController::StartupType QtServiceController::startupType() const 165 | { 166 | QSettings settings(QSettings::SystemScope, "QtSoftware"); 167 | settings.beginGroup("services"); 168 | settings.beginGroup(serviceName()); 169 | 170 | StartupType startupType = (StartupType)settings.value("startupType").toInt(); 171 | 172 | settings.endGroup(); 173 | settings.endGroup(); 174 | 175 | return startupType; 176 | } 177 | 178 | QString QtServiceController::serviceFilePath() const 179 | { 180 | QSettings settings(QSettings::SystemScope, "QtSoftware"); 181 | settings.beginGroup("services"); 182 | settings.beginGroup(serviceName()); 183 | 184 | QString path = settings.value("path").toString(); 185 | 186 | settings.endGroup(); 187 | settings.endGroup(); 188 | 189 | return path; 190 | } 191 | 192 | bool QtServiceController::uninstall() 193 | { 194 | QSettings settings(QSettings::SystemScope, "QtSoftware"); 195 | settings.beginGroup("services"); 196 | 197 | settings.remove(serviceName()); 198 | 199 | settings.endGroup(); 200 | settings.sync(); 201 | 202 | QSettings::Status ret = settings.status(); 203 | if (ret == QSettings::AccessError) { 204 | fprintf(stderr, "Cannot uninstall \"%s\". Cannot write to: %s. Check permissions.\n", 205 | serviceName().toLatin1().constData(), 206 | settings.fileName().toLatin1().constData()); 207 | } 208 | return (ret == QSettings::NoError); 209 | } 210 | 211 | 212 | bool QtServiceController::start(const QStringList &arguments) 213 | { 214 | if (!isInstalled()) 215 | return false; 216 | if (isRunning()) 217 | return false; 218 | return QProcess::startDetached(serviceFilePath(), arguments); 219 | } 220 | 221 | bool QtServiceController::stop() 222 | { 223 | return sendCmd(serviceName(), QLatin1String("terminate")); 224 | } 225 | 226 | bool QtServiceController::pause() 227 | { 228 | return sendCmd(serviceName(), QLatin1String("pause")); 229 | } 230 | 231 | bool QtServiceController::resume() 232 | { 233 | return sendCmd(serviceName(), QLatin1String("resume")); 234 | } 235 | 236 | bool QtServiceController::sendCommand(int code) 237 | { 238 | return sendCmd(serviceName(), QString(QLatin1String("num:") + QString::number(code))); 239 | } 240 | 241 | bool QtServiceController::isInstalled() const 242 | { 243 | QSettings settings(QSettings::SystemScope, "QtSoftware"); 244 | settings.beginGroup("services"); 245 | 246 | QStringList list = settings.childGroups(); 247 | 248 | settings.endGroup(); 249 | 250 | QStringListIterator it(list); 251 | while (it.hasNext()) { 252 | if (it.next() == serviceName()) 253 | return true; 254 | } 255 | 256 | return false; 257 | } 258 | 259 | bool QtServiceController::isRunning() const 260 | { 261 | QtUnixSocket sock; 262 | if (sock.connectTo(socketPath(serviceName()))) 263 | return true; 264 | return false; 265 | } 266 | 267 | 268 | 269 | 270 | /////////////////////////////////// 271 | 272 | class QtServiceSysPrivate : public QtUnixServerSocket 273 | { 274 | Q_OBJECT 275 | public: 276 | QtServiceSysPrivate(); 277 | ~QtServiceSysPrivate(); 278 | 279 | char *ident; 280 | 281 | QtServiceBase::ServiceFlags serviceFlags; 282 | 283 | protected: 284 | void incomingConnection(int socketDescriptor); 285 | 286 | private slots: 287 | void slotReady(); 288 | void slotClosed(); 289 | 290 | private: 291 | QString getCommand(const QTcpSocket *socket); 292 | QMap cache; 293 | }; 294 | 295 | QtServiceSysPrivate::QtServiceSysPrivate() 296 | : QtUnixServerSocket(), ident(0), serviceFlags(0) 297 | { 298 | } 299 | 300 | QtServiceSysPrivate::~QtServiceSysPrivate() 301 | { 302 | if (ident) 303 | delete[] ident; 304 | } 305 | 306 | void QtServiceSysPrivate::incomingConnection(int socketDescriptor) 307 | { 308 | QTcpSocket *s = new QTcpSocket(this); 309 | s->setSocketDescriptor(socketDescriptor); 310 | connect(s, SIGNAL(readyRead()), this, SLOT(slotReady())); 311 | connect(s, SIGNAL(disconnected()), this, SLOT(slotClosed())); 312 | } 313 | 314 | void QtServiceSysPrivate::slotReady() 315 | { 316 | QTcpSocket *s = (QTcpSocket *)sender(); 317 | cache[s] += QString(s->readAll()); 318 | QString cmd = getCommand(s); 319 | while (!cmd.isEmpty()) { 320 | bool retValue = false; 321 | if (cmd == QLatin1String("terminate")) { 322 | if (!(serviceFlags & QtServiceBase::CannotBeStopped)) { 323 | QtServiceBase::instance()->stop(); 324 | QCoreApplication::instance()->quit(); 325 | retValue = true; 326 | } 327 | } else if (cmd == QLatin1String("pause")) { 328 | if (serviceFlags & QtServiceBase::CanBeSuspended) { 329 | QtServiceBase::instance()->pause(); 330 | retValue = true; 331 | } 332 | } else if (cmd == QLatin1String("resume")) { 333 | if (serviceFlags & QtServiceBase::CanBeSuspended) { 334 | QtServiceBase::instance()->resume(); 335 | retValue = true; 336 | } 337 | } else if (cmd == QLatin1String("alive")) { 338 | retValue = true; 339 | } else if (cmd.length() > 4 && cmd.left(4) == QLatin1String("num:")) { 340 | cmd = cmd.mid(4); 341 | QtServiceBase::instance()->processCommand(cmd.toInt()); 342 | retValue = true; 343 | } 344 | QString retString; 345 | if (retValue) 346 | retString = QLatin1String("true"); 347 | else 348 | retString = QLatin1String("false"); 349 | s->write(retString.toLatin1().constData()); 350 | s->flush(); 351 | cmd = getCommand(s); 352 | } 353 | } 354 | 355 | void QtServiceSysPrivate::slotClosed() 356 | { 357 | QTcpSocket *s = (QTcpSocket *)sender(); 358 | s->deleteLater(); 359 | } 360 | 361 | QString QtServiceSysPrivate::getCommand(const QTcpSocket *socket) 362 | { 363 | int pos = cache[socket].indexOf("\r\n"); 364 | if (pos >= 0) { 365 | QString ret = cache[socket].left(pos); 366 | cache[socket].remove(0, pos+2); 367 | return ret; 368 | } 369 | return ""; 370 | } 371 | 372 | #include "qtservice_unix.moc" 373 | 374 | bool QtServiceBasePrivate::sysInit() 375 | { 376 | sysd = new QtServiceSysPrivate; 377 | sysd->serviceFlags = serviceFlags; 378 | // Restrict permissions on files that are created by the service 379 | ::umask(027); 380 | 381 | return true; 382 | } 383 | 384 | void QtServiceBasePrivate::sysSetPath() 385 | { 386 | if (sysd) 387 | sysd->setPath(socketPath(controller.serviceName())); 388 | } 389 | 390 | void QtServiceBasePrivate::sysCleanup() 391 | { 392 | if (sysd) { 393 | sysd->close(); 394 | delete sysd; 395 | sysd = 0; 396 | } 397 | } 398 | 399 | bool QtServiceBasePrivate::start() 400 | { 401 | if (sendCmd(controller.serviceName(), "alive")) { 402 | // Already running 403 | return false; 404 | } 405 | // Could just call controller.start() here, but that would fail if 406 | // we're not installed. We do not want to strictly require installation. 407 | ::setenv("QTSERVICE_RUN", "1", 1); // Tell the detached process it's it 408 | return QProcess::startDetached(filePath(), args.mid(1), "/"); 409 | } 410 | 411 | bool QtServiceBasePrivate::install(const QString &account, const QString &password) 412 | { 413 | Q_UNUSED(account) 414 | Q_UNUSED(password) 415 | QSettings settings(QSettings::SystemScope, "QtSoftware"); 416 | 417 | settings.beginGroup("services"); 418 | settings.beginGroup(controller.serviceName()); 419 | 420 | settings.setValue("path", filePath()); 421 | settings.setValue("description", serviceDescription); 422 | settings.setValue("automaticStartup", startupType); 423 | 424 | settings.endGroup(); 425 | settings.endGroup(); 426 | settings.sync(); 427 | 428 | QSettings::Status ret = settings.status(); 429 | if (ret == QSettings::AccessError) { 430 | fprintf(stderr, "Cannot install \"%s\". Cannot write to: %s. Check permissions.\n", 431 | controller.serviceName().toLatin1().constData(), 432 | settings.fileName().toLatin1().constData()); 433 | } 434 | return (ret == QSettings::NoError); 435 | } 436 | 437 | void QtServiceBase::logMessage(const QString &message, QtServiceBase::MessageType type, 438 | int, uint, const QByteArray &) 439 | { 440 | if (!d_ptr->sysd) 441 | return; 442 | int st; 443 | switch(type) { 444 | case QtServiceBase::Error: 445 | st = LOG_ERR; 446 | break; 447 | case QtServiceBase::Warning: 448 | st = LOG_WARNING; 449 | break; 450 | default: 451 | st = LOG_INFO; 452 | } 453 | if (!d_ptr->sysd->ident) { 454 | QString tmp = encodeName(serviceName(), true); 455 | int len = tmp.toLocal8Bit().size(); 456 | d_ptr->sysd->ident = new char[len+1]; 457 | d_ptr->sysd->ident[len] = '\0'; 458 | ::memcpy(d_ptr->sysd->ident, tmp.toLocal8Bit().constData(), len); 459 | } 460 | openlog(d_ptr->sysd->ident, LOG_PID, LOG_DAEMON); 461 | foreach(QString line, message.split('\n')) 462 | syslog(st, "%s", line.toLocal8Bit().constData()); 463 | closelog(); 464 | } 465 | 466 | void QtServiceBase::setServiceFlags(QtServiceBase::ServiceFlags flags) 467 | { 468 | if (d_ptr->serviceFlags == flags) 469 | return; 470 | d_ptr->serviceFlags = flags; 471 | if (d_ptr->sysd) 472 | d_ptr->sysd->serviceFlags = flags; 473 | } 474 | 475 | -------------------------------------------------------------------------------- /QtWebApp/qtservice/qtunixserversocket.cpp: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | ** 3 | ** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). 4 | ** Contact: http://www.qt-project.org/legal 5 | ** 6 | ** This file is part of the Qt Solutions component. 7 | ** 8 | ** $QT_BEGIN_LICENSE:BSD$ 9 | ** You may use this file under the terms of the BSD license as follows: 10 | ** 11 | ** "Redistribution and use in source and binary forms, with or without 12 | ** modification, are permitted provided that the following conditions are 13 | ** met: 14 | ** * Redistributions of source code must retain the above copyright 15 | ** notice, this list of conditions and the following disclaimer. 16 | ** * Redistributions in binary form must reproduce the above copyright 17 | ** notice, this list of conditions and the following disclaimer in 18 | ** the documentation and/or other materials provided with the 19 | ** distribution. 20 | ** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names 21 | ** of its contributors may be used to endorse or promote products derived 22 | ** from this software without specific prior written permission. 23 | ** 24 | ** 25 | ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 26 | ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 27 | ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 28 | ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 29 | ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 30 | ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 31 | ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 32 | ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 33 | ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 34 | ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 35 | ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." 36 | ** 37 | ** $QT_END_LICENSE$ 38 | ** 39 | ****************************************************************************/ 40 | 41 | #include "qtunixserversocket.h" 42 | #include 43 | #include 44 | #include 45 | #include 46 | #include 47 | 48 | #ifndef SUN_LEN 49 | #define SUN_LEN(ptr) ((size_t)(((struct sockaddr_un *) 0)->sun_path) \ 50 | +strlen ((ptr)->sun_path)) 51 | #endif 52 | 53 | QtUnixServerSocket::QtUnixServerSocket(const QString &path, QObject *parent) 54 | : QTcpServer(parent) 55 | { 56 | setPath(path); 57 | } 58 | 59 | QtUnixServerSocket::QtUnixServerSocket(QObject *parent) 60 | : QTcpServer(parent) 61 | { 62 | } 63 | 64 | void QtUnixServerSocket::setPath(const QString &path) 65 | { 66 | path_.clear(); 67 | 68 | int sock = ::socket(PF_UNIX, SOCK_STREAM, 0); 69 | if (sock != -1) { 70 | struct sockaddr_un addr; 71 | ::memset(&addr, 0, sizeof(struct sockaddr_un)); 72 | addr.sun_family = AF_UNIX; 73 | ::unlink(path.toLatin1().constData()); // ### This might need to be changed 74 | unsigned int pathlen = strlen(path.toLatin1().constData()); 75 | if (pathlen > sizeof(addr.sun_path)) pathlen = sizeof(addr.sun_path); 76 | ::memcpy(addr.sun_path, path.toLatin1().constData(), pathlen); 77 | if ((::bind(sock, (struct sockaddr *)&addr, SUN_LEN(&addr)) != -1) && 78 | (::listen(sock, 5) != -1)) { 79 | setSocketDescriptor(sock); 80 | path_ = path; 81 | } 82 | } 83 | } 84 | 85 | void QtUnixServerSocket::close() 86 | { 87 | QTcpServer::close(); 88 | if (!path_.isEmpty()) { 89 | ::unlink(path_.toLatin1().constData()); 90 | path_.clear(); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /QtWebApp/qtservice/qtunixserversocket.h: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | ** 3 | ** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). 4 | ** Contact: http://www.qt-project.org/legal 5 | ** 6 | ** This file is part of the Qt Solutions component. 7 | ** 8 | ** $QT_BEGIN_LICENSE:BSD$ 9 | ** You may use this file under the terms of the BSD license as follows: 10 | ** 11 | ** "Redistribution and use in source and binary forms, with or without 12 | ** modification, are permitted provided that the following conditions are 13 | ** met: 14 | ** * Redistributions of source code must retain the above copyright 15 | ** notice, this list of conditions and the following disclaimer. 16 | ** * Redistributions in binary form must reproduce the above copyright 17 | ** notice, this list of conditions and the following disclaimer in 18 | ** the documentation and/or other materials provided with the 19 | ** distribution. 20 | ** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names 21 | ** of its contributors may be used to endorse or promote products derived 22 | ** from this software without specific prior written permission. 23 | ** 24 | ** 25 | ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 26 | ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 27 | ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 28 | ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 29 | ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 30 | ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 31 | ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 32 | ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 33 | ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 34 | ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 35 | ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." 36 | ** 37 | ** $QT_END_LICENSE$ 38 | ** 39 | ****************************************************************************/ 40 | 41 | #ifndef QTUNIXSERVERSOCKET_H 42 | #define QTUNIXSERVERSOCKET_H 43 | 44 | #include 45 | 46 | class QtUnixServerSocket : public QTcpServer 47 | { 48 | Q_OBJECT 49 | public: 50 | QtUnixServerSocket(const QString &path, QObject *parent = 0); 51 | QtUnixServerSocket(QObject *parent = 0); 52 | 53 | void setPath(const QString &path); 54 | void close(); 55 | 56 | private: 57 | QString path_; 58 | }; 59 | 60 | 61 | #endif 62 | -------------------------------------------------------------------------------- /QtWebApp/qtservice/qtunixsocket.cpp: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | ** 3 | ** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). 4 | ** Contact: http://www.qt-project.org/legal 5 | ** 6 | ** This file is part of the Qt Solutions component. 7 | ** 8 | ** $QT_BEGIN_LICENSE:BSD$ 9 | ** You may use this file under the terms of the BSD license as follows: 10 | ** 11 | ** "Redistribution and use in source and binary forms, with or without 12 | ** modification, are permitted provided that the following conditions are 13 | ** met: 14 | ** * Redistributions of source code must retain the above copyright 15 | ** notice, this list of conditions and the following disclaimer. 16 | ** * Redistributions in binary form must reproduce the above copyright 17 | ** notice, this list of conditions and the following disclaimer in 18 | ** the documentation and/or other materials provided with the 19 | ** distribution. 20 | ** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names 21 | ** of its contributors may be used to endorse or promote products derived 22 | ** from this software without specific prior written permission. 23 | ** 24 | ** 25 | ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 26 | ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 27 | ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 28 | ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 29 | ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 30 | ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 31 | ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 32 | ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 33 | ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 34 | ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 35 | ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." 36 | ** 37 | ** $QT_END_LICENSE$ 38 | ** 39 | ****************************************************************************/ 40 | 41 | #include "qtunixsocket.h" 42 | #include 43 | #include 44 | #include 45 | #include 46 | #include 47 | 48 | #ifndef SUN_LEN 49 | #define SUN_LEN(ptr) ((size_t)(((struct sockaddr_un *) 0)->sun_path) \ 50 | +strlen ((ptr)->sun_path)) 51 | #endif 52 | 53 | QtUnixSocket::QtUnixSocket(QObject *parent) 54 | : QTcpSocket(parent) 55 | { 56 | } 57 | 58 | bool QtUnixSocket::connectTo(const QString &path) 59 | { 60 | bool ret = false; 61 | int sock = ::socket(PF_UNIX, SOCK_STREAM, 0); 62 | if (sock != -1) { 63 | struct sockaddr_un addr; 64 | ::memset(&addr, 0, sizeof(struct sockaddr_un)); 65 | addr.sun_family = AF_UNIX; 66 | size_t pathlen = strlen(path.toLatin1().constData()); 67 | pathlen = qMin(pathlen, sizeof(addr.sun_path)); 68 | ::memcpy(addr.sun_path, path.toLatin1().constData(), pathlen); 69 | int err = ::connect(sock, (struct sockaddr *)&addr, SUN_LEN(&addr)); 70 | if (err != -1) { 71 | setSocketDescriptor(sock); 72 | ret = true; 73 | } else { 74 | ::close(sock); 75 | } 76 | } 77 | return ret; 78 | } 79 | -------------------------------------------------------------------------------- /QtWebApp/qtservice/qtunixsocket.h: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | ** 3 | ** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). 4 | ** Contact: http://www.qt-project.org/legal 5 | ** 6 | ** This file is part of the Qt Solutions component. 7 | ** 8 | ** $QT_BEGIN_LICENSE:BSD$ 9 | ** You may use this file under the terms of the BSD license as follows: 10 | ** 11 | ** "Redistribution and use in source and binary forms, with or without 12 | ** modification, are permitted provided that the following conditions are 13 | ** met: 14 | ** * Redistributions of source code must retain the above copyright 15 | ** notice, this list of conditions and the following disclaimer. 16 | ** * Redistributions in binary form must reproduce the above copyright 17 | ** notice, this list of conditions and the following disclaimer in 18 | ** the documentation and/or other materials provided with the 19 | ** distribution. 20 | ** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names 21 | ** of its contributors may be used to endorse or promote products derived 22 | ** from this software without specific prior written permission. 23 | ** 24 | ** 25 | ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 26 | ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 27 | ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 28 | ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 29 | ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 30 | ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 31 | ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 32 | ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 33 | ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 34 | ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 35 | ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." 36 | ** 37 | ** $QT_END_LICENSE$ 38 | ** 39 | ****************************************************************************/ 40 | 41 | #ifndef QTUNIXSOCKET_H 42 | #define QTUNIXSOCKET_H 43 | 44 | #include 45 | 46 | class QtUnixSocket : public QTcpSocket 47 | { 48 | Q_OBJECT 49 | public: 50 | QtUnixSocket(QObject *parent = 0); 51 | 52 | bool connectTo(const QString &path); 53 | }; 54 | 55 | #endif 56 | -------------------------------------------------------------------------------- /QtWebApp/templateengine/template.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | @file 3 | @author Stefan Frings 4 | */ 5 | 6 | #include "template.h" 7 | #include 8 | 9 | Template::Template(QString source, QString sourceName) 10 | : QString(source) 11 | { 12 | this->sourceName=sourceName; 13 | this->warnings=false; 14 | } 15 | 16 | Template::Template(QFile& file, QTextCodec* textCodec) 17 | { 18 | this->warnings=false; 19 | sourceName=QFileInfo(file.fileName()).baseName(); 20 | if (!file.isOpen()) 21 | { 22 | file.open(QFile::ReadOnly | QFile::Text); 23 | } 24 | QByteArray data=file.readAll(); 25 | file.close(); 26 | if (data.size()==0 || file.error()) 27 | { 28 | qCritical("Template: cannot read from %s, %s",qPrintable(sourceName),qPrintable(file.errorString())); 29 | } 30 | else 31 | { 32 | append(textCodec->toUnicode(data)); 33 | } 34 | } 35 | 36 | 37 | int Template::setVariable(QString name, QString value) 38 | { 39 | int count=0; 40 | QString variable="{"+name+"}"; 41 | int start=indexOf(variable); 42 | while (start>=0) 43 | { 44 | replace(start, variable.length(), value); 45 | count++; 46 | start=indexOf(variable,start+value.length()); 47 | } 48 | if (count==0 && warnings) 49 | { 50 | qWarning("Template: missing variable %s in %s",qPrintable(variable),qPrintable(sourceName)); 51 | } 52 | return count; 53 | } 54 | 55 | int Template::setCondition(QString name, bool value) 56 | { 57 | int count=0; 58 | QString startTag=QString("{if %1}").arg(name); 59 | QString elseTag=QString("{else %1}").arg(name); 60 | QString endTag=QString("{end %1}").arg(name); 61 | // search for if-else-end 62 | int start=indexOf(startTag); 63 | while (start>=0) 64 | { 65 | int end=indexOf(endTag,start+startTag.length()); 66 | if (end>=0) 67 | { 68 | count++; 69 | int ellse=indexOf(elseTag,start+startTag.length()); 70 | if (ellse>start && ellse=0) 107 | { 108 | int end=indexOf(endTag,start+startTag2.length()); 109 | if (end>=0) 110 | { 111 | count++; 112 | int ellse=indexOf(elseTag,start+startTag2.length()); 113 | if (ellse>start && ellse=0); 156 | int count=0; 157 | QString startTag="{loop "+name+"}"; 158 | QString elseTag="{else "+name+"}"; 159 | QString endTag="{end "+name+"}"; 160 | // search for loop-else-end 161 | int start=indexOf(startTag); 162 | while (start>=0) 163 | { 164 | int end=indexOf(endTag,start+startTag.length()); 165 | if (end>=0) 166 | { 167 | count++; 168 | int ellse=indexOf(elseTag,start+startTag.length()); 169 | if (ellse>start && ellse0) 173 | { 174 | QString loopPart=mid(start+startTag.length(), ellse-start-startTag.length()); 175 | QString insertMe; 176 | for (int i=0; i0) 199 | { 200 | // and no else part 201 | QString loopPart=mid(start+startTag.length(), end-start-startTag.length()); 202 | QString insertMe; 203 | for (int i=0; i 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include "templateglobal.h" 16 | 17 | /** 18 | Enhanced version of QString for template processing. Templates 19 | are usually loaded from files, but may also be loaded from 20 | prepared Strings. 21 | Example template file: 22 |

 23 |  Hello {username}, how are you?
 24 | 
 25 |  {if locked}
 26 |      Your account is locked.
 27 |  {else locked}
 28 |      Welcome on our system.
 29 |  {end locked}
 30 | 
 31 |  The following users are on-line:
 32 |      Username       Time
 33 |  {loop user}
 34 |      {user.name}    {user.time}
 35 |  {end user}
 36 |  

37 |

38 | Example code to fill this template: 39 |

 40 |  Template t(QFile("test.tpl"),QTextCode::codecForName("UTF-8"));
 41 |  t.setVariable("username", "Stefan");
 42 |  t.setCondition("locked",false);
 43 |  t.loop("user",2);
 44 |  t.setVariable("user0.name,"Markus");
 45 |  t.setVariable("user0.time,"8:30");
 46 |  t.setVariable("user1.name,"Roland");
 47 |  t.setVariable("user1.time,"8:45");
 48 |  

49 |

50 | The code example above shows how variable within loops are numbered. 51 | Counting starts with 0. Loops can be nested, for example: 52 |

 53 |  <table>
 54 |  {loop row}
 55 |      <tr>
 56 |      {loop row.column}
 57 |          <td>{row.column.value}</td>
 58 |      {end row.column}
 59 |      </tr>
 60 |  {end row}
 61 |  </table>
 62 |  

63 |

64 | Example code to fill this nested loop with 3 rows and 4 columns: 65 |

 66 |  t.loop("row",3);
 67 | 
 68 |  t.loop("row0.column",4);
 69 |  t.setVariable("row0.column0.value","a");
 70 |  t.setVariable("row0.column1.value","b");
 71 |  t.setVariable("row0.column2.value","c");
 72 |  t.setVariable("row0.column3.value","d");
 73 | 
 74 |  t.loop("row1.column",4);
 75 |  t.setVariable("row1.column0.value","e");
 76 |  t.setVariable("row1.column1.value","f");
 77 |  t.setVariable("row1.column2.value","g");
 78 |  t.setVariable("row1.column3.value","h");
 79 | 
 80 |  t.loop("row2.column",4);
 81 |  t.setVariable("row2.column0.value","i");
 82 |  t.setVariable("row2.column1.value","j");
 83 |  t.setVariable("row2.column2.value","k");
 84 |  t.setVariable("row2.column3.value","l");
 85 |  

86 | @see TemplateLoader 87 | @see TemplateCache 88 | */ 89 | 90 | class DECLSPEC Template : public QString { 91 | public: 92 | 93 | /** 94 | Constructor that reads the template from a string. 95 | @param source The template source text 96 | @param sourceName Name of the source file, used for logging 97 | */ 98 | Template(QString source, QString sourceName); 99 | 100 | /** 101 | Constructor that reads the template from a file. Note that this class does not 102 | cache template files by itself, so using this constructor is only recommended 103 | to be used on local filesystem. 104 | @param file File that provides the source text 105 | @param textCodec Encoding of the source 106 | @see TemplateLoader 107 | @see TemplateCache 108 | */ 109 | Template(QFile& file, QTextCodec* textCodec); 110 | 111 | /** 112 | Replace a variable by the given value. 113 | Affects tags with the syntax 114 | 115 | - {name} 116 | 117 | After settings the 118 | value of a variable, the variable does not exist anymore, 119 | it it cannot be changed multiple times. 120 | @param name name of the variable 121 | @param value new value 122 | @return The count of variables that have been processed 123 | */ 124 | int setVariable(QString name, QString value); 125 | 126 | /** 127 | Set a condition. This affects tags with the syntax 128 | 129 | - {if name}...{end name} 130 | - {if name}...{else name}...{end name} 131 | - {ifnot name}...{end name} 132 | - {ifnot name}...{else name}...{end name} 133 | 134 | @param name Name of the condition 135 | @param value Value of the condition 136 | @return The count of conditions that have been processed 137 | */ 138 | int setCondition(QString name, bool value); 139 | 140 | /** 141 | Set number of repetitions of a loop. 142 | This affects tags with the syntax 143 | 144 | - {loop name}...{end name} 145 | - {loop name}...{else name}...{end name} 146 | 147 | @param name Name of the loop 148 | @param repetitions The number of repetitions 149 | @return The number of loops that have been processed 150 | */ 151 | int loop(QString name, int repetitions); 152 | 153 | /** 154 | Enable warnings for missing tags 155 | @param enable Warnings are enabled, if true 156 | */ 157 | void enableWarnings(bool enable=true); 158 | 159 | private: 160 | 161 | /** Name of the source file */ 162 | QString sourceName; 163 | 164 | /** Enables warnings, if true */ 165 | bool warnings; 166 | }; 167 | 168 | #endif // TEMPLATE_H 169 | -------------------------------------------------------------------------------- /QtWebApp/templateengine/templatecache.cpp: -------------------------------------------------------------------------------- 1 | #include "templatecache.h" 2 | #include 3 | #include 4 | #include 5 | 6 | TemplateCache::TemplateCache(QSettings* settings, QObject* parent) 7 | :TemplateLoader(settings,parent) 8 | { 9 | cache.setMaxCost(settings->value("cacheSize","1000000").toInt()); 10 | cacheTimeout=settings->value("cacheTime","60000").toInt(); 11 | qDebug("TemplateCache: timeout=%i, size=%i",cacheTimeout,cache.maxCost()); 12 | } 13 | 14 | QString TemplateCache::tryFile(QString localizedName) 15 | { 16 | qint64 now=QDateTime::currentMSecsSinceEpoch(); 17 | // search in cache 18 | qDebug("TemplateCache: trying cached %s",qPrintable(localizedName)); 19 | CacheEntry* entry=cache.object(localizedName); 20 | if (entry && (cacheTimeout==0 || entry->created>now-cacheTimeout)) 21 | { 22 | return entry->document; 23 | } 24 | // search on filesystem 25 | entry=new CacheEntry(); 26 | entry->created=now; 27 | entry->document=TemplateLoader::tryFile(localizedName); 28 | // Store in cache even when the file did not exist, to remember that there is no such file 29 | cache.insert(localizedName,entry,entry->document.size()); 30 | return entry->document; 31 | } 32 | 33 | -------------------------------------------------------------------------------- /QtWebApp/templateengine/templatecache.h: -------------------------------------------------------------------------------- 1 | #ifndef TEMPLATECACHE_H 2 | #define TEMPLATECACHE_H 3 | 4 | #include 5 | #include "templateglobal.h" 6 | #include "templateloader.h" 7 | 8 | /** 9 | Caching template loader, reduces the amount of I/O and improves performance 10 | on remote file systems. The cache has a limited size, it prefers to keep 11 | the last recently used files. Optionally, the maximum time of cached entries 12 | can be defined to enforce a reload of the template file after a while. 13 |

14 | In case of local file system, the use of this cache is optionally, since 15 | the operating system caches files already. 16 |

17 | Loads localized versions of template files. If the caller requests a file with the 18 | name "index" and the suffix is ".tpl" and the requested locale is "de_DE, de, en-US", 19 | then files are searched in the following order: 20 | 21 | - index-de_DE.tpl 22 | - index-de.tpl 23 | - index-en_US.tpl 24 | - index-en.tpl 25 | - index.tpl 26 |

27 | The following settings are required: 28 |

29 |   path=../templates
30 |   suffix=.tpl
31 |   encoding=UTF-8
32 |   cacheSize=1000000
33 |   cacheTime=60000
34 |   
35 | The path is relative to the directory of the config file. In case of windows, if the 36 | settings are in the registry, the path is relative to the current working directory. 37 |

38 | Files are cached as long as possible, when cacheTime=0. 39 | @see TemplateLoader 40 | */ 41 | 42 | class DECLSPEC TemplateCache : public TemplateLoader { 43 | Q_OBJECT 44 | Q_DISABLE_COPY(TemplateCache) 45 | public: 46 | 47 | /** 48 | Constructor. 49 | @param settings configurations settings 50 | @param parent Parent object 51 | */ 52 | TemplateCache(QSettings* settings, QObject* parent=0); 53 | 54 | protected: 55 | 56 | /** 57 | Try to get a file from cache or filesystem. 58 | @param localizedName Name of the template with locale to find 59 | @return The template document, or empty string if not found 60 | */ 61 | virtual QString tryFile(QString localizedName); 62 | 63 | private: 64 | 65 | struct CacheEntry { 66 | QString document; 67 | qint64 created; 68 | }; 69 | 70 | /** Timeout for each cached file */ 71 | int cacheTimeout; 72 | 73 | /** Cache storage */ 74 | QCache cache; 75 | 76 | }; 77 | 78 | #endif // TEMPLATECACHE_H 79 | -------------------------------------------------------------------------------- /QtWebApp/templateengine/templateengine.pri: -------------------------------------------------------------------------------- 1 | INCLUDEPATH += $$PWD 2 | DEPENDPATH += $$PWD 3 | 4 | HEADERS += $$PWD/templateglobal.h 5 | HEADERS += $$PWD/template.h 6 | HEADERS += $$PWD/templateloader.h 7 | HEADERS += $$PWD/templatecache.h 8 | 9 | SOURCES += $$PWD/template.cpp 10 | SOURCES += $$PWD/templateloader.cpp 11 | SOURCES += $$PWD/templatecache.cpp 12 | -------------------------------------------------------------------------------- /QtWebApp/templateengine/templateglobal.h: -------------------------------------------------------------------------------- 1 | /** 2 | @file 3 | @author Stefan Frings 4 | */ 5 | 6 | #ifndef TEMPLATEGLOBAL_H 7 | #define TEMPLATEGLOBAL_H 8 | 9 | #include 10 | 11 | // This is specific to Windows dll's 12 | #if defined(Q_OS_WIN) 13 | #if defined(QTWEBAPPLIB_EXPORT) 14 | #define DECLSPEC Q_DECL_EXPORT 15 | #elif defined(QTWEBAPPLIB_IMPORT) 16 | #define DECLSPEC Q_DECL_IMPORT 17 | #endif 18 | #endif 19 | #if !defined(DECLSPEC) 20 | #define DECLSPEC 21 | #endif 22 | 23 | #endif // TEMPLATEGLOBAL_H 24 | 25 | -------------------------------------------------------------------------------- /QtWebApp/templateengine/templateloader.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | @file 3 | @author Stefan Frings 4 | */ 5 | 6 | #include "templateloader.h" 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | TemplateLoader::TemplateLoader(QSettings* settings, QObject* parent) 14 | : QObject(parent) 15 | { 16 | templatePath=settings->value("path",".").toString(); 17 | // Convert relative path to absolute, based on the directory of the config file. 18 | #ifdef Q_OS_WIN32 19 | if (QDir::isRelativePath(templatePath) && settings->format()!=QSettings::NativeFormat) 20 | #else 21 | if (QDir::isRelativePath(templatePath)) 22 | #endif 23 | { 24 | QFileInfo configFile(settings->fileName()); 25 | templatePath=QFileInfo(configFile.absolutePath(),templatePath).absoluteFilePath(); 26 | } 27 | fileNameSuffix=settings->value("suffix",".tpl").toString(); 28 | QString encoding=settings->value("encoding").toString(); 29 | if (encoding.isEmpty()) 30 | { 31 | textCodec=QTextCodec::codecForLocale(); 32 | } 33 | else 34 | { 35 | textCodec=QTextCodec::codecForName(encoding.toLocal8Bit()); 36 | } 37 | qDebug("TemplateLoader: path=%s, codec=%s",qPrintable(templatePath),textCodec->name().data()); 38 | } 39 | 40 | TemplateLoader::~TemplateLoader() 41 | {} 42 | 43 | QString TemplateLoader::tryFile(QString localizedName) 44 | { 45 | QString fileName=templatePath+"/"+localizedName+fileNameSuffix; 46 | qDebug("TemplateCache: trying file %s",qPrintable(fileName)); 47 | QFile file(fileName); 48 | if (file.exists()) { 49 | file.open(QIODevice::ReadOnly); 50 | QString document=textCodec->toUnicode(file.readAll()); 51 | file.close(); 52 | if (file.error()) 53 | { 54 | qCritical("TemplateLoader: cannot load file %s, %s",qPrintable(fileName),qPrintable(file.errorString())); 55 | return ""; 56 | } 57 | else 58 | { 59 | return document; 60 | } 61 | } 62 | return ""; 63 | } 64 | 65 | Template TemplateLoader::getTemplate(QString templateName, QString locales) 66 | { 67 | mutex.lock(); 68 | QSet tried; // used to suppress duplicate attempts 69 | QStringList locs=locales.split(',',QString::SkipEmptyParts); 70 | 71 | // Search for exact match 72 | foreach (QString loc,locs) 73 | { 74 | loc.replace(QRegExp(";.*"),""); 75 | loc.replace('-','_'); 76 | QString localizedName=templateName+"-"+loc.trimmed(); 77 | if (!tried.contains(localizedName)) 78 | { 79 | QString document=tryFile(localizedName); 80 | if (!document.isEmpty()) { 81 | mutex.unlock(); 82 | return Template(document,localizedName); 83 | } 84 | tried.insert(localizedName); 85 | } 86 | } 87 | 88 | // Search for correct language but any country 89 | foreach (QString loc,locs) 90 | { 91 | loc.replace(QRegExp("[;_-].*"),""); 92 | QString localizedName=templateName+"-"+loc.trimmed(); 93 | if (!tried.contains(localizedName)) 94 | { 95 | QString document=tryFile(localizedName); 96 | if (!document.isEmpty()) 97 | { 98 | mutex.unlock(); 99 | return Template(document,localizedName); 100 | } 101 | tried.insert(localizedName); 102 | } 103 | } 104 | 105 | // Search for default file 106 | QString document=tryFile(templateName); 107 | if (!document.isEmpty()) 108 | { 109 | mutex.unlock(); 110 | return Template(document,templateName); 111 | } 112 | 113 | qCritical("TemplateCache: cannot find template %s",qPrintable(templateName)); 114 | mutex.unlock(); 115 | return Template("",templateName); 116 | } 117 | -------------------------------------------------------------------------------- /QtWebApp/templateengine/templateloader.h: -------------------------------------------------------------------------------- 1 | /** 2 | @file 3 | @author Stefan Frings 4 | */ 5 | 6 | #ifndef TEMPLATELOADER_H 7 | #define TEMPLATELOADER_H 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include "templateglobal.h" 14 | #include "template.h" 15 | 16 | /** 17 | Loads localized versions of template files. If the caller requests a file with the 18 | name "index" and the suffix is ".tpl" and the requested locale is "de_DE, de, en-US", 19 | then files are searched in the following order: 20 | 21 | - index-de_DE.tpl 22 | - index-de.tpl 23 | - index-en_US.tpl 24 | - index-en.tpl 25 | - index.tpl 26 | 27 | The following settings are required: 28 |

29 |   path=../templates
30 |   suffix=.tpl
31 |   encoding=UTF-8
32 |   
33 | The path is relative to the directory of the config file. In case of windows, if the 34 | settings are in the registry, the path is relative to the current working directory. 35 | @see TemplateCache 36 | */ 37 | 38 | class DECLSPEC TemplateLoader : public QObject { 39 | Q_OBJECT 40 | Q_DISABLE_COPY(TemplateLoader) 41 | public: 42 | 43 | /** 44 | Constructor. 45 | @param settings configurations settings 46 | @param parent parent object 47 | */ 48 | TemplateLoader(QSettings* settings, QObject* parent=0); 49 | 50 | /** Destructor */ 51 | virtual ~TemplateLoader(); 52 | 53 | /** 54 | Get a template for a given locale. 55 | This method is thread safe. 56 | @param templateName base name of the template file, without suffix and without locale 57 | @param locales Requested locale(s), e.g. "de_DE, en_EN". Strings in the format of 58 | the HTTP header Accept-Locale may be used. Badly formatted parts in the string are silently 59 | ignored. 60 | @return If the template cannot be loaded, an error message is logged and an empty template is returned. 61 | */ 62 | Template getTemplate(QString templateName, QString locales=QString()); 63 | 64 | protected: 65 | 66 | /** 67 | Try to get a file from cache or filesystem. 68 | @param localizedName Name of the template with locale to find 69 | @return The template document, or empty string if not found 70 | */ 71 | virtual QString tryFile(QString localizedName); 72 | 73 | /** Directory where the templates are searched */ 74 | QString templatePath; 75 | 76 | /** Suffix to the filenames */ 77 | QString fileNameSuffix; 78 | 79 | /** Codec for decoding the files */ 80 | QTextCodec* textCodec; 81 | 82 | /** Used to synchronize threads */ 83 | QMutex mutex; 84 | }; 85 | 86 | #endif // TEMPLATELOADER_H 87 | -------------------------------------------------------------------------------- /QtWebAppExample.pro: -------------------------------------------------------------------------------- 1 | TEMPLATE = subdirs 2 | 3 | SUBDIRS += \ 4 | QtWebApp \ 5 | common 6 | 7 | CONFIG += ordered 8 | 9 | common.files = common/html-static/* 10 | CONFIG(debug, debug|release) { 11 | common.path = $$OUT_PWD/../HttpServiceDebug/html-static 12 | } else { 13 | common.path = $$OUT_PWD/../HttpService/html-static 14 | } 15 | 16 | INSTALLS += common 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # QtWebAppExample 2 | Sample QtWebApp Application 3 | 4 | Пример демонстрирует базовые возможности по созданию Web-приложения с помощью библиотеки QtWebApp версии 1.6.4. 5 | В примере даны комментарии, описывающие поведение программного кода. 6 | 7 | ## Сборка проекта 8 | ``` 9 | qmake common.pro 10 | make 11 | make install 12 | ``` 13 | -------------------------------------------------------------------------------- /common/common.pro: -------------------------------------------------------------------------------- 1 | QT += core network 2 | QT -= gui 3 | 4 | TARGET = common 5 | CONFIG += console 6 | CONFIG -= app_bundle 7 | CONFIG += c++11 8 | 9 | TEMPLATE = app 10 | 11 | SOURCES += main.cpp \ 12 | webconfigurator.cpp \ 13 | webconfiguratorpage.cpp 14 | 15 | HEADERS += \ 16 | webconfigurator.h \ 17 | webconfiguratorpage.h \ 18 | httpsettings.hpp 19 | 20 | RESOURCES += \ 21 | resources.qrc 22 | 23 | CONFIG(debug, debug|release) { 24 | DESTDIR = $$OUT_PWD/../../HttpServiceDebug 25 | } else { 26 | DESTDIR = $$OUT_PWD/../../HttpService 27 | } 28 | 29 | win32:CONFIG(release, debug|release): LIBS += -L$$OUT_PWD/../QtWebApp/release/ -lQtWebApp 30 | else:win32:CONFIG(debug, debug|release): LIBS += -L$$OUT_PWD/../QtWebApp/debug/ -lQtWebApp 31 | else:unix: LIBS += -L$$OUT_PWD/../QtWebApp/ -lQtWebApp 32 | 33 | INCLUDEPATH += $$PWD/../QtWebApp/httpserver 34 | DEPENDPATH += $$PWD/../QtWebApp/httpserver 35 | INCLUDEPATH += $$PWD/../QtWebApp/templateengine 36 | DEPENDPATH += $$PWD/../QtWebApp/templateengine 37 | INCLUDEPATH += $$PWD/../QtWebApp/qtservice 38 | DEPENDPATH += $$PWD/../QtWebApp/qtservice 39 | 40 | win32-g++:CONFIG(release, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../QtWebApp/release/libQtWebApp.a 41 | else:win32-g++:CONFIG(debug, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../QtWebApp/debug/libQtWebApp.a 42 | else:win32:!win32-g++:CONFIG(release, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../QtWebApp/release/QtWebApp.lib 43 | else:win32:!win32-g++:CONFIG(debug, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../QtWebApp/debug/QtWebApp.lib 44 | else:unix: PRE_TARGETDEPS += $$OUT_PWD/../QtWebApp/libQtWebApp.a 45 | 46 | DISTFILES += \ 47 | html-static/style.css \ 48 | html-static/favicon-32x32.png \ 49 | html-static/favicon.png 50 | -------------------------------------------------------------------------------- /common/html-static/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edsd/QtWebAppExample/b3caf5a93552e19645f8331f6e4168f451ee1bb7/common/html-static/favicon-32x32.png -------------------------------------------------------------------------------- /common/html-static/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edsd/QtWebAppExample/b3caf5a93552e19645f8331f6e4168f451ee1bb7/common/html-static/favicon.png -------------------------------------------------------------------------------- /common/html-static/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; padding: 0; font: 14px Helvetica, Arial, sans-serif; 3 | background-color: white; 4 | } 5 | 6 | h1 { 7 | margin-left: 76px; 8 | margin-right: 0; 9 | margin-top: 0; 10 | margin-bottom: 16px; 11 | line-height: 48px; 12 | } 13 | 14 | h2 { 15 | margin-left: 20px; 16 | margin-right: 20px; 17 | } 18 | 19 | * { 20 | outline: none; 21 | } 22 | 23 | .logo { 24 | height: 48px; 25 | width: 48px; 26 | float: left; 27 | border-radius: 24px; 28 | margin-left: 20px; 29 | margin-right: 0; 30 | margin-top: 0; 31 | margin-bottom: 16px; 32 | background: url("favicon.png") no-repeat; 33 | background-size: cover; 34 | } 35 | 36 | div.content { 37 | width: 800px; 38 | margin: 5em auto; 39 | background-color: #f5f5f5; 40 | overflow: hidden; 41 | padding: 16px; 42 | } 43 | 44 | a:link, a:visited { 45 | color: #69c; 46 | text-decoration: none; 47 | } 48 | 49 | @media (max-width: 700px) 50 | { 51 | body { background-color: #fff; } 52 | div.content { 53 | width: auto; margin: 0 auto; border-radius: 0; padding: 1em; 54 | } 55 | } 56 | 57 | .menu { 58 | border-bottom: 1px solid #eee; 59 | margin: 0 0 20px 0; 60 | clear: both; 61 | padding: 5px 0; 62 | } 63 | 64 | div.sep { 65 | height:1px; 66 | border-bottom: 1px solid #eee; 67 | } 68 | 69 | .menuitem { 70 | display: inline-block; 71 | vertical-align: middle; 72 | } 73 | 74 | .menuitem a { 75 | float: left; 76 | width: 120px; 77 | text-align: center; 78 | } 79 | 80 | p { 81 | margin: 20px; 82 | padding: 0px; 83 | } 84 | 85 | -------------------------------------------------------------------------------- /common/html/common.htm: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {Title} 6 | 7 | 8 | 9 | 10 | 11 |
12 |

{Title}

13 | {if Navigation} 14 | 21 | {end Navigation} 22 | {Content} 23 |
24 | 25 | 26 | -------------------------------------------------------------------------------- /common/html/first.htm: -------------------------------------------------------------------------------- 1 |

Fisrt Page

2 | -------------------------------------------------------------------------------- /common/html/index.htm: -------------------------------------------------------------------------------- 1 |

EDISON

2 |

Центр разработки программного обеспечения

3 | -------------------------------------------------------------------------------- /common/html/second.htm: -------------------------------------------------------------------------------- 1 |

Second Page

2 | -------------------------------------------------------------------------------- /common/httpsettings.hpp: -------------------------------------------------------------------------------- 1 | #ifndef HTTPSETTINGS_H 2 | #define HTTPSETTINGS_H 3 | 4 | #include 5 | 6 | class HttpSettings : public QSettings 7 | { 8 | public: 9 | explicit HttpSettings(const QString& fileName, QObject* parent = nullptr) 10 | : QSettings(fileName,QSettings::IniFormat,parent) 11 | { 12 | // Настройки веб-сервера 13 | setValue("port", value("port", 8080)); 14 | setValue("minThreads", value("minThreads", 1)); 15 | setValue("maxThreads", value("maxThreads", 100)); 16 | setValue("cleanupInterval", value("cleanupInterval", 1000)); 17 | setValue("readTimeout", value("readTimeout", 60000)); 18 | setValue("maxRequestSize", value("maxRequestSize", 16000)); 19 | setValue("maxMultiPartSize", value("maxMultiPartSize", 10000000)); 20 | 21 | // Настройки для статических файлов 22 | setValue("html-static/path", value("html-static/path", "html-static")); 23 | setValue("html-static/encoding", value("html-static/encoding", "UTF-8")); 24 | setValue("html-static/maxAge", value("html-static/maxAge", 60000)); 25 | setValue("html-static/cacheTime", value("html-static/cacheTime", 60000)); 26 | setValue("html-static/cacheSize", value("html-static/cacheSize", 1000000)); 27 | setValue("html-static/maxCachedFileSize", value("html-static/maxCachedFileSize", 65536)); 28 | } 29 | }; 30 | 31 | #endif // HTTPSETTINGS_H 32 | -------------------------------------------------------------------------------- /common/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | int main(int argc, char *argv[]) 6 | { 7 | QCoreApplication a(argc, argv); 8 | 9 | a.setApplicationName("QtWebAppExample"); 10 | 11 | QString configPath = QDir::currentPath() + "/" + QCoreApplication::applicationName() + ".ini"; 12 | new WebConfigurator(configPath); 13 | 14 | return a.exec(); 15 | } 16 | -------------------------------------------------------------------------------- /common/resources.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | html/common.htm 4 | html/index.htm 5 | html/first.htm 6 | html/second.htm 7 | 8 | 9 | -------------------------------------------------------------------------------- /common/webconfigurator.cpp: -------------------------------------------------------------------------------- 1 | #include "webconfigurator.h" 2 | 3 | WebConfigurator::WebConfigurator(QString &configPath) : 4 | m_configPath(configPath), 5 | m_config(m_configPath), 6 | m_httpListener(&m_config, this) 7 | { 8 | /* Помещаем в QHash объекты всех динамических страниц, 9 | * которые будут использоваться на нашем веб-сервере 10 | * */ 11 | m_pages.insert("/index.html", new IndexPage()); 12 | m_pages.insert("/second.html", new SecondPage()); 13 | m_pages.insert("/first.html", new FirstPage()); 14 | 15 | /* Для работы контроллера статических файлов 16 | * необходимо обратиться к объекту настроек, перейти к группе 17 | * параметров настройки контроллера и создать новый контроллер 18 | * используя состояния объекта настроек, выставленное на группу 19 | * параметров статического контроллера файлов 20 | * */ 21 | m_config.beginGroup("html-static"); 22 | m_staticFileController = new StaticFileController(&m_config); 23 | m_config.endGroup(); 24 | } 25 | 26 | WebConfigurator::~WebConfigurator() 27 | { 28 | foreach(WebConfiguratorPage* page, m_pages) { 29 | delete page; 30 | } 31 | delete m_staticFileController; 32 | } 33 | 34 | void WebConfigurator::service(HttpRequest &request, HttpResponse &response) 35 | { 36 | /* В данном методе осуществляется проверка адреса запроса 37 | * на соответствие существующим страницам. 38 | * В данном случае, если страница существует, то мы 39 | * обращаемся к объекту страницы и передаём запрос на дальнейшую обработку. 40 | * В противном случаем возвращаем ошибку 404 41 | * */ 42 | QByteArray path = request.getPath(); 43 | for(auto i = m_pages.begin(); i != m_pages.end(); ++i) { 44 | if(path.startsWith(i.key().toLatin1())) { 45 | return i.value()->handleRequest(request,response); 46 | } 47 | } 48 | if(path=="/") { 49 | response.redirect("/index.html"); 50 | return; 51 | } 52 | if(path.startsWith("/style.css") || 53 | path.startsWith("/favicon-32x32.png") || 54 | path.startsWith("/favicon.png")){ 55 | return m_staticFileController->service(request, response); 56 | } 57 | response.setStatus(404,"Not found"); 58 | } 59 | -------------------------------------------------------------------------------- /common/webconfigurator.h: -------------------------------------------------------------------------------- 1 | #ifndef WEBCONFIGURATOR_H 2 | #define WEBCONFIGURATOR_H 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | class WebConfigurator : public HttpRequestHandler 12 | { 13 | Q_OBJECT 14 | Q_DISABLE_COPY(WebConfigurator) 15 | public: 16 | WebConfigurator(QString &configPath); 17 | virtual ~WebConfigurator(); 18 | virtual void service(HttpRequest& request, HttpResponse& response) override; 19 | 20 | private: 21 | QString m_configPath; 22 | HttpSettings m_config; 23 | HttpListener m_httpListener; 24 | QHash m_pages; 25 | StaticFileController *m_staticFileController; 26 | }; 27 | 28 | #endif // WEBCONFIGURATOR_H 29 | -------------------------------------------------------------------------------- /common/webconfiguratorpage.cpp: -------------------------------------------------------------------------------- 1 | #include "webconfiguratorpage.h" 2 | #include 3 | #include 4 | 5 | WebConfiguratorPage::WebConfiguratorPage(const QString &title) : 6 | m_title(title) 7 | { 8 | 9 | } 10 | 11 | Template WebConfiguratorPage::commonTemplate() const 12 | { 13 | /* Для формирования основного шаблона используется файл common.htm. 14 | * В него устанавилвается название страницы ... 15 | * */ 16 | QFile file(":/html/common.htm"); 17 | Template common(file, QTextCodec::codecForName("UTF-8")); 18 | common.setVariable("Title", m_title); 19 | 20 | /* А также формируется меню. 21 | * Формирование меню сделано с учетом проверки на то, 22 | * требуется ли данное меню на странице или нет. 23 | * В данном примере меню будет на всех страницах, поэтому 24 | * просто обозначим необходимость данного меню. 25 | * Если вы посмотрите содержимое файла common.htm, то 26 | * обнаружите там проверку на параметр "Navigation" 27 | * */ 28 | bool navigation = true; 29 | common.setCondition("Navigation", navigation); 30 | if(navigation) { 31 | /* А само меню будет формироваться с помощью цилического добавления 32 | * пунктов, что также отражено специальной конструкцией в файле common.htm 33 | * */ 34 | common.loop("Items", 3); 35 | common.setVariable("Items0.href", "/index.html"); 36 | common.setVariable("Items0.name", "Main page"); 37 | 38 | common.setVariable("Items1.href", "/first.html"); 39 | common.setVariable("Items1.name", "First page"); 40 | 41 | common.setVariable("Items2.href", "/second.html"); 42 | common.setVariable("Items2.name", "Second page"); 43 | } 44 | return common; 45 | } 46 | 47 | /* Далее идёт реализация обработчика запроса к каждой из страниц. 48 | * Фактически они идентичны в данном примере, но в реальном приложении 49 | * будут скорее всего отличаться по своей логике 50 | * */ 51 | 52 | void IndexPage::handleRequest(HttpRequest &request, HttpResponse &response) 53 | { 54 | if (request.getMethod() == "GET") 55 | { 56 | // Получаем родительски щаблон страницы 57 | Template common = commonTemplate(); 58 | QFile file(":/html/index.htm"); 59 | Template contents(file, QTextCodec::codecForName("UTF-8")); 60 | /* После чего добавляем собственный контент из шаблона для данной страницы 61 | * в родительском шаблоне место для добавления информации, равно как и другого шаблона 62 | * в данном примере обозначено как {Content} 63 | * */ 64 | common.setVariable("Content", contents); 65 | response.setHeader("Content-Type", "text/html; charset=ISO-8859-1"); 66 | response.write(common.toUtf8()); 67 | return; 68 | } 69 | else 70 | { 71 | return; 72 | } 73 | return; 74 | } 75 | 76 | void FirstPage::handleRequest(HttpRequest &request, HttpResponse &response) 77 | { 78 | if (request.getMethod() == "GET") 79 | { 80 | Template common = commonTemplate(); 81 | QFile file(":/html/first.htm"); 82 | Template contents(file, QTextCodec::codecForName("UTF-8")); 83 | common.setVariable("Content", contents); 84 | response.setHeader("Content-Type", "text/html; charset=ISO-8859-1"); 85 | response.write(common.toUtf8()); 86 | return; 87 | } 88 | else 89 | { 90 | return; 91 | } 92 | return; 93 | } 94 | 95 | void SecondPage::handleRequest(HttpRequest &request, HttpResponse &response) 96 | { 97 | if (request.getMethod() == "GET") 98 | { 99 | Template common = commonTemplate(); 100 | QFile file(":/html/second.htm"); 101 | Template contents(file, QTextCodec::codecForName("UTF-8")); 102 | common.setVariable("Content", contents); 103 | response.setHeader("Content-Type", "text/html; charset=ISO-8859-1"); 104 | response.write(common.toUtf8()); 105 | return; 106 | } 107 | else 108 | { 109 | return; 110 | } 111 | return; 112 | } 113 | -------------------------------------------------------------------------------- /common/webconfiguratorpage.h: -------------------------------------------------------------------------------- 1 | #ifndef WEBCONFIGURATORPAGE_H 2 | #define WEBCONFIGURATORPAGE_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | class WebConfiguratorPage : public QObject 10 | { 11 | Q_OBJECT 12 | public: 13 | WebConfiguratorPage(const QString& title); 14 | virtual void handleRequest(HttpRequest&, HttpResponse&) {} 15 | virtual ~WebConfiguratorPage() {} 16 | 17 | protected: 18 | Template commonTemplate() const; 19 | 20 | private: 21 | QString m_title; 22 | }; 23 | 24 | class IndexPage : public WebConfiguratorPage 25 | { 26 | Q_OBJECT 27 | public: 28 | IndexPage() : WebConfiguratorPage("EDISON") {} 29 | 30 | virtual ~IndexPage() {} 31 | public: 32 | virtual void handleRequest(HttpRequest &request, HttpResponse &response) override; 33 | }; 34 | 35 | class FirstPage : public WebConfiguratorPage 36 | { 37 | Q_OBJECT 38 | public: 39 | FirstPage() : WebConfiguratorPage("First Page") {} 40 | 41 | virtual ~FirstPage() {} 42 | public: 43 | virtual void handleRequest(HttpRequest &request, HttpResponse &response) override; 44 | }; 45 | 46 | class SecondPage : public WebConfiguratorPage 47 | { 48 | Q_OBJECT 49 | public: 50 | SecondPage() : WebConfiguratorPage("Second Page") {} 51 | 52 | virtual ~SecondPage() {} 53 | public: 54 | virtual void handleRequest(HttpRequest &request, HttpResponse &response) override; 55 | }; 56 | 57 | #endif // WEBCONFIGURATORPAGE_H 58 | --------------------------------------------------------------------------------