1// Copyright (C) 2024 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
3
4#include "qhttpserverhttp2protocolhandler_p.h"
5
6#include <QtCore/qloggingcategory.h>
7#include <QtHttpServer/qabstracthttpserver.h>
8#include <QtNetwork/private/qhttp2connection_p.h>
9#include <QtNetwork/qtcpsocket.h>
10
11#include <private/qhttpserverrequest_p.h>
12#include <private/qhttpserverliterals_p.h>
13#include <private/qhttpserverresponder_p.h>
14
15QT_BEGIN_NAMESPACE
16
17Q_LOGGING_CATEGORY(lcHttpServerHttp2Handler, "qt.httpserver.http2handler")
18
19namespace {
20
21void toHeaderPairs(HPack::HttpHeader &fields, const QHttpHeaders &headers)
22{
23 for (qsizetype i = 0; i < headers.size(); ++i) {
24 const auto name = headers.nameAt(i);
25 fields.push_back(x: HPack::HeaderField(QByteArray(name.data(), name.size()),
26 headers.valueAt(i).toByteArray()));
27 }
28}
29
30} // anonymous namespace
31
32QHttpServerHttp2ProtocolHandler::QHttpServerHttp2ProtocolHandler(QAbstractHttpServer *server,
33 QIODevice *socket)
34 : QHttpServerStream(server),
35 m_server(server),
36 m_socket(socket),
37 m_tcpSocket(qobject_cast<QTcpSocket *>(object: socket)),
38 m_request(QHttpServerStream::initRequestFromSocket(socket: m_tcpSocket))
39{
40 socket->setParent(this);
41
42 m_connection = QHttp2Connection::createDirectServerConnection(socket,
43 config: server->http2Configuration());
44 if (!m_connection)
45 return;
46
47 Q_ASSERT(m_tcpSocket);
48
49 connect(sender: m_tcpSocket,
50 signal: &QTcpSocket::readyRead,
51 context: m_connection,
52 slot: &QHttp2Connection::handleReadyRead);
53
54 connect(sender: m_tcpSocket,
55 signal: &QTcpSocket::disconnected,
56 context: m_connection,
57 slot: &QHttp2Connection::handleConnectionClosure);
58
59 connect(sender: m_tcpSocket,
60 signal: &QTcpSocket::disconnected,
61 context: this,
62 slot: &QHttpServerHttp2ProtocolHandler::socketDisconnected);
63
64 connect(sender: m_connection,
65 signal: &QHttp2Connection::newIncomingStream,
66 context: this,
67 slot: &QHttpServerHttp2ProtocolHandler::onStreamCreated);
68}
69
70void QHttpServerHttp2ProtocolHandler::responderDestroyed()
71{
72 m_responderCounter--;
73}
74
75void QHttpServerHttp2ProtocolHandler::startHandlingRequest()
76{
77 m_responderCounter++;
78}
79
80void QHttpServerHttp2ProtocolHandler::socketDisconnected()
81{
82 if (m_responderCounter == 0)
83 deleteLater();
84}
85
86void QHttpServerHttp2ProtocolHandler::write(const QByteArray &body, const QHttpHeaders &headers,
87 QHttpServerResponder::StatusCode status,
88 quint32 streamId)
89{
90 QHttp2Stream *stream = getStream(streamId);
91 if (!stream)
92 return;
93
94 writeHeadersAndStatus(headers, status, endStream: false, streamId);
95
96 QBuffer *buffer = new QBuffer(stream);
97 buffer->setData(body);
98 buffer->open(openMode: QIODevice::ReadOnly);
99
100 connect(sender: stream, signal: &QHttp2Stream::uploadFinished, context: buffer, slot: &QObject::deleteLater);
101 stream->sendDATA(device: buffer, endStream: true);
102}
103
104void QHttpServerHttp2ProtocolHandler::write(QHttpServerResponder::StatusCode status,
105 quint32 streamId)
106{
107 QHttpHeaders headers;
108 headers.append(name: QHttpHeaders::WellKnownHeader::ContentType,
109 value: QHttpServerLiterals::contentTypeXEmpty());
110 headers.append(name: QHttpHeaders::WellKnownHeader::ContentLength, value: "0");
111
112 // RFC 9113, 8.1
113 // A HEADERS frame with the END_STREAM flag set that carries
114 // an informational status code is malformed
115 bool isInfoStatus = QHttpServerResponder::StatusCode::Continue <= status
116 && status < QHttpServerResponder::StatusCode::Ok;
117 writeHeadersAndStatus(headers, status, endStream: !isInfoStatus, streamId);
118}
119
120void QHttpServerHttp2ProtocolHandler::write(QIODevice *data, const QHttpHeaders &headers,
121 QHttpServerResponder::StatusCode status, quint32 streamId)
122{
123 QHttp2Stream *stream = getStream(streamId);
124 if (!stream)
125 return;
126
127 std::unique_ptr<QIODevice, QScopedPointerDeleteLater> input(data);
128
129 if (!input->isOpen()) {
130 if (!input->open(mode: QIODevice::ReadOnly)) {
131 // TODO Add developer error handling
132 qCDebug(lcHttpServerHttp2Handler, "500: Could not open device %ls",
133 qUtf16Printable(input->errorString()));
134 write(status: QHttpServerResponder::StatusCode::InternalServerError, streamId);
135 return;
136 }
137 } else if (!(input->openMode() & QIODevice::ReadOnly)) {
138 // TODO Add developer error handling
139 qCDebug(lcHttpServerHttp2Handler) << "500: Device is opened in a wrong mode"
140 << input->openMode();
141 write(status: QHttpServerResponder::StatusCode::InternalServerError, streamId);
142 return;
143 }
144
145 QHttpHeaders allHeaders(headers);
146 if (!data->isSequential()) { // Non-sequential QIODevice should know its data size
147 allHeaders.append(name: QHttpHeaders::WellKnownHeader::ContentLength,
148 value: QByteArray::number(data->size()));
149 }
150
151 writeHeadersAndStatus(headers, status, endStream: false, streamId);
152
153 if (input->atEnd()) {
154 qCDebug(lcHttpServerHttp2Handler, "No more data available.");
155 return;
156 }
157
158 input->setParent(stream);
159 connect(sender: stream, signal: &QHttp2Stream::uploadFinished, context: input.get(), slot: &QObject::deleteLater);
160 stream->sendDATA(device: input.release(), endStream: true);
161}
162
163void QHttpServerHttp2ProtocolHandler::writeBeginChunked(const QHttpHeaders &headers,
164 QHttpServerResponder::StatusCode status,
165 quint32 streamId)
166{
167 writeHeadersAndStatus(headers, status, endStream: false, streamId);
168}
169
170void QHttpServerHttp2ProtocolHandler::writeChunk(const QByteArray &body, quint32 streamId)
171{
172 enqueueChunk(body, allEnqueued: false, trailers: {}, streamId);
173}
174
175void QHttpServerHttp2ProtocolHandler::writeEndChunked(const QByteArray &body,
176 const QHttpHeaders &trailers,
177 quint32 streamId)
178{
179 enqueueChunk(body, allEnqueued: true, trailers, streamId);
180}
181
182void QHttpServerHttp2ProtocolHandler::enqueueChunk(const QByteArray &body, bool allEnqueued,
183 const QHttpHeaders &trailers, quint32 streamId)
184{
185 QHttp2Stream *stream = getStream(streamId);
186 if (!stream)
187 return;
188
189 auto &queue = m_streamQueue[streamId];
190
191 if (!trailers.isEmpty()) {
192 Q_ASSERT(queue.trailers.empty());
193 toHeaderPairs(fields&: queue.trailers, headers: trailers);
194 }
195
196 queue.data.enqueue(t: body);
197 if (allEnqueued)
198 queue.allEnqueued = true;
199
200 if (!stream->isUploadingDATA())
201 sendToStream(streamId);
202}
203
204void QHttpServerHttp2ProtocolHandler::writeHeadersAndStatus(const QHttpHeaders &headers,
205 QHttpServerResponder::StatusCode status,
206 bool endStream, quint32 streamId)
207{
208 QHttp2Stream *stream = getStream(streamId);
209 if (!stream)
210 return;
211
212 HPack::HttpHeader h;
213 h.push_back(x: HPack::HeaderField(":status", QByteArray::number(quint32(status))));
214 toHeaderPairs(fields&: h, headers);
215 stream->sendHEADERS(headers: h, endStream);
216}
217
218QHttp2Stream *QHttpServerHttp2ProtocolHandler::getStream(quint32 streamId) const
219{
220 QHttp2Stream *stream = m_connection->getStream(streamId);
221 Q_ASSERT(stream);
222
223 if (stream && stream->isActive())
224 return stream;
225
226 return nullptr;
227}
228
229void QHttpServerHttp2ProtocolHandler::onStreamCreated(QHttp2Stream *stream)
230{
231 const quint32 id = stream->streamID();
232 m_streamQueue.insert(key: id, value: QHttpServerHttp2Queue());
233
234 auto onStateChanged = [this, id](QHttp2Stream::State newState) {
235 switch (newState) {
236 case QHttp2Stream::State::HalfClosedRemote:
237 onStreamHalfClosed(streamId: id);
238 break;
239 case QHttp2Stream::State::Closed:
240 onStreamClosed(streamId: id);
241 break;
242 default:
243 break;
244 }
245 };
246
247 auto &connections = m_streamConnections[id];
248 connections << connect(sender: stream,
249 signal: &QHttp2Stream::stateChanged,
250 context: this,
251 slot&: onStateChanged,
252 type: Qt::QueuedConnection);
253
254 connections << connect(sender: stream, signal: &QHttp2Stream::uploadFinished, context: this,
255 slot: [this, id]() { sendToStream(streamId: id); });
256}
257
258void QHttpServerHttp2ProtocolHandler::onStreamHalfClosed(quint32 streamId)
259{
260 auto stream = m_connection->getStream(streamId);
261 Q_ASSERT(stream);
262 if (!stream)
263 return;
264
265 m_request.d->parse(socket: stream);
266
267 qCDebug(lcHttpServerHttp2Handler) << "Request:" << m_request;
268
269 QHttpServerResponder responder(this);
270 responder.d_ptr->m_streamId = streamId;
271
272 if (!m_server->handleRequest(request: m_request, responder))
273 m_server->missingHandler(request: m_request, responder);
274}
275
276void QHttpServerHttp2ProtocolHandler::onStreamClosed(quint32 streamId)
277{
278 auto connections = m_streamConnections.take(key: streamId);
279 for (auto &c : connections)
280 disconnect(c);
281
282 m_streamQueue.remove(key: streamId);
283}
284
285void QHttpServerHttp2ProtocolHandler::sendToStream(quint32 streamId)
286{
287 QHttp2Stream *stream = getStream(streamId);
288 if (!stream)
289 return;
290
291 if (stream->isUploadingDATA())
292 return;
293
294 auto &queue = m_streamQueue[streamId];
295 if (!queue.data.isEmpty()) {
296 QBuffer *buffer = new QBuffer(stream);
297 buffer->setData(queue.data.dequeue());
298 buffer->open(openMode: QIODevice::ReadOnly);
299 connect(sender: stream, signal: &QHttp2Stream::uploadFinished, context: buffer, slot: &QObject::deleteLater);
300 bool endStream = queue.allEnqueued && queue.data.isEmpty() && queue.trailers.empty();
301 stream->sendDATA(device: buffer, endStream);
302 } else if (!queue.trailers.empty()) {
303 stream->sendHEADERS(headers: queue.trailers, endStream: true);
304 queue.trailers.clear();
305 }
306}
307
308QT_END_NAMESPACE
309

Provided by KDAB

Privacy Policy
Learn to use CMake with our Intro Training
Find out more

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