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 | |
22 | QT_BEGIN_NAMESPACE |
23 | |
24 | Q_LOGGING_CATEGORY(lcHttpServer, "qt.httpserver" ) |
25 | |
26 | /*! |
27 | \internal |
28 | */ |
29 | QAbstractHttpServerPrivate::QAbstractHttpServerPrivate() |
30 | { |
31 | } |
32 | |
33 | /*! |
34 | \internal |
35 | */ |
36 | void 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 | */ |
60 | QAbstractHttpServer::QAbstractHttpServer(QObject *parent) |
61 | : QAbstractHttpServer(*new QAbstractHttpServerPrivate, parent) |
62 | {} |
63 | |
64 | /*! |
65 | \internal |
66 | */ |
67 | QAbstractHttpServer::~QAbstractHttpServer() |
68 | = default; |
69 | |
70 | /*! |
71 | \internal |
72 | */ |
73 | QAbstractHttpServer::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 | */ |
88 | quint16 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 | */ |
125 | QList<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 | */ |
156 | void 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 | */ |
180 | QList<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 | */ |
200 | bool 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 | */ |
216 | std::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 | */ |
250 | void 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 | */ |
266 | void QAbstractHttpServer::sslSetup(const QSslConfiguration &sslConfiguration) |
267 | { |
268 | Q_D(QAbstractHttpServer); |
269 | d->sslConfiguration = sslConfiguration; |
270 | d->sslEnabled = true; |
271 | } |
272 | #endif |
273 | |
274 | QT_END_NAMESPACE |
275 | |
276 | #include "moc_qabstracthttpserver.cpp" |
277 | |