Qt
Internal/Contributor docs for the Qt SDK. <b>Note:</b> These are NOT official API docs; those are found <a href='https://doc.qt.io/'>here</a>.
Loading...
Searching...
No Matches
testhttpserver.cpp
Go to the documentation of this file.
1// Copyright (C) 2021 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
3
4#include "testhttpserver_p.h"
5#include <QTcpSocket>
6#include <QDebug>
7#include <QFile>
8#include <QTimer>
9#include <QTest>
10#include <QQmlFile>
11
13
58static QList<QByteArrayView> ignoredHeaders = {
59 "HTTP2-Settings", // We ignore this
60 "Upgrade", // We ignore this as well
61};
62
64{
65 QUrl url;
67 url.setHost(QStringLiteral("127.0.0.1"));
69 return url;
70}
71
73 : m_state(AwaitingHeader)
74{
75 QObject::connect(&m_server, &QTcpServer::newConnection, this, &TestHTTPServer::newConnection);
76}
77
79{
80 return m_server.listen(QHostAddress::LocalHost, 0);
81}
82
84{
85 return localHostUrl(m_server.serverPort());
86}
87
89{
90 return m_server.serverPort();
91}
92
93QUrl TestHTTPServer::url(const QString &documentPath) const
94{
95 return baseUrl().resolved(documentPath);
96}
97
98QString TestHTTPServer::urlString(const QString &documentPath) const
99{
100 return url(documentPath).toString();
101}
102
104{
105 return m_server.errorString();
106}
107
109{
110 m_directories.append(qMakePair(dir, mode));
111 return true;
112}
113
114/*
115 Add an alias, so that if filename is requested and does not exist,
116 alias may be returned.
117*/
118void TestHTTPServer::addAlias(const QString &filename, const QString &alias)
119{
120 m_aliases.insert(filename, alias);
121}
122
123void TestHTTPServer::addRedirect(const QString &filename, const QString &redirectName)
124{
125 m_redirects.insert(filename, redirectName);
126}
127
129{
130 m_contentSubstitutedFileNames.insert(fileName);
131}
132
133bool TestHTTPServer::wait(const QUrl &expect, const QUrl &reply, const QUrl &body)
134{
135 m_state = AwaitingHeader;
136 m_data.clear();
137
138 QFile expectFile(QQmlFile::urlToLocalFileOrQrc(expect));
139 if (!expectFile.open(QIODevice::ReadOnly))
140 return false;
141
143 if (!replyFile.open(QIODevice::ReadOnly))
144 return false;
145
146 m_bodyData = QByteArray();
147 if (body.isValid()) {
148 QFile bodyFile(QQmlFile::urlToLocalFileOrQrc(body));
149 if (!bodyFile.open(QIODevice::ReadOnly))
150 return false;
151 m_bodyData = bodyFile.readAll();
152 }
153
154 const QByteArray serverHostUrl
155 = QByteArrayLiteral("127.0.0.1:")+ QByteArray::number(m_server.serverPort());
156
158 bool headers_done = false;
159 while (!(line = expectFile.readLine()).isEmpty()) {
160 line.replace('\r', "");
161 if (headers_done) {
162 m_waitData.body.append(line);
163 } else if (line.at(0) == '\n') {
164 headers_done = true;
165 } else if (line.endsWith("{{Ignore}}\n")) {
166 m_waitData.headerPrefixes.append(line.left(line.size() - strlen("{{Ignore}}\n")));
167 } else {
168 line.replace("{{ServerHostUrl}}", serverHostUrl);
169 m_waitData.headerExactMatches.append(line);
170 }
171 }
172
173 m_replyData = replyFile.readAll();
174
175 if (!m_replyData.endsWith('\n'))
176 m_replyData.append('\n');
177 m_replyData.append("Content-length: ");
178 m_replyData.append(QByteArray::number(m_bodyData.size()));
179 m_replyData.append("\n\n");
180
181 for (int ii = 0; ii < m_replyData.size(); ++ii) {
182 if (m_replyData.at(ii) == '\n' && (!ii || m_replyData.at(ii - 1) != '\r')) {
183 m_replyData.insert(ii, '\r');
184 ++ii;
185 }
186 }
187 m_replyData.append(m_bodyData);
188
189 return true;
190}
191
193{
194 return m_state == Failed;
195}
196
197void TestHTTPServer::newConnection()
198{
200 if (!socket)
201 return;
202
203 if (!m_directories.isEmpty())
204 m_dataCache.insert(socket, QByteArray());
205
206 QObject::connect(socket, &QAbstractSocket::disconnected, this, &TestHTTPServer::disconnected);
207 QObject::connect(socket, &QIODevice::readyRead, this, &TestHTTPServer::readyRead);
208}
209
210void TestHTTPServer::disconnected()
211{
212 QTcpSocket *socket = qobject_cast<QTcpSocket *>(sender());
213 if (!socket)
214 return;
215
216 m_dataCache.remove(socket);
217 for (int ii = 0; ii < m_toSend.size(); ++ii) {
218 if (m_toSend.at(ii).first == socket) {
219 m_toSend.removeAt(ii);
220 --ii;
221 }
222 }
225}
226
227void TestHTTPServer::readyRead()
228{
229 QTcpSocket *socket = qobject_cast<QTcpSocket *>(sender());
231 return;
232
233 if (!m_directories.isEmpty()) {
234 serveGET(socket, socket->readAll());
235 return;
236 }
237
238 if (m_state == Failed || (m_waitData.body.isEmpty() && m_waitData.headerExactMatches.size() == 0)) {
239 qWarning() << "TestHTTPServer: Unexpected data" << socket->readAll();
240 return;
241 }
242
243 if (m_state == AwaitingHeader) {
245 while (!(line = socket->readLine()).isEmpty()) {
246 line.replace('\r', "");
247 if (line.at(0) == '\n') {
248 m_state = AwaitingData;
249 m_data += socket->readAll();
250 break;
251 } else {
252 bool prefixFound = false;
253 for (const QByteArray &prefix : m_waitData.headerPrefixes) {
254 if (line.startsWith(prefix)) {
255 prefixFound = true;
256 break;
257 }
258 }
259 for (QByteArrayView ignore : ignoredHeaders) {
260 if (line.startsWith(ignore)) {
261 prefixFound = true;
262 break;
263 }
264 }
265
266 if (!prefixFound && !m_waitData.headerExactMatches.contains(line)) {
267 qWarning() << "TestHTTPServer: Unexpected header:" << line
268 << "\nExpected exact headers: " << m_waitData.headerExactMatches
269 << "\nExpected header prefixes: " << m_waitData.headerPrefixes;
270 m_state = Failed;
272 return;
273 }
274 }
275 }
276 } else {
277 m_data += socket->readAll();
278 }
279
280 if (!m_data.isEmpty() || m_waitData.body.isEmpty()) {
281 if (m_waitData.body != m_data) {
282 qWarning() << "TestHTTPServer: Unexpected data" << m_data << "\nExpected: " << m_waitData.body;
283 m_state = Failed;
284 } else {
285 socket->write(m_replyData);
286 }
288 }
289}
290
291bool TestHTTPServer::reply(QTcpSocket *socket, const QByteArray &fileNameIn)
292{
293 const QString fileName = QLatin1String(fileNameIn);
294 if (m_redirects.contains(fileName)) {
295 const QByteArray response
296 = "HTTP/1.1 302 Found\r\nContent-length: 0\r\nContent-type: text/html; charset=UTF-8\r\nLocation: "
297 + m_redirects.value(fileName).toUtf8() + "\r\n\r\n";
298 socket->write(response);
299 return true;
300 }
301
302 for (int ii = 0; ii < m_directories.size(); ++ii) {
303 const QString &dir = m_directories.at(ii).first;
304 const Mode mode = m_directories.at(ii).second;
305
306 QString dirFile = dir + QLatin1Char('/') + fileName;
307
308 if (!QFile::exists(dirFile)) {
310 if (it != m_aliases.constEnd())
311 dirFile = dir + QLatin1Char('/') + it.value();
312 }
313
314 QFile file(dirFile);
316
317 if (mode == Disconnect)
318 return true;
319
321 if (m_contentSubstitutedFileNames.contains(QLatin1Char('/') + fileName))
322 data.replace(QByteArrayLiteral("{{ServerBaseUrl}}"), baseUrl().toString().toUtf8());
323
324 QByteArray response
325 = "HTTP/1.0 200 OK\r\nContent-type: text/html; charset=UTF-8\r\nContent-length: ";
326 response += QByteArray::number(data.size());
327 response += "\r\n\r\n";
328 response += data;
329
330 if (mode == Delay) {
331 m_toSend.append(qMakePair(socket, response));
332 QTimer::singleShot(500, this, &TestHTTPServer::sendOne);
333 return false;
334 } else {
335 socket->write(response);
336 return true;
337 }
338 }
339 }
340
341 socket->write("HTTP/1.0 404 Not found\r\nContent-type: text/html; charset=UTF-8\r\n\r\n");
342
343 return true;
344}
345
347{
348 sendOne();
349}
350
351void TestHTTPServer::sendOne()
352{
353 if (!m_toSend.isEmpty()) {
354 m_toSend.first().first->write(m_toSend.first().second);
355 m_toSend.first().first->close();
356 m_toSend.removeFirst();
357 }
358}
359
360void TestHTTPServer::serveGET(QTcpSocket *socket, const QByteArray &data)
361{
363 if (it == m_dataCache.end())
364 return;
365
366 QByteArray &total = it.value();
367 total.append(data);
368
369 if (total.contains("\n\r\n")) {
370 bool close = true;
371 if (total.startsWith("GET /")) {
372 const int space = total.indexOf(' ', 4);
373 if (space != -1)
374 close = reply(socket, total.mid(5, space - 5));
375 }
376 m_dataCache.erase(it);
377 if (close)
379 }
380}
381
383 m_port(0)
384{
385 m_dirs[dir] = mode;
386 start();
387}
388
389ThreadedTestHTTPServer::ThreadedTestHTTPServer(const QHash<QString, TestHTTPServer::Mode> &dirs) :
390 m_dirs(dirs), m_port(0)
391{
392 start();
393}
394
400
402{
403 return localHostUrl(m_port);
404}
405
406QUrl ThreadedTestHTTPServer::url(const QString &documentPath) const
407{
408 return baseUrl().resolved(documentPath);
409}
410
412{
413 return url(documentPath).toString();
414}
415
417{
419 {
420 QMutexLocker locker(&m_mutex);
421 QVERIFY2(server.listen(), qPrintable(server.errorString()));
422 m_port = server.port();
424 i != m_dirs.constEnd(); ++i) {
425 server.serveDirectory(i.key(), i.value());
426 }
427 m_condition.wakeAll();
428 }
429 exec();
430}
431
432void ThreadedTestHTTPServer::start()
433{
434 QMutexLocker locker(&m_mutex);
436 m_condition.wait(&m_mutex);
437}
438
440
441#include "moc_testhttpserver_p.cpp"
virtual void disconnectFromHost()
Attempts to close the socket.
SocketState state() const
Returns the state of the socket.
void disconnected()
This signal is emitted when the socket has been disconnected.
\inmodule QtCore
Definition qbytearray.h:57
bool endsWith(char c) const
This is an overloaded member function, provided for convenience. It differs from the above function o...
Definition qbytearray.h:227
qsizetype size() const noexcept
Returns the number of bytes in this byte array.
Definition qbytearray.h:494
qsizetype indexOf(char c, qsizetype from=0) const
This is an overloaded member function, provided for convenience. It differs from the above function o...
bool startsWith(QByteArrayView bv) const
Definition qbytearray.h:223
char at(qsizetype i) const
Returns the byte at index position i in the byte array.
Definition qbytearray.h:600
QByteArray & insert(qsizetype i, QByteArrayView data)
bool contains(char c) const
This is an overloaded member function, provided for convenience. It differs from the above function o...
Definition qbytearray.h:660
bool isEmpty() const noexcept
Returns true if the byte array has size 0; otherwise returns false.
Definition qbytearray.h:107
static QByteArray number(int, int base=10)
Returns a byte-array representing the whole number n as text.
void clear()
Clears the contents of the byte array and makes it null.
QByteArray & append(char c)
This is an overloaded member function, provided for convenience. It differs from the above function o...
QByteArray mid(qsizetype index, qsizetype len=-1) const &
QByteArray & replace(qsizetype index, qsizetype len, const char *s, qsizetype alen)
This is an overloaded member function, provided for convenience. It differs from the above function o...
Definition qbytearray.h:339
\inmodule QtCore
Definition qfile.h:93
QFILE_MAYBE_NODISCARD bool open(OpenMode flags) override
Opens the file using OpenMode mode, returning true if successful; otherwise false.
Definition qfile.cpp:904
bool exists() const
This is an overloaded member function, provided for convenience. It differs from the above function o...
Definition qfile.cpp:351
\inmodule QtCore
Definition qhash.h:1145
\inmodule QtCore
Definition qhash.h:1103
bool remove(const Key &key)
Removes the item that has the key from the hash.
Definition qhash.h:958
const_iterator constFind(const Key &key) const noexcept
Definition qhash.h:1299
const_iterator constEnd() const noexcept
Returns a const \l{STL-style iterators}{STL-style iterator} pointing to the imaginary item after the ...
Definition qhash.h:1219
iterator find(const Key &key)
Returns an iterator pointing to the item with the key in the hash.
Definition qhash.h:1291
const_iterator constBegin() const noexcept
Returns a const \l{STL-style iterators}{STL-style iterator} pointing to the first item in the hash.
Definition qhash.h:1215
iterator erase(const_iterator it)
Definition qhash.h:1233
bool contains(const Key &key) const noexcept
Returns true if the hash contains an item with the key; otherwise returns false.
Definition qhash.h:1007
T value(const Key &key) const noexcept
Definition qhash.h:1054
iterator end() noexcept
Returns an \l{STL-style iterators}{STL-style iterator} pointing to the imaginary item after the last ...
Definition qhash.h:1216
iterator insert(const Key &key, const T &value)
Inserts a new item with the key and a value of value.
Definition qhash.h:1303
void readyRead()
This signal is emitted once every time new data is available for reading from the device's current re...
qint64 readLine(char *data, qint64 maxlen)
This function reads a line of ASCII characters from the device, up to a maximum of maxSize - 1 bytes,...
QByteArray readAll()
Reads all remaining data from the device, and returns it as a byte array.
qint64 write(const char *data, qint64 len)
Writes at most maxSize bytes of data from data to the device.
qsizetype size() const noexcept
Definition qlist.h:397
void removeFirst() noexcept
Definition qlist.h:807
bool isEmpty() const noexcept
Definition qlist.h:401
T & first()
Definition qlist.h:645
void removeAt(qsizetype i)
Definition qlist.h:590
const_reference at(qsizetype i) const noexcept
Definition qlist.h:446
void append(parameter_type t)
Definition qlist.h:458
\inmodule QtCore
Definition qmutex.h:313
static QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *member, Qt::ConnectionType=Qt::AutoConnection)
\threadsafe
Definition qobject.cpp:2960
QObject * sender() const
Returns a pointer to the object that sent the signal, if called in a slot activated by a signal; othe...
Definition qobject.cpp:2658
static bool disconnect(const QObject *sender, const char *signal, const QObject *receiver, const char *member)
\threadsafe
Definition qobject.cpp:3236
void deleteLater()
\threadsafe
Definition qobject.cpp:2435
static QString urlToLocalFileOrQrc(const QString &)
If url is a local file returns a path suitable for passing to \l{QFile}.
Definition qqmlfile.cpp:742
bool contains(const T &value) const
Definition qset.h:71
iterator insert(const T &value)
Definition qset.h:155
\macro QT_RESTRICTED_CAST_FROM_ASCII
Definition qstring.h:129
QString left(qsizetype n) const &
Definition qstring.h:363
bool startsWith(const QString &s, Qt::CaseSensitivity cs=Qt::CaseSensitive) const
Returns true if the string starts with s; otherwise returns false.
Definition qstring.cpp:5455
QString & replace(qsizetype i, qsizetype len, QChar after)
Definition qstring.cpp:3824
qsizetype size() const noexcept
Returns the number of characters in this string.
Definition qstring.h:186
const QChar at(qsizetype i) const
Returns the character at the given index position in the string.
Definition qstring.h:1226
bool endsWith(const QString &s, Qt::CaseSensitivity cs=Qt::CaseSensitive) const
Returns true if the string ends with s; otherwise returns false.
Definition qstring.cpp:5506
QByteArray toUtf8() const &
Definition qstring.h:634
void newConnection()
This signal is emitted every time a new connection is available, regardless of whether it has been ad...
QString errorString() const
Returns a human readable description of the last error that occurred.
virtual QTcpSocket * nextPendingConnection()
Returns the next pending connection as a connected QTcpSocket object.
bool listen(const QHostAddress &address=QHostAddress::Any, quint16 port=0)
Tells the server to listen for incoming connections on address address and port port.
quint16 serverPort() const
Returns the server's port if the server is listening for connections; otherwise returns 0.
The QTcpSocket class provides a TCP socket.
Definition qtcpsocket.h:18
void start(Priority=InheritPriority)
Definition qthread.cpp:996
bool wait(QDeadlineTimer deadline=QDeadlineTimer(QDeadlineTimer::Forever))
Definition qthread.cpp:1023
int exec()
Definition qthread.cpp:991
void quit()
Definition qthread.cpp:1008
bool singleShot
whether the timer is a single-shot timer
Definition qtimer.h:22
\inmodule QtCore
Definition qurl.h:94
QUrl resolved(const QUrl &relative) const
Returns the result of the merge of this URL with relative.
Definition qurl.cpp:2725
bool isValid() const
Returns true if the URL is non-empty and valid; otherwise returns false.
Definition qurl.cpp:1882
void setScheme(const QString &scheme)
Sets the scheme of the URL to scheme.
Definition qurl.cpp:1967
void setHost(const QString &host, ParsingMode mode=DecodedMode)
Sets the host of the URL to host.
Definition qurl.cpp:2289
QString toString(FormattingOptions options=FormattingOptions(PrettyDecoded)) const
Returns a string representation of the URL.
Definition qurl.cpp:2831
void setPort(int port)
Sets the port of the URL to port.
Definition qurl.cpp:2358
bool wait(QMutex *, QDeadlineTimer=QDeadlineTimer(QDeadlineTimer::Forever))
provides a very, very basic HTTP server for testing.
QUrl baseUrl() const
QString errorString() const
void addAlias(const QString &filename, const QString &aliasName)
bool wait(const QUrl &expect, const QUrl &reply, const QUrl &body)
QUrl url(const QString &documentPath) const
bool serveDirectory(const QString &, Mode=Normal)
void registerFileNameForContentSubstitution(const QString &fileName)
void addRedirect(const QString &filename, const QString &redirectName)
quint16 port() const
bool hasFailed() const
QString urlString(const QString &documentPath) const
QString urlString(const QString &documentPath) const
QUrl url(const QString &documentPath) const
ThreadedTestHTTPServer(const QString &dir, TestHTTPServer::Mode mode=TestHTTPServer::Normal)
QSet< QString >::iterator it
Combined button and popup list for selecting options.
int toUtf8(char16_t u, OutputPtr &dst, InputPtr &src, InputPtr end)
#define QByteArrayLiteral(str)
Definition qbytearray.h:52
DBusConnection const char DBusError DBusBusType DBusError return DBusConnection DBusHandleMessageFunction void DBusFreeFunction return DBusConnection return DBusConnection return const char DBusError return DBusConnection DBusMessage dbus_uint32_t return DBusConnection dbus_bool_t DBusConnection DBusAddWatchFunction DBusRemoveWatchFunction DBusWatchToggledFunction void DBusFreeFunction return DBusConnection DBusDispatchStatusFunction void DBusFreeFunction DBusTimeout return DBusTimeout return DBusWatch return DBusWatch unsigned int return DBusError const DBusError return const DBusMessage return DBusMessage return DBusMessage return DBusMessage return DBusMessage return DBusMessage return DBusMessageIter int const void return DBusMessageIter DBusMessageIter return DBusMessageIter void DBusMessageIter void int return DBusMessage DBusMessageIter return DBusMessageIter return DBusMessageIter DBusMessageIter const char const char const char const char return DBusMessage return DBusMessage const char return DBusMessage dbus_bool_t return DBusMessage dbus_uint32_t return DBusMessage return DBusPendingCall DBusPendingCall return DBusPendingCall return dbus_int32_t return DBusServer * server
typedef QByteArray(EGLAPIENTRYP PFNQGSGETDISPLAYSPROC)()
EGLOutputPortEXT port
#define qWarning
Definition qlogging.h:166
GLenum mode
GLint GLsizei GLsizei GLenum GLenum GLsizei void * data
GLuint start
QT_BEGIN_NAMESPACE constexpr decltype(auto) qMakePair(T1 &&value1, T2 &&value2) noexcept(noexcept(std::make_pair(std::forward< T1 >(value1), std::forward< T2 >(value2))))
Definition qpair.h:19
#define qPrintable(string)
Definition qstring.h:1531
QLatin1StringView QLatin1String
Definition qstringfwd.h:31
#define QStringLiteral(str)
#define QVERIFY2(statement, description)
Definition qtestcase.h:70
unsigned short quint16
Definition qtypes.h:48
QFile file
[0]
QUrl url("example.com")
[constructor-url-reference]
QTcpSocket * socket
[1]
QString dir
[11]
QNetworkReply * reply
char * toString(const MyType &t)
[31]
\inmodule QtCore \reentrant
Definition qchar.h:18
static QUrl localHostUrl(quint16 port)
static QList< QByteArrayView > ignoredHeaders