cutelyst 4.3.0
A C++ Web Framework built on top of Qt, using the simple approach of Catalyst (Perl) framework.
tcpserverbalancer.cpp
1/*
2 * SPDX-FileCopyrightText: (C) 2017-2018 Daniel Nicoletti <dantti12@gmail.com>
3 * SPDX-License-Identifier: BSD-3-Clause
4 */
5#include "tcpserverbalancer.h"
6
7#include "server.h"
8#include "serverengine.h"
9#include "tcpserver.h"
10#include "tcpsslserver.h"
11
12#include <iostream>
13
14#include <QFile>
15#include <QLoggingCategory>
16#include <QSslKey>
17
18#ifdef Q_OS_LINUX
19# include <arpa/inet.h>
20# include <fcntl.h>
21# include <sys/socket.h>
22# include <sys/types.h>
23#endif
24
25Q_LOGGING_CATEGORY(C_SERVER_BALANCER, "cutelyst.server.tcpbalancer", QtWarningMsg)
26
27using namespace Cutelyst;
28
29#ifdef Q_OS_LINUX
30int listenReuse(const QHostAddress &address,
31 int listenQueue,
32 quint16 port,
33 bool reusePort,
34 bool startListening);
35#endif
36
37TcpServerBalancer::TcpServerBalancer(Server *wsgi)
38 : QTcpServer(wsgi)
39 , m_wsgi(wsgi)
40{
41}
42
43TcpServerBalancer::~TcpServerBalancer()
44{
45#ifndef QT_NO_SSL
46 delete m_sslConfiguration;
47#endif // QT_NO_SSL
48}
49
50bool TcpServerBalancer::listen(const QString &line, Protocol *protocol, bool secure)
51{
52 m_protocol = protocol;
53
54 int commaPos = line.indexOf(QLatin1Char(','));
55 const QString addressPortString = line.mid(0, commaPos);
56
57 QString addressString;
58 int closeBracketPos = addressPortString.indexOf(QLatin1Char(']'));
59 if (closeBracketPos != -1) {
60 if (!line.startsWith(QLatin1Char('['))) {
61 std::cerr << "Failed to parse address: " << qPrintable(addressPortString) << std::endl;
62 return false;
63 }
64 addressString = addressPortString.mid(1, closeBracketPos - 1);
65 } else {
66 addressString = addressPortString.section(QLatin1Char(':'), 0, -2);
67 }
68 const QString portString = addressPortString.section(QLatin1Char(':'), -1);
69
70 QHostAddress address;
71 if (addressString.isEmpty()) {
73 } else {
74 address.setAddress(addressString);
75 }
76
77 bool ok;
78 quint16 port = portString.toUInt(&ok);
79 if (!ok || (port < 1 || port > 35554)) {
80 port = 80;
81 }
82
83#ifndef QT_NO_SSL
84 if (secure) {
85 if (commaPos == -1) {
86 std::cerr << "No SSL certificate specified" << std::endl;
87 return false;
88 }
89
90 const QString sslString = line.mid(commaPos + 1);
91 const QString certPath = sslString.section(QLatin1Char(','), 0, 0);
92 QFile certFile(certPath);
93 if (!certFile.open(QFile::ReadOnly)) {
94 std::cerr << "Failed to open SSL certificate" << qPrintable(certPath)
95 << qPrintable(certFile.errorString()) << std::endl;
96 return false;
97 }
98 QSslCertificate cert(&certFile);
99 if (cert.isNull()) {
100 std::cerr << "Failed to parse SSL certificate" << std::endl;
101 return false;
102 }
103
104 const QString keyPath = sslString.section(QLatin1Char(','), 1, 1);
105 QFile keyFile(keyPath);
106 if (!keyFile.open(QFile::ReadOnly)) {
107 std::cerr << "Failed to open SSL private key" << qPrintable(keyPath)
108 << qPrintable(keyFile.errorString()) << std::endl;
109 return false;
110 }
111
112 QSsl::KeyAlgorithm algorithm = QSsl::Rsa;
113 const QString keyAlgorithm = sslString.section(QLatin1Char(','), 2, 2);
114 if (!keyAlgorithm.isEmpty()) {
115 if (keyAlgorithm.compare(QLatin1String("rsa"), Qt::CaseInsensitive) == 0) {
116 algorithm = QSsl::Rsa;
117 } else if (keyAlgorithm.compare(QLatin1String("ec"), Qt::CaseInsensitive) == 0) {
118 algorithm = QSsl::Ec;
119 } else {
120 std::cerr << "Failed to select SSL Key Algorithm" << qPrintable(keyAlgorithm)
121 << std::endl;
122 return false;
123 }
124 }
125
126 QSslKey key(&keyFile, algorithm);
127 if (key.isNull()) {
128 std::cerr << "Failed to parse SSL private key" << std::endl;
129 return false;
130 }
131
132 m_sslConfiguration = new QSslConfiguration;
133 m_sslConfiguration->setLocalCertificate(cert);
134 m_sslConfiguration->setPrivateKey(key);
135 m_sslConfiguration->setPeerVerifyMode(
136 QSslSocket::VerifyNone); // prevent asking for client certificate
137 if (m_wsgi->httpsH2()) {
138 m_sslConfiguration->setAllowedNextProtocols(
139 {QByteArrayLiteral("h2"), QSslConfiguration::NextProtocolHttp1_1});
140 }
141 }
142#endif // QT_NO_SSL
143
144 m_address = address;
145 m_port = port;
146
147#ifdef Q_OS_LINUX
148 int socket = listenReuse(
149 address, m_wsgi->listenQueue(), port, m_wsgi->reusePort(), !m_wsgi->reusePort());
150 if (socket > 0 && setSocketDescriptor(socket)) {
152 } else {
153 std::cerr << "Failed to listen on TCP: " << qPrintable(line) << " : "
154 << qPrintable(errorString()) << std::endl;
155 return false;
156 }
157#else
158 bool ret = QTcpServer::listen(address, port);
159 if (ret) {
161 } else {
162 std::cerr << "Failed to listen on TCP: " << qPrintable(line) << " : "
163 << qPrintable(errorString()) << std::endl;
164 return false;
165 }
166#endif
167
168 m_serverName = serverAddress().toString().toLatin1() + ':' + QByteArray::number(port);
169 return true;
170}
171
172#ifdef Q_OS_LINUX
173// UnixWare 7 redefines socket -> _socket
174static inline int qt_safe_socket(int domain, int type, int protocol, int flags = 0)
175{
176 Q_ASSERT((flags & ~O_NONBLOCK) == 0);
177
178 int fd;
179# ifdef QT_THREADSAFE_CLOEXEC
180 int newtype = type | SOCK_CLOEXEC;
181 if (flags & O_NONBLOCK)
182 newtype |= SOCK_NONBLOCK;
183 fd = ::socket(domain, newtype, protocol);
184 return fd;
185# else
186 fd = ::socket(domain, type, protocol);
187 if (fd == -1)
188 return -1;
189
190 ::fcntl(fd, F_SETFD, FD_CLOEXEC);
191
192 // set non-block too?
193 if (flags & O_NONBLOCK)
194 ::fcntl(fd, F_SETFL, ::fcntl(fd, F_GETFL) | O_NONBLOCK);
195
196 return fd;
197# endif
198}
199
200int createNewSocket(QAbstractSocket::NetworkLayerProtocol &socketProtocol)
201{
202 int protocol = 0;
203
204 int domain = (socketProtocol == QAbstractSocket::IPv6Protocol ||
205 socketProtocol == QAbstractSocket::AnyIPProtocol)
206 ? AF_INET6
207 : AF_INET;
208 int type = SOCK_STREAM;
209
210 int socket = qt_safe_socket(domain, type, protocol, O_NONBLOCK);
211 if (socket < 0 && socketProtocol == QAbstractSocket::AnyIPProtocol && errno == EAFNOSUPPORT) {
212 domain = AF_INET;
213 socket = qt_safe_socket(domain, type, protocol, O_NONBLOCK);
214 socketProtocol = QAbstractSocket::IPv4Protocol;
215 }
216
217 if (socket < 0) {
218 int ecopy = errno;
219 switch (ecopy) {
220 case EPROTONOSUPPORT:
221 case EAFNOSUPPORT:
222 case EINVAL:
223 qCDebug(C_SERVER_BALANCER)
224 << "setError(QAbstractSocket::UnsupportedSocketOperationError, "
225 "ProtocolUnsupportedErrorString)";
226 break;
227 case ENFILE:
228 case EMFILE:
229 case ENOBUFS:
230 case ENOMEM:
231 qCDebug(C_SERVER_BALANCER)
232 << "setError(QAbstractSocket::SocketResourceError, ResourceErrorString)";
233 break;
234 case EACCES:
235 qCDebug(C_SERVER_BALANCER)
236 << "setError(QAbstractSocket::SocketAccessError, AccessErrorString)";
237 break;
238 default:
239 break;
240 }
241
242# if defined(QNATIVESOCKETENGINE_DEBUG)
243 qCDebug(C_SERVER_BALANCER,
244 "QNativeSocketEnginePrivate::createNewSocket(%d, %d) == false (%s)",
245 socketType,
246 socketProtocol,
247 strerror(ecopy));
248# endif
249
250 return false;
251 }
252
253# if defined(QNATIVESOCKETENGINE_DEBUG)
254 qCDebug(C_SERVER_BALANCER,
255 "QNativeSocketEnginePrivate::createNewSocket(%d, %d) == true",
256 socketType,
257 socketProtocol);
258# endif
259
260 return socket;
261}
262
263union qt_sockaddr {
264 sockaddr a;
265 sockaddr_in a4;
266 sockaddr_in6 a6;
267};
268
269# define QT_SOCKLEN_T int
270# define QT_SOCKET_BIND ::bind
271
272namespace {
273namespace SetSALen {
274template <typename T>
275void set(T *sa, typename std::enable_if<(&T::sa_len, true), QT_SOCKLEN_T>::type len)
276{
277 sa->sa_len = len;
278}
279template <typename T>
280void set(T *sin6, typename std::enable_if<(&T::sin6_len, true), QT_SOCKLEN_T>::type len)
281{
282 sin6->sin6_len = len;
283}
284template <typename T>
285void set(T *, ...)
286{
287}
288} // namespace SetSALen
289} // namespace
290
291void setPortAndAddress(quint16 port,
292 const QHostAddress &address,
294 qt_sockaddr *aa,
295 int *sockAddrSize)
296{
297 if (address.protocol() == QAbstractSocket::IPv6Protocol ||
299 socketProtocol == QAbstractSocket::IPv6Protocol ||
300 socketProtocol == QAbstractSocket::AnyIPProtocol) {
301 memset(&aa->a6, 0, sizeof(sockaddr_in6));
302 aa->a6.sin6_family = AF_INET6;
303 // #if QT_CONFIG(networkinterface)
304 // aa->a6.sin6_scope_id = scopeIdFromString(address.scopeId());
305 // #endif
306 aa->a6.sin6_port = htons(port);
307 Q_IPV6ADDR tmp = address.toIPv6Address();
308 memcpy(&aa->a6.sin6_addr, &tmp, sizeof(tmp));
309 *sockAddrSize = sizeof(sockaddr_in6);
310 SetSALen::set(&aa->a, sizeof(sockaddr_in6));
311 } else {
312 memset(&aa->a, 0, sizeof(sockaddr_in));
313 aa->a4.sin_family = AF_INET;
314 aa->a4.sin_port = htons(port);
315 aa->a4.sin_addr.s_addr = htonl(address.toIPv4Address());
316 *sockAddrSize = sizeof(sockaddr_in);
317 SetSALen::set(&aa->a, sizeof(sockaddr_in));
318 }
319}
320
321bool nativeBind(int socketDescriptor, const QHostAddress &address, quint16 port)
322{
323 qt_sockaddr aa;
324 int sockAddrSize;
325 setPortAndAddress(port, address, address.protocol(), &aa, &sockAddrSize);
326
327# ifdef IPV6_V6ONLY
328 if (aa.a.sa_family == AF_INET6) {
329 int ipv6only = 0;
331 ipv6only = 1;
332 // default value of this socket option varies depending on unix variant (or system
333 // configuration on BSD), so always set it explicitly
334 ::setsockopt(
335 socketDescriptor, IPPROTO_IPV6, IPV6_V6ONLY, (char *) &ipv6only, sizeof(ipv6only));
336 }
337# endif
338
339 int bindResult = ::bind(socketDescriptor, &aa.a, sockAddrSize);
340 if (bindResult < 0 && errno == EAFNOSUPPORT &&
342 // retry with v4
343 aa.a4.sin_family = AF_INET;
344 aa.a4.sin_port = htons(port);
345 aa.a4.sin_addr.s_addr = htonl(address.toIPv4Address());
346 sockAddrSize = sizeof(aa.a4);
347 bindResult = QT_SOCKET_BIND(socketDescriptor, &aa.a, sockAddrSize);
348 }
349
350 if (bindResult < 0) {
351# if defined(QNATIVESOCKETENGINE_DEBUG)
352 int ecopy = errno;
353# endif
354 // switch(errno) {
355 // case EADDRINUSE:
356 // setError(QAbstractSocket::AddressInUseError, AddressInuseErrorString);
357 // break;
358 // case EACCES:
359 // setError(QAbstractSocket::SocketAccessError, AddressProtectedErrorString);
360 // break;
361 // case EINVAL:
362 // setError(QAbstractSocket::UnsupportedSocketOperationError,
363 // OperationUnsupportedErrorString); break;
364 // case EADDRNOTAVAIL:
365 // setError(QAbstractSocket::SocketAddressNotAvailableError,
366 // AddressNotAvailableErrorString); break;
367 // default:
368 // break;
369 // }
370
371# if defined(QNATIVESOCKETENGINE_DEBUG)
372 qCDebug(C_SERVER_BALANCER,
373 "QNativeSocketEnginePrivate::nativeBind(%s, %i) == false (%s)",
374 address.toString().toLatin1().constData(),
375 port,
376 strerror(ecopy));
377# endif
378
379 return false;
380 }
381
382# if defined(QNATIVESOCKETENGINE_DEBUG)
383 qCDebug(C_SERVER_BALANCER,
384 "QNativeSocketEnginePrivate::nativeBind(%s, %i) == true",
385 address.toString().toLatin1().constData(),
386 port);
387# endif
388 // socketState = QAbstractSocket::BoundState;
389 return true;
390}
391
392int listenReuse(const QHostAddress &address,
393 int listenQueue,
394 quint16 port,
395 bool reusePort,
396 bool startListening)
397{
399
400 int socket = createNewSocket(proto);
401 if (socket < 0) {
402 qCCritical(C_SERVER_BALANCER) << "Failed to create new socket";
403 return -1;
404 }
405
406 int optval = 1;
407 // SO_REUSEADDR is set by default on QTcpServer and allows to bind again
408 // without having to wait all previous connections to close
409 if (::setsockopt(socket, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval))) {
410 qCCritical(C_SERVER_BALANCER) << "Failed to set SO_REUSEADDR on socket" << socket;
411 return -1;
412 }
413
414 if (reusePort) {
415 if (::setsockopt(socket, SOL_SOCKET, SO_REUSEPORT, &optval, sizeof(optval))) {
416 qCCritical(C_SERVER_BALANCER) << "Failed to set SO_REUSEPORT on socket" << socket;
417 return -1;
418 }
419 }
420
421 if (!nativeBind(socket, address, port)) {
422 qCCritical(C_SERVER_BALANCER) << "Failed to bind to socket" << socket;
423 return -1;
424 }
425
426 if (startListening && ::listen(socket, listenQueue) < 0) {
427 qCCritical(C_SERVER_BALANCER) << "Failed to listen to socket" << socket;
428 return -1;
429 }
430
431 return socket;
432}
433#endif // Q_OS_LINUX
434
435void TcpServerBalancer::setBalancer(bool enable)
436{
437 m_balancer = enable;
438}
439
440void TcpServerBalancer::incomingConnection(qintptr handle)
441{
442 TcpServer *serverIdle = m_servers.at(m_currentServer++ % m_servers.size());
443
444 Q_EMIT serverIdle->createConnection(handle);
445}
446
447TcpServer *TcpServerBalancer::createServer(ServerEngine *engine)
448{
449 TcpServer *server;
450 if (m_sslConfiguration) {
451#ifndef QT_NO_SSL
452 auto sslServer = new TcpSslServer(m_serverName, m_protocol, m_wsgi, engine);
453 sslServer->setSslConfiguration(*m_sslConfiguration);
454 server = sslServer;
455#endif // QT_NO_SSL
456 } else {
457 server = new TcpServer(m_serverName, m_protocol, m_wsgi, engine);
458 }
459 connect(engine, &ServerEngine::shutdown, server, &TcpServer::shutdown);
460
461 if (m_balancer) {
462 connect(engine, &ServerEngine::started, this, [this, server]() {
463 m_servers.push_back(server);
466 connect(server,
467 &TcpServer::createConnection,
468 server,
469 &TcpServer::incomingConnection,
471 } else {
472
473#ifdef Q_OS_LINUX
474 if (m_wsgi->reusePort()) {
475 connect(engine, &ServerEngine::started, this, [this, server]() {
476 int socket = listenReuse(
477 m_address, m_wsgi->listenQueue(), m_port, m_wsgi->reusePort(), true);
478 if (!server->setSocketDescriptor(socket)) {
479 qFatal("Failed to set server socket descriptor, reuse-port");
480 }
482 return server;
483 }
484#endif
485
486 if (server->setSocketDescriptor(socketDescriptor())) {
487 server->pauseAccepting();
488 connect(engine,
489 &ServerEngine::started,
490 server,
493 } else {
494 qFatal("Failed to set server socket descriptor");
495 }
496 }
497
498 return server;
499}
500
501#include "moc_tcpserverbalancer.cpp"
Implements a web server.
Definition server.h:60
The Cutelyst namespace holds all public Cutelyst API.
const char * constData() const const
QByteArray number(double n, char format, int precision)
int protocol() const const
bool setAddress(const QString &address)
quint32 toIPv4Address(bool *ok) const const
Q_IPV6ADDR toIPv6Address() const const
QString toString() const const
Q_EMITQ_EMIT
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
KeyAlgorithm
void setAllowedNextProtocols(const QList< QByteArray > &protocols)
void setLocalCertificate(const QSslCertificate &certificate)
void setPeerVerifyMode(QSslSocket::PeerVerifyMode mode)
void setPrivateKey(const QSslKey &key)
int compare(QLatin1StringView s1, const QString &s2, Qt::CaseSensitivity cs)
qsizetype indexOf(QChar ch, qsizetype from, Qt::CaseSensitivity cs) const const
bool isEmpty() const const
QString mid(qsizetype position, qsizetype n) const const
QString section(QChar sep, qsizetype start, qsizetype end, QString::SectionFlags flags) const const
bool startsWith(QChar c, Qt::CaseSensitivity cs) const const
QByteArray toLatin1() const const
uint toUInt(bool *ok, int base) const const
CaseInsensitive
QueuedConnection
QString errorString() const const
bool listen(const QHostAddress &address, quint16 port)
void pauseAccepting()
void resumeAccepting()
QHostAddress serverAddress() const const
bool setSocketDescriptor(qintptr socketDescriptor)
qintptr socketDescriptor() const const