Commit 7194e54b authored by jan.koester's avatar jan.koester
Browse files

tes

parent 2fb81acb
Loading
Loading
Loading
Loading
+50 −0
Original line number Diff line number Diff line
@@ -449,4 +449,54 @@ std::string Encoder::encodeResponseHeaders(uint16_t status_code,
    return out;
}

std::string Encoder::encodeRequestHeaders(const std::string &method,
                                           const std::string &path,
                                           const std::string &scheme,
                                           const std::string &authority,
                                           const std::vector<HeaderField> &extra) {
    std::string out;

    // :method — static table index 2 = GET, 3 = POST
    if (method == "GET") {
        out.push_back(static_cast<char>(0x82)); // indexed: 2
    } else if (method == "POST") {
        out.push_back(static_cast<char>(0x83)); // indexed: 3
    } else {
        // :method name is at index 2, literal with name index
        appendLiteralWithNameIndex(out, 0x02, method, false);
    }

    // :path — static table index 4 = "/", 5 = "/index.html"
    if (path == "/") {
        out.push_back(static_cast<char>(0x84)); // indexed: 4
    } else if (path == "/index.html") {
        out.push_back(static_cast<char>(0x85)); // indexed: 5
    } else {
        // :path name is at index 4, literal with name index
        appendLiteralWithNameIndex(out, 0x04, path, false);
    }

    // :scheme — static table index 6 = "http", 7 = "https"
    if (scheme == "http") {
        out.push_back(static_cast<char>(0x86)); // indexed: 6
    } else if (scheme == "https") {
        out.push_back(static_cast<char>(0x87)); // indexed: 7
    } else {
        appendLiteralWithNameIndex(out, 0x06, scheme, false);
    }

    // :authority — static table index 1 (name only, no value)
    if (!authority.empty()) {
        appendLiteralWithNameIndex(out, 0x01, authority, false);
    }

    // Extra headers
    for (const auto &h : extra) {
        if (h.name.empty()) continue;
        appendLiteral(out, h.name, h.value, h.incremental_index);
    }

    return out;
}

} // namespace libhttppp::hpack
+6 −0
Original line number Diff line number Diff line
@@ -48,6 +48,12 @@ public:
                                              size_t content_length,
                                              const std::vector<HeaderField> &extra = {});

    static std::string encodeRequestHeaders(const std::string &method,
                                             const std::string &path,
                                             const std::string &scheme,
                                             const std::string &authority,
                                             const std::vector<HeaderField> &extra = {});

private:
    static void appendLiteral(std::string &out,
                              const std::string &name,
+285 −1
Original line number Diff line number Diff line
@@ -210,13 +210,29 @@ libhttppp::HttpClient::HttpClient(const HttpUrl& desturl)
    try {
        if (_url.getProtocol() == HttpUrl::HTTPS) {
            std::map<std::string, netplus::ssl::CertificateBundle> certs;
            _cltsock = std::make_unique<netplus::ssl>(certs,-1);
            auto sslsock = std::make_unique<netplus::ssl>(certs,-1);

            // Advertise h2 and http/1.1 via ALPN
            sslsock->getTls().client_alpn_protocols =
                std::string("\x02h2\x08http/1.1", 12);

            _cltsock = std::move(sslsock);
        } else {
            _cltsock = std::make_unique<netplus::tcp>(-1);
        }

        _cltsock->connect(_url.getHost(), _url.getPort(), false);

        // Check negotiated ALPN after TLS handshake
        if (auto *sslsock = dynamic_cast<netplus::ssl*>(_cltsock.get())) {
            const std::string &alpn = sslsock->getSelectedAlpn();
            std::cerr << "[HttpClient] ALPN negotiated: '" << alpn << "'" << std::endl;
            _isH2 = (alpn == "h2");
            std::cerr << "[HttpClient] _isH2 = " << _isH2 << std::endl;
        } else {
            std::cerr << "[HttpClient] Not an SSL socket" << std::endl;
        }

    } catch (netplus::NetException &e) {
        HTTPException ex;
        ex[HTTPException::Error] << "HttpClient ctor connect failed: " << e.what();
@@ -241,8 +257,271 @@ static inline void sleep_note() {
    std::this_thread::sleep_for(std::chrono::milliseconds(1));
}

// --------------- HTTP/2 client frame helpers ---------------
static constexpr uint8_t H2C_FRAME_DATA         = 0x00;
static constexpr uint8_t H2C_FRAME_HEADERS      = 0x01;
static constexpr uint8_t H2C_FRAME_SETTINGS     = 0x04;
static constexpr uint8_t H2C_FRAME_PING         = 0x06;
static constexpr uint8_t H2C_FRAME_GOAWAY       = 0x07;
static constexpr uint8_t H2C_FRAME_WINDOW_UPDATE = 0x08;

static constexpr uint8_t H2C_FLAG_END_STREAM  = 0x01;
static constexpr uint8_t H2C_FLAG_ACK         = 0x01;
static constexpr uint8_t H2C_FLAG_END_HEADERS = 0x04;

static constexpr size_t  H2C_FRAME_HEADER_LEN = 9;
static constexpr size_t  H2C_MAX_FRAME_SIZE   = 16384;

static const char H2C_CLIENT_PREFACE[] = "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n";
static constexpr size_t H2C_CLIENT_PREFACE_LEN = 24;

static std::string h2cBuildFrame(uint8_t type, uint8_t flags, uint32_t stream_id,
                                 const std::string &payload) {
    std::string f(9 + payload.size(), '\0');
    uint32_t len = static_cast<uint32_t>(payload.size());
    f[0] = static_cast<char>((len >> 16) & 0xff);
    f[1] = static_cast<char>((len >> 8) & 0xff);
    f[2] = static_cast<char>(len & 0xff);
    f[3] = static_cast<char>(type);
    f[4] = static_cast<char>(flags);
    f[5] = static_cast<char>((stream_id >> 24) & 0x7f);
    f[6] = static_cast<char>((stream_id >> 16) & 0xff);
    f[7] = static_cast<char>((stream_id >> 8) & 0xff);
    f[8] = static_cast<char>(stream_id & 0xff);
    if (!payload.empty())
        std::memcpy(&f[9], payload.data(), payload.size());
    return f;
}

static std::string h2cBuildSettings() {
    // Send empty SETTINGS (accept defaults)
    return h2cBuildFrame(H2C_FRAME_SETTINGS, 0, 0, "");
}

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

static std::string h2cBuildWindowUpdate(uint32_t stream_id, uint32_t increment) {
    std::string p(4, '\0');
    p[0] = static_cast<char>((increment >> 24) & 0x7f);
    p[1] = static_cast<char>((increment >> 16) & 0xff);
    p[2] = static_cast<char>((increment >> 8) & 0xff);
    p[3] = static_cast<char>(increment & 0xff);
    return h2cBuildFrame(H2C_FRAME_WINDOW_UPDATE, 0, stream_id, p);
}

// Generic HTTP/2 client request (GET or POST)
const std::vector<char> libhttppp::HttpClient::_h2Request(
    const std::string &method,
    HttpRequest &nreq,
    const std::vector<char> *postBody)
{
    std::vector<char> ret;

    try {
        // Helper: blocking recv
        auto recv_blocking = [&](netplus::buffer& b) -> size_t {
            for (;;) {
                try {
                    return _cltsock->recvData(b, 0);
                } catch (netplus::NetException& e) {
                    if (e.getErrorType() == netplus::NetException::Note) {
                        sleep_note();
                        continue;
                    }
                    throw;
                }
            }
        };

        auto send_all = [&](const std::string &data) {
            size_t off = 0;
            while (off < data.size()) {
                netplus::buffer buf(data.c_str() + off, data.size() - off);
                try {
                    off += _cltsock->sendData(buf, 0);
                } catch (netplus::NetException& e) {
                    if (e.getErrorType() == netplus::NetException::Note) {
                        sleep_note();
                        continue;
                    }
                    throw;
                }
            }
        };

        // 1) Send connection preface + SETTINGS
        std::string preface(H2C_CLIENT_PREFACE, H2C_CLIENT_PREFACE_LEN);
        preface += h2cBuildSettings();
        send_all(preface);

        // 2) Build HEADERS frame for stream 1
        std::string path = nreq.getRequestURL();
        if (path.empty()) path = _url.getPath();

        std::string scheme = (_url.getProtocol() == HttpUrl::HTTPS) ? "https" : "http";

        std::stringstream auth;
        auth << _url.getHost() << ":" << _url.getPort();

        // Collect extra headers from the request
        std::vector<hpack::HeaderField> extra;
        for (HttpHeader::HeaderData *hd = nreq.getfirstHeaderData(); hd; hd = hd->nextHeaderData()) {
            const std::string &key = hd->getkey();
            if (key.empty() || key[0] == ':') continue;
            if (key == "host") continue; // :authority replaces host in H2
            for (HttpHeader::HeaderData::Values *v = hd->getfirstValue(); v; v = v->nextvalue()) {
                extra.push_back({key, v->getvalue(), false});
            }
        }

        std::string hpack_block = hpack::Encoder::encodeRequestHeaders(
            method, path, scheme, auth.str(), extra);

        uint32_t stream_id = 1;
        bool end_stream = (postBody == nullptr || postBody->empty());
        uint8_t hdr_flags = H2C_FLAG_END_HEADERS;
        if (end_stream) hdr_flags |= H2C_FLAG_END_STREAM;

        std::string headers_frame = h2cBuildFrame(H2C_FRAME_HEADERS, hdr_flags,
                                                   stream_id, hpack_block);
        send_all(headers_frame);

        // 3) Send DATA frames for POST body if present
        if (postBody && !postBody->empty()) {
            size_t offset = 0;
            while (offset < postBody->size()) {
                size_t chunk = std::min(postBody->size() - offset, 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);
                offset += chunk;
            }
        }

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

        hpack::Decoder decoder;

        // Read enough data into raw buffer
        auto ensure_bytes = [&](size_t need) {
            while (raw.size() < need) {
                netplus::buffer buf(BLOCKSIZE);
                size_t n = recv_blocking(buf);
                if (n == 0) {
                    HTTPException he;
                    he[HTTPException::Error] << "HTTP/2: EOF while reading frames";
                    throw he;
                }
                raw.insert(raw.end(),
                           reinterpret_cast<uint8_t*>(buf.data.buf),
                           reinterpret_cast<uint8_t*>(buf.data.buf) + n);
            }
        };

        while (!got_end_stream) {
            // Read frame header (9 bytes)
            ensure_bytes(H2C_FRAME_HEADER_LEN);

            uint32_t frame_len = (static_cast<uint32_t>(raw[0]) << 16)
                               | (static_cast<uint32_t>(raw[1]) << 8)
                               | static_cast<uint32_t>(raw[2]);
            uint8_t frame_type = raw[3];
            uint8_t frame_flags = raw[4];
            uint32_t frame_stream = ((static_cast<uint32_t>(raw[5]) & 0x7f) << 24)
                                  | (static_cast<uint32_t>(raw[6]) << 16)
                                  | (static_cast<uint32_t>(raw[7]) << 8)
                                  | static_cast<uint32_t>(raw[8]);

            // Read frame payload
            ensure_bytes(H2C_FRAME_HEADER_LEN + frame_len);

            const uint8_t *payload = raw.data() + H2C_FRAME_HEADER_LEN;

            switch (frame_type) {
                case H2C_FRAME_SETTINGS: {
                    if (!(frame_flags & H2C_FLAG_ACK)) {
                        // Server SETTINGS — acknowledge it
                        send_all(h2cBuildSettingsAck());
                    }
                    break;
                }

                case H2C_FRAME_HEADERS: {
                    if (frame_stream == stream_id) {
                        // Decode HPACK response headers
                        auto headers = decoder.decode(payload, frame_len);
                        (void)headers; // We don't need to inspect status for now
                        got_response_headers = true;
                        if (frame_flags & H2C_FLAG_END_STREAM) {
                            got_end_stream = true;
                        }
                    }
                    break;
                }

                case H2C_FRAME_DATA: {
                    if (frame_stream == stream_id) {
                        ret.insert(ret.end(), payload, payload + frame_len);
                        if (frame_flags & H2C_FLAG_END_STREAM) {
                            got_end_stream = true;
                        }
                        // Send WINDOW_UPDATE for connection and stream
                        if (frame_len > 0) {
                            send_all(h2cBuildWindowUpdate(0, frame_len));
                            send_all(h2cBuildWindowUpdate(stream_id, frame_len));
                        }
                    }
                    break;
                }

                case H2C_FRAME_WINDOW_UPDATE:
                case H2C_FRAME_PING: {
                    if (frame_type == H2C_FRAME_PING && !(frame_flags & H2C_FLAG_ACK)) {
                        // Respond to PING
                        std::string ping_payload(reinterpret_cast<const char*>(payload), frame_len);
                        send_all(h2cBuildFrame(H2C_FRAME_PING, H2C_FLAG_ACK, 0, ping_payload));
                    }
                    break;
                }

                case H2C_FRAME_GOAWAY: {
                    got_end_stream = true;
                    break;
                }

                default:
                    // Ignore unknown frame types
                    break;
            }

            // Consume processed frame from buffer
            raw.erase(raw.begin(), raw.begin() + H2C_FRAME_HEADER_LEN + frame_len);
        }

        return ret;

    } catch (netplus::NetException &e) {
        HTTPException ee;
        ee[HTTPException::Error] << e.what();
        throw ee;
    }
}

const std::vector<char> libhttppp::HttpClient::Get(libhttppp::HttpRequest &nreq)
{
    // HTTP/2: delegate to binary framing path
    if (_isH2) {
        return _h2Request("GET", nreq);
    }

    std::vector<char> ret;

    try {
@@ -534,6 +813,11 @@ const std::vector<char> libhttppp::HttpClient::Get(libhttppp::HttpRequest &nreq)
const std::vector<char> libhttppp::HttpClient::Post(libhttppp::HttpRequest &nreq,
                                                    const std::vector<char> &post)
{
    // HTTP/2: delegate to binary framing path
    if (_isH2) {
        return _h2Request("POST", nreq, &post);
    }

    netplus::buffer data(BLOCKSIZE);
    std::vector<char> ret;

+6 −0
Original line number Diff line number Diff line
@@ -91,6 +91,12 @@ namespace libhttppp {
      const std::vector<char> Post(HttpRequest &nreq,const std::vector<char> &post);
  private:
      int readchunk(const char *data,int datasize,int &pos);

      // HTTP/2 client helpers
      bool _isH2 = false;
      const std::vector<char> _h2Request(const std::string &method,
                                          HttpRequest &nreq,
                                          const std::vector<char> *postBody = nullptr);
  private:
      HttpUrl _url;
      std::unique_ptr<netplus::socket> _cltsock;