Loading src/http.cpp +138 −50 Original line number Diff line number Diff line Loading @@ -208,6 +208,15 @@ const std::string & libhttppp::HttpUrl::getPath() const{ libhttppp::HttpClient::HttpClient(const HttpUrl& desturl) : _url(desturl){ try { resetConnection(); } catch (netplus::NetException &e) { HTTPException ex; ex[HTTPException::Error] << "HttpClient ctor connect failed: " << e.what(); throw ex; // !!! IMPORTANT: throw HTTPException, nicht NetException } } void libhttppp::HttpClient::resetConnection(){ if (_url.getProtocol() == HttpUrl::HTTPS) { std::map<std::string, netplus::ssl::CertificateBundle> certs; auto sslsock = std::make_unique<netplus::ssl>(certs,-1); Loading @@ -229,14 +238,9 @@ libhttppp::HttpClient::HttpClient(const HttpUrl& desturl) _isH2 = (alpn == "h2"); std::cerr << "[HttpClient] ALPN negotiated: '" << alpn << "' isH2=" << _isH2 << std::endl; } else { _isH2 = false; std::cerr << "[HttpClient] Not an SSL socket" << std::endl; } } catch (netplus::NetException &e) { HTTPException ex; ex[HTTPException::Error] << "HttpClient ctor connect failed: " << e.what(); throw ex; // !!! IMPORTANT: throw HTTPException, nicht NetException } } Loading @@ -256,6 +260,31 @@ static inline void sleep_note() { std::this_thread::sleep_for(std::chrono::milliseconds(1)); } static bool is_redirect_status(int code) { return code == 301 || code == 302 || code == 303 || code == 307 || code == 308; } static std::string extract_location_from_response(const libhttppp::HttpResponse &res) { auto *hd = res.getHeaderData("location"); if (!hd) return {}; auto *v = hd->getfirstValue(); if (!v) return {}; return v->getvalue(); } static std::string normalize_path(const std::string &location, const std::string &base_path) { if (location.empty()) return base_path.empty() ? std::string("/") : base_path; if (location[0] == '/') return location; if (location[0] == '?') { std::string base = base_path.empty() ? std::string("/") : base_path; return base + location; } std::string base = base_path.empty() ? std::string("/") : base_path; size_t slash = base.find_last_of('/'); if (slash == std::string::npos) return std::string("/") + location; return base.substr(0, slash + 1) + location; } // --------------- HTTP/2 client frame helpers --------------- static constexpr uint8_t H2C_FRAME_DATA = 0x00; static constexpr uint8_t H2C_FRAME_HEADERS = 0x01; Loading Loading @@ -365,18 +394,20 @@ const std::vector<char> libhttppp::HttpClient::_h2Request( auth << _url.getHost() << ":" << _url.getPort(); // Collect extra headers from the request bool has_content_length = false; 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 if (key == "content-length") has_content_length = true; for (HttpHeader::HeaderData::Values *v = hd->getfirstValue(); v; v = v->nextvalue()) { extra.push_back({key, v->getvalue(), false}); } } // Add content-length for POST body (servers may rely on it) if (postBody && !postBody->empty()) { if (postBody && !has_content_length) { extra.push_back({"content-length", std::to_string(postBody->size()), false}); } Loading Loading @@ -521,6 +552,13 @@ const std::vector<char> libhttppp::HttpClient::_h2Request( const std::vector<char> libhttppp::HttpClient::Get(libhttppp::HttpRequest &nreq) { std::vector<char> ret; try { const int kMaxRedirects = 1; int redirects = 0; for (;;) { std::stringstream host; host << _url.getHost() << ":" << _url.getPort(); Loading @@ -535,9 +573,6 @@ const std::vector<char> libhttppp::HttpClient::Get(libhttppp::HttpRequest &nreq) return _h2Request("GET", nreq); } std::vector<char> ret; try { // --------------------------------------------------------- // 0) Build request headers // --------------------------------------------------------- Loading Loading @@ -614,6 +649,23 @@ const std::vector<char> libhttppp::HttpClient::Get(libhttppp::HttpRequest &nreq) throw he; } if (is_redirect_status(res.getStatusCode()) && redirects < kMaxRedirects) { std::string location = extract_location_from_response(res); if (!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(); continue; } } // bytes after header already read size_t body_off = parsed_hsize; size_t body_avail = raw.size() - body_off; Loading Loading @@ -808,6 +860,8 @@ const std::vector<char> libhttppp::HttpClient::Get(libhttppp::HttpRequest &nreq) return ret; } } catch (netplus::NetException &e) { libhttppp::HTTPException ee; ee[libhttppp::HTTPException::Error] << e.what(); Loading @@ -818,14 +872,13 @@ 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); } std::vector<char> ret; try { const int kMaxRedirects = 1; int redirects = 0; for (;;) { std::stringstream host; host << _url.getHost() << ":" << _url.getPort(); Loading @@ -836,12 +889,18 @@ const std::vector<char> libhttppp::HttpClient::Post(libhttppp::HttpRequest &nreq if (nreq.getRequestVersion().empty()) nreq.setRequestVersion(HTTPVERSION(1.1)); // Always send content-length for POST body nreq.setHeaderData("content-length")->push_back(std::to_string(post.size())); // HTTP/2: delegate to binary framing path if (_isH2) { return _h2Request("POST", nreq, &post); } // Debug logging std::cerr << "[HttpClient::Post] URL path: '" << nreq.getRequestURL() << "' | POST body size: " << post.size() << std::endl; // IMPORTANT: length as string nreq.setHeaderData("content-length")->push_back(std::to_string(post.size())); // (optionally) set content-type: // nreq.setHeaderData("Content-Type")->push_back("application/json"); Loading Loading @@ -923,6 +982,23 @@ const std::vector<char> libhttppp::HttpClient::Post(libhttppp::HttpRequest &nreq throw he; } if (is_redirect_status(res.getStatusCode()) && redirects < kMaxRedirects) { std::string location = extract_location_from_response(res); if (!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(); continue; } } size_t body_off = parsed_hsize; // --------------------------------------------------------- Loading Loading @@ -1099,13 +1175,13 @@ const std::vector<char> libhttppp::HttpClient::Post(libhttppp::HttpRequest &nreq std::cerr << "[HttpClient::Post] Response first 200 bytes: " << std::string(ret.begin(), ret.begin() + 200) << std::endl; } return ret; } } catch (netplus::NetException &e) { libhttppp::HTTPException ee; ee[libhttppp::HTTPException::Error] << e.what(); throw ee; } return ret; } int libhttppp::HttpClient::readchunk(const char *data, int datasize, int &pos) Loading Loading @@ -1465,6 +1541,7 @@ libhttppp::HttpHeader::HeaderData::Values::Values(const Values& val) libhttppp::HttpResponse::HttpResponse() : HttpHeader(){ setState(HTTP200); _StatusCode = 200; _ContentType=nullptr; _ContentLength=setHeaderData("content-length"); _ContentLength->push_back(0); Loading @@ -1474,6 +1551,9 @@ libhttppp::HttpResponse::HttpResponse() : HttpHeader(){ } libhttppp::HttpResponse::HttpResponse(const HttpResponse &src) : HttpResponse(){ _State = src._State; _Version = src._Version; _StatusCode = src._StatusCode; for(HttpHeader::HeaderData *curdat=src._firstHeaderData.get(); curdat; curdat=curdat->nextHeaderData()){ HttpHeader::HeaderData *newdat=setHeaderData(curdat->getkey()); for(HttpHeader::HeaderData::Values *curval=curdat->getfirstValue(); curval; curval=curval->nextvalue()){ Loading Loading @@ -1515,6 +1595,10 @@ const std ::string &libhttppp::HttpResponse::getState() const{ return _State; } int libhttppp::HttpResponse::getStatusCode() const{ return _StatusCode; } size_t libhttppp::HttpResponse::getContentLength() const{ if(!_ContentLength || _ContentLength->empty()) return 0; Loading Loading @@ -1797,7 +1881,11 @@ size_t libhttppp::HttpResponse::parse(const char *data, size_t inlen) { v_end = status_line.length(); } std::string status_code_str = status_line.substr(v_start, v_end - v_start); // you might want to convert this to int, but I'm leaving your original logic try { _StatusCode = std::stoi(status_code_str); } catch (...) { _StatusCode = 0; } // reason phrase v_start = v_end + 1; Loading src/http.h +3 −0 Original line number Diff line number Diff line Loading @@ -90,6 +90,7 @@ namespace libhttppp { const std::vector<char> Get(HttpRequest &nreq); const std::vector<char> Post(HttpRequest &nreq,const std::vector<char> &post); private: void resetConnection(); int readchunk(const char *data,int datasize,int &pos); // HTTP/2 client helpers Loading Loading @@ -196,6 +197,7 @@ namespace libhttppp { /*client methods*/ const std ::string &getState() const; int getStatusCode() const; const std ::string &getContentType() const; size_t getContentLength() const; const std ::string &getConnection() const; Loading @@ -217,6 +219,7 @@ namespace libhttppp { std::string _State=HTTP200; std::string _Version; int _StatusCode=200; HeaderData *_TransferEncoding; HeaderData *_Connection; HeaderData *_ContentType; Loading Loading
src/http.cpp +138 −50 Original line number Diff line number Diff line Loading @@ -208,6 +208,15 @@ const std::string & libhttppp::HttpUrl::getPath() const{ libhttppp::HttpClient::HttpClient(const HttpUrl& desturl) : _url(desturl){ try { resetConnection(); } catch (netplus::NetException &e) { HTTPException ex; ex[HTTPException::Error] << "HttpClient ctor connect failed: " << e.what(); throw ex; // !!! IMPORTANT: throw HTTPException, nicht NetException } } void libhttppp::HttpClient::resetConnection(){ if (_url.getProtocol() == HttpUrl::HTTPS) { std::map<std::string, netplus::ssl::CertificateBundle> certs; auto sslsock = std::make_unique<netplus::ssl>(certs,-1); Loading @@ -229,14 +238,9 @@ libhttppp::HttpClient::HttpClient(const HttpUrl& desturl) _isH2 = (alpn == "h2"); std::cerr << "[HttpClient] ALPN negotiated: '" << alpn << "' isH2=" << _isH2 << std::endl; } else { _isH2 = false; std::cerr << "[HttpClient] Not an SSL socket" << std::endl; } } catch (netplus::NetException &e) { HTTPException ex; ex[HTTPException::Error] << "HttpClient ctor connect failed: " << e.what(); throw ex; // !!! IMPORTANT: throw HTTPException, nicht NetException } } Loading @@ -256,6 +260,31 @@ static inline void sleep_note() { std::this_thread::sleep_for(std::chrono::milliseconds(1)); } static bool is_redirect_status(int code) { return code == 301 || code == 302 || code == 303 || code == 307 || code == 308; } static std::string extract_location_from_response(const libhttppp::HttpResponse &res) { auto *hd = res.getHeaderData("location"); if (!hd) return {}; auto *v = hd->getfirstValue(); if (!v) return {}; return v->getvalue(); } static std::string normalize_path(const std::string &location, const std::string &base_path) { if (location.empty()) return base_path.empty() ? std::string("/") : base_path; if (location[0] == '/') return location; if (location[0] == '?') { std::string base = base_path.empty() ? std::string("/") : base_path; return base + location; } std::string base = base_path.empty() ? std::string("/") : base_path; size_t slash = base.find_last_of('/'); if (slash == std::string::npos) return std::string("/") + location; return base.substr(0, slash + 1) + location; } // --------------- HTTP/2 client frame helpers --------------- static constexpr uint8_t H2C_FRAME_DATA = 0x00; static constexpr uint8_t H2C_FRAME_HEADERS = 0x01; Loading Loading @@ -365,18 +394,20 @@ const std::vector<char> libhttppp::HttpClient::_h2Request( auth << _url.getHost() << ":" << _url.getPort(); // Collect extra headers from the request bool has_content_length = false; 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 if (key == "content-length") has_content_length = true; for (HttpHeader::HeaderData::Values *v = hd->getfirstValue(); v; v = v->nextvalue()) { extra.push_back({key, v->getvalue(), false}); } } // Add content-length for POST body (servers may rely on it) if (postBody && !postBody->empty()) { if (postBody && !has_content_length) { extra.push_back({"content-length", std::to_string(postBody->size()), false}); } Loading Loading @@ -521,6 +552,13 @@ const std::vector<char> libhttppp::HttpClient::_h2Request( const std::vector<char> libhttppp::HttpClient::Get(libhttppp::HttpRequest &nreq) { std::vector<char> ret; try { const int kMaxRedirects = 1; int redirects = 0; for (;;) { std::stringstream host; host << _url.getHost() << ":" << _url.getPort(); Loading @@ -535,9 +573,6 @@ const std::vector<char> libhttppp::HttpClient::Get(libhttppp::HttpRequest &nreq) return _h2Request("GET", nreq); } std::vector<char> ret; try { // --------------------------------------------------------- // 0) Build request headers // --------------------------------------------------------- Loading Loading @@ -614,6 +649,23 @@ const std::vector<char> libhttppp::HttpClient::Get(libhttppp::HttpRequest &nreq) throw he; } if (is_redirect_status(res.getStatusCode()) && redirects < kMaxRedirects) { std::string location = extract_location_from_response(res); if (!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(); continue; } } // bytes after header already read size_t body_off = parsed_hsize; size_t body_avail = raw.size() - body_off; Loading Loading @@ -808,6 +860,8 @@ const std::vector<char> libhttppp::HttpClient::Get(libhttppp::HttpRequest &nreq) return ret; } } catch (netplus::NetException &e) { libhttppp::HTTPException ee; ee[libhttppp::HTTPException::Error] << e.what(); Loading @@ -818,14 +872,13 @@ 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); } std::vector<char> ret; try { const int kMaxRedirects = 1; int redirects = 0; for (;;) { std::stringstream host; host << _url.getHost() << ":" << _url.getPort(); Loading @@ -836,12 +889,18 @@ const std::vector<char> libhttppp::HttpClient::Post(libhttppp::HttpRequest &nreq if (nreq.getRequestVersion().empty()) nreq.setRequestVersion(HTTPVERSION(1.1)); // Always send content-length for POST body nreq.setHeaderData("content-length")->push_back(std::to_string(post.size())); // HTTP/2: delegate to binary framing path if (_isH2) { return _h2Request("POST", nreq, &post); } // Debug logging std::cerr << "[HttpClient::Post] URL path: '" << nreq.getRequestURL() << "' | POST body size: " << post.size() << std::endl; // IMPORTANT: length as string nreq.setHeaderData("content-length")->push_back(std::to_string(post.size())); // (optionally) set content-type: // nreq.setHeaderData("Content-Type")->push_back("application/json"); Loading Loading @@ -923,6 +982,23 @@ const std::vector<char> libhttppp::HttpClient::Post(libhttppp::HttpRequest &nreq throw he; } if (is_redirect_status(res.getStatusCode()) && redirects < kMaxRedirects) { std::string location = extract_location_from_response(res); if (!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(); continue; } } size_t body_off = parsed_hsize; // --------------------------------------------------------- Loading Loading @@ -1099,13 +1175,13 @@ const std::vector<char> libhttppp::HttpClient::Post(libhttppp::HttpRequest &nreq std::cerr << "[HttpClient::Post] Response first 200 bytes: " << std::string(ret.begin(), ret.begin() + 200) << std::endl; } return ret; } } catch (netplus::NetException &e) { libhttppp::HTTPException ee; ee[libhttppp::HTTPException::Error] << e.what(); throw ee; } return ret; } int libhttppp::HttpClient::readchunk(const char *data, int datasize, int &pos) Loading Loading @@ -1465,6 +1541,7 @@ libhttppp::HttpHeader::HeaderData::Values::Values(const Values& val) libhttppp::HttpResponse::HttpResponse() : HttpHeader(){ setState(HTTP200); _StatusCode = 200; _ContentType=nullptr; _ContentLength=setHeaderData("content-length"); _ContentLength->push_back(0); Loading @@ -1474,6 +1551,9 @@ libhttppp::HttpResponse::HttpResponse() : HttpHeader(){ } libhttppp::HttpResponse::HttpResponse(const HttpResponse &src) : HttpResponse(){ _State = src._State; _Version = src._Version; _StatusCode = src._StatusCode; for(HttpHeader::HeaderData *curdat=src._firstHeaderData.get(); curdat; curdat=curdat->nextHeaderData()){ HttpHeader::HeaderData *newdat=setHeaderData(curdat->getkey()); for(HttpHeader::HeaderData::Values *curval=curdat->getfirstValue(); curval; curval=curval->nextvalue()){ Loading Loading @@ -1515,6 +1595,10 @@ const std ::string &libhttppp::HttpResponse::getState() const{ return _State; } int libhttppp::HttpResponse::getStatusCode() const{ return _StatusCode; } size_t libhttppp::HttpResponse::getContentLength() const{ if(!_ContentLength || _ContentLength->empty()) return 0; Loading Loading @@ -1797,7 +1881,11 @@ size_t libhttppp::HttpResponse::parse(const char *data, size_t inlen) { v_end = status_line.length(); } std::string status_code_str = status_line.substr(v_start, v_end - v_start); // you might want to convert this to int, but I'm leaving your original logic try { _StatusCode = std::stoi(status_code_str); } catch (...) { _StatusCode = 0; } // reason phrase v_start = v_end + 1; Loading
src/http.h +3 −0 Original line number Diff line number Diff line Loading @@ -90,6 +90,7 @@ namespace libhttppp { const std::vector<char> Get(HttpRequest &nreq); const std::vector<char> Post(HttpRequest &nreq,const std::vector<char> &post); private: void resetConnection(); int readchunk(const char *data,int datasize,int &pos); // HTTP/2 client helpers Loading Loading @@ -196,6 +197,7 @@ namespace libhttppp { /*client methods*/ const std ::string &getState() const; int getStatusCode() const; const std ::string &getContentType() const; size_t getContentLength() const; const std ::string &getConnection() const; Loading @@ -217,6 +219,7 @@ namespace libhttppp { std::string _State=HTTP200; std::string _Version; int _StatusCode=200; HeaderData *_TransferEncoding; HeaderData *_Connection; HeaderData *_ContentType; Loading