1 /****************************************************************************
3 ** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
4 ** All rights reserved.
5 ** Contact: Nokia Corporation (qt-info@nokia.com)
7 ** This file is part of the test suite of the Qt Toolkit.
9 ** $QT_BEGIN_LICENSE:LGPL$
10 ** GNU Lesser General Public License Usage
11 ** This file may be used under the terms of the GNU Lesser General Public
12 ** License version 2.1 as published by the Free Software Foundation and
13 ** appearing in the file LICENSE.LGPL included in the packaging of this
14 ** file. Please review the following information to ensure the GNU Lesser
15 ** General Public License version 2.1 requirements will be met:
16 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
18 ** In addition, as a special exception, Nokia gives you certain additional
19 ** rights. These rights are described in the Nokia Qt LGPL Exception
20 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
22 ** GNU General Public License Usage
23 ** Alternatively, this file may be used under the terms of the GNU General
24 ** Public License version 3.0 as published by the Free Software Foundation
25 ** and appearing in the file LICENSE.GPL included in the packaging of this
26 ** file. Please review the following information to ensure the GNU General
27 ** Public License version 3.0 requirements will be met:
28 ** http://www.gnu.org/copyleft/gpl.html.
31 ** Alternatively, this file may be used in accordance with the terms and
32 ** conditions contained in a signed written agreement between you and Nokia.
40 ****************************************************************************/
42 #include "testhttpserver.h"
51 \brief provides a very, very basic HTTP server for testing.
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.
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.
63 If you have the following directory structure:
66 disconnect/disconnectTest.qml
69 files/content/WebView.qml
70 slowFiles/slowMain.qml
72 it can be added like this:
74 TestHTTPServer server(14445);
75 server.serveDirectory("disconnect", TestHTTPServer::Disconnect);
76 server.serveDirectory("files");
77 server.serveDirectory("slowFiles", TestHTTPServer::Delay);
80 The following request urls will then result in the appropriate action:
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
90 TestHTTPServer::TestHTTPServer(quint16 port)
93 QObject::connect(&server, SIGNAL(newConnection()), this, SLOT(newConnection()));
95 server.listen(QHostAddress::LocalHost, port);
98 bool TestHTTPServer::isValid() const
100 return server.isListening();
103 bool TestHTTPServer::serveDirectory(const QString &dir, Mode mode)
105 dirs.append(qMakePair(dir, mode));
110 Add an alias, so that if filename is requested and does not exist,
111 alias may be returned.
113 void TestHTTPServer::addAlias(const QString &filename, const QString &alias)
115 aliases.insert(filename, alias);
118 void TestHTTPServer::addRedirect(const QString &filename, const QString &redirectName)
120 redirects.insert(filename, redirectName);
123 bool TestHTTPServer::wait(const QUrl &expect, const QUrl &reply, const QUrl &body)
127 QFile expectFile(expect.toLocalFile());
128 if (!expectFile.open(QIODevice::ReadOnly)) return false;
130 QFile replyFile(reply.toLocalFile());
131 if (!replyFile.open(QIODevice::ReadOnly)) return false;
133 bodyData = QByteArray();
134 if (body.isValid()) {
135 QFile bodyFile(body.toLocalFile());
136 if (!bodyFile.open(QIODevice::ReadOnly)) return false;
137 bodyData = bodyFile.readAll();
140 waitData = expectFile.readAll();
142 while (waitData.endsWith('\n'))
143 waitData = waitData.left(waitData.count() - 1);
146 replyData = replyFile.readAll();
148 if (!replyData.endsWith('\n'))
149 replyData.append("\n");
150 replyData.append("Content-length: " + QByteArray::number(bodyData.length()));
151 replyData .append("\n\n");
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');
159 replyData.append(bodyData);
164 bool TestHTTPServer::hasFailed() const
169 void TestHTTPServer::newConnection()
171 QTcpSocket *socket = server.nextPendingConnection();
175 dataCache.insert(socket, QByteArray());
177 QObject::connect(socket, SIGNAL(disconnected()), this, SLOT(disconnected()));
178 QObject::connect(socket, SIGNAL(readyRead()), this, SLOT(readyRead()));
181 void TestHTTPServer::disconnected()
183 QTcpSocket *socket = qobject_cast<QTcpSocket *>(sender());
186 dataCache.remove(socket);
187 for (int ii = 0; ii < toSend.count(); ++ii) {
188 if (toSend.at(ii).first == socket) {
193 socket->disconnect();
194 socket->deleteLater();
197 void TestHTTPServer::readyRead()
199 QTcpSocket *socket = qobject_cast<QTcpSocket *>(sender());
200 if (!socket || socket->state() == QTcpSocket::ClosingState) return;
202 QByteArray ba = socket->readAll();
204 if (!dirs.isEmpty()) {
205 serveGET(socket, ba);
209 if (m_hasFailed || waitData.isEmpty()) {
210 qWarning() << "TestHTTPServer: Unexpected data" << ba;
214 for (int ii = 0; ii < ba.count(); ++ii) {
215 const char c = ba.at(ii);
216 if (c == '\r' && waitData.isEmpty())
218 else if (!waitData.isEmpty() && c == waitData.at(0))
219 waitData = waitData.mid(1);
223 QByteArray data = ba.mid(ii);
224 qWarning() << "TestHTTPServer: Unexpected data" << data << "\nExpected: " << waitData;
226 socket->disconnectFromHost();
231 if (waitData.isEmpty()) {
232 socket->write(replyData);
233 socket->disconnectFromHost();
237 bool TestHTTPServer::reply(QTcpSocket *socket, const QByteArray &fileName)
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);
245 for (int ii = 0; ii < dirs.count(); ++ii) {
246 QString dir = dirs.at(ii).first;
247 Mode mode = dirs.at(ii).second;
249 QString dirFile = dir + QLatin1String("/") + QLatin1String(fileName);
251 if (!QFile::exists(dirFile)) {
252 if (aliases.contains(fileName))
253 dirFile = dir + QLatin1String("/") + aliases.value(fileName);
257 if (file.open(QIODevice::ReadOnly)) {
259 if (mode == Disconnect)
262 QByteArray data = file.readAll();
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";
270 toSend.append(qMakePair(socket, response));
271 QTimer::singleShot(500, this, SLOT(sendOne()));
274 socket->write(response);
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);
287 void TestHTTPServer::sendOne()
289 if (!toSend.isEmpty()) {
290 toSend.first().first->write(toSend.first().second);
291 toSend.first().first->close();
292 toSend.removeFirst();
296 void TestHTTPServer::serveGET(QTcpSocket *socket, const QByteArray &data)
298 if (!dataCache.contains(socket))
301 QByteArray total = dataCache[socket] + data;
302 dataCache[socket] = total;
304 if (total.contains("\n\r\n")) {
308 if (total.startsWith("GET /")) {
310 int space = total.indexOf(' ', 4);
313 QByteArray req = total.mid(5, space - 5);
314 close = reply(socket, req);
318 dataCache.remove(socket);
321 socket->disconnectFromHost();