Cutelyst  1.11.0
engine.cpp
1 /*
2  * Copyright (C) 2013-2017 Daniel Nicoletti <dantti12@gmail.com>
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2.1 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this library; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17  */
18 #include "engine_p.h"
19 
20 #include "context_p.h"
21 
22 #include "common.h"
23 #include "request_p.h"
24 #include "application.h"
25 #include "response_p.h"
26 #include "context_p.h"
27 
28 #include <QUrl>
29 #include <QSettings>
30 #include <QDir>
31 #include <QThread>
32 #include <QByteArray>
33 #include <QJsonDocument>
34 
35 using namespace Cutelyst;
36 
57 Engine::Engine(Cutelyst::Application *app, int workerCore, const QVariantMap &opts)
58  : d_ptr(new EnginePrivate)
59 {
60  Q_D(Engine);
61 
62  // Debug messages should be disabled by default
63  QLoggingCategory::setFilterRules(QLatin1String("cutelyst.*.debug=false"));
64 
65  d->opts = opts;
66  d->workerCore = workerCore;
67 
68  // If workerCore is greater than 0 we need a new application instance
69  if (workerCore) {
70  auto newApp = qobject_cast<Application *>(app->metaObject()->newInstance());
71  if (!newApp) {
72  qFatal("*** FATAL *** Could not create a NEW instance of your Cutelyst::Application, "
73  "make sure your constructor has Q_INVOKABLE macro or disable threaded mode.");
74  }
75  d->app = newApp;
76  } else {
77  d->app = app;
78  }
79 
80  // To make easier for engines to clean up
81  // the app must be a child of it
82  d->app->setParent(this);
83 }
84 
85 Engine::~Engine()
86 {
87  delete d_ptr;
88 }
89 
91 {
92  Response *res = c->response();
93  Headers &headers = res->headers();
94  const auto cookies = res->cookies();
95  for (const QNetworkCookie &cookie : cookies) {
96  headers.pushHeader(QStringLiteral("set_cookie"), QString::fromLatin1(cookie.toRawForm()));
97  }
98 }
99 
101 {
102  EngineRequest *conn = c->response()->d_ptr->engineRequest;
103  return conn->finalizeHeaders(c);
104 }
105 
107 {
108  EngineRequest *conn = c->response()->d_ptr->engineRequest;
109  return conn->finalizeBody(c);
110 }
111 
113 {
114  Response *res = c->response();
115 
116  res->setContentType(QStringLiteral("text/html; charset=utf-8"));
117 
118  QByteArray body;
119 
120  // Trick IE. Old versions of IE would display their own error page instead
121  // of ours if we'd give it less than 512 bytes.
122  body.reserve(512);
123 
124  body.append(c->errors().join(QLatin1Char('\n')).toUtf8());
125 
126  res->setBody(body);
127 
128  // Return 500
129  res->setStatus(Response::InternalServerError);
130 }
131 
137 {
138  Q_D(const Engine);
139  Q_ASSERT(d->app);
140  return d->app;
141 }
142 
163 {
164  Q_D(const Engine);
165  return d->workerCore;
166 }
167 
169 {
170  Q_D(Engine);
171 
172  if (thread() != QThread::currentThread()) {
173  qCCritical(CUTELYST_ENGINE) << "Cannot init application on a different thread";
174  return false;
175  }
176 
177  if (!d->app->setup(this)) {
178  qCCritical(CUTELYST_ENGINE) << "Failed to setup application";
179  return false;
180  }
181 
182  return true;
183 }
184 
186 {
187  Q_D(Engine);
188 
189  if (!d->app) {
190  qCCritical(CUTELYST_ENGINE) << "Failed to postForkApplication on a null application";
191  return false;
192  }
193 
194  QThread::currentThread()->setObjectName(QString::number(d->workerCore));
195 
196  return d->app->enginePostFork();
197 }
198 
199 quint64 Engine::time()
200 {
201  return QDateTime::currentMSecsSinceEpoch() * 1000;
202 }
203 
204 qint64 Engine::write(Context *c, const char *data, qint64 len, void *engineData)
205 {
206  Q_UNUSED(engineData)
207  return c->response()->d_ptr->engineRequest->write(data, len);
208 }
209 
210 const char *Engine::httpStatusMessage(quint16 status, int *len)
211 {
212  const char *ret;
213  switch (status) {
214  case Response::OK:
215  ret = "HTTP/1.1 200 OK";
216  break;
217  case Response::Found:
218  ret = "HTTP/1.1 302 Found";
219  break;
220  case Response::NotFound:
221  ret = "HTTP/1.1 404 Not Found";
222  break;
223  case Response::InternalServerError:
224  ret = "HTTP/1.1 500 Internal Server Error";
225  break;
226  case Response::MovedPermanently:
227  ret = "HTTP/1.1 301 Moved Permanently";
228  break;
229  case Response::NotModified:
230  ret = "HTTP/1.1 304 Not Modified";
231  break;
232  case Response::SeeOther:
233  ret = "HTTP/1.1 303 See Other";
234  break;
235  case Response::Forbidden:
236  ret = "HTTP/1.1 403 Forbidden";
237  break;
238  case Response::TemporaryRedirect:
239  ret = "HTTP/1.1 307 Temporary Redirect";
240  break;
241  case Response::Unauthorized:
242  ret = "HTTP/1.1 401 Unauthorized";
243  break;
244  case Response::BadRequest:
245  ret = "HTTP/1.1 400 Bad Request";
246  break;
247  case Response::MethodNotAllowed:
248  ret = "HTTP/1.1 405 Method Not Allowed";
249  break;
250  case Response::RequestTimeout:
251  ret = "HTTP/1.1 408 Request Timeout";
252  break;
253  case Response::Continue:
254  ret = "HTTP/1.1 100 Continue";
255  break;
256  case Response::SwitchingProtocols:
257  ret = "HTTP/1.1 101 Switching Protocols";
258  break;
259  case Response::Created:
260  ret = "HTTP/1.1 201 Created";
261  break;
262  case Response::Accepted:
263  ret = "HTTP/1.1 202 Accepted";
264  break;
265  case Response::NonAuthoritativeInformation:
266  ret = "HTTP/1.1 203 Non-Authoritative Information";
267  break;
268  case Response::NoContent:
269  ret = "HTTP/1.1 204 No Content";
270  break;
271  case Response::ResetContent:
272  ret = "HTTP/1.1 205 Reset Content";
273  break;
274  case Response::PartialContent:
275  ret = "HTTP/1.1 206 Partial Content";
276  break;
277  case Response::MultipleChoices:
278  ret = "HTTP/1.1 300 Multiple Choices";
279  break;
280  case Response::UseProxy:
281  ret = "HTTP/1.1 305 Use Proxy";
282  break;
283  case Response::PaymentRequired:
284  ret = "HTTP/1.1 402 Payment Required";
285  break;
286  case Response::NotAcceptable:
287  ret = "HTTP/1.1 406 Not Acceptable";
288  break;
289  case Response::ProxyAuthenticationRequired:
290  ret = "HTTP/1.1 407 Proxy Authentication Required";
291  break;
292  case Response::Conflict:
293  ret = "HTTP/1.1 409 Conflict";
294  break;
295  case Response::Gone:
296  ret = "HTTP/1.1 410 Gone";
297  break;
298  case Response::LengthRequired:
299  ret = "HTTP/1.1 411 Length Required";
300  break;
301  case Response::PreconditionFailed:
302  ret = "HTTP/1.1 412 Precondition Failed";
303  break;
304  case Response::RequestEntityTooLarge:
305  ret = "HTTP/1.1 413 Request Entity Too Large";
306  break;
307  case Response::RequestURITooLong:
308  ret = "HTTP/1.1 414 Request-URI Too Long";
309  break;
310  case Response::UnsupportedMediaType:
311  ret = "HTTP/1.1 415 Unsupported Media Type";
312  break;
313  case Response::RequestedRangeNotSatisfiable:
314  ret = "HTTP/1.1 416 Requested Range Not Satisfiable";
315  break;
316  case Response::ExpectationFailed:
317  ret = "HTTP/1.1 417 Expectation Failed";
318  break;
319  case Response::NotImplemented:
320  ret = "HTTP/1.1 501 Not Implemented";
321  break;
322  case Response::BadGateway:
323  ret = "HTTP/1.1 502 Bad Gateway";
324  break;
325  case Response::ServiceUnavailable:
326  ret = "HTTP/1.1 503 Service Unavailable";
327  break;
328  case Response::GatewayTimeout:
329  ret = "HTTP/1.1 504 Gateway Timeout";
330  break;
331  case Response::HTTPVersionNotSupported:
332  ret = "HTTP/1.1 505 HTTP Version Not Supported";
333  break;
334  case Response::BandwidthLimitExceeded:
335  ret = "HTTP/1.1 509 Bandwidth Limit Exceeded";
336  break;
337  default:
338  ret = QByteArrayLiteral("HTTP/1.1 ").append(QByteArray::number(status)).constData();
339  break;
340  }
341 
342  if (len) {
343  *len = strlen(ret);
344  }
345  return ret;
346 }
347 
349 {
350  Q_D(Engine);
351  return d->app->defaultHeaders();
352 }
353 
355 {
356  Q_D(Engine);
357 
358  Q_UNUSED(req)
359 
360  auto request = new Request(new RequestPrivate(nullptr));
361  return d->app->handleRequest2(request);
362 }
363 
364 Context *Engine::processRequest(EngineRequest *req)
365 {
366  Q_D(Engine);
367 
368  auto request = new Request(new RequestPrivate(req));
369  return d->app->handleRequest2(request);
370 }
371 
372 void Engine::processRequest(const EngineRequest &req)
373 {
374  delete processRequest2(req);
375 }
376 
377 QVariantMap Engine::opts() const
378 {
379  Q_D(const Engine);
380  return d->opts;
381 }
382 
383 QVariantMap Engine::config(const QString &entity) const
384 {
385  Q_D(const Engine);
386  return d->config.value(entity).toMap();
387 }
388 
389 void Engine::setConfig(const QVariantMap &config)
390 {
391  Q_D(Engine);
392  d->config = config;
393 }
394 
395 QVariantMap Engine::loadIniConfig(const QString &filename)
396 {
397  QVariantMap ret;
398  QSettings settings(filename, QSettings::IniFormat);
399  if (settings.status() != QSettings::NoError) {
400  qCWarning(CUTELYST_ENGINE) << "Failed to load INI file:" << settings.status();
401  return ret;
402  }
403 
404  const auto groups = settings.childGroups();
405  for (const QString &group : groups) {
406  QVariantMap configGroup;
407  settings.beginGroup(group);
408  const auto child = settings.childKeys();
409  for (const QString &key : child) {
410  configGroup.insert(key, settings.value(key));
411  }
412  settings.endGroup();
413  ret.insert(group, configGroup);
414  }
415 
416  return ret;
417 }
418 
419 QVariantMap Engine::loadJsonConfig(const QString &filename)
420 {
421  QVariantMap ret;
422  QFile file(filename);
423  if (!file.open(QIODevice::ReadOnly)) {
424  return ret;
425  }
426  QJsonDocument doc = QJsonDocument::fromJson(file.readAll());
427 
428  ret = doc.toVariant().toMap();
429 
430  return ret;
431 }
432 
434 {
435  c->response()->d_ptr->engineRequest->finalize(c);
436 }
437 
438 bool Engine::webSocketHandshake(Context *c, const QString &key, const QString &origin, const QString &protocol)
439 {
440  ResponsePrivate *priv = c->response()->d_ptr;
441  return priv->engineRequest->webSocketHandshake(c, key, origin, protocol);
442 }
443 
444 bool Engine::webSocketHandshakeDo(Context *c, const QString &key, const QString &origin, const QString &protocol, void *engineData)
445 {
446  Q_UNUSED(c)
447  Q_UNUSED(key)
448  Q_UNUSED(origin)
449  Q_UNUSED(protocol)
450  Q_UNUSED(engineData)
451  return false;
452 }
453 
454 bool Engine::webSocketSendTextMessage(Context *c, const QString &message)
455 {
456  Q_UNUSED(c)
457  Q_UNUSED(message)
458  return false;
459 }
460 
461 bool Engine::webSocketSendBinaryMessage(Context *c, const QByteArray &message)
462 {
463  Q_UNUSED(c)
464  Q_UNUSED(message)
465  return false;
466 }
467 
468 bool Engine::webSocketSendPing(Context *c, const QByteArray &payload)
469 {
470  Q_UNUSED(c)
471  Q_UNUSED(payload)
472  return false;
473 }
474 
475 bool Engine::webSocketClose(Context *c, quint16 code, const QString &reason)
476 {
477  Q_UNUSED(c)
478  Q_UNUSED(code)
479  Q_UNUSED(reason)
480  return false;
481 }
482 
483 void Engine::processRequest(const QString &method,
484  const QString &path,
485  const QByteArray &query,
486  const QString &protocol,
487  bool isSecure,
488  const QString &serverAddress,
489  const QHostAddress &remoteAddress,
490  quint16 remotePort,
491  const QString &remoteUser,
492  const Headers &headers,
493  quint64 startOfRequest,
494  QIODevice *body,
495  void *requestPtr)
496 {
497  Q_D(Engine);
498 
499  DummyRequest req(this, this);
500  req.method = method;
501  req.path = path;
502  req.query = query;
503  req.protocol = protocol;
504  req.isSecure = isSecure;
505  req.serverAddress = serverAddress;
506  req.remoteAddress = remoteAddress;
507  req.remotePort = remotePort;
508  req.remoteUser = remoteUser;
509  req.headers = headers;
510  req.startOfRequest = startOfRequest;
511  req.body = body;
512  req.requestPtr = requestPtr;
513 
514  auto request = new Request(new RequestPrivate(nullptr));
515  delete d->app->handleRequest2(request);
516 }
517 
518 #include "moc_engine.cpp"
void pushHeader(const QString &field, const QString &value)
Definition: headers.cpp:368
void setContentType(const QString &type)
Definition: response.h:205
bool webSocketHandshake(const QString &key=QString(), const QString &origin=QString(), const QString &protocol=QString())
Sends the websocket handshake, if no parameters are defined it will use header data.
Definition: response.cpp:320
QVariantMap opts() const
Definition: engine.cpp:377
void setConfig(const QVariantMap &config)
Definition: engine.cpp:389
Context * processRequest2(const EngineRequest &req)
Definition: engine.cpp:354
static const char * httpStatusMessage(quint16 status, int *len=nullptr)
Definition: engine.cpp:210
virtual void finalizeError(Context *c)
Definition: engine.cpp:112
bool initApplication()
initApplication
Definition: engine.cpp:168
void setStatus(quint16 status)
Definition: response.cpp:90
qint64 write(Context *c, const char *data, qint64 len, void *engineData)
Definition: engine.cpp:204
QList< QNetworkCookie > cookies() const
Definition: response.cpp:212
bool postForkApplication()
postForkApplication
Definition: engine.cpp:185
virtual void finalizeBody(Context *c)
Engines must reimplement this to write the response body back to the caller.
static QVariantMap loadIniConfig(const QString &filename)
Definition: engine.cpp:395
Application * app() const
application
Definition: engine.cpp:136
int workerCore() const
Each worker process migth have a number of worker cores (threads), a single process with two worker t...
Definition: engine.cpp:162
The Cutelyst Context.
Definition: context.h:50
Engine(Application *app, int workerCore, const QVariantMap &opts)
Definition: engine.cpp:57
virtual Q_DECL_DEPRECATED bool finalizeHeaders(Context *c)
Definition: engine.cpp:100
Headers & headers()
Definition: response.cpp:297
static QVariantMap loadJsonConfig(const QString &filename)
Definition: engine.cpp:419
The Cutelyst namespace holds all public Cutelyst API.
Definition: Mainpage.dox:7
virtual bool finalizeHeaders(Context *c)
Finalize the headers, and call doWriteHeader(), reimplemententions must call this first...
QStringList errors() const
Returns a list of errors that were defined.
Definition: context.cpp:81
Response * response() const
Definition: context.cpp:111
virtual void finalizeCookies(Context *c)
Definition: engine.cpp:90
Q_DECL_DEPRECATED void finalize(Context *c)
Definition: engine.cpp:433
virtual quint64 time()
Definition: engine.cpp:199
The Cutelyst Application.
Definition: application.h:54
void setBody(QIODevice *body)
Definition: response.cpp:119
Headers & defaultHeaders()
Definition: engine.cpp:348
The Cutelyst Engine.
Definition: engine.h:33
virtual void finalizeBody(Context *c)
Definition: engine.cpp:106
QVariantMap config(const QString &entity) const
user configuration for the application
Definition: engine.cpp:383