1// Copyright (C) 2024 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
3// Qt-Security score:significant reason:default
4
5#include "qhttpserverhttp1protocolhandler_p.h"
6
7#include <QtCore/qloggingcategory.h>
8#include <QtCore/qmetaobject.h>
9#include <QtCore/qthread.h>
10#include <QtCore/qpointer.h>
11#include <QtHttpServer/qabstracthttpserver.h>
12#include <QtHttpServer/qhttpserverrequest.h>
13#include <QtHttpServer/qhttpserverresponder.h>
14#include <QtHttpServer/qhttpserverresponse.h>
15#if QT_CONFIG(localserver)
16#include <QtNetwork/qlocalsocket.h>
17#endif
18#include <QtNetwork/qtcpsocket.h>
19
20#include <private/qabstracthttpserver_p.h>
21#include <private/qhttpserverliterals_p.h>
22#include <private/qhttpserverrequest_p.h>
23
24QT_BEGIN_NAMESPACE
25
26Q_STATIC_LOGGING_CATEGORY(lcHttpServerHttp1Handler, "qt.httpserver.http1handler")
27
28// https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
29static const std::map<QHttpServerResponder::StatusCode, QByteArray> statusString{
30#define XX(name, string) { QHttpServerResponder::StatusCode::name, QByteArrayLiteral(string) }
31 XX(Continue, "Continue"),
32 XX(SwitchingProtocols, "Switching Protocols"),
33 XX(Processing, "Processing"),
34 XX(Ok, "OK"),
35 XX(Created, "Created"),
36 XX(Accepted, "Accepted"),
37 XX(NonAuthoritativeInformation, "Non-Authoritative Information"),
38 XX(NoContent, "No Content"),
39 XX(ResetContent, "Reset Content"),
40 XX(PartialContent, "Partial Content"),
41 XX(MultiStatus, "Multi-Status"),
42 XX(AlreadyReported, "Already Reported"),
43 XX(IMUsed, "I'm Used"),
44 XX(MultipleChoices, "Multiple Choices"),
45 XX(MovedPermanently, "Moved Permanently"),
46 XX(Found, "Found"),
47 XX(SeeOther, "See Other"),
48 XX(NotModified, "Not Modified"),
49 XX(UseProxy, "Use Proxy"),
50 XX(TemporaryRedirect, "Temporary Redirect"),
51 XX(PermanentRedirect, "Permanent Redirect"),
52 XX(BadRequest, "Bad Request"),
53 XX(Unauthorized, "Unauthorized"),
54 XX(PaymentRequired, "Payment Required"),
55 XX(Forbidden, "Forbidden"),
56 XX(NotFound, "Not Found"),
57 XX(MethodNotAllowed, "Method Not Allowed"),
58 XX(NotAcceptable, "Not Acceptable"),
59 XX(ProxyAuthenticationRequired, "Proxy Authentication Required"),
60 XX(RequestTimeout, "Request Timeout"),
61 XX(Conflict, "Conflict"),
62 XX(Gone, "Gone"),
63 XX(LengthRequired, "Length Required"),
64 XX(PreconditionFailed, "Precondition Failed"),
65 XX(PayloadTooLarge, "Request Entity Too Large"),
66 XX(UriTooLong, "Request-URI Too Long"),
67 XX(UnsupportedMediaType, "Unsupported Media Type"),
68 XX(RequestRangeNotSatisfiable, "Requested Range Not Satisfiable"),
69 XX(ExpectationFailed, "Expectation Failed"),
70 XX(ImATeapot, "I'm a teapot"),
71 XX(MisdirectedRequest, "Misdirected Request"),
72 XX(UnprocessableEntity, "Unprocessable Entity"),
73 XX(Locked, "Locked"),
74 XX(FailedDependency, "Failed Dependency"),
75 XX(UpgradeRequired, "Upgrade Required"),
76 XX(PreconditionRequired, "Precondition Required"),
77 XX(TooManyRequests, "Too Many Requests"),
78 XX(RequestHeaderFieldsTooLarge, "Request Header Fields Too Large"),
79 XX(UnavailableForLegalReasons, "Unavailable For Legal Reasons"),
80 XX(InternalServerError, "Internal Server Error"),
81 XX(NotImplemented, "Not Implemented"),
82 XX(BadGateway, "Bad Gateway"),
83 XX(ServiceUnavailable, "Service Unavailable"),
84 XX(GatewayTimeout, "Gateway Timeout"),
85 XX(HttpVersionNotSupported, "HTTP Version Not Supported"),
86 XX(VariantAlsoNegotiates, "Variant Also Negotiates"),
87 XX(InsufficientStorage, "Insufficient Storage"),
88 XX(LoopDetected, "Loop Detected"),
89 XX(NotExtended, "Not Extended"),
90 XX(NetworkAuthenticationRequired, "Network Authentication Required"),
91 XX(NetworkConnectTimeoutError, "Network Connect Timeout Error"),
92#undef XX
93};
94
95template <qint64 BUFFERSIZE = 128 * 1024>
96struct QHttpServerHttp1IOChunkedTransfer
97{
98 // TODO This is not the fastest implementation, as it does read & write
99 // in a sequential fashion, but these operation could potentially overlap.
100 // TODO Can we implement it without the buffer? Direct write to the target buffer
101 // would be great.
102
103 static constexpr qint64 bufferSize = BUFFERSIZE;
104 static constexpr qint64 targetWriteBufferSaturation = bufferSize / 2;
105 char buffer[BUFFERSIZE];
106 qint64 beginIndex = -1;
107 qint64 endIndex = -1;
108 QPointer<QIODevice> source;
109 const QPointer<QIODevice> sink;
110 const QMetaObject::Connection bytesWrittenConnection;
111 const QMetaObject::Connection readyReadConnection;
112 const QMetaObject::Connection readChannelFinished;
113 QPointer<QHttpServerHttp1ProtocolHandler> handler;
114 bool inRead = false;
115 bool gotReadChannelFinished = false;
116 bool writingIsComplete = false;
117 bool useHttp1_1 = false;
118
119 QHttpServerHttp1IOChunkedTransfer(QIODevice *input, QIODevice *output, bool http1_1,
120 QHttpServerHttp1ProtocolHandler *callback) :
121 source(input),
122 sink(output),
123 bytesWrittenConnection(connectToBytesWritten(that: this, device: output)),
124 readyReadConnection(QObject::connect(source.data(), &QIODevice::readyRead, source.data(),
125 [this]() { readFromInput(); })),
126 readChannelFinished(QObject::connect(source.data(), &QIODevice::readChannelFinished,
127 source.data(),
128 [this]() { readChannelFinishedHandler(); })),
129 handler(callback),
130 useHttp1_1(http1_1)
131 {
132 QObject::connect(sink.data(), &QObject::destroyed, source.data(), &QObject::deleteLater);
133 QObject::connect(source.data(), &QObject::destroyed, source.data(), [this]() {
134 delete this;
135 });
136 readFromInput();
137 }
138
139 ~QHttpServerHttp1IOChunkedTransfer()
140 {
141 QObject::disconnect(bytesWrittenConnection);
142 QObject::disconnect(readyReadConnection);
143 QObject::disconnect(readChannelFinished);
144 }
145
146 static QMetaObject::Connection connectToBytesWritten(QHttpServerHttp1IOChunkedTransfer *that,
147 QIODevice *device)
148 {
149 auto send = [that]() { that->writeToOutput(); };
150#if QT_CONFIG(ssl)
151 if (auto *sslSocket = qobject_cast<QSslSocket *>(object: device)) {
152 return QObject::connect(sslSocket, &QSslSocket::encryptedBytesWritten, sslSocket,
153 std::move(send));
154 }
155#endif
156 return QObject::connect(device, &QIODevice::bytesWritten, device, std::move(send));
157 }
158
159 inline bool isBufferEmpty()
160 {
161 Q_ASSERT(beginIndex <= endIndex);
162 return beginIndex == endIndex;
163 }
164
165 void readChannelFinishedHandler()
166 {
167 gotReadChannelFinished = true;
168 readFromInput();
169 }
170
171 void readFromInput()
172 {
173 if (inRead)
174 return;
175 if (source.isNull())
176 return;
177
178 if (!isBufferEmpty()) // We haven't consumed all the data yet.
179 return;
180 QScopedValueRollback inReadGuard(inRead, true);
181
182 while (isBufferEmpty()) {
183 beginIndex = 0;
184 endIndex = source->read(buffer, bufferSize);
185 if (endIndex < 0) {
186 endIndex = 0; // Mark the buffer as empty
187 if (!source->isSequential()) {
188 qCWarning(lcHttpServerHttp1Handler, "Error reading chunk: %ls",
189 qUtf16Printable(source->errorString()));
190 return;
191 }
192 }
193 if (endIndex == 0) { // Nothing was read
194 if (!writingIsComplete
195 && ((!source->isSequential() && source->atEnd()) || gotReadChannelFinished))
196 completeWriting();
197 return;
198 }
199 memset(buffer + endIndex, 0, sizeof(buffer) - std::size_t(endIndex));
200 writeToOutput();
201 }
202 }
203
204 void writeToOutput()
205 {
206 if (sink.isNull() || source.isNull())
207 return;
208 if (isBufferEmpty())
209 return;
210
211 // If downstream has enough data to write already,
212 // don't bother writing more now. That would only lead to
213 // higher, unnecessary memory usage.
214 if (sink->bytesToWrite() >= targetWriteBufferSaturation)
215 return;
216#if QT_CONFIG(ssl)
217 if (auto *sslSocket = qobject_cast<QSslSocket *>(object: sink.data())) {
218 const qint64 budget = targetWriteBufferSaturation - sink->bytesToWrite();
219 if (sslSocket->encryptedBytesToWrite() >= budget)
220 return;
221 }
222#endif
223
224 if (useHttp1_1 && source->isSequential()) {
225 sink->write(data: QByteArray::number(endIndex - beginIndex, base: 16));
226 sink->write(data: "\r\n");
227 }
228 const auto writtenBytes = sink->write(buffer + beginIndex, endIndex - beginIndex);
229 if (writtenBytes < 0) {
230 qCWarning(lcHttpServerHttp1Handler, "Error writing chunk: %ls",
231 qUtf16Printable(sink->errorString()));
232 return;
233 }
234 if (useHttp1_1 && source->isSequential())
235 sink->write(data: "\r\n");
236 beginIndex += writtenBytes;
237 if (isBufferEmpty() && !inRead)
238 readFromInput();
239 }
240
241 void completeWriting()
242 {
243 if (sink.isNull())
244 return;
245 Q_ASSERT(!source.isNull());
246 Q_ASSERT(isBufferEmpty());
247
248 if (source->isSequential()) {
249 if (useHttp1_1)
250 sink->write(data: "0\r\n\r\n");
251 else
252 sink->close();
253 }
254 source->deleteLater();
255 writingIsComplete = true;
256 if (!handler.isNull())
257 handler->completeWriting();
258 }
259};
260
261QHttpServerHttp1ProtocolHandler::QHttpServerHttp1ProtocolHandler(QAbstractHttpServer *server,
262 QIODevice *socket,
263 QHttpServerRequestFilter *filter)
264 : QHttpServerStream(socket, server),
265 server(server),
266 socket(socket),
267 tcpSocket(qobject_cast<QTcpSocket *>(object: socket)),
268#if QT_CONFIG(localserver)
269 localSocket(qobject_cast<QLocalSocket*>(object: socket)),
270#endif
271 m_filter(filter)
272{
273 socket->setParent(this);
274
275 if (tcpSocket) {
276 qCDebug(lcHttpServerHttp1Handler) << "Connection from:" << tcpSocket->peerAddress();
277 connect(sender: socket, signal: &QTcpSocket::readyRead,
278 context: this, slot: &QHttpServerHttp1ProtocolHandler::handleReadyRead);
279 connect(sender: tcpSocket, signal: &QTcpSocket::disconnected,
280 context: this, slot: &QHttpServerHttp1ProtocolHandler::socketDisconnected);
281#if QT_CONFIG(localserver)
282 } else if (localSocket) {
283 qCDebug(lcHttpServerHttp1Handler) << "Connection from:" << localSocket->serverName();
284 connect(sender: socket, signal: &QLocalSocket::readyRead,
285 context: this, slot: &QHttpServerHttp1ProtocolHandler::handleReadyRead);
286 connect(sender: localSocket, signal: &QLocalSocket::disconnected,
287 context: this, slot: &QHttpServerHttp1ProtocolHandler::socketDisconnected);
288#endif
289 }
290
291 lastActiveTimer.start();
292}
293
294void QHttpServerHttp1ProtocolHandler::responderDestroyed()
295{
296 Q_ASSERT(QThread::currentThread() == thread());
297 if (protocolChanged) {
298 deleteLater();
299 return;
300 }
301 Q_ASSERT(handlingRequest);
302 handlingRequest = false;
303 if (state == TransferState::Ready)
304 resumeListening();
305}
306
307void QHttpServerHttp1ProtocolHandler::resumeListening()
308{
309 if (tcpSocket) {
310 if (tcpSocket->state() != QAbstractSocket::ConnectedState) {
311 deleteLater();
312 } else {
313 connect(sender: tcpSocket, signal: &QTcpSocket::readyRead,
314 context: this, slot: &QHttpServerHttp1ProtocolHandler::handleReadyRead);
315 QMetaObject::invokeMethod(object: tcpSocket, function: &QTcpSocket::readyRead, type: Qt::QueuedConnection);
316 }
317#if QT_CONFIG(localserver)
318 } else if (localSocket) {
319 if (localSocket->state() != QLocalSocket::ConnectedState) {
320 deleteLater();
321 } else {
322 connect(sender: localSocket, signal: &QLocalSocket::readyRead,
323 context: this, slot: &QHttpServerHttp1ProtocolHandler::handleReadyRead);
324 QMetaObject::invokeMethod(object: localSocket, function: &QLocalSocket::readyRead, type: Qt::QueuedConnection);
325 }
326#endif
327 }
328}
329
330void QHttpServerHttp1ProtocolHandler::startHandlingRequest()
331{
332 handlingRequest = true;
333}
334
335void QHttpServerHttp1ProtocolHandler::socketDisconnected()
336{
337 if (!handlingRequest)
338 deleteLater();
339}
340
341void QHttpServerHttp1ProtocolHandler::handleReadyRead()
342{
343 if (handlingRequest || state != TransferState::Ready)
344 return;
345
346 lastActiveTimer.restart();
347
348 if (!socket->isTransactionStarted())
349 socket->startTransaction();
350
351 auto requestReceived = parser.parse(socket);
352 if (!requestReceived) {
353 if (tcpSocket)
354 tcpSocket->disconnectFromHost();
355#if QT_CONFIG(localserver)
356 else if (localSocket)
357 localSocket->disconnectFromServer();
358#endif
359 return;
360 }
361
362#if QT_CONFIG(ssl)
363 auto request = QHttpServerRequest::create(parser, configuration: sslConfiguration);
364#else
365 auto request = QHttpServerRequest::create(parser);
366#endif
367
368 if (parser.state != QHttpServerParser::State::AllDone)
369 return; // Partial read
370
371 qCDebug(lcHttpServerHttp1Handler) << "Request:" << request;
372 useHttp1_1 = request.d->minorVersion == 1;
373
374 QHttpServerResponder responder(this);
375
376#if defined(QT_WEBSOCKETS_LIB)
377 if (auto *tcpSocket = qobject_cast<QTcpSocket*>(socket)) {
378 if (parser.upgrade) { // Upgrade
379 const auto &upgradeValue = request.value(QByteArrayLiteral("upgrade"));
380 if (upgradeValue.compare(QByteArrayLiteral("websocket"), Qt::CaseInsensitive) == 0) {
381 const auto upgradeResponse = server->verifyWebSocketUpgrade(request);
382 static const auto signal =
383 QMetaMethod::fromSignal(&QAbstractHttpServer::newWebSocketConnection);
384 if (server->isSignalConnected(signal)
385 && upgradeResponse.type()
386 != QHttpServerWebSocketUpgradeResponse::ResponseType::PassToNext) {
387 if (upgradeResponse.type()
388 == QHttpServerWebSocketUpgradeResponse::ResponseType::Accept) {
389 // Socket will now be managed by websocketServer
390 protocolChanged = true;
391 socket->disconnect();
392 socket->rollbackTransaction();
393 socket->setParent(nullptr);
394 server->d_func()->websocketServer.handleConnection(tcpSocket);
395 Q_EMIT socket->readyRead();
396 } else {
397 qCDebug(lcHttpServerHttp1Handler, "WebSocket upgrade denied: %ls",
398 qUtf16Printable(QLatin1StringView(upgradeResponse.denyMessage())));
399 QByteArray buffer;
400 buffer.append("HTTP/1.1 ");
401 buffer.append(QByteArray::number(quint32(upgradeResponse.denyStatus())));
402 buffer.append(" ");
403 buffer.append(upgradeResponse.denyMessage());
404 buffer.append("\r\n\r\n");
405 tcpSocket->write(buffer);
406 }
407 } else {
408 if (!server->isSignalConnected(signal)) {
409 qCWarning(lcHttpServerHttp1Handler,
410 "WebSocket received but no slots connected to "
411 "QWebSocketServer::newConnection");
412 }
413 server->missingHandler(request, responder);
414 tcpSocket->disconnectFromHost();
415 }
416 return;
417 }
418 }
419 }
420#endif // QT_WEBSOCKETS_LIB
421
422 socket->commitTransaction();
423
424 QHostAddress peerAddress = tcpSocket ? tcpSocket->peerAddress()
425 : QHostAddress::LocalHost;
426 if (!m_filter->isRequestAllowed(peerAddress)) {
427 responder.sendResponse(
428 response: QHttpServerResponse(QHttpServerResponder::StatusCode::Forbidden));
429 }
430 if (!m_filter->isRequestWithinRate(peerAddress)) {
431 responder.sendResponse(
432 response: QHttpServerResponse(QHttpServerResponder::StatusCode::TooManyRequests));
433 } else if (!server->handleRequest(request, responder)) {
434 server->missingHandler(request, responder);
435 }
436
437 if (handlingRequest || state != TransferState::Ready)
438 disconnect(sender: socket, signal: &QIODevice::readyRead, receiver: this, slot: &QHttpServerHttp1ProtocolHandler::handleReadyRead);
439 else if (socket->bytesAvailable() > 0)
440 QMetaObject::invokeMethod(object: socket, function: &QIODevice::readyRead, type: Qt::QueuedConnection);
441}
442
443void QHttpServerHttp1ProtocolHandler::write(const QByteArray &body, const QHttpHeaders &headers,
444 QHttpServerResponder::StatusCode status, quint32 streamId)
445{
446 Q_UNUSED(streamId);
447 Q_ASSERT(state == TransferState::Ready);
448 writeStatusAndHeaders(status, headers);
449 write(data: body);
450 state = TransferState::Ready;
451}
452
453void QHttpServerHttp1ProtocolHandler::write(QHttpServerResponder::StatusCode status, quint32 streamId)
454{
455 Q_UNUSED(streamId);
456 Q_ASSERT(state == TransferState::Ready);
457 QHttpHeaders headers;
458 headers.append(name: QHttpHeaders::WellKnownHeader::ContentType,
459 value: QHttpServerLiterals::contentTypeXEmpty());
460 headers.append(name: QHttpHeaders::WellKnownHeader::ContentLength, value: "0");
461 writeStatusAndHeaders(status, headers);
462 state = TransferState::Ready;
463}
464
465void QHttpServerHttp1ProtocolHandler::write(QIODevice *data, const QHttpHeaders &headers,
466 QHttpServerResponder::StatusCode status, quint32 streamId)
467{
468 Q_UNUSED(streamId);
469 Q_ASSERT(state == TransferState::Ready);
470 std::unique_ptr<QIODevice, QScopedPointerDeleteLater> input(data);
471
472 input->setParent(nullptr);
473 if (!input->isOpen()) {
474 if (!input->open(mode: QIODevice::ReadOnly)) {
475 // TODO Add developer error handling
476 qCDebug(lcHttpServerHttp1Handler, "500: Could not open device %ls",
477 qUtf16Printable(input->errorString()));
478 write(status: QHttpServerResponder::StatusCode::InternalServerError, streamId: 0);
479 return;
480 }
481 } else if (!(input->openMode() & QIODevice::ReadOnly)) {
482 // TODO Add developer error handling
483 qCDebug(lcHttpServerHttp1Handler) << "500: Device is opened in a wrong mode"
484 << input->openMode();
485 write(status: QHttpServerResponder::StatusCode::InternalServerError, streamId: 0);
486 return;
487 }
488
489 QHttpHeaders allHeaders(headers);
490 if (input->isSequential()) {
491 if (useHttp1_1)
492 allHeaders.append(name: QHttpHeaders::WellKnownHeader::TransferEncoding, value: "chunked");
493 else
494 allHeaders.append(name: QHttpHeaders::WellKnownHeader::Connection, value: "close");
495 } else { // Non-sequential QIODevice should know its data size
496 allHeaders.append(name: QHttpHeaders::WellKnownHeader::ContentLength,
497 value: QByteArray::number(input->size()));
498 }
499 writeStatusAndHeaders(status, headers: allHeaders);
500
501 state = TransferState::IODeviceTransferBegun;
502 // input takes ownership of the QHttpServerHttp1IOChunkedTransfer pointer inside his constructor
503 new QHttpServerHttp1IOChunkedTransfer<>(input.release(), socket, useHttp1_1, this);
504}
505
506void QHttpServerHttp1ProtocolHandler::writeBeginChunked(const QHttpHeaders &headers,
507 QHttpServerResponder::StatusCode status,
508 quint32 streamId)
509{
510 Q_UNUSED(streamId);
511 Q_ASSERT(state == TransferState::Ready);
512 QHttpHeaders allHeaders(headers);
513 allHeaders.append(name: QHttpHeaders::WellKnownHeader::TransferEncoding, value: "chunked");
514 writeStatusAndHeaders(status, headers: allHeaders);
515 state = TransferState::ChunkedTransferBegun;
516}
517
518void QHttpServerHttp1ProtocolHandler::writeChunk(const QByteArray &data, quint32 streamId)
519{
520 Q_UNUSED(streamId);
521 Q_ASSERT(state == TransferState::ChunkedTransferBegun);
522
523 if (data.length() == 0) {
524 qCWarning(lcHttpServerHttp1Handler, "Chunk must have length > 0");
525 return;
526 }
527
528 write(data: QByteArray::number(data.length(), base: 16));
529 write(data: "\r\n");
530 write(data);
531 write(data: "\r\n");
532}
533
534void QHttpServerHttp1ProtocolHandler::writeEndChunked(const QByteArray &data,
535 const QHttpHeaders &trailers,
536 quint32 streamId)
537{
538 Q_UNUSED(streamId);
539 Q_ASSERT(state == TransferState::ChunkedTransferBegun);
540 writeChunk(data, streamId: 0);
541 write(data: "0\r\n");
542 for (qsizetype i = 0; i < trailers.size(); ++i) {
543 const auto name = trailers.nameAt(i);
544 const auto value = trailers.valueAt(i);
545 writeHeader(key: { name.data(), name.size() }, value: value.toByteArray());
546 }
547 write(data: "\r\n");
548 state = TransferState::Ready;
549}
550
551void QHttpServerHttp1ProtocolHandler::writeStatusAndHeaders(QHttpServerResponder::StatusCode status,
552 const QHttpHeaders &headers)
553{
554 Q_ASSERT(state == TransferState::Ready);
555 QByteArray payload;
556 payload.append(s: "HTTP/1.1 ");
557 payload.append(a: QByteArray::number(quint32(status)));
558 const auto it = statusString.find(x: status);
559 if (it != statusString.end()) {
560 payload.append(s: " ");
561 payload.append(a: statusString.at(k: status));
562 }
563 payload.append(s: "\r\n");
564
565 for (qsizetype i = 0; i < headers.size(); ++i) {
566 const auto name = headers.nameAt(i);
567 payload.append(a: QByteArrayView(name.data(), name.size()) + ": "
568 + headers.valueAt(i).toByteArray() + "\r\n");
569 }
570 payload.append(s: "\r\n");
571 write(data: payload);
572 state = TransferState::HeadersSent;
573}
574
575void QHttpServerHttp1ProtocolHandler::writeHeader(const QByteArray &key, const QByteArray &value)
576{
577 write(data: key + ": " + value + "\r\n");
578}
579
580void QHttpServerHttp1ProtocolHandler::write(const QByteArray &ba)
581{
582 Q_ASSERT(QThread::currentThread() == thread());
583 socket->write(data: ba);
584}
585
586void QHttpServerHttp1ProtocolHandler::write(const char *body, qint64 size)
587{
588 Q_ASSERT(QThread::currentThread() == thread());
589 socket->write(data: body, len: size);
590}
591
592void QHttpServerHttp1ProtocolHandler::completeWriting()
593{
594 Q_ASSERT(state == TransferState::IODeviceTransferBegun);
595 state = TransferState::Ready;
596 if (!handlingRequest)
597 resumeListening();
598}
599
600void QHttpServerHttp1ProtocolHandler::checkKeepAliveTimeout()
601{
602 if (handlingRequest || state != TransferState::Ready)
603 return;
604
605 if (lastActiveTimer.durationElapsed() > server->configuration().keepAliveTimeout()) {
606 if (tcpSocket)
607 tcpSocket->abort();
608#if QT_CONFIG(localserver)
609 else if (localSocket)
610 localSocket->abort();
611#endif
612 }
613}
614
615QT_END_NAMESPACE
616

source code of qthttpserver/src/httpserver/qhttpserverhttp1protocolhandler.cpp