tests/auto/declarative/shared/testhttpserver.cpp
changeset 30 5dc02b23752f
equal deleted inserted replaced
29:b72c6db6890b 30:5dc02b23752f
       
     1 /****************************************************************************
       
     2 **
       
     3 ** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
       
     4 ** All rights reserved.
       
     5 ** Contact: Nokia Corporation (qt-info@nokia.com)
       
     6 **
       
     7 ** This file is part of the test suite of the Qt Toolkit.
       
     8 **
       
     9 ** $QT_BEGIN_LICENSE:LGPL$
       
    10 ** No Commercial Usage
       
    11 ** This file contains pre-release code and may not be distributed.
       
    12 ** You may use this file in accordance with the terms and conditions
       
    13 ** contained in the Technology Preview License Agreement accompanying
       
    14 ** this package.
       
    15 **
       
    16 ** GNU Lesser General Public License Usage
       
    17 ** Alternatively, this file may be used under the terms of the GNU Lesser
       
    18 ** General Public License version 2.1 as published by the Free Software
       
    19 ** Foundation and appearing in the file LICENSE.LGPL included in the
       
    20 ** packaging of this file.  Please review the following information to
       
    21 ** ensure the GNU Lesser General Public License version 2.1 requirements
       
    22 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
       
    23 **
       
    24 ** In addition, as a special exception, Nokia gives you certain additional
       
    25 ** rights.  These rights are described in the Nokia Qt LGPL Exception
       
    26 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
       
    27 **
       
    28 ** If you have questions regarding the use of this file, please contact
       
    29 ** Nokia at qt-info@nokia.com.
       
    30 **
       
    31 **
       
    32 **
       
    33 **
       
    34 **
       
    35 **
       
    36 **
       
    37 **
       
    38 ** $QT_END_LICENSE$
       
    39 **
       
    40 ****************************************************************************/
       
    41 
       
    42 #include "testhttpserver.h"
       
    43 #include <QTcpSocket>
       
    44 #include <QDebug>
       
    45 #include <QFile>
       
    46 #include <QTimer>
       
    47 
       
    48 /*!
       
    49 \internal
       
    50 \class TestHTTPServer
       
    51 \brief provides a very, very basic HTTP server for testing.
       
    52 
       
    53 Inside the test case, an instance of TestHTTPServer should be created, with the
       
    54 appropriate port to listen on.  The server will listen on the localhost interface.
       
    55 
       
    56 Directories to serve can then be added to server, which will be added as "roots".
       
    57 Each root can be added as a Normal, Delay or Disconnect root.  Requests for files
       
    58 within a Normal root are returned immediately.  Request for files within a Delay
       
    59 root are delayed for 500ms, and then served.  Requests for files within a Disconnect
       
    60 directory cause the server to disconnect immediately.  A request for a file that isn't
       
    61 found in any root will return a 404 error.
       
    62 
       
    63 If you have the following directory structure:
       
    64 
       
    65 \code
       
    66 disconnect/disconnectTest.qml
       
    67 files/main.qml
       
    68 files/Button.qml
       
    69 files/content/WebView.qml
       
    70 slowFiles/slowMain.qml
       
    71 \endcode
       
    72 it can be added like this:
       
    73 \code
       
    74 TestHTTPServer server(14445);
       
    75 server.serveDirectory("disconnect", TestHTTPServer::Disconnect);
       
    76 server.serveDirectory("files");
       
    77 server.serveDirectory("slowFiles", TestHTTPServer::Delay);
       
    78 \endcode
       
    79 
       
    80 The following request urls will then result in the appropriate action:
       
    81 \table
       
    82 \header \o URL \o Action
       
    83 \row \o http://localhost:14445/disconnectTest.qml \o Disconnection
       
    84 \row \o http://localhost:14445/main.qml \o main.qml returned immediately
       
    85 \row \o http://localhost:14445/Button.qml \o Button.qml returned immediately
       
    86 \row \o http://localhost:14445/content/WebView.qml \o content/WebView.qml returned immediately
       
    87 \row \o http://localhost:14445/slowMain.qml \o slowMain.qml returned after 500ms
       
    88 \endtable
       
    89 */
       
    90 TestHTTPServer::TestHTTPServer(quint16 port)
       
    91 : m_hasFailed(false)
       
    92 {
       
    93     QObject::connect(&server, SIGNAL(newConnection()), this, SLOT(newConnection()));
       
    94 
       
    95     server.listen(QHostAddress::LocalHost, port);
       
    96 }
       
    97 
       
    98 bool TestHTTPServer::isValid() const
       
    99 {
       
   100     return server.isListening();
       
   101 }
       
   102 
       
   103 bool TestHTTPServer::serveDirectory(const QString &dir, Mode mode)
       
   104 {
       
   105     dirs.append(qMakePair(dir, mode));
       
   106     return true;
       
   107 }
       
   108 
       
   109 /*
       
   110    Add an alias, so that if filename is requested and does not exist,
       
   111    alias may be returned.
       
   112 */
       
   113 void TestHTTPServer::addAlias(const QString &filename, const QString &alias)
       
   114 {
       
   115     aliases.insert(filename, alias);
       
   116 }
       
   117 
       
   118 void TestHTTPServer::addRedirect(const QString &filename, const QString &redirectName)
       
   119 {
       
   120     redirects.insert(filename, redirectName);
       
   121 }
       
   122 
       
   123 bool TestHTTPServer::wait(const QUrl &expect, const QUrl &reply, const QUrl &body)
       
   124 {
       
   125     m_hasFailed = false;
       
   126 
       
   127     QFile expectFile(expect.toLocalFile());
       
   128     if (!expectFile.open(QIODevice::ReadOnly)) return false;
       
   129     
       
   130     QFile replyFile(reply.toLocalFile());
       
   131     if (!replyFile.open(QIODevice::ReadOnly)) return false;
       
   132 
       
   133     bodyData = QByteArray();
       
   134     if (body.isValid()) {
       
   135         QFile bodyFile(body.toLocalFile());
       
   136         if (!bodyFile.open(QIODevice::ReadOnly)) return false;
       
   137         bodyData = bodyFile.readAll();
       
   138     }
       
   139 
       
   140     waitData = expectFile.readAll();
       
   141     /*
       
   142     while (waitData.endsWith('\n'))
       
   143         waitData = waitData.left(waitData.count() - 1);
       
   144         */
       
   145 
       
   146     replyData = replyFile.readAll();
       
   147 
       
   148     if (!replyData.endsWith('\n'))
       
   149         replyData.append("\n");
       
   150     replyData.append("Content-length: " + QByteArray::number(bodyData.length()));
       
   151     replyData .append("\n\n");
       
   152 
       
   153     for (int ii = 0; ii < replyData.count(); ++ii) {
       
   154         if (replyData.at(ii) == '\n' && (!ii || replyData.at(ii - 1) != '\r')) {
       
   155             replyData.insert(ii, '\r');
       
   156             ++ii;
       
   157         }
       
   158     }
       
   159     replyData.append(bodyData);
       
   160 
       
   161     return true;
       
   162 }
       
   163 
       
   164 bool TestHTTPServer::hasFailed() const
       
   165 {
       
   166     return m_hasFailed;
       
   167 }
       
   168 
       
   169 void TestHTTPServer::newConnection()
       
   170 {
       
   171     QTcpSocket *socket = server.nextPendingConnection();
       
   172     if (!socket) return;
       
   173 
       
   174     if (!dirs.isEmpty())
       
   175         dataCache.insert(socket, QByteArray());
       
   176 
       
   177     QObject::connect(socket, SIGNAL(disconnected()), this, SLOT(disconnected()));
       
   178     QObject::connect(socket, SIGNAL(readyRead()), this, SLOT(readyRead()));
       
   179 }
       
   180 
       
   181 void TestHTTPServer::disconnected()
       
   182 {
       
   183     QTcpSocket *socket = qobject_cast<QTcpSocket *>(sender());
       
   184     if (!socket) return;
       
   185 
       
   186     dataCache.remove(socket);
       
   187     for (int ii = 0; ii < toSend.count(); ++ii) {
       
   188         if (toSend.at(ii).first == socket) {
       
   189             toSend.removeAt(ii);
       
   190             --ii;
       
   191         }
       
   192     }
       
   193     socket->disconnect();
       
   194     socket->deleteLater();
       
   195 }
       
   196 
       
   197 void TestHTTPServer::readyRead()
       
   198 {
       
   199     QTcpSocket *socket = qobject_cast<QTcpSocket *>(sender());
       
   200     if (!socket || socket->state() == QTcpSocket::ClosingState) return;
       
   201 
       
   202     QByteArray ba = socket->readAll();
       
   203 
       
   204     if (!dirs.isEmpty()) {
       
   205         serveGET(socket, ba);
       
   206         return;
       
   207     }
       
   208 
       
   209     if (m_hasFailed || waitData.isEmpty()) {
       
   210         qWarning() << "TestHTTPServer: Unexpected data" << ba;
       
   211         return;
       
   212     }
       
   213 
       
   214     for (int ii = 0; ii < ba.count(); ++ii) {
       
   215         const char c = ba.at(ii);
       
   216         if (c == '\r' && waitData.isEmpty())
       
   217            continue;
       
   218         else if (!waitData.isEmpty() && c == waitData.at(0))
       
   219             waitData = waitData.mid(1);
       
   220         else if (c == '\r')
       
   221             continue;
       
   222         else {
       
   223             QByteArray data = ba.mid(ii);
       
   224             qWarning() << "TestHTTPServer: Unexpected data" << data << "\nExpected: " << waitData;
       
   225             m_hasFailed = true;
       
   226             socket->disconnectFromHost();
       
   227             return;
       
   228         }
       
   229     }
       
   230 
       
   231     if (waitData.isEmpty()) {
       
   232         socket->write(replyData);
       
   233         socket->disconnectFromHost();
       
   234     }
       
   235 }
       
   236 
       
   237 bool TestHTTPServer::reply(QTcpSocket *socket, const QByteArray &fileName)
       
   238 {
       
   239     if (redirects.contains(fileName)) {
       
   240         QByteArray response = "HTTP/1.1 302 Found\r\nContent-length: 0\r\nContent-type: text/html; charset=UTF-8\r\nLocation: " + redirects[fileName].toUtf8() + "\r\n\r\n";
       
   241         socket->write(response);
       
   242         return true;
       
   243     }
       
   244 
       
   245     for (int ii = 0; ii < dirs.count(); ++ii) {
       
   246         QString dir = dirs.at(ii).first;
       
   247         Mode mode = dirs.at(ii).second;
       
   248 
       
   249         QString dirFile = dir + QLatin1String("/") + QLatin1String(fileName);
       
   250 
       
   251         if (!QFile::exists(dirFile)) {
       
   252             if (aliases.contains(fileName))
       
   253                 dirFile = dir + QLatin1String("/") + aliases.value(fileName);
       
   254         }
       
   255 
       
   256         QFile file(dirFile);
       
   257         if (file.open(QIODevice::ReadOnly)) {
       
   258 
       
   259             if (mode == Disconnect)
       
   260                 return true;
       
   261 
       
   262             QByteArray data = file.readAll();
       
   263 
       
   264             QByteArray response = "HTTP/1.0 200 OK\r\nContent-type: text/html; charset=UTF-8\r\nContent-length: ";
       
   265             response += QByteArray::number(data.count());
       
   266             response += "\r\n\r\n";
       
   267             response += data;
       
   268 
       
   269             if (mode == Delay) {
       
   270                 toSend.append(qMakePair(socket, response));
       
   271                 QTimer::singleShot(500, this, SLOT(sendOne()));
       
   272                 return false;
       
   273             } else {
       
   274                 socket->write(response);
       
   275                 return true;
       
   276             }
       
   277         }
       
   278     }
       
   279 
       
   280 
       
   281     QByteArray response = "HTTP/1.0 404 Not found\r\nContent-type: text/html; charset=UTF-8\r\n\r\n";
       
   282     socket->write(response);
       
   283 
       
   284     return true;
       
   285 }
       
   286 
       
   287 void TestHTTPServer::sendOne()
       
   288 {
       
   289     if (!toSend.isEmpty()) {
       
   290         toSend.first().first->write(toSend.first().second);
       
   291         toSend.first().first->close();
       
   292         toSend.removeFirst();
       
   293     }
       
   294 }
       
   295 
       
   296 void TestHTTPServer::serveGET(QTcpSocket *socket, const QByteArray &data)
       
   297 {
       
   298     if (!dataCache.contains(socket))
       
   299         return;
       
   300 
       
   301     QByteArray total = dataCache[socket] + data;
       
   302     dataCache[socket] = total;
       
   303     
       
   304     if (total.contains("\n\r\n")) {
       
   305 
       
   306         bool close = true;
       
   307 
       
   308         if (total.startsWith("GET /")) {
       
   309 
       
   310             int space = total.indexOf(' ', 4);
       
   311             if (space != -1) {
       
   312 
       
   313                 QByteArray req = total.mid(5, space - 5);
       
   314                 close = reply(socket, req);
       
   315 
       
   316             }
       
   317         }
       
   318         dataCache.remove(socket);
       
   319 
       
   320         if (close) 
       
   321             socket->disconnectFromHost();
       
   322     }
       
   323 }
       
   324