15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
66 url.setScheme(QStringLiteral(
"http"));
67 url.setHost(QStringLiteral(
"127.0.0.1"));
72TestHTTPServer::TestHTTPServer()
73 : m_state(AwaitingHeader)
75 QObject::connect(&m_server, &QTcpServer::newConnection,
this, &TestHTTPServer::newConnection);
78bool TestHTTPServer::listen()
80 return m_server.listen(QHostAddress::LocalHost, 0);
83QUrl TestHTTPServer::baseUrl()
const
85 return localHostUrl(m_server.serverPort());
88quint16 TestHTTPServer::port()
const
90 return m_server.serverPort();
93QUrl TestHTTPServer::url(
const QString &documentPath)
const
95 return baseUrl().resolved(documentPath);
98QString TestHTTPServer::urlString(
const QString &documentPath)
const
100 return url(documentPath).toString();
103QString TestHTTPServer::errorString()
const
105 return m_server.errorString();
108bool TestHTTPServer::serveDirectory(
const QString &dir, Mode mode)
110 m_directories.append(std::make_pair(dir, mode));
115
116
117
118void TestHTTPServer::addAlias(
const QString &filename,
const QString &alias)
120 m_aliases.insert(filename, alias);
123void TestHTTPServer::addRedirect(
const QString &filename,
const QString &redirectName)
125 m_redirects.insert(filename, redirectName);
128void TestHTTPServer::registerFileNameForContentSubstitution(
const QString &fileName)
130 m_contentSubstitutedFileNames.insert(fileName);
133bool TestHTTPServer::wait(
const QUrl &expect,
const QUrl &reply,
const QUrl &body)
135 m_state = AwaitingHeader;
138 QFile expectFile(QQmlFile::urlToLocalFileOrQrc(expect));
139 if (!expectFile.open(QIODevice::ReadOnly))
142 QFile replyFile(QQmlFile::urlToLocalFileOrQrc(reply));
143 if (!replyFile.open(QIODevice::ReadOnly))
146 m_bodyData = QByteArray();
147 if (body.isValid()) {
148 QFile bodyFile(QQmlFile::urlToLocalFileOrQrc(body));
149 if (!bodyFile.open(QIODevice::ReadOnly))
151 m_bodyData = bodyFile.readAll();
154 const QByteArray serverHostUrl
155 = QByteArrayLiteral(
"127.0.0.1:")+ QByteArray::number(m_server.serverPort());
158 bool headers_done =
false;
159 while (!(line = expectFile.readLine()).isEmpty()) {
160 line.replace(
'\r',
"");
162 m_waitData.body.append(line);
163 }
else if (line.at(0) ==
'\n') {
165 }
else if (line.endsWith(
"{{Ignore}}\n")) {
166 m_waitData.headerPrefixes.append(line.left(line.size() - strlen(
"{{Ignore}}\n")));
168 line.replace(
"{{ServerHostUrl}}", serverHostUrl);
169 m_waitData.headerExactMatches.append(line);
173 m_replyData = replyFile.readAll();
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");
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');
187 m_replyData.append(m_bodyData);
192bool TestHTTPServer::hasFailed()
const
194 return m_state == Failed;
197void TestHTTPServer::newConnection()
199 QTcpSocket *socket = m_server.nextPendingConnection();
203 if (!m_directories.isEmpty())
204 m_dataCache.insert(socket, QByteArray());
206 QObject::connect(socket, &QAbstractSocket::disconnected,
this, &TestHTTPServer::disconnected);
207 QObject::connect(socket, &QIODevice::readyRead,
this, &TestHTTPServer::readyRead);
210void TestHTTPServer::disconnected()
212 QTcpSocket *socket = qobject_cast<QTcpSocket *>(sender());
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);
223 socket->disconnect();
224 socket->deleteLater();
227void TestHTTPServer::readyRead()
229 QTcpSocket *socket = qobject_cast<QTcpSocket *>(sender());
230 if (!socket || socket->state() == QTcpSocket::ClosingState)
233 if (!m_directories.isEmpty()) {
234 serveGET(socket, socket->readAll());
238 if (m_state == Failed || (m_waitData.body.isEmpty() && m_waitData.headerExactMatches.size() == 0)) {
239 qWarning() <<
"TestHTTPServer: Unexpected data" << socket->readAll();
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();
252 qsizetype prefixIndex = line.indexOf(
':');
253 if (prefixIndex > 0) {
254 const QByteArray prefix = line.left(prefixIndex);
255 line =
std::move(line).replace(prefix, prefix.toLower());
257 bool prefixFound =
false;
258 for (
const QByteArray &prefix : m_waitData.headerPrefixes) {
259 if (line.startsWith(prefix)) {
264 for (QByteArrayView ignore : ignoredHeaders) {
265 if (line.startsWith(ignore)) {
271 if (!prefixFound && !m_waitData.headerExactMatches.contains(line)) {
272 qWarning() <<
"TestHTTPServer: Unexpected header:" << line
273 <<
"\nExpected exact headers: " << m_waitData.headerExactMatches
274 <<
"\nExpected header prefixes: " << m_waitData.headerPrefixes;
276 socket->disconnectFromHost();
282 m_data += socket->readAll();
285 if (!m_data.isEmpty() || m_waitData.body.isEmpty()) {
286 if (m_waitData.body != m_data) {
287 qWarning() <<
"TestHTTPServer: Unexpected data" << m_data <<
"\nExpected: " << m_waitData.body;
290 socket->write(m_replyData);
292 socket->disconnectFromHost();
296bool TestHTTPServer::reply(QTcpSocket *socket,
const QByteArray &fileNameIn)
298 const QString fileName = QLatin1String(fileNameIn);
299 if (m_redirects.contains(fileName)) {
300 const QByteArray response
301 =
"HTTP/1.1 302 Found\r\nContent-length: 0\r\nContent-type: text/html; charset=UTF-8\r\nLocation: "
302 + m_redirects.value(fileName).toUtf8() +
"\r\n\r\n";
303 socket->write(response);
307 for (
int ii = 0; ii < m_directories.size(); ++ii) {
308 const QString &dir = m_directories.at(ii).first;
309 const Mode mode = m_directories.at(ii).second;
311 QString dirFile = dir + QLatin1Char(
'/') + fileName;
313 if (!QFile::exists(dirFile)) {
314 const QHash<QString, QString>::const_iterator it = m_aliases.constFind(fileName);
315 if (it != m_aliases.constEnd())
316 dirFile = dir + QLatin1Char(
'/') + it.value();
320 if (file.open(QIODevice::ReadOnly)) {
322 if (mode == Disconnect)
325 QByteArray data = file.readAll();
326 if (m_contentSubstitutedFileNames.contains(QLatin1Char(
'/') + fileName))
327 data.replace(QByteArrayLiteral(
"{{ServerBaseUrl}}"), baseUrl().toString().toUtf8());
330 =
"HTTP/1.0 200 OK\r\nContent-type: text/html; charset=UTF-8\r\nContent-length: ";
331 response += QByteArray::number(data.size());
332 response +=
"\r\n\r\n";
336 m_toSend.append(std::make_pair(socket, response));
337 QTimer::singleShot(500,
this, &TestHTTPServer::sendOne);
341 if (response.length() <= m_chunkSize) {
342 socket->write(response);
346 socket->write(response.left(m_chunkSize));
347 for (qsizetype offset = m_chunkSize, end = response.length(); offset < end;
348 offset += m_chunkSize) {
349 m_toSend.append(std::make_pair(socket, response.mid(offset, m_chunkSize)));
352 QTimer::singleShot(1,
this, &TestHTTPServer::sendChunk);
357 socket->write(
"HTTP/1.0 404 Not found\r\nContent-type: text/html; charset=UTF-8\r\n\r\n");
362void TestHTTPServer::sendDelayedItem()
367void TestHTTPServer::sendOne()
369 if (!m_toSend.isEmpty()) {
370 m_toSend.first().first->write(m_toSend.first().second);
371 m_toSend.first().first->close();
372 m_toSend.removeFirst();
376void TestHTTPServer::sendChunk()
378 const auto chunk = m_toSend.takeFirst();
379 chunk.first->write(chunk.second);
380 if (m_toSend.isEmpty())
381 chunk.first->close();
383 QTimer::singleShot(1,
this, &TestHTTPServer::sendChunk);
386void TestHTTPServer::serveGET(QTcpSocket *socket,
const QByteArray &data)
388 const QHash<QTcpSocket *, QByteArray>::iterator it = m_dataCache.find(socket);
389 if (it == m_dataCache.end())
392 QByteArray &total = it.value();
395 if (total.contains(
"\n\r\n")) {
397 if (total.startsWith(
"GET /")) {
398 const int space = total.indexOf(
' ', 4);
400 close = reply(socket, total.mid(5, space - 5));
402 m_dataCache.erase(it);
404 socket->disconnectFromHost();
429 return localHostUrl(m_port);
434 return baseUrl().resolved(documentPath);
439 return url(documentPath).toString();
444 TestHTTPServer server;
446 QMutexLocker locker(&m_mutex);
447 QVERIFY2(server.listen(), qPrintable(server.errorString()));
448 m_port = server.port();
449 for (QHash<QString, TestHTTPServer::Mode>::ConstIterator i = m_dirs.constBegin();
450 i != m_dirs.constEnd(); ++i) {
451 server.serveDirectory(i.key(), i.value());
453 m_condition.wakeAll();
460 QMutexLocker locker(&m_mutex);
462 m_condition.wait(&m_mutex);
467#include "moc_testhttpserver_p.cpp"
QString urlString(const QString &documentPath) const
ThreadedTestHTTPServer(const QHash< QString, TestHTTPServer::Mode > &dirs)
QUrl url(const QString &documentPath) const
~ThreadedTestHTTPServer()
Combined button and popup list for selecting options.
static QList< QByteArrayView > ignoredHeaders
static QUrl localHostUrl(quint16 port)