cutelyst 4.0.0
A C++ Web Framework built on top of Qt, using the simple approach of Catalyst (Perl) framework.
response.cpp
1/*
2 * SPDX-FileCopyrightText: (C) 2013-2022 Daniel Nicoletti <dantti12@gmail.com>
3 * SPDX-License-Identifier: BSD-3-Clause
4 */
5#include "common.h"
6#include "context_p.h"
7#include "engine.h"
8#include "enginerequest.h"
9#include "response_p.h"
10
11#include <QCryptographicHash>
12#include <QEventLoop>
13#include <QJsonArray>
14#include <QJsonObject>
15#include <QtCore/QJsonDocument>
16
17using namespace Cutelyst;
18
19Response::Response(const Headers &defaultHeaders, EngineRequest *engineRequest)
20 : d_ptr(new ResponsePrivate(defaultHeaders, engineRequest))
21{
22 open(QIODevice::WriteOnly);
23}
24
25qint64 Response::readData(char *data, qint64 maxlen)
26{
27 Q_UNUSED(data)
28 Q_UNUSED(maxlen)
29 return -1;
30}
31
32qint64 Response::writeData(const char *data, qint64 len)
33{
34 Q_D(Response);
35
36 if (len <= 0) {
37 return len;
38 }
39
40 // Finalize headers if someone manually writes output
41 if (!(d->engineRequest->status & EngineRequest::FinalizedHeaders)) {
42 if (d->headers.header("Transfer-Encoding"_qba).compare("chunked") == 0) {
43 d->engineRequest->status |= EngineRequest::IOWrite | EngineRequest::Chunked;
44 } else {
45 // When chunked encoding is not set the client can only know
46 // that data is finished if we close the connection
47 d->headers.setHeader("Connection"_qba, "Close"_qba);
48 d->engineRequest->status |= EngineRequest::IOWrite;
49 }
50 delete d->bodyIODevice;
51 d->bodyIODevice = nullptr;
52 d->bodyData = QByteArray();
53
54 d->engineRequest->finalizeHeaders();
55 }
56
57 return d->engineRequest->write(data, len);
58}
59
60Response::~Response()
61{
62 delete d_ptr->bodyIODevice;
63 delete d_ptr;
64}
65
66quint16 Response::status() const noexcept
67{
68 Q_D(const Response);
69 return d->status;
70}
71
72void Response::setStatus(quint16 status) noexcept
73{
74 Q_D(Response);
75 d->status = status;
76}
77
78bool Response::hasBody() const noexcept
79{
80 Q_D(const Response);
81 return !d->bodyData.isEmpty() || d->bodyIODevice ||
82 d->engineRequest->status & EngineRequest::IOWrite;
83}
84
85QByteArray &Response::body()
86{
87 Q_D(Response);
88 if (d->bodyIODevice) {
89 delete d->bodyIODevice;
90 d->bodyIODevice = nullptr;
91 }
92
93 return d->bodyData;
94}
95
96QIODevice *Response::bodyDevice() const noexcept
97{
98 Q_D(const Response);
99 return d->bodyIODevice;
100}
101
102void Response::setBody(QIODevice *body)
103{
104 Q_D(Response);
105 Q_ASSERT(body && body->isOpen() && body->isReadable());
106
107 if (!(d->engineRequest->status & EngineRequest::IOWrite)) {
108 d->bodyData = QByteArray();
109 if (d->bodyIODevice) {
110 delete d->bodyIODevice;
111 }
112 d->bodyIODevice = body;
113 }
114}
115
116void Response::setBody(const QByteArray &body)
117{
118 Q_D(Response);
119 d->setBodyData(body);
120}
121
122void Response::setJsonBody(const QByteArray &json)
123{
124 Q_D(Response);
125 d->setBodyData(json);
126 d->headers.setContentType("application/json"_qba);
127}
128
129void Response::setJsonObjectBody(const QJsonObject &obj)
130{
131 setJsonBody(QJsonDocument(obj).toJson(QJsonDocument::Compact));
132}
133
134void Response::setJsonArrayBody(const QJsonArray &array)
135{
136 setJsonBody(QJsonDocument(array).toJson(QJsonDocument::Compact));
137}
138
139QByteArray Response::contentEncoding() const noexcept
140{
141 Q_D(const Response);
142 return d->headers.contentEncoding();
143}
144
145void Cutelyst::Response::setContentEncoding(const QByteArray &encoding)
146{
147 Q_D(Response);
148 Q_ASSERT_X(!(d->engineRequest->status & EngineRequest::FinalizedHeaders),
149 "setContentEncoding",
150 "setting a header value after finalize_headers and the response callback has been "
151 "called. Not what you want.");
152
153 d->headers.setContentEncoding(encoding);
154}
155
157{
158 Q_D(const Response);
159 return d->headers.contentLength();
160}
161
162void Response::setContentLength(qint64 length)
163{
164 Q_D(Response);
165 Q_ASSERT_X(!(d->engineRequest->status & EngineRequest::FinalizedHeaders),
166 "setContentLength",
167 "setting a header value after finalize_headers and the response callback has been "
168 "called. Not what you want.");
169
170 d->headers.setContentLength(length);
171}
172
173QByteArray Response::contentType() const
174{
175 Q_D(const Response);
176 return d->headers.contentType();
177}
178
180{
181 Q_D(const Response);
182 return d->headers.contentTypeCharset();
183}
184
185QVariant Response::cookie(const QByteArray &name) const
186{
187 Q_D(const Response);
188 return QVariant::fromValue(d->cookies.value(name));
189}
190
191QList<QNetworkCookie> Response::cookies() const
192{
193 Q_D(const Response);
194 return d->cookies.values();
195}
196
197void Response::setCookie(const QNetworkCookie &cookie)
198{
199 Q_D(Response);
200 d->cookies.insert(cookie.name(), cookie);
201}
202
203void Response::setCookies(const QList<QNetworkCookie> &cookies)
204{
205 Q_D(Response);
206 for (const QNetworkCookie &cookie : cookies) {
207 d->cookies.insert(cookie.name(), cookie);
208 }
209}
210
211int Response::removeCookies(const QByteArray &name)
212{
213 Q_D(Response);
214 return d->cookies.remove(name);
215}
216
217void Response::redirect(const QUrl &url, quint16 status)
218{
219 Q_D(Response);
220 d->location = url;
221 d->status = status;
222
223 if (url.isValid()) {
224 const auto location = url.toEncoded(QUrl::FullyEncoded);
225 qCDebug(CUTELYST_RESPONSE) << "Redirecting to" << location << status;
226
227 d->headers.setHeader("Location"_qba, location);
228 d->headers.setContentType("text/html; charset=utf-8"_qba);
229
230 const QByteArray buf = R"V0G0N(<!DOCTYPE html>
231<html xmlns="http://www.w3.org/1999/xhtml">
232 <head>
233 <title>Moved</title>
234 </head>
235 <body>
236 <p>This item has moved <a href=")V0G0N" +
237 location + R"V0G0N(">here</a>.</p>
238 </body>
239</html>
240)V0G0N";
241 setBody(buf);
242 } else {
243 d->headers.removeHeader("Location"_qba);
244 qCDebug(CUTELYST_ENGINE) << "Invalid redirect removing header" << url << status;
245 }
246}
247
248void Response::redirect(const QString &url, quint16 status)
249{
250 redirect(QUrl(url), status);
251}
252
253void Response::redirectSafe(const QUrl &url, const QUrl &fallback)
254{
255 Q_D(const Response);
256 if (url.matches(d->engineRequest->context->req()->uri(),
257 QUrl::RemovePath | QUrl::RemoveQuery)) {
258 redirect(url);
259 } else {
260 redirect(fallback);
261 }
262}
263
264QUrl Response::location() const noexcept
265{
266 Q_D(const Response);
267 return d->location;
268}
269
270QByteArray Response::header(const QByteArray &field) const noexcept
271{
272 Q_D(const Response);
273 return d->headers.header(field);
274}
275
276void Response::setHeader(const QByteArray &key, const QByteArray &value)
277{
278 Q_D(Response);
279 Q_ASSERT_X(!(d->engineRequest->status & EngineRequest::FinalizedHeaders),
280 "setHeader",
281 "setting a header value after finalize_headers and the response callback has been "
282 "called. Not what you want.");
283
284 d->headers.setHeader(key, value);
285}
286
287Headers &Response::headers() noexcept
288{
289 Q_D(Response);
290 return d->headers;
291}
292
293bool Response::isFinalizedHeaders() const noexcept
294{
295 Q_D(const Response);
296 return d->engineRequest->status & EngineRequest::FinalizedHeaders;
297}
298
299bool Response::isSequential() const noexcept
300{
301 return true;
302}
303
304qint64 Response::size() const noexcept
305{
306 Q_D(const Response);
307 if (d->engineRequest->status & EngineRequest::IOWrite) {
308 return -1;
309 } else if (d->bodyIODevice) {
310 return d->bodyIODevice->size();
311 } else {
312 return d->bodyData.size();
313 }
314}
315
316bool Response::webSocketHandshake(const QByteArray &key,
317 const QByteArray &origin,
318 const QByteArray &protocol)
319{
320 Q_D(Response);
321 return d->engineRequest->webSocketHandshake(key, origin, protocol);
322}
323
324bool Response::webSocketTextMessage(const QString &message)
325{
326 Q_D(Response);
327 return d->engineRequest->webSocketSendTextMessage(message);
328}
329
330bool Response::webSocketBinaryMessage(const QByteArray &message)
331{
332 Q_D(Response);
333 return d->engineRequest->webSocketSendBinaryMessage(message);
334}
335
336bool Response::webSocketPing(const QByteArray &payload)
337{
338 Q_D(Response);
339 return d->engineRequest->webSocketSendPing(payload);
340}
341
342bool Response::webSocketClose(quint16 code, const QString &reason)
343{
344 Q_D(Response);
345 return d->engineRequest->webSocketClose(code, reason);
346}
347
348void ResponsePrivate::setBodyData(const QByteArray &body)
349{
350 if (!(engineRequest->status & EngineRequest::IOWrite)) {
351 if (bodyIODevice) {
352 delete bodyIODevice;
353 bodyIODevice = nullptr;
354 }
355 bodyData = body;
356 headers.setContentLength(body.size());
357 }
358}
359
360#include "moc_response.cpp"
qint64 size() const noexcept override
QByteArray header(const QByteArray &field) const noexcept
void redirect(const QUrl &url, quint16 status=Found)
Definition: response.cpp:217
QVariant cookie(const QByteArray &name) const
Definition: response.cpp:185
qint64 contentLength() const
Definition: response.cpp:156
bool webSocketHandshake(const QByteArray &key={}, const QByteArray &origin={}, const QByteArray &protocol={})
Sends the websocket handshake, if no parameters are defined it will use header data.
bool hasBody() const noexcept
Definition: response.cpp:78
bool webSocketTextMessage(const QString &message)
Sends a WebSocket text message.
void setStatus(quint16 status) noexcept
Definition: response.cpp:72
bool webSocketPing(const QByteArray &payload={})
Sends a WebSocket ping with an optional payload limited to 125 bytes, which will be truncated if larg...
void setHeader(const QByteArray &key, const QByteArray &value)
Response(const Headers &defaultHeaders, EngineRequest *conn=nullptr)
Definition: response.cpp:19
QByteArray contentEncoding() const noexcept
Definition: response.cpp:139
QByteArray contentTypeCharset() const
Definition: response.cpp:179
void setBody(QIODevice *body)
Definition: response.cpp:102
void setJsonArrayBody(const QJsonArray &array)
Definition: response.cpp:134
void redirectSafe(const QUrl &url, const QUrl &fallback)
Headers & headers() noexcept
bool isSequential() const noexcept override
void setCookies(const QList< QNetworkCookie > &cookies)
Definition: response.cpp:203
virtual qint64 writeData(const char *data, qint64 len) override
Definition: response.cpp:32
QList< QNetworkCookie > cookies() const
Definition: response.cpp:191
bool webSocketBinaryMessage(const QByteArray &message)
Sends a WebSocket binary message.
bool isFinalizedHeaders() const noexcept
void setContentLength(qint64 length)
Definition: response.cpp:162
int removeCookies(const QByteArray &name)
Definition: response.cpp:211
void setJsonObjectBody(const QJsonObject &obj)
Definition: response.cpp:129
virtual qint64 readData(char *data, qint64 maxlen) override
Definition: response.cpp:25
QIODevice * bodyDevice() const noexcept
Definition: response.cpp:96
QByteArray & body()
Definition: response.cpp:85
void setJsonBody(QStringView json)
Definition: response.h:402
bool webSocketClose(quint16 code=Response::CloseCodeNormal, const QString &reason={})
Sends a WebSocket close frame, with both optional close code and a string reason.
quint16 status() const noexcept
Definition: response.cpp:66
void setCookie(const QNetworkCookie &cookie)
Definition: response.cpp:197
QUrl location() const noexcept
void setContentEncoding(const QByteArray &encoding)
Definition: response.cpp:145
QByteArray contentType() const
Definition: response.cpp:173
The Cutelyst namespace holds all public Cutelyst API.
Definition: Mainpage.dox:8