// Copyright (C) 2016 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #ifndef HTTP2SRV_H #define HTTP2SRV_H #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include QT_BEGIN_NAMESPACE // At the moment we do not have any public API parsing HTTP headers. Even worse - // the code that can do this exists only in QHttpNetworkReplyPrivate class. // To be able to access reply's d_func() we have these classes: class Http11ReplyPrivate : public QHttpNetworkReplyPrivate { }; class Http11Reply : public QHttpNetworkReply { public: Q_DECLARE_PRIVATE(Http11Reply) }; enum class H2Type { h2Alpn, // Secure connection, ALPN to negotiate h2. h2c, // Clear text with protocol upgrade. h2Direct, // Secure connection, ALPN not supported. h2cDirect, // Clear text direct }; using RawSettings = QMap; class Http2Server : public QTcpServer { Q_OBJECT public: Http2Server(H2Type type, const RawSettings &serverSettings, const RawSettings &clientSettings); ~Http2Server(); // To be called before server started: void enablePushPromise(bool enabled, const QByteArray &path = QByteArray()); void setResponseBody(const QByteArray &body); // No content encoding is actually performed, call setResponseBody with already encoded data void setContentEncoding(const QByteArray &contentEncoding); // No authentication data is generated for the method, the full header value must be set void setAuthenticationHeader(const QByteArray &authentication); // Set the redirect URL and count. The server will return a redirect response with the url // 'count' amount of times void setRedirect(const QByteArray &redirectUrl, int count); // Send a trailing HEADERS frame with PRIORITY and END_STREAM flag void setSendTrailingHEADERS(bool enable); void emulateGOAWAY(int timeout); void redirectOpenStream(quint16 targetPort); bool isClearText() const; QByteArray requestAuthorizationHeader(); // Invokables, since we can call them from the main thread, // but server (can) work on its own thread. Q_INVOKABLE void startServer(); bool sendProtocolSwitchReply(); Q_INVOKABLE void sendServerSettings(); Q_INVOKABLE void sendGOAWAY(quint32 streamID, quint32 error, quint32 lastStreamID); Q_INVOKABLE void sendRST_STREAM(quint32 streamID, quint32 error); Q_INVOKABLE void sendDATA(quint32 streamID, quint32 windowSize); Q_INVOKABLE void sendWINDOW_UPDATE(quint32 streamID, quint32 delta); Q_INVOKABLE void handleProtocolUpgrade(); Q_INVOKABLE void handleConnectionPreface(); Q_INVOKABLE void handleIncomingFrame(); Q_INVOKABLE void handleSETTINGS(); Q_INVOKABLE void handleDATA(); Q_INVOKABLE void handleWINDOW_UPDATE(); Q_INVOKABLE void sendResponse(quint32 streamID, bool emptyBody); void stopSendingDATAFrames(); private: void processRequest(); Q_SIGNALS: void serverStarted(quint16 port); // Error/success notifications: void clientPrefaceOK(); void clientPrefaceError(); void serverSettingsAcked(); void invalidFrame(); void invalidRequest(quint32 streamID); void decompressionFailed(quint32 streamID); void receivedRequest(quint32 streamID); void receivedData(quint32 streamID); // Emitted for every DATA frame. Includes the content of the frame as \a body. void receivedDATAFrame(quint32 streamID, const QByteArray &body); void windowUpdate(quint32 streamID); void sendingData(); private slots: void connectionEstablished(); void readReady(); private: void incomingConnection(qintptr socketDescriptor) override; quint32 clientSetting(Http2::Settings identifier, quint32 defaultValue); bool readMethodLine(); bool verifyProtocolUpgradeRequest(); void triggerGOAWAYEmulation(); QScopedPointer socket; H2Type connectionType = H2Type::h2Alpn; // Connection preface: bool waitingClientPreface = false; bool waitingClientSettings = false; bool settingsSent = false; bool waitingClientAck = false; RawSettings serverSettings; RawSettings expectedClientSettings; bool connectionError = false; Http2::FrameReader reader; Http2::Frame inboundFrame; Http2::FrameWriter writer; using FrameSequence = std::vector; FrameSequence continuedRequest; std::map streamWindows; HPack::Decoder decoder{HPack::FieldLookupTable::DefaultSize}; HPack::Encoder encoder{HPack::FieldLookupTable::DefaultSize, true}; using Http2Requests = std::map; Http2Requests activeRequests; // 'remote half-closed' streams to keep // track of streams with END_STREAM set: std::set closedStreams; // streamID + offset in response body to send. std::map suspendedStreams; // We potentially reset this once (see sendServerSettings) // and do not change later: quint32 sessionRecvWindowSize = Http2::defaultSessionWindowSize; // This changes in the range [0, sessionRecvWindowSize] // while handling DATA frames: quint32 sessionCurrRecvWindow = sessionRecvWindowSize; // This we potentially update only once (sendServerSettings). quint32 streamRecvWindowSize = Http2::defaultSessionWindowSize; QByteArray responseBody; bool pushPromiseEnabled = false; quint32 lastPromisedStream = 0; QByteArray pushPath; bool testingGOAWAY = false; int goawayTimeout = 0; // Clear text HTTP/2, we have to deal with the protocol upgrade request // from the initial HTTP/1.1 request. bool upgradeProtocol = false; QByteArray requestLine; QHttpNetworkRequest::Operation requestType; // We need QHttpNetworkReply (actually its private d-object) to handle the // first HTTP/1.1 request. QHttpNetworkReplyPrivate does parsing + in case // of POST it is also reading the body for us. QScopedPointer protocolUpgradeHandler; // We need it for PUSH_PROMISE, with the correct port number appended, // when replying to essentially 1.1 request. QByteArray authority; // Redirect, with status code 308, as soon as we've seen headers, while client // may still be sending DATA frames. See tst_Http2::earlyResponse(). bool redirectWhileReading = false; bool redirectSent = false; quint16 targetPort = 0; QAtomicInt interrupted; QByteArray contentEncoding; QByteArray authenticationHeader; QByteArray redirectUrl; int redirectCount = 0; bool sendTrailingHEADERS = false; protected slots: void ignoreErrorSlot(); }; QT_END_NAMESPACE #endif