1// Copyright (C) 2022 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
3
4#include <QtHttpServer/qabstracthttpserver.h>
5
6#include <QtHttpServer/qhttpserverrequest.h>
7#include <QtHttpServer/qhttpserverresponder.h>
8
9#include <private/qabstracthttpserver_p.h>
10#include <private/qhttpserverrequest_p.h>
11#include <private/qhttpserverstream_p.h>
12
13#include <QtCore/qloggingcategory.h>
14#include <QtNetwork/qtcpserver.h>
15
16#if QT_CONFIG(ssl)
17#include <QtNetwork/qsslserver.h>
18#endif
19
20#include <algorithm>
21
22QT_BEGIN_NAMESPACE
23
24Q_LOGGING_CATEGORY(lcHttpServer, "qt.httpserver")
25
26/*!
27 \internal
28*/
29QAbstractHttpServerPrivate::QAbstractHttpServerPrivate()
30{
31}
32
33/*!
34 \internal
35*/
36void QAbstractHttpServerPrivate::handleNewConnections()
37{
38 Q_Q(QAbstractHttpServer);
39 auto tcpServer = qobject_cast<QTcpServer *>(object: q->sender());
40 Q_ASSERT(tcpServer);
41
42 while (auto socket = tcpServer->nextPendingConnection())
43 new QHttpServerStream(q, socket);
44}
45
46/*!
47 \class QAbstractHttpServer
48 \since 6.4
49 \inmodule QtHttpServer
50 \brief API to subclass to implement an HTTP server.
51
52 Subclass this class and override \c handleRequest() to create an HTTP
53 server. Use \c listen() or \c bind() to start listening to incoming
54 connections.
55*/
56
57/*!
58 Creates an instance of QAbstractHttpServer with the parent \a parent.
59*/
60QAbstractHttpServer::QAbstractHttpServer(QObject *parent)
61 : QAbstractHttpServer(*new QAbstractHttpServerPrivate, parent)
62{}
63
64/*!
65 \internal
66*/
67QAbstractHttpServer::~QAbstractHttpServer()
68 = default;
69
70/*!
71 \internal
72*/
73QAbstractHttpServer::QAbstractHttpServer(QAbstractHttpServerPrivate &dd, QObject *parent)
74 : QObject(dd, parent)
75{
76#if defined(QT_WEBSOCKETS_LIB)
77 Q_D(QAbstractHttpServer);
78 connect(&d->websocketServer, &QWebSocketServer::newConnection,
79 this, &QAbstractHttpServer::newWebSocketConnection);
80#endif
81}
82
83/*!
84 Tries to bind a \c QTcpServer to \a address and \a port.
85
86 Returns the server port upon success, 0 otherwise.
87*/
88quint16 QAbstractHttpServer::listen(const QHostAddress &address, quint16 port)
89{
90#if QT_CONFIG(ssl)
91 Q_D(QAbstractHttpServer);
92 QTcpServer *tcpServer;
93 if (d->sslEnabled) {
94 auto sslServer = new QSslServer(this);
95 sslServer->setSslConfiguration(d->sslConfiguration);
96 tcpServer = sslServer;
97 } else {
98 tcpServer = new QTcpServer(this);
99 }
100#else
101 auto tcpServer = new QTcpServer(this);
102#endif
103 const auto listening = tcpServer->listen(address, port);
104 if (listening) {
105 bind(server: tcpServer);
106 return tcpServer->serverPort();
107 } else {
108 qCCritical(lcHttpServer, "listen failed: %ls",
109 qUtf16Printable(tcpServer->errorString()));
110 }
111
112 delete tcpServer;
113 return 0;
114}
115
116/*!
117 Returns the list of ports this instance of QAbstractHttpServer
118 is listening to.
119
120 This function has the same guarantee as QObject::children,
121 the latest server added is the last entry in the vector.
122
123 \sa servers()
124*/
125QList<quint16> QAbstractHttpServer::serverPorts()
126{
127 QList<quint16> ports;
128 auto children = findChildren<QTcpServer *>();
129 ports.reserve(size: children.size());
130 std::transform(first: children.cbegin(), last: children.cend(), result: std::back_inserter(x&: ports),
131 unary_op: [](const QTcpServer *server) { return server->serverPort(); });
132 return ports;
133}
134
135/*!
136 Bind the HTTP server to given TCP \a server over which
137 the transmission happens. It is possible to call this function
138 multiple times with different instances of TCP \a server to
139 handle multiple connections and ports, for example both SSL and
140 non-encrypted connections.
141
142 After calling this function, every _new_ connection will be
143 handled and forwarded by the HTTP server.
144
145 It is the user's responsibility to call QTcpServer::listen() on
146 the \a server.
147
148 If the \a server is null, then a new, default-constructed TCP
149 server will be constructed, which will be listening on a random
150 port and all interfaces.
151
152 The \a server will be parented to this HTTP server.
153
154 \sa QTcpServer, QTcpServer::listen()
155*/
156void QAbstractHttpServer::bind(QTcpServer *server)
157{
158 Q_D(QAbstractHttpServer);
159 if (!server) {
160 server = new QTcpServer(this);
161 if (!server->listen()) {
162 qCCritical(lcHttpServer, "QTcpServer listen failed (%ls)",
163 qUtf16Printable(server->errorString()));
164 }
165 } else {
166 if (!server->isListening())
167 qCWarning(lcHttpServer) << "The TCP server" << server << "is not listening.";
168 server->setParent(this);
169 }
170 QObjectPrivate::connect(sender: server, signal: &QTcpServer::pendingConnectionAvailable, receiverPrivate: d,
171 slot: &QAbstractHttpServerPrivate::handleNewConnections,
172 type: Qt::UniqueConnection);
173}
174
175/*!
176 Returns list of child TCP servers of this HTTP server.
177
178 \sa serverPorts()
179 */
180QList<QTcpServer *> QAbstractHttpServer::servers() const
181{
182 return findChildren<QTcpServer *>().toVector();
183}
184
185#if defined(QT_WEBSOCKETS_LIB)
186/*!
187 \fn QAbstractHttpServer::newWebSocketConnection()
188 This signal is emitted every time a new WebSocket connection is
189 available.
190
191 \sa hasPendingWebSocketConnections(), nextPendingWebSocketConnection()
192*/
193
194/*!
195 Returns \c true if the server has pending WebSocket connections;
196 otherwise returns \c false.
197
198 \sa newWebSocketConnection(), nextPendingWebSocketConnection()
199*/
200bool QAbstractHttpServer::hasPendingWebSocketConnections() const
201{
202 Q_D(const QAbstractHttpServer);
203 return d->websocketServer.hasPendingConnections();
204}
205
206/*!
207 Returns the next pending connection as a connected QWebSocket
208 object. \nullptr is returned if this function is called when
209 there are no pending connections.
210
211 \note The returned QWebSocket object cannot be used from another
212 thread.
213
214 \sa newWebSocketConnection(), hasPendingWebSocketConnections()
215*/
216std::unique_ptr<QWebSocket> QAbstractHttpServer::nextPendingWebSocketConnection()
217{
218 Q_D(QAbstractHttpServer);
219 return std::unique_ptr<QWebSocket>(d->websocketServer.nextPendingConnection());
220}
221#endif
222
223/*!
224 \fn QAbstractHttpServer::handleRequest(const QHttpServerRequest &request,
225 QHttpServerResponder &responder)
226 Overload this function to handle each incoming \a request, by examining
227 the \a request and sending the appropriate response back to \a responder.
228 Return \c true if the \a request was handled successfully. If this method
229 returns \c false, \c missingHandler() will be called afterwards.
230
231 This function must move out of \a responder before returning \c true.
232*/
233
234/*!
235 \fn QAbstractHttpServer::missingHandler(const QHttpServerRequest &request,
236 QHttpServerResponder &&responder)
237
238 This function is called whenever \c handleRequest() returns \c false.
239 The \a request and \a responder parameters are the same as
240 \c handleRequest() was called with.
241*/
242
243#if QT_CONFIG(ssl)
244/*!
245 Turns the server into an HTTPS server.
246
247 The next listen() call will use the given \a certificate, \a privateKey,
248 and \a protocol.
249*/
250void QAbstractHttpServer::sslSetup(const QSslCertificate &certificate,
251 const QSslKey &privateKey,
252 QSsl::SslProtocol protocol)
253{
254 QSslConfiguration conf;
255 conf.setLocalCertificate(certificate);
256 conf.setPrivateKey(privateKey);
257 conf.setProtocol(protocol);
258 sslSetup(sslConfiguration: conf);
259}
260
261/*!
262 Turns the server into an HTTPS server.
263
264 The next listen() call will use the given \a sslConfiguration.
265*/
266void QAbstractHttpServer::sslSetup(const QSslConfiguration &sslConfiguration)
267{
268 Q_D(QAbstractHttpServer);
269 d->sslConfiguration = sslConfiguration;
270 d->sslEnabled = true;
271}
272#endif
273
274QT_END_NAMESPACE
275
276#include "moc_qabstracthttpserver.cpp"
277

source code of qthttpserver/src/httpserver/qabstracthttpserver.cpp