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 | |
19 | QT_BEGIN_NAMESPACE |
20 | |
21 | QT_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 | */ |
116 | static 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 |
123 | static 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 | */ |
192 | template <qint64 BUFFERSIZE = 512> |
193 | struct 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 | */ |
276 | QHttpServerResponder::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 | */ |
288 | QHttpServerResponder::QHttpServerResponder(QHttpServerResponder &&other) |
289 | : d_ptr(std::move(other.d_ptr)) |
290 | {} |
291 | |
292 | /*! |
293 | Destroys a QHttpServerResponder. |
294 | */ |
295 | QHttpServerResponder::~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 | */ |
314 | void QHttpServerResponder::write(QIODevice *data, |
315 | HeaderList , |
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 && : 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 | */ |
368 | void 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 | */ |
383 | void QHttpServerResponder::write(const QJsonDocument &document, |
384 | HeaderList , |
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 | */ |
404 | void 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 | */ |
416 | void QHttpServerResponder::write(const QByteArray &data, |
417 | HeaderList , |
418 | StatusCode status) |
419 | { |
420 | writeStatusLine(status); |
421 | |
422 | for (auto && : 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 | */ |
434 | void 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 | */ |
448 | void 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 | */ |
457 | void QHttpServerResponder::write(HeaderList , 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 | */ |
465 | void 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 | */ |
484 | void QHttpServerResponder::(const QByteArray &, |
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 | */ |
498 | void QHttpServerResponder::(HeaderList ) |
499 | { |
500 | for (auto && : 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 | */ |
507 | void 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 | */ |
524 | void 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 | */ |
532 | void 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 | */ |
542 | void QHttpServerResponder::sendResponse(const QHttpServerResponse &response) |
543 | { |
544 | const auto &d = response.d_ptr; |
545 | |
546 | writeStatusLine(status: d->statusCode); |
547 | |
548 | for (auto && : 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 | |
557 | QT_END_NAMESPACE |
558 | |