1// Copyright (C) 2022 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
3
4#include <QtHttpServer/qhttpserverresponder.h>
5#include <QtHttpServer/qhttpserverrequest.h>
6#include <QtHttpServer/qhttpserverresponse.h>
7#include <private/qhttpserverresponder_p.h>
8#include <private/qhttpserverliterals_p.h>
9#include <private/qhttpserverrequest_p.h>
10#include <private/qhttpserverresponse_p.h>
11#include <private/qhttpserverstream_p.h>
12#include <QtCore/qjsondocument.h>
13#include <QtCore/qloggingcategory.h>
14#include <QtCore/qtimer.h>
15#include <QtNetwork/qtcpsocket.h>
16#include <map>
17#include <memory>
18
19QT_BEGIN_NAMESPACE
20
21QT_IMPL_METATYPE_EXTERN_TAGGED(QHttpServerResponder::StatusCode, QHttpServerResponder__StatusCode)
22
23/*!
24 \class QHttpServerResponder
25 \since 6.4
26 \inmodule QtHttpServer
27 \brief API for sending replies from an HTTP server.
28
29 Provides functions for writing back to an HTTP client with overloads for
30 serializing JSON objects. It also has support for writing HTTP headers and
31 status code.
32*/
33
34/*!
35 \typealias QHttpServerResponder::HeaderList
36
37 Type alias for std::initializer_list<std::pair<QByteArray, QByteArray>>
38*/
39
40/*!
41 \enum QHttpServerResponder::StatusCode
42
43 HTTP status codes
44
45 \value Continue
46 \value SwitchingProtocols
47 \value Processing
48
49 \value Ok
50 \value Created
51 \value Accepted
52 \value NonAuthoritativeInformation
53 \value NoContent
54 \value ResetContent
55 \value PartialContent
56 \value MultiStatus
57 \value AlreadyReported
58 \value IMUsed
59
60 \value MultipleChoices
61 \value MovedPermanently
62 \value Found
63 \value SeeOther
64 \value NotModified
65 \value UseProxy
66
67 \value TemporaryRedirect
68 \value PermanentRedirect
69
70 \value BadRequest
71 \value Unauthorized
72 \value PaymentRequired
73 \value Forbidden
74 \value NotFound
75 \value MethodNotAllowed
76 \value NotAcceptable
77 \value ProxyAuthenticationRequired
78 \value RequestTimeout
79 \value Conflict
80 \value Gone
81 \value LengthRequired
82 \value PreconditionFailed
83 \value PayloadTooLarge
84 \value UriTooLong
85 \value UnsupportedMediaType
86 \value RequestRangeNotSatisfiable
87 \value ExpectationFailed
88 \value ImATeapot
89 \value MisdirectedRequest
90 \value UnprocessableEntity
91 \value Locked
92 \value FailedDependency
93 \value UpgradeRequired
94 \value PreconditionRequired
95 \value TooManyRequests
96 \value RequestHeaderFieldsTooLarge
97 \value UnavailableForLegalReasons
98
99 \value InternalServerError
100 \value NotImplemented
101 \value BadGateway
102 \value ServiceUnavailable
103 \value GatewayTimeout
104 \value HttpVersionNotSupported
105 \value VariantAlsoNegotiates
106 \value InsufficientStorage
107 \value LoopDetected
108 \value NotExtended
109 \value NetworkAuthenticationRequired
110 \value NetworkConnectTimeoutError
111*/
112
113/*!
114 \internal
115*/
116static const QLoggingCategory &rspLc()
117{
118 static const QLoggingCategory category("qt.httpserver.response");
119 return category;
120}
121
122// https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
123static const std::map<QHttpServerResponder::StatusCode, QByteArray> statusString{
124#define XX(name, string) { QHttpServerResponder::StatusCode::name, QByteArrayLiteral(string) }
125 XX(Continue, "Continue"),
126 XX(SwitchingProtocols, "Switching Protocols"),
127 XX(Processing, "Processing"),
128 XX(Ok, "OK"),
129 XX(Created, "Created"),
130 XX(Accepted, "Accepted"),
131 XX(NonAuthoritativeInformation, "Non-Authoritative Information"),
132 XX(NoContent, "No Content"),
133 XX(ResetContent, "Reset Content"),
134 XX(PartialContent, "Partial Content"),
135 XX(MultiStatus, "Multi-Status"),
136 XX(AlreadyReported, "Already Reported"),
137 XX(IMUsed, "I'm Used"),
138 XX(MultipleChoices, "Multiple Choices"),
139 XX(MovedPermanently, "Moved Permanently"),
140 XX(Found, "Found"),
141 XX(SeeOther, "See Other"),
142 XX(NotModified, "Not Modified"),
143 XX(UseProxy, "Use Proxy"),
144 XX(TemporaryRedirect, "Temporary Redirect"),
145 XX(PermanentRedirect, "Permanent Redirect"),
146 XX(BadRequest, "Bad Request"),
147 XX(Unauthorized, "Unauthorized"),
148 XX(PaymentRequired, "Payment Required"),
149 XX(Forbidden, "Forbidden"),
150 XX(NotFound, "Not Found"),
151 XX(MethodNotAllowed, "Method Not Allowed"),
152 XX(NotAcceptable, "Not Acceptable"),
153 XX(ProxyAuthenticationRequired, "Proxy Authentication Required"),
154 XX(RequestTimeout, "Request Timeout"),
155 XX(Conflict, "Conflict"),
156 XX(Gone, "Gone"),
157 XX(LengthRequired, "Length Required"),
158 XX(PreconditionFailed, "Precondition Failed"),
159 XX(PayloadTooLarge, "Request Entity Too Large"),
160 XX(UriTooLong, "Request-URI Too Long"),
161 XX(UnsupportedMediaType, "Unsupported Media Type"),
162 XX(RequestRangeNotSatisfiable, "Requested Range Not Satisfiable"),
163 XX(ExpectationFailed, "Expectation Failed"),
164 XX(ImATeapot, "I'm a teapot"),
165 XX(MisdirectedRequest, "Misdirected Request"),
166 XX(UnprocessableEntity, "Unprocessable Entity"),
167 XX(Locked, "Locked"),
168 XX(FailedDependency, "Failed Dependency"),
169 XX(UpgradeRequired, "Upgrade Required"),
170 XX(PreconditionRequired, "Precondition Required"),
171 XX(TooManyRequests, "Too Many Requests"),
172 XX(RequestHeaderFieldsTooLarge, "Request Header Fields Too Large"),
173 XX(UnavailableForLegalReasons, "Unavailable For Legal Reasons"),
174 XX(InternalServerError, "Internal Server Error"),
175 XX(NotImplemented, "Not Implemented"),
176 XX(BadGateway, "Bad Gateway"),
177 XX(ServiceUnavailable, "Service Unavailable"),
178 XX(GatewayTimeout, "Gateway Timeout"),
179 XX(HttpVersionNotSupported, "HTTP Version Not Supported"),
180 XX(VariantAlsoNegotiates, "Variant Also Negotiates"),
181 XX(InsufficientStorage, "Insufficient Storage"),
182 XX(LoopDetected, "Loop Detected"),
183 XX(NotExtended, "Not Extended"),
184 XX(NetworkAuthenticationRequired, "Network Authentication Required"),
185 XX(NetworkConnectTimeoutError, "Network Connect Timeout Error"),
186#undef XX
187};
188
189/*!
190 \internal
191*/
192template <qint64 BUFFERSIZE = 512>
193struct IOChunkedTransfer
194{
195 // TODO This is not the fastest implementation, as it does read & write
196 // in a sequential fashion, but these operation could potentially overlap.
197 // TODO Can we implement it without the buffer? Direct write to the target buffer
198 // would be great.
199
200 const qint64 bufferSize = BUFFERSIZE;
201 char buffer[BUFFERSIZE];
202 qint64 beginIndex = -1;
203 qint64 endIndex = -1;
204 QPointer<QIODevice> source;
205 const QPointer<QIODevice> sink;
206 const QMetaObject::Connection bytesWrittenConnection;
207 const QMetaObject::Connection readyReadConnection;
208 IOChunkedTransfer(QIODevice *input, QIODevice *output) :
209 source(input),
210 sink(output),
211 bytesWrittenConnection(QObject::connect(sink.data(), &QIODevice::bytesWritten, [this] () {
212 writeToOutput();
213 })),
214 readyReadConnection(QObject::connect(source.data(), &QIODevice::readyRead, [this] () {
215 readFromInput();
216 }))
217 {
218 Q_ASSERT(!source->atEnd()); // TODO error out
219 QObject::connect(sink.data(), &QObject::destroyed, source.data(), &QObject::deleteLater);
220 QObject::connect(source.data(), &QObject::destroyed, [this] () {
221 delete this;
222 });
223 readFromInput();
224 }
225
226 ~IOChunkedTransfer()
227 {
228 QObject::disconnect(bytesWrittenConnection);
229 QObject::disconnect(readyReadConnection);
230 }
231
232 inline bool isBufferEmpty()
233 {
234 Q_ASSERT(beginIndex <= endIndex);
235 return beginIndex == endIndex;
236 }
237
238 void readFromInput()
239 {
240 if (!isBufferEmpty()) // We haven't consumed all the data yet.
241 return;
242 beginIndex = 0;
243 endIndex = source->read(buffer, bufferSize);
244 if (endIndex < 0) {
245 endIndex = beginIndex; // Mark the buffer as empty
246 qCWarning(rspLc, "Error reading chunk: %ls", qUtf16Printable(source->errorString()));
247 } else if (endIndex) {
248 memset(buffer + endIndex, 0, sizeof(buffer) - std::size_t(endIndex));
249 writeToOutput();
250 }
251 }
252
253 void writeToOutput()
254 {
255 if (isBufferEmpty())
256 return;
257
258 const auto writtenBytes = sink->write(buffer + beginIndex, endIndex);
259 if (writtenBytes < 0) {
260 qCWarning(rspLc, "Error writing chunk: %ls", qUtf16Printable(sink->errorString()));
261 return;
262 }
263 beginIndex += writtenBytes;
264 if (isBufferEmpty()) {
265 if (source->bytesAvailable())
266 QTimer::singleShot(0, source.data(), [this]() { readFromInput(); });
267 else if (source->atEnd()) // Finishing
268 source->deleteLater();
269 }
270 }
271};
272
273/*!
274 \internal
275*/
276QHttpServerResponder::QHttpServerResponder(QHttpServerStream *stream)
277 : d_ptr(new QHttpServerResponderPrivate(stream))
278{
279 Q_ASSERT(stream);
280 Q_ASSERT(!stream->handlingRequest);
281 stream->handlingRequest = true;
282}
283
284/*!
285 Move-constructs a QHttpServerResponder instance, making it point
286 at the same object that \a other was pointing to.
287*/
288QHttpServerResponder::QHttpServerResponder(QHttpServerResponder &&other)
289 : d_ptr(std::move(other.d_ptr))
290{}
291
292/*!
293 Destroys a QHttpServerResponder.
294*/
295QHttpServerResponder::~QHttpServerResponder()
296{
297 Q_D(QHttpServerResponder);
298 if (d) {
299 Q_ASSERT(d->stream);
300 d->stream->responderDestroyed();
301 }
302}
303
304/*!
305 Answers a request with an HTTP status code \a status and
306 HTTP headers \a headers. The I/O device \a data provides the body
307 of the response. If \a data is sequential, the body of the
308 message is sent in chunks: otherwise, the function assumes all
309 the content is available and sends it all at once but the read
310 is done in chunks.
311
312 \note This function takes the ownership of \a data.
313*/
314void QHttpServerResponder::write(QIODevice *data,
315 HeaderList headers,
316 StatusCode status)
317{
318 Q_D(QHttpServerResponder);
319 Q_ASSERT(d->stream);
320 std::unique_ptr<QIODevice, QScopedPointerDeleteLater> input(data);
321
322 input->setParent(nullptr);
323 if (!input->isOpen()) {
324 if (!input->open(mode: QIODevice::ReadOnly)) {
325 // TODO Add developer error handling
326 qCDebug(rspLc, "500: Could not open device %ls", qUtf16Printable(input->errorString()));
327 write(status: StatusCode::InternalServerError);
328 return;
329 }
330 } else if (!(input->openMode() & QIODevice::ReadOnly)) {
331 // TODO Add developer error handling
332 qCDebug(rspLc) << "500: Device is opened in a wrong mode" << input->openMode();
333 write(status: StatusCode::InternalServerError);
334 return;
335 }
336
337 writeStatusLine(status);
338
339 if (!input->isSequential()) { // Non-sequential QIODevice should know its data size
340 writeHeader(key: QHttpServerLiterals::contentLengthHeader(),
341 value: QByteArray::number(input->size()));
342 }
343
344 for (auto &&header : headers)
345 writeHeader(key: header.first, value: header.second);
346
347 d->stream->write(data: "\r\n");
348
349 if (input->atEnd()) {
350 qCDebug(rspLc, "No more data available.");
351 return;
352 }
353
354 // input takes ownership of the IOChunkedTransfer pointer inside his constructor
355 new IOChunkedTransfer<>(input.release(), d->stream->socket);
356}
357
358/*!
359 Answers a request with an HTTP status code \a status and a
360 MIME type \a mimeType. The I/O device \a data provides the body
361 of the response. If \a data is sequential, the body of the
362 message is sent in chunks: otherwise, the function assumes all
363 the content is available and sends it all at once but the read
364 is done in chunks.
365
366 \note This function takes the ownership of \a data.
367*/
368void QHttpServerResponder::write(QIODevice *data,
369 const QByteArray &mimeType,
370 StatusCode status)
371{
372 write(data,
373 headers: {{ QHttpServerLiterals::contentTypeHeader(), mimeType }},
374 status);
375}
376
377/*!
378 Answers a request with an HTTP status code \a status, JSON
379 document \a document and HTTP headers \a headers.
380
381 Note: This function sets HTTP Content-Type header as "application/json".
382*/
383void QHttpServerResponder::write(const QJsonDocument &document,
384 HeaderList headers,
385 StatusCode status)
386{
387 const QByteArray &json = document.toJson();
388
389 writeStatusLine(status);
390 writeHeader(key: QHttpServerLiterals::contentTypeHeader(),
391 value: QHttpServerLiterals::contentTypeJson());
392 writeHeader(key: QHttpServerLiterals::contentLengthHeader(),
393 value: QByteArray::number(json.size()));
394 writeHeaders(headers: std::move(headers));
395 writeBody(body: document.toJson());
396}
397
398/*!
399 Answers a request with an HTTP status code \a status, and JSON
400 document \a document.
401
402 Note: This function sets HTTP Content-Type header as "application/json".
403*/
404void QHttpServerResponder::write(const QJsonDocument &document,
405 StatusCode status)
406{
407 write(document, headers: {}, status);
408}
409
410/*!
411 Answers a request with an HTTP status code \a status,
412 HTTP Headers \a headers and a body \a data.
413
414 Note: This function sets HTTP Content-Length header.
415*/
416void QHttpServerResponder::write(const QByteArray &data,
417 HeaderList headers,
418 StatusCode status)
419{
420 writeStatusLine(status);
421
422 for (auto &&header : headers)
423 writeHeader(key: header.first, value: header.second);
424
425 writeHeader(key: QHttpServerLiterals::contentLengthHeader(),
426 value: QByteArray::number(data.size()));
427 writeBody(body: data);
428}
429
430/*!
431 Answers a request with an HTTP status code \a status, a
432 MIME type \a mimeType and a body \a data.
433*/
434void QHttpServerResponder::write(const QByteArray &data,
435 const QByteArray &mimeType,
436 StatusCode status)
437{
438 write(data,
439 headers: {{ QHttpServerLiterals::contentTypeHeader(), mimeType }},
440 status);
441}
442
443/*!
444 Answers a request with an HTTP status code \a status.
445
446 Note: This function sets HTTP Content-Type header as "application/x-empty".
447*/
448void QHttpServerResponder::write(StatusCode status)
449{
450 write(data: QByteArray(), mimeType: QHttpServerLiterals::contentTypeXEmpty(), status);
451}
452
453/*!
454 Answers a request with an HTTP status code \a status and
455 HTTP Headers \a headers.
456*/
457void QHttpServerResponder::write(HeaderList headers, StatusCode status)
458{
459 write(data: QByteArray(), headers: std::move(headers), status);
460}
461
462/*!
463 This function writes HTTP status line with an HTTP status code \a status.
464*/
465void QHttpServerResponder::writeStatusLine(StatusCode status)
466{
467 Q_D(QHttpServerResponder);
468 Q_ASSERT(d->stream);
469 d->bodyStarted = false;
470 d->stream->write(data: "HTTP/1.1 ");
471 d->stream->write(data: QByteArray::number(quint32(status)));
472 const auto it = statusString.find(x: status);
473 if (it != statusString.end()) {
474 d->stream->write(data: " ");
475 d->stream->write(data: statusString.at(k: status));
476 }
477 d->stream->write(data: "\r\n");
478}
479
480/*!
481 This function writes an HTTP header \a header
482 with \a value.
483*/
484void QHttpServerResponder::writeHeader(const QByteArray &header,
485 const QByteArray &value)
486{
487 Q_D(const QHttpServerResponder);
488 Q_ASSERT(d->stream);
489 d->stream->write(data: header);
490 d->stream->write(data: ": ");
491 d->stream->write(data: value);
492 d->stream->write(data: "\r\n");
493}
494
495/*!
496 This function writes HTTP headers \a headers.
497*/
498void QHttpServerResponder::writeHeaders(HeaderList headers)
499{
500 for (auto &&header : headers)
501 writeHeader(header: header.first, value: header.second);
502}
503
504/*!
505 This function writes HTTP body \a body with size \a size.
506*/
507void QHttpServerResponder::writeBody(const char *body, qint64 size)
508{
509 Q_D(QHttpServerResponder);
510
511 Q_ASSERT(d->stream);
512
513 if (!d->bodyStarted) {
514 d->stream->write(data: "\r\n");
515 d->bodyStarted = true;
516 }
517
518 d->stream->write(body, size);
519}
520
521/*!
522 This function writes HTTP body \a body.
523*/
524void QHttpServerResponder::writeBody(const char *body)
525{
526 writeBody(body, size: qstrlen(str: body));
527}
528
529/*!
530 This function writes HTTP body \a body.
531*/
532void QHttpServerResponder::writeBody(const QByteArray &body)
533{
534 writeBody(body: body.constData(), size: body.size());
535}
536
537/*!
538 Sends a HTTP \a response to the client.
539
540 \since 6.5
541*/
542void QHttpServerResponder::sendResponse(const QHttpServerResponse &response)
543{
544 const auto &d = response.d_ptr;
545
546 writeStatusLine(status: d->statusCode);
547
548 for (auto &&header : d->headers)
549 writeHeader(header: header.first, value: header.second);
550
551 writeHeader(header: QHttpServerLiterals::contentLengthHeader(),
552 value: QByteArray::number(d->data.size()));
553
554 writeBody(body: d->data);
555}
556
557QT_END_NAMESPACE
558

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