MythTV master
httpserver.cpp
Go to the documentation of this file.
1
2// Program Name: httpserver.cpp
3// Created : Oct. 1, 2005
4//
5// Purpose : HTTP 1.1 Mini Server Implmenetation
6// Used for UPnp/AV implementation & status information
7//
8// Copyright (c) 2005 David Blain <dblain@mythtv.org>
9// 2014 Stuart Morgan <smorgan@mythtv.org>
10//
11// Licensed under the GPL v2 or later, see LICENSE for details
12//
14
15// Own headers
16#include "httpserver.h"
17
18// ANSI C headers
19#include <cmath>
20#include <thread>
21
22// POSIX headers
23#ifndef _WIN32
24#include <sys/utsname.h>
25#endif
26
27// Qt headers
28#include <QSslConfiguration>
29#include <QSslSocket>
30#include <QSslCipher>
31#include <QSslCertificate>
32#include <QThread>
33#include <QUuid>
34
35// MythTV headers
36#include "libmythbase/compat.h"
41#include "libmythbase/mythversion.h"
42
43#include "upnputil.h"
44#include "htmlserver.h"
45
50{
52 pRequest->m_nResponseStatus = 200; // OK
53 // RFC 2616 Sect. 9.2 - If no response body is included, the response
54 // MUST include a Content-Length field with a
55 // field-value of "0".
56 pRequest->SetResponseHeader("Content-Length", "0");
57
58 QStringList allowedMethods;
60 allowedMethods.append("GET");
62 allowedMethods.append("HEAD");
64 allowedMethods.append("POST");
65// if (m_nSupportedMethods & RequestTypePut)
66// allowedMethods.append("PUT");
67// if (m_nSupportedMethods & RequestTypeDelete)
68// allowedMethods.append("DELETE");
69// if (m_nSupportedMethods & RequestTypeConnect)
70// allowedMethods.append("CONNECT");
72 allowedMethods.append("OPTIONS");
73// if (m_nSupportedMethods & RequestTypeTrace)
74// allowedMethods.append("TRACE");
76 allowedMethods.append("M-SEARCH");
78 allowedMethods.append("SUBSCRIBE");
80 allowedMethods.append("UNSUBSCRIBE");
82 allowedMethods.append("NOTIFY");
83
84 if (!allowedMethods.isEmpty())
85 {
86 pRequest->SetResponseHeader("Allow", allowedMethods.join(", "));
87 return true;
88 }
89
90 LOG(VB_GENERAL, LOG_ERR, QString("HttpServerExtension::ProcessOptions(): "
91 "Error: No methods supported for "
92 "extension - %1").arg(m_sName));
93
94 return false;
95}
96
97
100//
101// HttpServer Class Implementation
102//
105
108
110//
112
114 m_sSharePath(GetShareDir()),
115 m_threadPool("HttpServerPool"),
116 m_privateToken(QUuid::createUuid().toString()) // Cryptographically random and sufficiently long enough to act as a secure token
117{
118 // Number of connections processed concurrently
119 int maxHttpWorkers = std::max(QThread::idealThreadCount() * 2, 2); // idealThreadCount can return -1
120 // Don't allow more connections than we can process, it causes browsers
121 // to open lots of new connections instead of reusing existing ones
122 setMaxPendingConnections(maxHttpWorkers);
123 m_threadPool.setMaxThreadCount(maxHttpWorkers);
124
125 LOG(VB_HTTP, LOG_NOTICE, QString("HttpServer(): Max Thread Count %1")
127
128 // ----------------------------------------------------------------------
129 // Build Platform String
130 // ----------------------------------------------------------------------
131 {
132 QMutexLocker locker(&s_platformLock);
133#ifdef _WIN32
134 s_platform = QString("Windows/%1.%2")
135 .arg(LOBYTE(LOWORD(GetVersion())))
136 .arg(HIBYTE(LOWORD(GetVersion())));
137#else
138 struct utsname uname_info {};
139 uname( &uname_info );
140 s_platform = QString("%1/%2")
141 .arg(uname_info.sysname, uname_info.release);
142#endif
143 }
144
145 LOG(VB_HTTP, LOG_INFO, QString("HttpServer() - SharePath = %1")
146 .arg(m_sSharePath));
147
148 // -=>TODO: Load Config XML
149 // -=>TODO: Load & initialize - HttpServerExtensions
150
152}
153
155//
157
159{
160 m_rwlock.lockForWrite();
161 m_running = false;
162 m_rwlock.unlock();
163
165
166 while (!m_extensions.empty())
167 {
168 delete m_extensions.takeFirst();
169 }
170}
171
173{
174#ifndef QT_NO_OPENSSL
175 m_sslConfig = QSslConfiguration::defaultConfiguration();
176
177 m_sslConfig.setProtocol(QSsl::SecureProtocols); // Includes SSLv3 which is insecure, but can't be helped
178 m_sslConfig.setSslOption(QSsl::SslOptionDisableLegacyRenegotiation, true); // Potential DoS multiplier
179 m_sslConfig.setSslOption(QSsl::SslOptionDisableCompression, true); // CRIME attack
180
181 QList<QSslCipher> availableCiphers = QSslConfiguration::supportedCiphers();
182 QList<QSslCipher> secureCiphers;
183 QList<QSslCipher>::iterator it;
184 for (it = availableCiphers.begin(); it != availableCiphers.end(); ++it)
185 {
186 // Remove weak ciphers from the cipher list
187 if ((*it).usedBits() < 128)
188 continue;
189
190 if ((*it).name().startsWith("RC4") || // Weak cipher
191 (*it).name().startsWith("EXP") || // Weak authentication
192 (*it).name().startsWith("ADH") || // No authentication
193 (*it).name().contains("NULL")) // No encryption
194 continue;
195
196 secureCiphers.append(*it);
197 }
198 m_sslConfig.setCiphers(secureCiphers);
199#endif
200
201 QString hostKeyPath = gCoreContext->GetSetting("hostSSLKey", "");
202
203 if (hostKeyPath.isEmpty()) // No key, assume no SSL
204 return;
205
206 QFile hostKeyFile(hostKeyPath);
207 if (!hostKeyFile.exists() || !hostKeyFile.open(QIODevice::ReadOnly))
208 {
209 LOG(VB_GENERAL, LOG_ERR, QString("HttpServer: SSL Host key file (%1) does not exist or is not readable").arg(hostKeyPath));
210 return;
211 }
212
213#ifndef QT_NO_OPENSSL
214 QByteArray rawHostKey = hostKeyFile.readAll();
215 QSslKey hostKey = QSslKey(rawHostKey, QSsl::Rsa, QSsl::Pem, QSsl::PrivateKey);
216 if (!hostKey.isNull())
217 m_sslConfig.setPrivateKey(hostKey);
218 else
219 {
220 LOG(VB_GENERAL, LOG_ERR, QString("HttpServer: Unable to load host key from file (%1)").arg(hostKeyPath));
221 return;
222 }
223
224 QString hostCertPath = gCoreContext->GetSetting("hostSSLCertificate", "");
225 QSslCertificate hostCert;
226 QList<QSslCertificate> certList = QSslCertificate::fromPath(hostCertPath);
227 if (!certList.isEmpty())
228 hostCert = certList.first();
229
230 if (!hostCert.isNull())
231 {
232 if (hostCert.effectiveDate() > QDateTime::currentDateTime())
233 {
234 LOG(VB_GENERAL, LOG_ERR, QString("HttpServer: Host certificate start date in future (%1)").arg(hostCertPath));
235 return;
236 }
237
238 if (hostCert.expiryDate() < QDateTime::currentDateTime())
239 {
240 LOG(VB_GENERAL, LOG_ERR, QString("HttpServer: Host certificate has expired (%1)").arg(hostCertPath));
241 return;
242 }
243
244 m_sslConfig.setLocalCertificate(hostCert);
245 }
246 else
247 {
248 LOG(VB_GENERAL, LOG_ERR, QString("HttpServer: Unable to load host cert from file (%1)").arg(hostCertPath));
249 return;
250 }
251
252 QString caCertPath = gCoreContext->GetSetting("caSSLCertificate", "");
253 QList< QSslCertificate > CACertList = QSslCertificate::fromPath(caCertPath);
254
255 if (!CACertList.isEmpty())
256 m_sslConfig.setCaCertificates(CACertList);
257 else if (!caCertPath.isEmpty()) // Only warn if a path was actually configured, this isn't an error otherwise
258 LOG(VB_GENERAL, LOG_ERR, QString("HttpServer: Unable to load CA cert file (%1)").arg(caCertPath));
259#endif
260}
261
263//
265
267{
268 QMutexLocker locker(&s_platformLock);
269 return s_platform;
270}
271
273//
275
277{
278 QString mythVersion = GetMythSourceVersion();
279 if (mythVersion.startsWith("v"))
280 mythVersion = mythVersion.right(mythVersion.length() - 1); // Trim off the leading 'v'
281 return QString("MythTV/%2 %1 UPnP/1.0").arg(HttpServer::GetPlatform(),
282 mythVersion);
283}
284
286//
288
290{
292 auto *server = qobject_cast<PrivTcpServer *>(QObject::sender());
293 if (server)
294 type = server->GetServerType();
295
297 new HttpWorker(*this, socket, type
298#ifndef QT_NO_OPENSSL
300#endif
301 ),
302 QString("HttpServer%1").arg(socket));
303}
304
306//
308
310{
311 if (pExtension != nullptr )
312 {
313 LOG(VB_HTTP, LOG_INFO, QString("HttpServer: Registering %1 extension").arg(pExtension->m_sName));
314 m_rwlock.lockForWrite();
315 m_extensions.append( pExtension );
316
317 // Add to multimap for quick lookup.
318
319 QStringList list = pExtension->GetBasePaths();
320
321 for( const QString& base : std::as_const(list))
322 {
323 m_basePaths.insert( base, pExtension );
324 LOG(VB_HTTP, LOG_INFO, QString("HttpServer: Registering %1 extension path %2")
325 .arg(pExtension->m_sName, base));
326 }
327 m_rwlock.unlock();
328 }
329}
330
332//
334
336{
337 if (pExtension != nullptr )
338 {
339 m_rwlock.lockForWrite();
340
341 QStringList list = pExtension->GetBasePaths();
342
343 for( const QString& base : std::as_const(list))
344 m_basePaths.remove( base, pExtension );
345
346 m_extensions.removeAll(pExtension);
347
348 delete pExtension;
349
350 m_rwlock.unlock();
351 }
352}
353
355//
357
359{
360 bool bProcessed = false;
361
362 LOG(VB_HTTP, LOG_DEBUG, QString("m_sBaseUrl: %1").arg( pRequest->m_sBaseUrl ));
363 m_rwlock.lockForRead();
364
365 QList< HttpServerExtension* > list = m_basePaths.values( pRequest->m_sBaseUrl );
366
367 for (int nIdx=0; nIdx < list.size() && !bProcessed; nIdx++ )
368 {
369 try
370 {
371 if (pRequest->m_eType == RequestTypeOptions)
372 bProcessed = list[ nIdx ]->ProcessOptions(pRequest);
373 else
374 bProcessed = list[ nIdx ]->ProcessRequest(pRequest);
375 }
376 catch(...)
377 {
378 LOG(VB_GENERAL, LOG_ERR, QString("HttpServer::DelegateRequest - "
379 "Unexpected Exception - "
380 "pExtension->ProcessRequest()."));
381 }
382 }
383
384 for (const auto& ext : std::as_const(m_extensions))
385 {
386 if (bProcessed)
387 break;
388 try
389 {
390 if (pRequest->m_eType == RequestTypeOptions)
391 bProcessed = ext->ProcessOptions(pRequest);
392 else
393 bProcessed = ext->ProcessRequest(pRequest);
394 }
395 catch(...)
396 {
397 LOG(VB_GENERAL, LOG_ERR, QString("HttpServer::DelegateRequest - "
398 "Unexpected Exception - "
399 "pExtension->ProcessRequest()."));
400 }
401 }
402 m_rwlock.unlock();
403
404// if (!bProcessed)
405// bProcessed = m_pHtmlServer->ProcessRequest(pRequest);
406
407 if (!bProcessed)
408 {
410 pRequest->m_nResponseStatus = 404;
411 pRequest->m_response.write( pRequest->GetResponsePage() );
412 }
413}
414
416{
417 int timeout = -1;
418
419 m_rwlock.lockForRead();
420 QList< HttpServerExtension* > list = m_basePaths.values( pRequest->m_sBaseUrl );
421 if (!list.isEmpty())
422 timeout = list.first()->GetSocketTimeout();
423 m_rwlock.unlock();
424
425 if (timeout < 0)
426 timeout = gCoreContext->GetNumSetting("HTTP/KeepAliveTimeoutSecs", 10);
427
428 return timeout;
429}
430
433//
434// HttpWorkerThread Class Implementation
435//
438
439HttpWorker::HttpWorker(HttpServer &httpServer, qintptr sock,
441#ifndef QT_NO_OPENSSL
442 , const QSslConfiguration& sslConfig
443#endif
444)
445 : m_httpServer(httpServer), m_socket(sock),
446 m_socketTimeout(5s), m_connectionType(type)
447#ifndef QT_NO_OPENSSL
448 , m_sslConfig(sslConfig)
449#endif
450{
451 LOG(VB_HTTP, LOG_INFO, QString("HttpWorker(%1): New connection")
452 .arg(m_socket));
453}
454
456//
458
460{
461 LOG(VB_HTTP, LOG_DEBUG,
462 QString("HttpWorker::run() socket=%1 -- begin").arg(m_socket));
463
464 bool bKeepAlive = true;
465 HTTPRequest *pRequest = nullptr;
466 QTcpSocket *pSocket = nullptr;
467 bool bEncrypted = false;
468 MythTimer attempt_time;
469 static constexpr std::chrono::milliseconds k_poll_interval {1ms};
470
472 {
473
474#ifndef QT_NO_OPENSSL
475 auto *pSslSocket = new QSslSocket();
476 if (pSslSocket->setSocketDescriptor(m_socket)
477 && gCoreContext->CheckSubnet(pSslSocket))
478 {
479 pSslSocket->setSslConfiguration(m_sslConfig);
480 pSslSocket->startServerEncryption();
481 attempt_time.start();
482 while (m_httpServer.IsRunning() && attempt_time.elapsed() < 5s && !pSslSocket->isEncrypted())
483 {
484 pSslSocket->waitForEncrypted(k_poll_interval.count());
485 }
486 if (pSslSocket->isEncrypted())
487 {
488 LOG(VB_HTTP, LOG_INFO, "SSL Handshake occurred, connection encrypted");
489 LOG(VB_HTTP, LOG_INFO, QString("Using %1 cipher").arg(pSslSocket->sessionCipher().name()));
490 bEncrypted = true;
491 }
492 else
493 {
494 LOG(VB_HTTP, LOG_WARNING, "SSL Handshake FAILED, connection terminated");
495 delete pSslSocket;
496 pSslSocket = nullptr;
497 }
498 }
499 else
500 {
501 delete pSslSocket;
502 pSslSocket = nullptr;
503 }
504
505 if (pSslSocket)
506 pSocket = pSslSocket;
507 else
508 return;
509#else
510 return;
511#endif
512 }
513 else // Plain old unencrypted socket
514 {
515 pSocket = new QTcpSocket();
516 pSocket->setSocketDescriptor(m_socket);
517 if (!gCoreContext->CheckSubnet(pSocket))
518 {
519 delete pSocket;
520 pSocket = nullptr;
521 return;
522 }
523
524 }
525
526 pSocket->setSocketOption(QAbstractSocket::KeepAliveOption, QVariant(1));
527 int nRequestsHandled = 0; // Allow debugging of keep-alive and connection re-use
528
529 try
530 {
531 while (m_httpServer.IsRunning() && bKeepAlive && pSocket->isValid() &&
532 pSocket->state() == QAbstractSocket::ConnectedState)
533 {
534 // We set a timeout on keep-alive connections to avoid blocking
535 // new clients from connecting - Default at time of writing was
536 // 5 seconds for initial connection, then up to 10 seconds of idle
537 // time between each subsequent request on the same connection
538 attempt_time.start();
539 while (m_httpServer.IsRunning() && pSocket->isValid()
540 && pSocket->state() == QAbstractSocket::ConnectedState
541 && pSocket->bytesAvailable() <= 0
542 && attempt_time.elapsed() < m_socketTimeout
543 )
544 {
545 pSocket->waitForReadyRead(k_poll_interval.count());
546 }
547
548 if (!m_httpServer.IsRunning() || pSocket->bytesAvailable() <= 0)
549 break;
550
551 {
552 // ----------------------------------------------------------
553 // See if this is a valid request
554 // ----------------------------------------------------------
555
556 pRequest = new BufferedSocketDeviceRequest( pSocket );
557 if (pRequest != nullptr)
558 {
559 pRequest->m_bEncrypted = bEncrypted;
560 if ( pRequest->ParseRequest() )
561 {
562 bKeepAlive = pRequest->GetKeepAlive();
563 // The timeout is defined by the Server/Server Extension
564 // but must appear in the response headers
565 auto nTimeout = std::chrono::seconds(m_httpServer.GetSocketTimeout(pRequest));
566 pRequest->SetKeepAliveTimeout(nTimeout);
567 m_socketTimeout = nTimeout; // Converts to milliseconds
568
569 // ------------------------------------------------------
570 // Request Parsed... Pass on to Main HttpServer class to
571 // delegate processing to HttpServerExtensions.
572 // ------------------------------------------------------
573 if ((pRequest->m_nResponseStatus != 400) &&
574 (pRequest->m_nResponseStatus != 401) &&
575 (pRequest->m_nResponseStatus != 403) &&
576 pRequest->m_eType != RequestTypeUnknown)
578
579 nRequestsHandled++;
580 }
581 else
582 {
583 LOG(VB_HTTP, LOG_ERR, "ParseRequest Failed.");
584
585 pRequest->m_nResponseStatus = 501;
586 pRequest->m_response.write( pRequest->GetResponsePage() );
587 bKeepAlive = false;
588 }
589
590 // -------------------------------------------------------
591 // Always MUST send a response.
592 // -------------------------------------------------------
593 if (pRequest->SendResponse() < 0)
594 {
595 bKeepAlive = false;
596 LOG(VB_HTTP, LOG_ERR,
597 QString("socket(%1) - Error returned from "
598 "SendResponse... Closing connection")
599 .arg(pSocket->socketDescriptor()));
600 }
601
602 // -------------------------------------------------------
603 // Check to see if a PostProcess was registered
604 // -------------------------------------------------------
605 if ( pRequest->m_pPostProcess != nullptr )
607
608 delete pRequest;
609 pRequest = nullptr;
610 }
611 else
612 {
613 LOG(VB_GENERAL, LOG_ERR,
614 "Error Creating BufferedSocketDeviceRequest");
615 bKeepAlive = false;
616 }
617 }
618 }
619 }
620 catch(...)
621 {
622 LOG(VB_GENERAL, LOG_ERR,
623 "HttpWorkerThread::ProcessWork - Unexpected Exception.");
624 }
625
626 delete pRequest;
627
628 if ((pSocket->error() != QAbstractSocket::UnknownSocketError) &&
629 (!bKeepAlive || pSocket->error() != QAbstractSocket::SocketTimeoutError)) // This 'error' isn't an error when keep-alive is active
630 {
631 LOG(VB_HTTP, LOG_WARNING, QString("HttpWorker(%1): Error %2 (%3)")
632 .arg(m_socket)
633 .arg(pSocket->errorString())
634 .arg(pSocket->error()));
635 }
636
637 std::chrono::milliseconds writeTimeout = 5s;
638 // Make sure any data in the buffer is flushed before the socket is closed
639 if (pSocket->bytesToWrite() > 0)
640 {
641 LOG(VB_HTTP, LOG_DEBUG,
642 QString("HttpWorker(%1): Waiting for %2 bytes to be written before closing the connection.")
643 .arg(m_socket).arg(pSocket->bytesToWrite())
644 );
645 }
646 attempt_time.start();
647 while (m_httpServer.IsRunning() &&
648 pSocket->isValid() &&
649 pSocket->state() == QAbstractSocket::ConnectedState &&
650 pSocket->bytesToWrite() > 0 &&
651 attempt_time.elapsed() < writeTimeout
652 )
653 {
654 // If the client stops reading for longer than 'writeTimeout' then
655 // stop waiting for them. We can't afford to leave the socket
656 // connected indefinately, it could be used by another client.
657 //
658 // NOTE: Some clients deliberately stall as a way of 'pausing' A/V
659 // streaming. We should create a new server extension or adjust the
660 // timeout according to the User-Agent, instead of increasing the
661 // standard timeout. However we should ALWAYS have a timeout.
662 pSocket->waitForBytesWritten(k_poll_interval.count());
663 }
664
665 if (pSocket->bytesToWrite() > 0)
666 {
667 LOG(VB_HTTP, LOG_WARNING, QString("HttpWorker(%1): "
668 "Failed to write %2 bytes to "
669 "socket, (%3)")
670 .arg(m_socket)
671 .arg(pSocket->bytesToWrite())
672 .arg(pSocket->errorString()));
673 }
674
675 LOG(VB_HTTP, LOG_INFO, QString("HttpWorker(%1): Connection %2 closed. %3 requests were handled")
676 .arg(m_socket)
677 .arg(pSocket->socketDescriptor())
678 .arg(nRequestsHandled));
679
680 pSocket->close();
681 delete pSocket;
682 pSocket = nullptr;
683
684 LOG(VB_HTTP, LOG_DEBUG, QString("HttpWorker::run() socket=%1 -- end").arg(m_socket));
685}
686
687
QByteArray GetResponsePage(void)
HttpResponseType m_eResponseType
Definition: httprequest.h:150
long m_nResponseStatus
Definition: httprequest.h:153
qint64 SendResponse(void)
bool ParseRequest()
void SetKeepAliveTimeout(std::chrono::seconds nTimeout)
Definition: httprequest.h:258
IPostProcess * m_pPostProcess
Definition: httprequest.h:160
bool m_bEncrypted
Definition: httprequest.h:143
HttpRequestType m_eType
Definition: httprequest.h:121
bool GetKeepAlive() const
Definition: httprequest.h:238
QString m_sBaseUrl
Definition: httprequest.h:128
void SetResponseHeader(const QString &sKey, const QString &sValue, bool replace=false)
QBuffer m_response
Definition: httprequest.h:158
virtual QStringList GetBasePaths()=0
virtual bool ProcessOptions(HTTPRequest *pRequest)
Handle an OPTIONS request.
Definition: httpserver.cpp:49
QMultiMap< QString, HttpServerExtension * > m_basePaths
Definition: httpserver.h:144
QString m_sSharePath
Definition: httpserver.h:145
uint GetSocketTimeout(HTTPRequest *pRequest) const
Get the idle socket timeout value for the relevant extension.
Definition: httpserver.cpp:415
void DelegateRequest(HTTPRequest *pRequest)
Definition: httpserver.cpp:358
static QMutex s_platformLock
Definition: httpserver.h:149
MThreadPool m_threadPool
Definition: httpserver.h:146
static QString GetServerVersion(void)
Definition: httpserver.cpp:276
void UnregisterExtension(HttpServerExtension *pExtension)
Definition: httpserver.cpp:335
void newTcpConnection(qintptr socket) override
Definition: httpserver.cpp:289
QReadWriteLock m_rwlock
Definition: httpserver.h:141
bool IsRunning(void) const
Definition: httpserver.h:129
HttpServerExtensionList m_extensions
Definition: httpserver.h:142
static QString s_platform
Definition: httpserver.h:150
static QString GetPlatform(void)
Definition: httpserver.cpp:266
bool m_running
Definition: httpserver.h:147
QSslConfiguration m_sslConfig
Definition: httpserver.h:153
void LoadSSLConfig()
Definition: httpserver.cpp:172
~HttpServer() override
Definition: httpserver.cpp:158
void RegisterExtension(HttpServerExtension *pExtension)
Definition: httpserver.cpp:309
HttpWorker(HttpServer &httpServer, qintptr sock, PoolServerType type, const QSslConfiguration &sslConfig)
Definition: httpserver.cpp:439
HttpServer & m_httpServer
Definition: httpserver.h:192
PoolServerType m_connectionType
Definition: httpserver.h:195
qintptr m_socket
Definition: httpserver.h:193
std::chrono::milliseconds m_socketTimeout
Definition: httpserver.h:194
void run(void) override
Definition: httpserver.cpp:459
QSslConfiguration m_sslConfig
Definition: httpserver.h:198
virtual void ExecutePostProcess()=0
int maxThreadCount(void) const
void setMaxThreadCount(int maxThreadCount)
void startReserved(QRunnable *runnable, const QString &debugName, std::chrono::milliseconds waitForAvailMS=0ms)
void Stop(void)
QString GetSetting(const QString &key, const QString &defaultval="")
bool CheckSubnet(const QAbstractSocket *socket)
Check if a socket is connected to an approved peer.
int GetNumSetting(const QString &key, int defaultval=0)
A QElapsedTimer based timer to replace use of QTime as a timer.
Definition: mythtimer.h:14
std::chrono::milliseconds elapsed(void)
Returns milliseconds elapsed since last start() or restart()
Definition: mythtimer.cpp:91
void start(void)
starts measuring elapsed time.
Definition: mythtimer.cpp:47
void setMaxPendingConnections(int n)
Definition: serverpool.h:94
unsigned int uint
Definition: freesurround.h:24
@ ResponseTypeHTML
Definition: httprequest.h:80
@ ResponseTypeHeader
Definition: httprequest.h:87
@ RequestTypeMSearch
Definition: httprequest.h:58
@ RequestTypeSubscribe
Definition: httprequest.h:59
@ RequestTypeNotify
Definition: httprequest.h:61
@ RequestTypePost
Definition: httprequest.h:51
@ RequestTypeOptions
Definition: httprequest.h:55
@ RequestTypeUnsubscribe
Definition: httprequest.h:60
@ RequestTypeGet
Definition: httprequest.h:49
@ RequestTypeHead
Definition: httprequest.h:50
@ RequestTypeUnknown
Definition: httprequest.h:47
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
QString GetShareDir(void)
Definition: mythdirs.cpp:261
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:39
const char * GetMythSourceVersion()
Definition: mythversion.cpp:7
QString toString(const QDateTime &raw_dt, uint format)
Returns formatted string representing the time.
Definition: mythdate.cpp:93
PoolServerType
Definition: serverpool.h:30
@ kSSLServer
Definition: serverpool.h:33
@ kTCPServer
Definition: serverpool.h:31