Loading src/http.cpp +290 −3 Original line number Diff line number Diff line Loading @@ -90,7 +90,6 @@ libhttppp::HttpUrl::HttpUrl(const std::string& url, bool http3) { exp[HTTPException::Error] << "Httpurl: protocol not supported!"; throw exp; } _path = "/"; const char* host_start = p; Loading Loading @@ -216,6 +215,20 @@ libhttppp::HttpClient::HttpClient(const HttpUrl& desturl) } } bool libhttppp::HttpClient::tryHttp3First(){ try { _cltsock = std::make_unique<netplus::quic>(); _cltsock->connect(_url.getHost(), _url.getPort(), false); _cltsock->setNonBlock(); _isH2 = false; return true; } catch (netplus::NetException&) { _cltsock.reset(); _isH2 = false; return false; } } void libhttppp::HttpClient::resetConnection(){ if (_url.getProtocol() == HttpUrl::HTTP3) { _cltsock = std::make_unique<netplus::quic>(); Loading @@ -223,6 +236,9 @@ void libhttppp::HttpClient::resetConnection(){ _cltsock->setNonBlock(); _isH2 = false; } else if (_url.getProtocol() == HttpUrl::HTTPS) { if (tryHttp3First()) { return; } std::map<std::string, netplus::ssl::CertificateBundle> certs; auto sslsock = std::make_unique<netplus::ssl>(certs,-1); Loading @@ -249,7 +265,13 @@ void libhttppp::HttpClient::resetConnection(){ void libhttppp::HttpClient::reconnect(){ if (_url.getProtocol() == HttpUrl::HTTP3) { _cltsock = std::make_unique<netplus::quic>(); _cltsock->connect(_url.getHost(), _url.getPort(), false); _cltsock->setNonBlock(); _isH2 = false; } else if(_url.getProtocol() == HttpUrl::HTTPS) { if (tryHttp3First()) { return; } std::map<std::string, netplus::ssl::CertificateBundle> certs; auto sslsock = std::make_unique<netplus::ssl>(certs,-1); Loading @@ -258,11 +280,17 @@ void libhttppp::HttpClient::reconnect(){ std::string("\x02h2\x08http/1.1", 12); _cltsock = std::move(sslsock); _cltsock->connect(_url.getHost(), _url.getPort(), false); _cltsock->setNonBlock(); const std::string &alpn = dynamic_cast<netplus::ssl*>(_cltsock.get())->getSelectedAlpn(); _isH2 = (alpn == "h2"); } else { _cltsock = std::make_unique<netplus::tcp>(-1); } _cltsock->connect(_url.getHost().c_str(), _url.getPort()); _cltsock->connect(_url.getHost(), _url.getPort(), false); _cltsock->setNonBlock(); _isH2 = false; } } static inline std::string tolower_copy(std::string s) { Loading Loading @@ -300,6 +328,76 @@ static std::string normalize_path(const std::string &location, const std::string return base.substr(0, slash + 1) + location; } // --------------- HTTP/3 client frame helpers --------------- static size_t h3EncodeVarInt(uint64_t value, uint8_t *out) { if (value < 64) { out[0] = static_cast<uint8_t>(value); return 1; } if (value < 16384) { out[0] = static_cast<uint8_t>(0x40 | (value >> 8)); out[1] = static_cast<uint8_t>(value & 0xff); return 2; } if (value < 1073741824) { out[0] = static_cast<uint8_t>(0x80 | (value >> 24)); out[1] = static_cast<uint8_t>((value >> 16) & 0xff); out[2] = static_cast<uint8_t>((value >> 8) & 0xff); out[3] = static_cast<uint8_t>(value & 0xff); return 4; } out[0] = static_cast<uint8_t>(0xc0 | (value >> 56)); out[1] = static_cast<uint8_t>((value >> 48) & 0xff); out[2] = static_cast<uint8_t>((value >> 40) & 0xff); out[3] = static_cast<uint8_t>((value >> 32) & 0xff); out[4] = static_cast<uint8_t>((value >> 24) & 0xff); out[5] = static_cast<uint8_t>((value >> 16) & 0xff); out[6] = static_cast<uint8_t>((value >> 8) & 0xff); out[7] = static_cast<uint8_t>(value & 0xff); return 8; } static uint64_t h3DecodeVarInt(const uint8_t *data, size_t len, size_t &bytes_read) { if (len == 0) { bytes_read = 0; return 0; } uint8_t prefix = data[0] >> 6; uint64_t value = data[0] & 0x3f; if (prefix == 0) { bytes_read = 1; return value; } if (prefix == 1) { if (len < 2) { bytes_read = 0; return 0; } bytes_read = 2; return (value << 8) | data[1]; } if (prefix == 2) { if (len < 4) { bytes_read = 0; return 0; } bytes_read = 4; value = (value << 8) | data[1]; value = (value << 8) | data[2]; value = (value << 8) | data[3]; return value; } if (len < 8) { bytes_read = 0; return 0; } bytes_read = 8; for (int i = 1; i < 8; ++i) { value = (value << 8) | data[i]; } return value; } static void h3AppendFrame(std::vector<uint8_t> &out, uint64_t type, const std::vector<uint8_t> &payload) { uint8_t buf[8]; size_t tlen = h3EncodeVarInt(type, buf); out.insert(out.end(), buf, buf + tlen); size_t plen = h3EncodeVarInt(payload.size(), buf); out.insert(out.end(), buf, buf + plen); out.insert(out.end(), payload.begin(), payload.end()); } // --------------- HTTP/2 client frame helpers --------------- static constexpr uint8_t H2C_FRAME_DATA = 0x00; static constexpr uint8_t H2C_FRAME_HEADERS = 0x01; Loading Loading @@ -565,6 +663,179 @@ const std::vector<char> libhttppp::HttpClient::_h2Request( } } // Generic HTTP/3 client request (GET/POST/PUT/DELETE) const std::vector<char> libhttppp::HttpClient::_h3Request( const std::string &method, HttpRequest &nreq, const std::vector<char> *postBody) { std::vector<char> ret; auto *q = dynamic_cast<netplus::quic*>(_cltsock.get()); if (!q) { HTTPException ee; ee[HTTPException::Error] << "HTTP/3 requested on non-QUIC socket"; throw ee; } const int kMaxRedirects = 1; int redirects = 0; for (;;) { std::string path = nreq.getRequestURL(); if (path.empty()) path = _url.getPath(); std::string scheme = (_url.getProtocol() == HttpUrl::HTTP) ? "http" : "https"; std::stringstream authority; authority << _url.getHost() << ":" << _url.getPort(); bool include_content_length = false; size_t content_length = 0; std::vector<qpack::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" || key == "connection") continue; for (HttpHeader::HeaderData::Values *v = hd->getfirstValue(); v; v = v->nextvalue()) { if (key == "content-length") { include_content_length = true; content_length = static_cast<size_t>(std::stoul(v->getvalue())); } else { extra.push_back({key, v->getvalue(), false}); } } } if (postBody) { include_content_length = true; content_length = postBody->size(); } std::vector<uint8_t> headers = qpack::Encoder::encodeRequestHeaders( method, scheme, authority.str(), path, extra, include_content_length, content_length); std::vector<uint8_t> stream_payload; h3AppendFrame(stream_payload, 0x01, headers); if (postBody && !postBody->empty()) { std::vector<uint8_t> body(postBody->begin(), postBody->end()); h3AppendFrame(stream_payload, 0x00, body); } uint64_t stream_id = q->openStream(true); q->sendStreamData(stream_id, stream_payload, true); std::vector<uint8_t> raw; std::vector<char> body; bool got_headers = false; size_t expected_body = static_cast<size_t>(-1); int status_code = 0; std::string location; auto last_data = std::chrono::steady_clock::now(); const auto idle_timeout = std::chrono::seconds(15); for (;;) { uint8_t buf[4096]; size_t n = q->recvStreamData(stream_id, buf, sizeof(buf)); if (n > 0) { raw.insert(raw.end(), buf, buf + n); last_data = std::chrono::steady_clock::now(); } else { try { netplus::buffer rb(65535); q->recvData(rb, MSG_DONTWAIT); } catch (netplus::NetException &e) { if (e.getErrorType() != netplus::NetException::Note) { HTTPException ee; ee[HTTPException::Error] << e.what(); throw ee; } } } size_t pos = 0; while (pos < raw.size()) { size_t bytes = 0; uint64_t frame_type = h3DecodeVarInt(raw.data() + pos, raw.size() - pos, bytes); if (bytes == 0) break; pos += bytes; if (pos >= raw.size()) break; uint64_t frame_len = h3DecodeVarInt(raw.data() + pos, raw.size() - pos, bytes); if (bytes == 0) break; pos += bytes; if (pos + frame_len > raw.size()) break; if (frame_type == 0x01) { auto decoded = qpack::Decoder::decode(raw.data() + pos, frame_len); for (const auto &h : decoded) { if (h.name == ":status") { try { status_code = std::stoi(h.value); } catch (...) { status_code = 0; } } else if (h.name == "location") { location = h.value; } else if (h.name == "content-length") { try { expected_body = static_cast<size_t>(std::stoul(h.value)); } catch (...) {} } } got_headers = true; } else if (frame_type == 0x00) { body.insert(body.end(), raw.begin() + static_cast<ptrdiff_t>(pos), raw.begin() + static_cast<ptrdiff_t>(pos + frame_len)); } pos += frame_len; } if (pos > 0) { raw.erase(raw.begin(), raw.begin() + static_cast<ptrdiff_t>(pos)); } if (got_headers && expected_body != static_cast<size_t>(-1) && body.size() >= expected_body) { ret = std::move(body); break; } if (got_headers && expected_body == static_cast<size_t>(-1) && q->isStreamComplete(stream_id) && raw.empty()) { ret = std::move(body); break; } if (std::chrono::steady_clock::now() - last_data > idle_timeout) { HTTPException ee; ee[HTTPException::Error] << "HTTP/3 response timeout"; throw ee; } sleep_note(); } if (is_redirect_status(status_code) && redirects < kMaxRedirects && !location.empty()) { ++redirects; if (location.rfind("http://", 0) == 0 || location.rfind("https://", 0) == 0) { _url = HttpUrl(location, false); nreq.setRequestURL(_url.getPath()); } else { std::string base = nreq.getRequestURL().empty() ? _url.getPath() : nreq.getRequestURL(); nreq.setRequestURL(normalize_path(location, base)); } resetConnection(); q = dynamic_cast<netplus::quic*>(_cltsock.get()); if (!q) { return ret; } continue; } return ret; } } const std::vector<char> libhttppp::HttpClient::Get(libhttppp::HttpRequest &nreq) { std::vector<char> ret; Loading @@ -587,6 +858,10 @@ const std::vector<char> libhttppp::HttpClient::Get(libhttppp::HttpRequest &nreq) if (nreq.getRequestURL().empty()) nreq.setRequestURL(_url.getPath()); if (dynamic_cast<netplus::quic*>(_cltsock.get())) { return _h3Request("GET", nreq); } // HTTP/2: delegate to binary framing path if (_isH2) { return _h2Request("GET", nreq); Loading Loading @@ -913,6 +1188,10 @@ const std::vector<char> libhttppp::HttpClient::Post(libhttppp::HttpRequest &nreq // Always send content-length for POST body nreq.setHeaderData("content-length")->push_back(std::to_string(post.size())); if (dynamic_cast<netplus::quic*>(_cltsock.get())) { return _h3Request("POST", nreq, &post); } // HTTP/2: delegate to binary framing path if (_isH2) { return _h2Request("POST", nreq, &post); Loading Loading @@ -1250,6 +1529,10 @@ const std::vector<char> libhttppp::HttpClient::Delete(libhttppp::HttpRequest &nr if (nreq.getRequestURL().empty()) nreq.setRequestURL(_url.getPath()); if (dynamic_cast<netplus::quic*>(_cltsock.get())) { return _h3Request("DELETE", nreq); } // HTTP/2: delegate to binary framing path if (_isH2) { return _h2Request("DELETE", nreq); Loading Loading @@ -1549,6 +1832,10 @@ const std::vector<char> libhttppp::HttpClient::Put(libhttppp::HttpRequest &nreq, nreq.setHeaderData("content-length")->push_back(std::to_string(put.size())); if (dynamic_cast<netplus::quic*>(_cltsock.get())) { return _h3Request("PUT", nreq, &put); } if (_isH2) { return _h2Request("PUT", nreq, &put); } Loading src/http.h +4 −0 Original line number Diff line number Diff line Loading @@ -93,6 +93,7 @@ namespace libhttppp { const std::vector<char> Delete(HttpRequest &nreq); private: void resetConnection(); bool tryHttp3First(); int readchunk(const char *data,int datasize,int &pos); // HTTP/2 client helpers Loading @@ -100,6 +101,9 @@ namespace libhttppp { const std::vector<char> _h2Request(const std::string &method, HttpRequest &nreq, const std::vector<char> *postBody = nullptr); const std::vector<char> _h3Request(const std::string &method, HttpRequest &nreq, const std::vector<char> *postBody = nullptr); private: HttpUrl _url; std::unique_ptr<netplus::socket> _cltsock; Loading src/qpack.cpp +53 −0 Original line number Diff line number Diff line Loading @@ -336,4 +336,57 @@ std::vector<uint8_t> Encoder::encodeResponseHeaders(uint16_t status_code, return out; } std::vector<uint8_t> Encoder::encodeRequestHeaders(const std::string &method, const std::string &scheme, const std::string &authority, const std::string &path, const std::vector<HeaderField> &extra, bool include_content_length, size_t content_length) { std::vector<uint8_t> out; // Section prefix: Required Insert Count = 0, S=0, Delta Base = 0 out.push_back(0x00); out.push_back(0x00); // :method if (method == "GET") encodeIndexedStatic(out, 17); else if (method == "POST") encodeIndexedStatic(out, 20); else if (method == "PUT") encodeIndexedStatic(out, 21); else if (method == "DELETE") encodeIndexedStatic(out, 16); else { // :method name reference at index 15 encodeLiteralStaticNameRef(out, 15, method); } // :scheme if (scheme == "https") encodeIndexedStatic(out, 23); else encodeIndexedStatic(out, 22); // :path (name index 1) if (path == "/") { encodeIndexedStatic(out, 1); } else { encodeLiteralStaticNameRef(out, 1, path); } // :authority (name index 0) if (!authority.empty()) { encodeLiteralStaticNameRef(out, 0, authority); } if (include_content_length) { encodeLiteralStaticNameRef(out, 4, std::to_string(content_length)); } for (const auto &h : extra) { if (h.name.empty()) { continue; } encodeLiteralName(out, h.name, h.value); } return out; } } // namespace libhttppp::qpack src/qpack.h +7 −0 Original line number Diff line number Diff line Loading @@ -46,6 +46,13 @@ public: const std::string &content_type, size_t content_length, const std::vector<HeaderField> &extra = {}); static std::vector<uint8_t> encodeRequestHeaders(const std::string &method, const std::string &scheme, const std::string &authority, const std::string &path, const std::vector<HeaderField> &extra = {}, bool include_content_length = false, size_t content_length = 0); private: static void encodeInt(std::vector<uint8_t> &out, uint8_t prefix_byte, Loading Loading
src/http.cpp +290 −3 Original line number Diff line number Diff line Loading @@ -90,7 +90,6 @@ libhttppp::HttpUrl::HttpUrl(const std::string& url, bool http3) { exp[HTTPException::Error] << "Httpurl: protocol not supported!"; throw exp; } _path = "/"; const char* host_start = p; Loading Loading @@ -216,6 +215,20 @@ libhttppp::HttpClient::HttpClient(const HttpUrl& desturl) } } bool libhttppp::HttpClient::tryHttp3First(){ try { _cltsock = std::make_unique<netplus::quic>(); _cltsock->connect(_url.getHost(), _url.getPort(), false); _cltsock->setNonBlock(); _isH2 = false; return true; } catch (netplus::NetException&) { _cltsock.reset(); _isH2 = false; return false; } } void libhttppp::HttpClient::resetConnection(){ if (_url.getProtocol() == HttpUrl::HTTP3) { _cltsock = std::make_unique<netplus::quic>(); Loading @@ -223,6 +236,9 @@ void libhttppp::HttpClient::resetConnection(){ _cltsock->setNonBlock(); _isH2 = false; } else if (_url.getProtocol() == HttpUrl::HTTPS) { if (tryHttp3First()) { return; } std::map<std::string, netplus::ssl::CertificateBundle> certs; auto sslsock = std::make_unique<netplus::ssl>(certs,-1); Loading @@ -249,7 +265,13 @@ void libhttppp::HttpClient::resetConnection(){ void libhttppp::HttpClient::reconnect(){ if (_url.getProtocol() == HttpUrl::HTTP3) { _cltsock = std::make_unique<netplus::quic>(); _cltsock->connect(_url.getHost(), _url.getPort(), false); _cltsock->setNonBlock(); _isH2 = false; } else if(_url.getProtocol() == HttpUrl::HTTPS) { if (tryHttp3First()) { return; } std::map<std::string, netplus::ssl::CertificateBundle> certs; auto sslsock = std::make_unique<netplus::ssl>(certs,-1); Loading @@ -258,11 +280,17 @@ void libhttppp::HttpClient::reconnect(){ std::string("\x02h2\x08http/1.1", 12); _cltsock = std::move(sslsock); _cltsock->connect(_url.getHost(), _url.getPort(), false); _cltsock->setNonBlock(); const std::string &alpn = dynamic_cast<netplus::ssl*>(_cltsock.get())->getSelectedAlpn(); _isH2 = (alpn == "h2"); } else { _cltsock = std::make_unique<netplus::tcp>(-1); } _cltsock->connect(_url.getHost().c_str(), _url.getPort()); _cltsock->connect(_url.getHost(), _url.getPort(), false); _cltsock->setNonBlock(); _isH2 = false; } } static inline std::string tolower_copy(std::string s) { Loading Loading @@ -300,6 +328,76 @@ static std::string normalize_path(const std::string &location, const std::string return base.substr(0, slash + 1) + location; } // --------------- HTTP/3 client frame helpers --------------- static size_t h3EncodeVarInt(uint64_t value, uint8_t *out) { if (value < 64) { out[0] = static_cast<uint8_t>(value); return 1; } if (value < 16384) { out[0] = static_cast<uint8_t>(0x40 | (value >> 8)); out[1] = static_cast<uint8_t>(value & 0xff); return 2; } if (value < 1073741824) { out[0] = static_cast<uint8_t>(0x80 | (value >> 24)); out[1] = static_cast<uint8_t>((value >> 16) & 0xff); out[2] = static_cast<uint8_t>((value >> 8) & 0xff); out[3] = static_cast<uint8_t>(value & 0xff); return 4; } out[0] = static_cast<uint8_t>(0xc0 | (value >> 56)); out[1] = static_cast<uint8_t>((value >> 48) & 0xff); out[2] = static_cast<uint8_t>((value >> 40) & 0xff); out[3] = static_cast<uint8_t>((value >> 32) & 0xff); out[4] = static_cast<uint8_t>((value >> 24) & 0xff); out[5] = static_cast<uint8_t>((value >> 16) & 0xff); out[6] = static_cast<uint8_t>((value >> 8) & 0xff); out[7] = static_cast<uint8_t>(value & 0xff); return 8; } static uint64_t h3DecodeVarInt(const uint8_t *data, size_t len, size_t &bytes_read) { if (len == 0) { bytes_read = 0; return 0; } uint8_t prefix = data[0] >> 6; uint64_t value = data[0] & 0x3f; if (prefix == 0) { bytes_read = 1; return value; } if (prefix == 1) { if (len < 2) { bytes_read = 0; return 0; } bytes_read = 2; return (value << 8) | data[1]; } if (prefix == 2) { if (len < 4) { bytes_read = 0; return 0; } bytes_read = 4; value = (value << 8) | data[1]; value = (value << 8) | data[2]; value = (value << 8) | data[3]; return value; } if (len < 8) { bytes_read = 0; return 0; } bytes_read = 8; for (int i = 1; i < 8; ++i) { value = (value << 8) | data[i]; } return value; } static void h3AppendFrame(std::vector<uint8_t> &out, uint64_t type, const std::vector<uint8_t> &payload) { uint8_t buf[8]; size_t tlen = h3EncodeVarInt(type, buf); out.insert(out.end(), buf, buf + tlen); size_t plen = h3EncodeVarInt(payload.size(), buf); out.insert(out.end(), buf, buf + plen); out.insert(out.end(), payload.begin(), payload.end()); } // --------------- HTTP/2 client frame helpers --------------- static constexpr uint8_t H2C_FRAME_DATA = 0x00; static constexpr uint8_t H2C_FRAME_HEADERS = 0x01; Loading Loading @@ -565,6 +663,179 @@ const std::vector<char> libhttppp::HttpClient::_h2Request( } } // Generic HTTP/3 client request (GET/POST/PUT/DELETE) const std::vector<char> libhttppp::HttpClient::_h3Request( const std::string &method, HttpRequest &nreq, const std::vector<char> *postBody) { std::vector<char> ret; auto *q = dynamic_cast<netplus::quic*>(_cltsock.get()); if (!q) { HTTPException ee; ee[HTTPException::Error] << "HTTP/3 requested on non-QUIC socket"; throw ee; } const int kMaxRedirects = 1; int redirects = 0; for (;;) { std::string path = nreq.getRequestURL(); if (path.empty()) path = _url.getPath(); std::string scheme = (_url.getProtocol() == HttpUrl::HTTP) ? "http" : "https"; std::stringstream authority; authority << _url.getHost() << ":" << _url.getPort(); bool include_content_length = false; size_t content_length = 0; std::vector<qpack::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" || key == "connection") continue; for (HttpHeader::HeaderData::Values *v = hd->getfirstValue(); v; v = v->nextvalue()) { if (key == "content-length") { include_content_length = true; content_length = static_cast<size_t>(std::stoul(v->getvalue())); } else { extra.push_back({key, v->getvalue(), false}); } } } if (postBody) { include_content_length = true; content_length = postBody->size(); } std::vector<uint8_t> headers = qpack::Encoder::encodeRequestHeaders( method, scheme, authority.str(), path, extra, include_content_length, content_length); std::vector<uint8_t> stream_payload; h3AppendFrame(stream_payload, 0x01, headers); if (postBody && !postBody->empty()) { std::vector<uint8_t> body(postBody->begin(), postBody->end()); h3AppendFrame(stream_payload, 0x00, body); } uint64_t stream_id = q->openStream(true); q->sendStreamData(stream_id, stream_payload, true); std::vector<uint8_t> raw; std::vector<char> body; bool got_headers = false; size_t expected_body = static_cast<size_t>(-1); int status_code = 0; std::string location; auto last_data = std::chrono::steady_clock::now(); const auto idle_timeout = std::chrono::seconds(15); for (;;) { uint8_t buf[4096]; size_t n = q->recvStreamData(stream_id, buf, sizeof(buf)); if (n > 0) { raw.insert(raw.end(), buf, buf + n); last_data = std::chrono::steady_clock::now(); } else { try { netplus::buffer rb(65535); q->recvData(rb, MSG_DONTWAIT); } catch (netplus::NetException &e) { if (e.getErrorType() != netplus::NetException::Note) { HTTPException ee; ee[HTTPException::Error] << e.what(); throw ee; } } } size_t pos = 0; while (pos < raw.size()) { size_t bytes = 0; uint64_t frame_type = h3DecodeVarInt(raw.data() + pos, raw.size() - pos, bytes); if (bytes == 0) break; pos += bytes; if (pos >= raw.size()) break; uint64_t frame_len = h3DecodeVarInt(raw.data() + pos, raw.size() - pos, bytes); if (bytes == 0) break; pos += bytes; if (pos + frame_len > raw.size()) break; if (frame_type == 0x01) { auto decoded = qpack::Decoder::decode(raw.data() + pos, frame_len); for (const auto &h : decoded) { if (h.name == ":status") { try { status_code = std::stoi(h.value); } catch (...) { status_code = 0; } } else if (h.name == "location") { location = h.value; } else if (h.name == "content-length") { try { expected_body = static_cast<size_t>(std::stoul(h.value)); } catch (...) {} } } got_headers = true; } else if (frame_type == 0x00) { body.insert(body.end(), raw.begin() + static_cast<ptrdiff_t>(pos), raw.begin() + static_cast<ptrdiff_t>(pos + frame_len)); } pos += frame_len; } if (pos > 0) { raw.erase(raw.begin(), raw.begin() + static_cast<ptrdiff_t>(pos)); } if (got_headers && expected_body != static_cast<size_t>(-1) && body.size() >= expected_body) { ret = std::move(body); break; } if (got_headers && expected_body == static_cast<size_t>(-1) && q->isStreamComplete(stream_id) && raw.empty()) { ret = std::move(body); break; } if (std::chrono::steady_clock::now() - last_data > idle_timeout) { HTTPException ee; ee[HTTPException::Error] << "HTTP/3 response timeout"; throw ee; } sleep_note(); } if (is_redirect_status(status_code) && redirects < kMaxRedirects && !location.empty()) { ++redirects; if (location.rfind("http://", 0) == 0 || location.rfind("https://", 0) == 0) { _url = HttpUrl(location, false); nreq.setRequestURL(_url.getPath()); } else { std::string base = nreq.getRequestURL().empty() ? _url.getPath() : nreq.getRequestURL(); nreq.setRequestURL(normalize_path(location, base)); } resetConnection(); q = dynamic_cast<netplus::quic*>(_cltsock.get()); if (!q) { return ret; } continue; } return ret; } } const std::vector<char> libhttppp::HttpClient::Get(libhttppp::HttpRequest &nreq) { std::vector<char> ret; Loading @@ -587,6 +858,10 @@ const std::vector<char> libhttppp::HttpClient::Get(libhttppp::HttpRequest &nreq) if (nreq.getRequestURL().empty()) nreq.setRequestURL(_url.getPath()); if (dynamic_cast<netplus::quic*>(_cltsock.get())) { return _h3Request("GET", nreq); } // HTTP/2: delegate to binary framing path if (_isH2) { return _h2Request("GET", nreq); Loading Loading @@ -913,6 +1188,10 @@ const std::vector<char> libhttppp::HttpClient::Post(libhttppp::HttpRequest &nreq // Always send content-length for POST body nreq.setHeaderData("content-length")->push_back(std::to_string(post.size())); if (dynamic_cast<netplus::quic*>(_cltsock.get())) { return _h3Request("POST", nreq, &post); } // HTTP/2: delegate to binary framing path if (_isH2) { return _h2Request("POST", nreq, &post); Loading Loading @@ -1250,6 +1529,10 @@ const std::vector<char> libhttppp::HttpClient::Delete(libhttppp::HttpRequest &nr if (nreq.getRequestURL().empty()) nreq.setRequestURL(_url.getPath()); if (dynamic_cast<netplus::quic*>(_cltsock.get())) { return _h3Request("DELETE", nreq); } // HTTP/2: delegate to binary framing path if (_isH2) { return _h2Request("DELETE", nreq); Loading Loading @@ -1549,6 +1832,10 @@ const std::vector<char> libhttppp::HttpClient::Put(libhttppp::HttpRequest &nreq, nreq.setHeaderData("content-length")->push_back(std::to_string(put.size())); if (dynamic_cast<netplus::quic*>(_cltsock.get())) { return _h3Request("PUT", nreq, &put); } if (_isH2) { return _h2Request("PUT", nreq, &put); } Loading
src/http.h +4 −0 Original line number Diff line number Diff line Loading @@ -93,6 +93,7 @@ namespace libhttppp { const std::vector<char> Delete(HttpRequest &nreq); private: void resetConnection(); bool tryHttp3First(); int readchunk(const char *data,int datasize,int &pos); // HTTP/2 client helpers Loading @@ -100,6 +101,9 @@ namespace libhttppp { const std::vector<char> _h2Request(const std::string &method, HttpRequest &nreq, const std::vector<char> *postBody = nullptr); const std::vector<char> _h3Request(const std::string &method, HttpRequest &nreq, const std::vector<char> *postBody = nullptr); private: HttpUrl _url; std::unique_ptr<netplus::socket> _cltsock; Loading
src/qpack.cpp +53 −0 Original line number Diff line number Diff line Loading @@ -336,4 +336,57 @@ std::vector<uint8_t> Encoder::encodeResponseHeaders(uint16_t status_code, return out; } std::vector<uint8_t> Encoder::encodeRequestHeaders(const std::string &method, const std::string &scheme, const std::string &authority, const std::string &path, const std::vector<HeaderField> &extra, bool include_content_length, size_t content_length) { std::vector<uint8_t> out; // Section prefix: Required Insert Count = 0, S=0, Delta Base = 0 out.push_back(0x00); out.push_back(0x00); // :method if (method == "GET") encodeIndexedStatic(out, 17); else if (method == "POST") encodeIndexedStatic(out, 20); else if (method == "PUT") encodeIndexedStatic(out, 21); else if (method == "DELETE") encodeIndexedStatic(out, 16); else { // :method name reference at index 15 encodeLiteralStaticNameRef(out, 15, method); } // :scheme if (scheme == "https") encodeIndexedStatic(out, 23); else encodeIndexedStatic(out, 22); // :path (name index 1) if (path == "/") { encodeIndexedStatic(out, 1); } else { encodeLiteralStaticNameRef(out, 1, path); } // :authority (name index 0) if (!authority.empty()) { encodeLiteralStaticNameRef(out, 0, authority); } if (include_content_length) { encodeLiteralStaticNameRef(out, 4, std::to_string(content_length)); } for (const auto &h : extra) { if (h.name.empty()) { continue; } encodeLiteralName(out, h.name, h.value); } return out; } } // namespace libhttppp::qpack
src/qpack.h +7 −0 Original line number Diff line number Diff line Loading @@ -46,6 +46,13 @@ public: const std::string &content_type, size_t content_length, const std::vector<HeaderField> &extra = {}); static std::vector<uint8_t> encodeRequestHeaders(const std::string &method, const std::string &scheme, const std::string &authority, const std::string &path, const std::vector<HeaderField> &extra = {}, bool include_content_length = false, size_t content_length = 0); private: static void encodeInt(std::vector<uint8_t> &out, uint8_t prefix_byte, Loading