24using namespace std::chrono_literals;
26#define LOC QString(m_peer + ": ")
30 m_config(
std::move(Config))
36 QSslSocket* sslsocket =
nullptr;
40 sslsocket =
new QSslSocket(
this);
68 LOG(VB_HTTP, LOG_INFO,
LOC +
"New connection");
71 m_timer.setTimerType(Qt::PreciseTimer);
83 auto service = MythHTTPService::Create<MythHTTPServices>();
86 if (services ==
nullptr)
88 LOG(VB_HTTP, LOG_ERR,
LOC +
"Failed to get root services handler.");
147 LOG(VB_HTTP, LOG_INFO,
LOC +
"Disconnected");
152 if (
Error != QAbstractSocket::RemoteHostClosedError)
153 LOG(VB_HTTP, LOG_INFO,
LOC + QString(
"Error %1 (%2)").arg(
Error).arg(
m_socket->errorString()));
162 LOG(VB_HTTP, LOG_DEBUG,
LOC +
"Timed out waiting for activity");
167 if (
m_socket->state() == QAbstractSocket::ConnectedState)
174 LOG(VB_HTTP, LOG_INFO,
LOC +
"Timeout during write. Quitting.");
191 LOG(VB_HTTP, LOG_INFO,
LOC +
"Stop");
202 QThread::currentThread()->quit();
216 LOG(VB_GENERAL, LOG_WARNING,
LOC +
"New request but queue is not empty");
224 LOG(VB_HTTP, LOG_WARNING,
LOC +
"HTTP error");
237 if (request->m_status !=
HTTPOK)
252 if (response ==
nullptr)
257 if (path == request->m_url.toString())
259 response = std::invoke(function, request);
266 const QString& rpath = request->m_path;
269 if (response ==
nullptr)
271 LOG(VB_HTTP, LOG_INFO,
LOC + QString(
"Processing: path '%1' file '%2'")
272 .arg(rpath, request->m_fileName));
276 if (service->Name() == rpath)
278 response = service->HTTPRequest(request);
286 if (response ==
nullptr)
293 auto instance = std::invoke(constructor);
294 response = instance->HTTPRequest(request);
304 if (response ==
nullptr)
311 response = std::invoke(function, request);
319 if (response ==
nullptr)
333 if (response ==
nullptr || response->m_status ==
HTTPNotFound)
338 response = std::invoke(function, request);
343 if (response ==
nullptr)
360 if (!Response || (
m_socket->state() != QAbstractSocket::ConnectedState))
365 LOG(VB_GENERAL, LOG_WARNING,
LOC +
"Responding but queue is not empty");
378 for (
const auto & header : std::as_const(Response->m_responseHeaders))
380 LOG(VB_HTTP, LOG_DEBUG, header->trimmed().constData());
385 if (!std::get_if<std::monostate>(&Response->m_response))
386 m_queue.push_back(Response->m_response);
388 LOG(VB_HTTP, LOG_DEBUG,
LOC +
"No content in response");
395 if (
const auto * data = std::get_if<HTTPData>(&
content))
397 m_totalToSend += (*data)->m_partialSize > 0 ? (*data)->m_partialSize : (*data)->size();
400 else if (
const auto *
file = std::get_if<HTTPFile>(&
content))
402 m_totalToSend += (*file)->m_partialSize > 0 ? (*file)->m_partialSize : (*file)->size();
420 if (!(Socket && Response))
423 Response->Finalise(Config);
424 auto * socket =
new QTcpSocket();
425 socket->setSocketDescriptor(Socket);
426 for (
const auto & header : std::as_const(Response->m_responseHeaders))
427 socket->write(*header);
429 socket->disconnectFromHost();
435 auto chunkheader = [&](
const QByteArray& Data)
437 int64_t wrote =
m_socket->write(Data);
440 LOG(VB_GENERAL, LOG_ERR,
"Error writing chunk header");
461 int64_t buffered =
m_socket->bytesToWrite();
462 LOG(VB_HTTP, LOG_DEBUG,
LOC + QString(
"Socket sent %1bytes (still to send %2)")
463 .arg(Written).arg(buffered));
470 LOG(VB_HTTP, LOG_DEBUG,
LOC +
"Draining buffers");
476 auto seconds =
static_cast<double>(
m_writeTime.nsecsElapsed()) / 1000000000.0;
477 auto rate =
static_cast<uint64_t
>(
static_cast<double>(
m_totalSent) / seconds);
478 LOG(VB_HTTP, LOG_INFO,
LOC + QString(
"Wrote %1bytes in %2seconds (%3)")
491 LOG(VB_GENERAL, LOG_ERR,
LOC +
"Write complete but queue not empty");
498 while ((available > 0) && !
m_queue.empty())
503 int64_t itemsize = 0;
506 auto * data = std::get_if<HTTPData>(&
m_queue.front());
507 auto *
file = std::get_if<HTTPFile>(&
m_queue.front());
512 written = (*data)->m_written;
513 itemsize = (*data)->m_partialSize > 0 ? (*data)->m_partialSize :
static_cast<int64_t
>((*data)->size());
514 towrite = std::min(itemsize - written, available);
516 HTTPMulti multipart {
nullptr,
nullptr };
517 if (!(*data)->m_ranges.empty())
520 chunkheader(QStringLiteral(
"%1\r\n").arg(towrite, 0, 16).toLatin1().constData());
522 wrote +=
m_socket->write(multipart.first->constData());
523 wrote +=
m_socket->write((*data)->constData() + written + offset, towrite);
524 if (multipart.second)
525 wrote +=
m_socket->write(multipart.second->constData());
532 written = (*file)->m_written;
533 itemsize = (*file)->m_partialSize > 0 ? (*file)->m_partialSize :
static_cast<int64_t
>((*file)->size());
534 towrite = std::min(itemsize - written, available);
535 HTTPMulti multipart {
nullptr,
nullptr };
536 if (!(*file)->m_ranges.empty())
540 if (offset != (*file)->pos())
541 (*file)->seek(offset);
543 int64_t writebufsize = std::min(itemsize,
static_cast<int64_t
>(
HTTP_CHUNKSIZE));
552 chunkheader(QStringLiteral(
"%1\r\n").arg(
read, 0, 16).toLatin1().constData());
554 wrote +=
m_socket->write(multipart.first->constData());
556 if (multipart.second)
557 wrote +=
m_socket->write(multipart.second->constData());
563 if (wrote < 0 ||
read < 0)
565 LOG(VB_GENERAL, LOG_ERR,
LOC + QString(
"Write error (Read: %1 Write: %2)")
566 .arg(
read).arg(wrote));
572 if (data) (*data)->m_written = written;
573 if (
file) (*file)->m_written = written;
578 LOG(VB_HTTP, LOG_DEBUG,
LOC + QString(
"Sent %1 bytes (cum: %2 of %3)")
583 if (written >= itemsize)
586 chunkheader(
"0\r\n\r\n");
609 LOG(VB_GENERAL, LOG_ERR,
LOC +
"Failed to create websocket");
617 LOG(VB_GENERAL, LOG_ERR,
LOC +
"Failed to create websocket event listener");
644 if (Payloads.empty())
651 if (Payloads.empty())
bool CheckSubnet(const QAbstractSocket *socket)
Check if a socket is connected to an approved peer.
QSslConfiguration m_sslConfig
std::chrono::milliseconds m_timeout
HTTPHandler m_errorPageHandler
QStringList m_allowedOrigins
static HTTPResponse ProcessFile(const HTTPRequest2 &Request)
bool Read(QTcpSocket *Socket, bool &Ready)
HTTPRequest2 GetRequest(const MythHTTPConfig &Config, QTcpSocket *Socket)
static HTTPMulti HandleRangeWrite(HTTPVariant Data, int64_t Available, int64_t &ToWrite, int64_t &Offset)
static HTTPResponse UpgradeResponse(const HTTPRequest2 &Request, MythSocketProtocol &Protocol, bool &Testing)
static HTTPResponse ErrorResponse(MythHTTPStatus Status, const QString &ServerName)
static void InitSSLSocket(QSslSocket *Socket, QSslConfiguration &Config)
void UpdateServices(const HTTPServices &Services)
QElapsedTimer m_writeTime
void Write(int64_t Written=0)
void NewRawTextMessage(const DataPayloads &Payloads)
void NewTextMessage(const StringPayload &Text)
~MythHTTPSocket() override
static void RespondDirect(qintptr Socket, const HTTPResponse &Response, const MythHTTPConfig &Config)
Send an (error) response directly without creating a thread.
void Respond(const HTTPResponse &Response)
Send response to client.
void ThreadUpgraded(QThread *Thread)
void Disconnected()
The socket was disconnected.
void Timeout()
Close the socket after a period of inactivity.
void Stop()
Close the socket and quit the thread.
void PathsChanged(const QStringList &Paths)
Update our list of recognised file paths.
static void NewBinaryMessage(const DataPayloads &Payloads)
HTTPServicePtrs m_activeServices
void UpdateServices(const HTTPServices &Services)
void HandlersChanged(const HTTPHandlers &Handlers)
void OriginsChanged(const QStringList &Origins)
Update the list of allowed Origins.
void Read()
Read data from the socket which is parsed by MythHTTPParser.
MythHTTPConnection m_nextConnection
MythWebSocket * m_websocket
MythWebSocketEvent * m_websocketevent
void Error(QAbstractSocket::SocketError Error)
MythHTTPSocket(qintptr Socket, bool SSL, MythHTTPConfig Config)
void ServicesChanged(const HTTPServices &Services)
void SetupWebSocket()
Transition socket to a WebSocket.
void HostsChanged(const QStringList &Hosts)
MythSocketProtocol m_protocol
static QString BitrateToString(uint64_t Rate)
static QString GetHeader(const HTTPHeaders &Headers, const QString &Value, const QString &Default="")
bool HandleTextMessage(const StringPayload &Text)
bool HandleRawTextMessage(const DataPayloads &Payloads)
void SendTextMessage(const QString &)
void Close()
Initiate the close handshake when we are exiting.
void SendTextFrame(const QString &Text)
static MythWebSocket * CreateWebsocket(bool Server, QTcpSocket *Socket, MythSocketProtocol Protocol, bool Testing)
void NewRawTextMessage(const DataPayloads &Payloads)
void NewBinaryMessage(const DataPayloads &Payloads)
void NewTextMessage(const StringPayload &Text)
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
#define HTTP_CHUNKSIZE
A group of functions shared between HTTP and WebSocket code.
std::vector< DataPayload > DataPayloads
std::shared_ptr< MythSharedString > StringPayload
std::shared_ptr< MythHTTPRequest > HTTPRequest2
std::vector< HTTPHandler > HTTPHandlers
std::shared_ptr< MythHTTPResponse > HTTPResponse
std::vector< HTTPService > HTTPServices
std::pair< HTTPData, HTTPData > HTTPMulti
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
def read(device=None, features=[])