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 bool prefixFound =
false;
253 for (
const QByteArray &prefix : m_waitData.headerPrefixes) {
254 if (line.startsWith(prefix)) {
259 for (QByteArrayView ignore : ignoredHeaders) {
260 if (line.startsWith(ignore)) {
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;
271 socket->disconnectFromHost();
277 m_data += socket->readAll();
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;
285 socket->write(m_replyData);
287 socket->disconnectFromHost();
291bool TestHTTPServer::reply(QTcpSocket *socket,
const QByteArray &fileNameIn)
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);
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;
306 QString dirFile = dir + QLatin1Char(
'/') + fileName;
308 if (!QFile::exists(dirFile)) {
309 const QHash<QString, QString>::const_iterator it = m_aliases.constFind(fileName);
310 if (it != m_aliases.constEnd())
311 dirFile = dir + QLatin1Char(
'/') + it.value();
315 if (file.open(QIODevice::ReadOnly)) {
317 if (mode == Disconnect)
320 QByteArray data = file.readAll();
321 if (m_contentSubstitutedFileNames.contains(QLatin1Char(
'/') + fileName))
322 data.replace(QByteArrayLiteral(
"{{ServerBaseUrl}}"), baseUrl().toString().toUtf8());
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";
331 m_toSend.append(std::make_pair(socket, response));
332 QTimer::singleShot(500,
this, &TestHTTPServer::sendOne);
336 if (response.length() <= m_chunkSize) {
337 socket->write(response);
341 socket->write(response.left(m_chunkSize));
342 for (qsizetype offset = m_chunkSize, end = response.length(); offset < end;
343 offset += m_chunkSize) {
344 m_toSend.append(std::make_pair(socket, response.mid(offset, m_chunkSize)));
347 QTimer::singleShot(1,
this, &TestHTTPServer::sendChunk);
352 socket->write(
"HTTP/1.0 404 Not found\r\nContent-type: text/html; charset=UTF-8\r\n\r\n");
357void TestHTTPServer::sendDelayedItem()
362void TestHTTPServer::sendOne()
364 if (!m_toSend.isEmpty()) {
365 m_toSend.first().first->write(m_toSend.first().second);
366 m_toSend.first().first->close();
367 m_toSend.removeFirst();
371void TestHTTPServer::sendChunk()
373 const auto chunk = m_toSend.takeFirst();
374 chunk.first->write(chunk.second);
375 if (m_toSend.isEmpty())
376 chunk.first->close();
378 QTimer::singleShot(1,
this, &TestHTTPServer::sendChunk);
381void TestHTTPServer::serveGET(QTcpSocket *socket,
const QByteArray &data)
383 const QHash<QTcpSocket *, QByteArray>::iterator it = m_dataCache.find(socket);
384 if (it == m_dataCache.end())
387 QByteArray &total = it.value();
390 if (total.contains(
"\n\r\n")) {
392 if (total.startsWith(
"GET /")) {
393 const int space = total.indexOf(
' ', 4);
395 close = reply(socket, total.mid(5, space - 5));
397 m_dataCache.erase(it);
399 socket->disconnectFromHost();
424 return localHostUrl(m_port);
429 return baseUrl().resolved(documentPath);
434 return url(documentPath).toString();
439 TestHTTPServer server;
441 QMutexLocker locker(&m_mutex);
442 QVERIFY2(server.listen(), qPrintable(server.errorString()));
443 m_port = server.port();
444 for (QHash<QString, TestHTTPServer::Mode>::ConstIterator i = m_dirs.constBegin();
445 i != m_dirs.constEnd(); ++i) {
446 server.serveDirectory(i.key(), i.value());
448 m_condition.wakeAll();
455 QMutexLocker locker(&m_mutex);
457 m_condition.wait(&m_mutex);
462#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()
static QList< QByteArrayView > ignoredHeaders
static QUrl localHostUrl(quint16 port)