Commit 8bd8611f authored by jan.koester's avatar jan.koester
Browse files

test

parent 15718379
Loading
Loading
Loading
Loading
+126 −54
Original line number Diff line number Diff line
@@ -285,7 +285,6 @@ void libhttppp::HttpClient::resetConnection(){
  _h2PrefaceSent = false;
  _h2NextStreamId = 1;
  _h2Decoder.reset();
  _h3ControlSent = false;
  if (_url.getProtocol() == HttpUrl::HTTP3) {
    auto q = std::make_unique<netplus::quic>();
    q->connect(_url.getHost(), _url.getPort(), false);
@@ -360,7 +359,6 @@ void libhttppp::HttpClient::reconnect(){
  _h2PrefaceSent = false;
  _h2NextStreamId = 1;
  _h2Decoder.reset();
  _h3ControlSent = false;
  if (_url.getProtocol() == HttpUrl::HTTP3) {
    auto q = std::make_unique<netplus::quic>();
    q->connect(_url.getHost(), _url.getPort(), false);
@@ -750,6 +748,7 @@ static std::string h2cBuildFrame(uint8_t type, uint8_t flags, uint32_t stream_id
static std::string h2cBuildSettings();
static std::string h2cBuildSettingsAck();
static std::string h2cBuildWindowUpdate(uint32_t stream_id, uint32_t increment);
static std::string h2cConnectionWindowBoost();

// ── Streaming API ──────────────────────────────────────────────────────

@@ -794,26 +793,6 @@ libhttppp::HttpResponse libhttppp::HttpClient::GetStream(libhttppp::HttpRequest

        // ── HTTP/3 (QUIC) streaming path ──
        if (auto *q = dynamic_cast<netplus::quic*>(_cltsock.get())) {
            // Send HTTP/3 control streams once per connection
            if (!_h3ControlSent) {
                uint64_t ctrl_id = q->openStream(false);
                std::vector<uint8_t> ctrl_payload;
                ctrl_payload.push_back(0x00);
                ctrl_payload.push_back(0x04);
                ctrl_payload.push_back(0x00);
                q->sendStreamData(ctrl_id, ctrl_payload, false);

                uint64_t enc_id = q->openStream(false);
                std::vector<uint8_t> enc_payload = {0x02};
                q->sendStreamData(enc_id, enc_payload, false);

                uint64_t dec_id = q->openStream(false);
                std::vector<uint8_t> dec_payload = {0x03};
                q->sendStreamData(dec_id, dec_payload, false);

                _h3ControlSent = true;
            }

            std::string path = nreq.getRequestURL();
            if (path.empty()) path = _url.getPath();
            std::string scheme = (_url.getProtocol() == HttpUrl::HTTP) ? "http" : "https";
@@ -966,6 +945,7 @@ libhttppp::HttpResponse libhttppp::HttpClient::GetStream(libhttppp::HttpRequest
            if (!_h2PrefaceSent) {
                std::string preface(H2C_CLIENT_PREFACE, H2C_CLIENT_PREFACE_LEN);
                preface += h2cBuildSettings();
                preface += h2cConnectionWindowBoost();
                _sendAll(preface);
                _h2PrefaceSent = true;
            }
@@ -1946,15 +1926,33 @@ static std::string h2cBuildFrame(uint8_t type, uint8_t flags, uint32_t stream_id
    return f;
}

// RFC 7540: SETTINGS_INITIAL_WINDOW_SIZE — use 16 MB so both large
// downloads and uploads don't stall on the default 65535-byte window.
static constexpr uint32_t H2C_INITIAL_WINDOW_SIZE = 16u * 1024 * 1024;

static std::string h2cBuildSettings() {
    // Send empty SETTINGS (accept defaults)
    return h2cBuildFrame(H2C_FRAME_SETTINGS, 0, 0, "");
    // SETTINGS_INITIAL_WINDOW_SIZE (0x04) = H2C_INITIAL_WINDOW_SIZE
    std::string p(6, '\0');
    p[0] = 0x00; p[1] = 0x04;
    p[2] = static_cast<char>((H2C_INITIAL_WINDOW_SIZE >> 24) & 0xff);
    p[3] = static_cast<char>((H2C_INITIAL_WINDOW_SIZE >> 16) & 0xff);
    p[4] = static_cast<char>((H2C_INITIAL_WINDOW_SIZE >>  8) & 0xff);
    p[5] = static_cast<char>((H2C_INITIAL_WINDOW_SIZE      ) & 0xff);
    return h2cBuildFrame(H2C_FRAME_SETTINGS, 0, 0, p);
}

static std::string h2cBuildSettingsAck() {
    return h2cBuildFrame(H2C_FRAME_SETTINGS, H2C_FLAG_ACK, 0, "");
}

// Connection-level WINDOW_UPDATE to raise the default 65535 connection
// window to match the per-stream INITIAL_WINDOW_SIZE (RFC 7540 §6.9.2).
static std::string h2cConnectionWindowBoost() {
    uint32_t boost = H2C_INITIAL_WINDOW_SIZE - 65535u;
    if (boost == 0) return "";
    return h2cBuildWindowUpdate(0, boost);
}

static std::string h2cBuildWindowUpdate(uint32_t stream_id, uint32_t increment) {
    std::string p(4, '\0');
    p[0] = static_cast<char>((increment >> 24) & 0x7f);
@@ -2030,6 +2028,7 @@ const std::vector<char> libhttppp::HttpClient::_h2Request(
        if (!_h2PrefaceSent) {
            std::string preface(H2C_CLIENT_PREFACE, H2C_CLIENT_PREFACE_LEN);
            preface += h2cBuildSettings();
            preface += h2cConnectionWindowBoost();
            send_all(preface);
            _h2PrefaceSent = true;
        }
@@ -2074,23 +2073,120 @@ const std::vector<char> libhttppp::HttpClient::_h2Request(
                                                   stream_id, hpack_block);
        send_all(headers_frame);

        // 3) Send DATA frames for POST body if present
        // 3) Send DATA frames for POST body with flow control (RFC 7540 §6.9)
        // raw is shared between POST-send and response-read phases
        std::vector<uint8_t> raw;
        raw.reserve(16384);

        if (postBody && !postBody->empty()) {
            // Track the peer's (server's) flow control window.
            // Initial value is 65535 until we receive the server's SETTINGS.
            int32_t peer_conn_window = 65535;
            int32_t peer_stream_window = 65535;

            // Helper: parse buffered frames for SETTINGS / WINDOW_UPDATE / PING
            auto process_fc_frames = [&]() {
                size_t foff = 0;
                while (foff + H2C_FRAME_HEADER_LEN <= raw.size()) {
                    uint32_t flen = (static_cast<uint32_t>(raw[foff]) << 16)
                                  | (static_cast<uint32_t>(raw[foff+1]) << 8)
                                  | static_cast<uint32_t>(raw[foff+2]);
                    uint8_t ftype = raw[foff+3];
                    uint8_t fflags = raw[foff+4];
                    uint32_t fsid = ((static_cast<uint32_t>(raw[foff+5]) & 0x7f) << 24)
                                  | (static_cast<uint32_t>(raw[foff+6]) << 16)
                                  | (static_cast<uint32_t>(raw[foff+7]) << 8)
                                  | static_cast<uint32_t>(raw[foff+8]);
                    if (foff + H2C_FRAME_HEADER_LEN + flen > raw.size()) break;
                    const uint8_t *fp = raw.data() + foff + H2C_FRAME_HEADER_LEN;

                    if (ftype == H2C_FRAME_SETTINGS && !(fflags & H2C_FLAG_ACK)) {
                        for (size_t si = 0; si + 6 <= flen; si += 6) {
                            uint16_t setting_id = (static_cast<uint16_t>(fp[si]) << 8)
                                                | fp[si+1];
                            uint32_t setting_val = (static_cast<uint32_t>(fp[si+2]) << 24)
                                                 | (static_cast<uint32_t>(fp[si+3]) << 16)
                                                 | (static_cast<uint32_t>(fp[si+4]) << 8)
                                                 | fp[si+5];
                            if (setting_id == 0x04) { // SETTINGS_INITIAL_WINDOW_SIZE
                                int32_t delta = static_cast<int32_t>(setting_val)
                                              - peer_stream_window;
                                peer_stream_window += delta;
                            }
                        }
                        send_all(h2cBuildSettingsAck());
                    } else if (ftype == H2C_FRAME_WINDOW_UPDATE && flen >= 4) {
                        uint32_t inc = ((static_cast<uint32_t>(fp[0]) & 0x7f) << 24)
                                     | (static_cast<uint32_t>(fp[1]) << 16)
                                     | (static_cast<uint32_t>(fp[2]) << 8)
                                     | fp[3];
                        if (inc > 0) {
                            if (fsid == 0)
                                peer_conn_window += static_cast<int32_t>(inc);
                            else if (fsid == stream_id)
                                peer_stream_window += static_cast<int32_t>(inc);
                        }
                    } else if (ftype == H2C_FRAME_PING && !(fflags & H2C_FLAG_ACK)
                               && flen == 8) {
                        std::string pp(reinterpret_cast<const char*>(fp), flen);
                        send_all(h2cBuildFrame(H2C_FRAME_PING, H2C_FLAG_ACK, 0, pp));
                    }
                    // Leave HEADERS / DATA in the buffer for the response phase
                    // but only consume control frames here.
                    foff += H2C_FRAME_HEADER_LEN + flen;
                }
                if (foff > 0)
                    raw.erase(raw.begin(), raw.begin() + static_cast<ptrdiff_t>(foff));
            };

            // Try a non-blocking read to pick up the server's SETTINGS
            // (which usually arrives right after TLS handshake).
            {
                netplus::buffer nbuf(BLOCKSIZE);
                size_t n = 0;
                try { n = _cltsock->recvData(nbuf, 0); } catch (...) {}
                if (n > 0) {
                    raw.insert(raw.end(),
                        reinterpret_cast<uint8_t*>(nbuf.data.buf),
                        reinterpret_cast<uint8_t*>(nbuf.data.buf) + n);
                    process_fc_frames();
                }
            }

            size_t offset = 0;
            while (offset < postBody->size()) {
                size_t chunk = std::min(postBody->size() - offset, H2C_MAX_FRAME_SIZE);
                int32_t allowed = std::min(peer_conn_window, peer_stream_window);

                // If window exhausted, block-read for WINDOW_UPDATE
                while (allowed <= 0) {
                    netplus::buffer nbuf(BLOCKSIZE);
                    size_t n = recv_blocking(nbuf);
                    if (n == 0) {
                        HTTPException he;
                        he[HTTPException::Error] << "HTTP/2: EOF waiting for WINDOW_UPDATE";
                        throw he;
                    }
                    raw.insert(raw.end(),
                        reinterpret_cast<uint8_t*>(nbuf.data.buf),
                        reinterpret_cast<uint8_t*>(nbuf.data.buf) + n);
                    process_fc_frames();
                    allowed = std::min(peer_conn_window, peer_stream_window);
                }

                size_t chunk = std::min({postBody->size() - offset,
                                         static_cast<size_t>(allowed),
                                         H2C_MAX_FRAME_SIZE});
                bool last = (offset + chunk >= postBody->size());
                uint8_t flags = last ? H2C_FLAG_END_STREAM : 0;
                std::string payload(postBody->data() + offset, chunk);
                std::string frame = h2cBuildFrame(H2C_FRAME_DATA, flags, stream_id, payload);
                send_all(frame);
                send_all(h2cBuildFrame(H2C_FRAME_DATA, flags, stream_id, payload));
                peer_conn_window -= static_cast<int32_t>(chunk);
                peer_stream_window -= static_cast<int32_t>(chunk);
                offset += chunk;
            }
        }

        // 4) Read response frames
        std::vector<uint8_t> raw;
        raw.reserve(16384);
        bool got_response_headers = false;
        bool got_end_stream = false;

@@ -2218,30 +2314,6 @@ const std::vector<char> libhttppp::HttpClient::_h2Request(
      throw ee;
    }

    // Send HTTP/3 control streams once per connection (RFC 9114 §6.2)
    if (!_h3ControlSent) {
      // 1) Control stream: unidirectional, stream type 0x00, then SETTINGS frame
      uint64_t ctrl_id = q->openStream(false);
      std::vector<uint8_t> ctrl_payload;
      ctrl_payload.push_back(0x00); // stream type: control
      // SETTINGS frame: type=0x04, length=0 (accept all defaults)
      ctrl_payload.push_back(0x04); // frame type
      ctrl_payload.push_back(0x00); // frame length (empty)
      q->sendStreamData(ctrl_id, ctrl_payload, false);

      // 2) QPACK encoder stream: unidirectional, stream type 0x02
      uint64_t enc_id = q->openStream(false);
      std::vector<uint8_t> enc_payload = {0x02};
      q->sendStreamData(enc_id, enc_payload, false);

      // 3) QPACK decoder stream: unidirectional, stream type 0x03
      uint64_t dec_id = q->openStream(false);
      std::vector<uint8_t> dec_payload = {0x03};
      q->sendStreamData(dec_id, dec_payload, false);

      _h3ControlSent = true;
    }

    const int kMaxRedirects = 1;
    int redirects = 0;

+23 −1
Original line number Diff line number Diff line
@@ -138,7 +138,6 @@ namespace libhttppp {
      bool _h2PrefaceSent = false;       // true after connection preface sent

      // HTTP/3 client helpers
      bool _h3ControlSent = false;       // true after H3 control streams sent
      uint32_t _h2NextStreamId = 1;     // next client-initiated stream ID (odd)
      std::unique_ptr<hpack::Decoder> _h2Decoder; // persistent HPACK decoder for connection
      const std::vector<char> _h2Request(const std::string &method,
@@ -371,6 +370,22 @@ namespace libhttppp {
      size_t      offset = 0; // how far into body we've sent
    };

    // Active streaming response state — lives on the connection's H2State
    // so Http2RequestEvent can resume sending after WINDOW_UPDATE.
    struct H2StreamingResponse {
      uint32_t    streamId = 0;
      size_t      contentLength = 0;
      size_t      totalSent = 0;
      std::string pendingData;     // buffered DATA not yet framed
      size_t      pendingOffset = 0;
      std::unique_ptr<HttpRequest> tempreq; // per-stream request for ResponseEvent
      int         tid = 0;
      ULONG_PTR   args = 0;
      size_t      emptyCount = 0;
      unsigned int backoffMs = 1;
      bool        finished = false;
    };

    struct H2PendingIncoming {
      std::vector<hpack::HeaderField> headers;
      std::string body;
@@ -382,11 +397,18 @@ namespace libhttppp {
    struct H2State {
      uint32_t          streamId = 0;
      bool              headersSent = false;
      bool              serverPrefaceSent = false;
      size_t            expectedContentLength = 0;
      size_t            bodyBytesSent = 0;
      std::deque<H2PendingResponse>            pendingResponses;
      std::map<uint32_t, H2PendingIncoming>    pendingIncoming;
      hpack::Decoder                           hpackDecoder;
      // Peer flow-control windows (RFC 7540 §6.9)
      int32_t peerConnWindow = 65535;     // connection-level
      int32_t peerInitialStreamWindow = 65535; // from peer SETTINGS
      std::map<uint32_t, int32_t> peerStreamWindows; // per-stream
      // Active streaming responses (one per stream)
      std::map<uint32_t, std::shared_ptr<H2StreamingResponse>> activeStreams;
    };

    // Lazily allocated when the connection is upgraded to HTTP/2.
+346 −195

File changed.

Preview size limit exceeded, changes collapsed.

+2 −2
Original line number Diff line number Diff line
@@ -79,14 +79,14 @@ namespace libhttppp {
        };
        std::mutex                            _h3BufferMutex;
        std::map<uint64_t, H3StreamBuffer>    _h3StreamBuffers;
        std::set<netplus::socket*>            _h3ControlStreamsSent;
        std::atomic<int>                      _h3NextTid{0};
        void _initH3ControlStreams(netplus::quic *q);
        void _dispatchH2Stream(HttpRequest &cureq, std::string &out,
                               uint32_t sid,
                               const std::vector<hpack::HeaderField> &decoded,
                               const std::string &reqBody,
                               const int tid, ULONG_PTR args);
        void _resumeH2Streams(HttpRequest &cureq, std::string &out,
                              const int tid, ULONG_PTR args);
        void RequestEvent(netplus::con &curcon, const int tid, ULONG_PTR args);
        void ResponseEvent(netplus::con &curcon,const int tid,ULONG_PTR args);
        void ConnectEvent(netplus::con &curcon,const int tid,ULONG_PTR args);