From 4a75daa77d9bca738e1d46e6facff94014796a6c Mon Sep 17 00:00:00 2001 From: lifegpc Date: Sun, 7 Jan 2024 10:58:30 +0800 Subject: [PATCH] Update --- http_client.cpp | 134 ++++++++++++++++++++++++++++++++++++++++++++++-- http_client.h | 35 +++++++++---- str_util.cpp | 10 ++++ str_util.h | 6 +++ 4 files changed, 171 insertions(+), 14 deletions(-) diff --git a/http_client.cpp b/http_client.cpp index 70ba1ee..93d595f 100644 --- a/http_client.cpp +++ b/http_client.cpp @@ -9,6 +9,7 @@ #include #include +#include "inttypes.h" #if _WIN32 static bool inited = false; @@ -19,6 +20,14 @@ static WSADATA wsaData = { 0 }; static bool ssl_inited = false; #endif +#if HAVE_PRINTF_S +#define printf printf_s +#endif + +#if HAVE_SSCANF_S +#define sscanf sscanf_s +#endif + AIException::AIException(int code) { this->code = code; } @@ -108,6 +117,7 @@ Socket::Socket(std::string host, std::string protocol) { } Socket::~Socket() { + if (this->moved) return; #if HAVE_OPENSSL if (this->ssl_ctx) { SSL_CTX_free(this->ssl_ctx); @@ -272,7 +282,7 @@ Request::Request(std::string host, std::string path, std::string method, HeaderM this->options = options; } -void Request::send() { +Response Request::send() { std::string data; data += this->method + " " + this->path + " HTTP/1.1\r\n"; auto hasBody = !this->body || !this->body->isFinished(); @@ -289,7 +299,6 @@ void Request::send() { data += header.first + ": " + header.second + "\r\n"; } data += "\r\n"; - printf("%s", data.c_str()); #if HAVE_OPENSSL Socket socket(this->host, this->options.https ? "https" : "http"); #else @@ -302,12 +311,15 @@ void Request::send() { char buf[1024]; size_t len = this->body->pullData(buf, 1024); socket.send(buf, len, 0); - printf("%s", std::string(buf, len).c_str()); } socket.send("\r\n"); } - auto s = socket.recv(10240); - printf("%s\n", s.c_str()); + return Response(socket); +} + +void Request::setBody(HttpBody* body) { + if (this->body != nullptr) delete this->body; + this->body = body; } HttpClient::HttpClient(std::string host) { @@ -421,3 +433,115 @@ Request::~Request() { delete this->body; } } + +Response::Response(Socket socket): socket(socket) { + parseStatus(); + parseHeader(); +} + +bool Response::pullData() { + if (!this->buff.empty()) return false; + this->buff = this->socket.recv(1024); + return this->buff.empty(); +} + +void Response::parseStatus() { + if (this->code) return; + auto line = this->readLine(); + auto parts = str_util::str_splitv(line, " ", 3); + if (parts.size() < 3) { + throw std::runtime_error("Invalid HTTP status line"); + } + if (cstr_stricmp(parts[0].c_str(), "http/1.1")) { + throw std::runtime_error("Unspported HTTP version"); + } + if (sscanf(parts[1].c_str(), "%" SCNu8, &this->code) != 1) { + throw std::runtime_error("Invalid HTTP status code"); + } + this->reason = parts[2]; +} + +void Response::parseHeader() { + if (!this->code) parseStatus(); + if (this->headerParsed) return; + this->headerParsed = true; + auto line = this->readLine(); + while (!line.empty()) { + auto kv = str_util::str_splitv(line, ": ", 2); + if (kv.size() < 2) { + throw std::runtime_error("Invalid HTTP header"); + } + this->headers[kv[0]] = kv[1]; + if (!cstr_stricmp(kv[0].c_str(), "transfer-encoding")) { + auto list = str_util::str_splitv(kv[1], ","); + for (auto& item : list) { + auto it = str_util::str_trim(item); + if (!cstr_stricmp(it.c_str(), "chunked")) { + this->chunked = true; + break; + } else { + throw std::runtime_error("Unspported transfer-encoding"); + } + } + } + line = this->readLine(); + } +} + +std::string Response::readLine() { + std::string line; + while (true) { + if (this->pullData()) break; + auto pos = this->buff.find("\r\n"); + if (pos == std::string::npos) { + line += this->buff; + this->buff.clear(); + } else { + line += this->buff.substr(0, pos); + this->buff = this->buff.substr(pos + 2); + break; + } + } + return line; +} + +std::string Response::read() { + if (!this->code) this->parseStatus(); + if (!this->headerParsed) this->parseHeader(); + if (this->chunked) { + std::string data; + size_t size = -1; + auto line = this->readLine(); + if (sscanf(line.c_str(), "%zx", &size) != 1) { + throw std::runtime_error("Invalid chunk size"); + } + size_t osize = size; + while (size > 0) { + if (this->pullData()) break; + auto len = std::min(this->buff.length(), size); + data += this->buff.substr(0, len); + this->buff = this->buff.substr(len); + size -= len; + } + if (osize != data.length()) { + throw std::runtime_error("Chunk size != data length"); + } + this->readLine(); + return data; + } else { + if (this->pullData()) return ""; + auto data = this->buff; + this->buff.clear(); + return data; + } +} + +std::string Response::readAll() { + std::string data; + auto d = this->read(); + while (!d.empty()) { + data += d; + d = this->read(); + } + return data; +} diff --git a/http_client.h b/http_client.h index 781232e..58ea189 100644 --- a/http_client.h +++ b/http_client.h @@ -2,6 +2,7 @@ #define _UTIL_HTTP_CLIENT_H #include +#include #include #include #include @@ -95,6 +96,10 @@ private: class Socket { public: Socket(std::string host, std::string protocol); + Socket(Socket& socket) { + *this = socket; + socket.moved = true; + } ~Socket(); void connect(); size_t send(const char* data, size_t len, int flags = 0); @@ -116,6 +121,7 @@ private: BIO* web = nullptr; SSL* ssl = nullptr; #endif + bool moved = false; std::string host; std::string protocol; int socket = -1; @@ -123,30 +129,41 @@ private: addrinfo* addr = nullptr; }; +typedef struct Response Response; + class Request { public: Request(std::string host, std::string path, std::string method, HeaderMap headers, HttpClientOptions options); ~Request(); - void send(); + Response send(); HeaderMap headers; HttpClientOptions options; - void setBody(HttpBody* body) { - if (this->body != nullptr) delete this->body; - this->body = body; - } -private: - HttpBody* body = nullptr; + void setBody(HttpBody* body); std::string host; std::string path; std::string method; +private: + HttpBody* body = nullptr; }; class Response { public: - Response(Request req, Socket socket); + Response() = delete; + explicit Response(Socket socket); + HeaderMap headers; + uint8_t code = 0; + std::string reason; + std::string read(); + std::string readAll(); private: - Request req; + std::string readLine(); + void parseHeader(); + void parseStatus(); + bool pullData(); + bool headerParsed = false; + bool chunked = false; Socket socket; + std::string buff; }; class HttpClient { diff --git a/str_util.cpp b/str_util.cpp index 20edb4c..c9cb663 100644 --- a/str_util.cpp +++ b/str_util.cpp @@ -99,3 +99,13 @@ std::string str_util::remove_quote(std::string input) { return input; } } + +std::string str_util::str_trim(std::string input) { + while (!input.empty() && (input.front() == ' ' || input.front() == '\t' || input.front() == '\r' || input.front() == '\n')) { + input.erase(input.begin()); + } + while (!input.empty() && (input.back() == ' ' || input.back() == '\t' || input.back() == '\r' || input.back() == '\n')) { + input.erase(input.end() - 1); + } + return input; +} diff --git a/str_util.h b/str_util.h index 156fa64..8b3dd1b 100644 --- a/str_util.h +++ b/str_util.h @@ -58,6 +58,12 @@ namespace str_util { * @return true if input ends with pattern */ bool str_endswith(std::string input, std::string pattern); + /** + * @brief Trim a string + * @param input Input string + * @return Result + */ + std::string str_trim(std::string input); /** * @brief Remove quote from a string * @param input Input string