mirror of
https://github.com/lifegpc/c-utils.git
synced 2026-07-08 01:30:39 +08:00
add http client
This commit is contained in:
@@ -4,6 +4,7 @@ option(ENABLE_ICONV "Use libiconv to convert encoding" ON)
|
|||||||
option(ENABLE_STANDALONE "Build utils standalone" OFF)
|
option(ENABLE_STANDALONE "Build utils standalone" OFF)
|
||||||
option(ENABLE_CXX17 "Enable C++ 17" OFF)
|
option(ENABLE_CXX17 "Enable C++ 17" OFF)
|
||||||
option(INSTALL_DEP_FILES "Install a file with dependences." OFF)
|
option(INSTALL_DEP_FILES "Install a file with dependences." OFF)
|
||||||
|
option(ENABLE_SSL "Enable SSL" OFF)
|
||||||
|
|
||||||
if (ENABLE_STANDALONE)
|
if (ENABLE_STANDALONE)
|
||||||
project(utils)
|
project(utils)
|
||||||
@@ -23,6 +24,11 @@ if (ENABLE_ICONV)
|
|||||||
find_package(Iconv)
|
find_package(Iconv)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
if (ENABLE_SSL)
|
||||||
|
find_package(OpenSSL 3.0.0 REQUIRED)
|
||||||
|
set(HAVE_OPENSSL 1)
|
||||||
|
endif()
|
||||||
|
|
||||||
if (Iconv_FOUND)
|
if (Iconv_FOUND)
|
||||||
set(HAVE_ICONV 1)
|
set(HAVE_ICONV 1)
|
||||||
endif()
|
endif()
|
||||||
@@ -82,6 +88,7 @@ set(SOURCE_FILE
|
|||||||
c_linked_list.cpp
|
c_linked_list.cpp
|
||||||
file_reader.c
|
file_reader.c
|
||||||
urlparse.cpp
|
urlparse.cpp
|
||||||
|
http_client.cpp
|
||||||
)
|
)
|
||||||
set(SOURCE_FILE_HEADERS
|
set(SOURCE_FILE_HEADERS
|
||||||
cfileop.h
|
cfileop.h
|
||||||
@@ -101,6 +108,7 @@ set(SOURCE_FILE_HEADERS
|
|||||||
c_linked_list.h
|
c_linked_list.h
|
||||||
file_reader.h
|
file_reader.h
|
||||||
urlparse.h
|
urlparse.h
|
||||||
|
http_client.h
|
||||||
)
|
)
|
||||||
|
|
||||||
add_library(utils STATIC ${SOURCE_FILE} ${SOURCE_FILE_HEADERS})
|
add_library(utils STATIC ${SOURCE_FILE} ${SOURCE_FILE_HEADERS})
|
||||||
@@ -118,6 +126,12 @@ if (INSTALL_DEP_FILES)
|
|||||||
target_link_libraries(utils shell32)
|
target_link_libraries(utils shell32)
|
||||||
endif()
|
endif()
|
||||||
endif()
|
endif()
|
||||||
|
if (WIN32)
|
||||||
|
target_link_libraries(utils Ws2_32)
|
||||||
|
endif()
|
||||||
|
if (ENABLE_SSL)
|
||||||
|
target_link_libraries(utils OpenSSL::SSL OpenSSL::Crypto)
|
||||||
|
endif()
|
||||||
if (ENABLE_CXX17)
|
if (ENABLE_CXX17)
|
||||||
target_compile_features(utils PRIVATE cxx_std_17)
|
target_compile_features(utils PRIVATE cxx_std_17)
|
||||||
endif()
|
endif()
|
||||||
|
|||||||
15
cstr_util.c
15
cstr_util.c
@@ -92,6 +92,21 @@ int cstr_tolowercase(const char* str, size_t input_len, char** output) {
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int cstr_touppercase(const char* str, size_t input_len, char** output) {
|
||||||
|
if (!str || !output) return 0;
|
||||||
|
if (!input_len) input_len = strlen(str);
|
||||||
|
if (input_len == (size_t)-1) return 0;
|
||||||
|
char* tmp = malloc(input_len + 1);
|
||||||
|
if (!tmp) return 0;
|
||||||
|
size_t i = 0;
|
||||||
|
for (; i < input_len; i++) {
|
||||||
|
tmp[i] = toupper(str[i]);
|
||||||
|
}
|
||||||
|
tmp[input_len] = 0;
|
||||||
|
*output = tmp;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
uint32_t cstr_read_uint32(const uint8_t* bytes, int big) {
|
uint32_t cstr_read_uint32(const uint8_t* bytes, int big) {
|
||||||
if (!bytes) return 0;
|
if (!bytes) return 0;
|
||||||
return big ? (bytes[0] << 24) + (bytes[1] << 16) + (bytes[2] << 8) + bytes[3] : bytes[0] + (bytes[1] << 8) + (bytes[2] << 16) + (bytes[3] << 24);
|
return big ? (bytes[0] << 24) + (bytes[1] << 16) + (bytes[2] << 8) + bytes[3] : bytes[0] + (bytes[1] << 8) + (bytes[2] << 16) + (bytes[3] << 24);
|
||||||
|
|||||||
@@ -27,6 +27,14 @@ int cstr_is_integer(const char* str, int allow_sign);
|
|||||||
* @return 1 if successed otherwise 0.
|
* @return 1 if successed otherwise 0.
|
||||||
*/
|
*/
|
||||||
int cstr_tolowercase(const char* str, size_t input_len, char** output);
|
int cstr_tolowercase(const char* str, size_t input_len, char** output);
|
||||||
|
/**
|
||||||
|
* @brief Convert string to uppercase
|
||||||
|
* @param str Origin string
|
||||||
|
* @param input_len The length of origin string. If is 0, strlen will be called to calculate length.
|
||||||
|
* @param output Output string. Need free memory by calling free.
|
||||||
|
* @return 1 if successed otherwise 0.
|
||||||
|
*/
|
||||||
|
int cstr_touppercase(const char* str, size_t input_len, char** output);
|
||||||
/**
|
/**
|
||||||
* @brief Convert bytes to uint32
|
* @brief Convert bytes to uint32
|
||||||
* @param bytes Bytes (at least 4 bytes)
|
* @param bytes Bytes (at least 4 bytes)
|
||||||
|
|||||||
423
http_client.cpp
Normal file
423
http_client.cpp
Normal file
@@ -0,0 +1,423 @@
|
|||||||
|
#include "http_client.h"
|
||||||
|
|
||||||
|
#ifndef _WIN32
|
||||||
|
#include <unistd.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "err.h"
|
||||||
|
#include "str_util.h"
|
||||||
|
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#if _WIN32
|
||||||
|
static bool inited = false;
|
||||||
|
static WSADATA wsaData = { 0 };
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if HAVE_OPENSSL
|
||||||
|
static bool ssl_inited = false;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
AIException::AIException(int code) {
|
||||||
|
this->code = code;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* AIException::what() {
|
||||||
|
#if _WIN32
|
||||||
|
return gai_strerrorA(this->code);
|
||||||
|
#else
|
||||||
|
return gai_strerror(this->code);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string decodeURIComponent(std::string str) {
|
||||||
|
std::string re;
|
||||||
|
for (size_t i = 0; i < str.length(); i++) {
|
||||||
|
if (str[i] == '%') {
|
||||||
|
if (i + 2 >= str.length()) {
|
||||||
|
throw std::runtime_error("Invalid Percent-encoding");
|
||||||
|
}
|
||||||
|
char buf[3] = { str[i + 1], str[i + 2], 0 };
|
||||||
|
int c = strtol(buf, nullptr, 16);
|
||||||
|
re += (char)c;
|
||||||
|
i += 2;
|
||||||
|
} else {
|
||||||
|
re += str[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return re;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string encodeURIComponent(std::string str) {
|
||||||
|
std::string re;
|
||||||
|
for (size_t i = 0; i < str.length(); i++) {
|
||||||
|
if (str[i] == ' ') {
|
||||||
|
re += '+';
|
||||||
|
} else if (str[i] == '-' || str[i] == '_' || str[i] == '.' || str[i] == '~' ||
|
||||||
|
(str[i] >= 'a' && str[i] <= 'z') ||
|
||||||
|
(str[i] >= 'A' && str[i] <= 'Z') ||
|
||||||
|
(str[i] >= '0' && str[i] <= '9')) {
|
||||||
|
re += str[i];
|
||||||
|
} else {
|
||||||
|
char buf[4] = { '%', 0 };
|
||||||
|
snprintf(buf + 1, 3, "%02X", (unsigned char)str[i]);
|
||||||
|
re += buf;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return re;
|
||||||
|
}
|
||||||
|
|
||||||
|
SocketError::SocketError() {
|
||||||
|
#if _WIN32
|
||||||
|
this->code = WSAGetLastError();
|
||||||
|
#else
|
||||||
|
this->code = errno;
|
||||||
|
#endif
|
||||||
|
#if _WIN32
|
||||||
|
if (!err::get_winerror(this->message, this->code)) {
|
||||||
|
#else
|
||||||
|
if (!err::get_errno_message(this->message, this->code)) {
|
||||||
|
#endif
|
||||||
|
char buf[32];
|
||||||
|
snprintf(buf, 32, "Unknown error %d", this->code);
|
||||||
|
this->message = std::string(buf);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* SocketError::what() {
|
||||||
|
return this->message.c_str();
|
||||||
|
}
|
||||||
|
|
||||||
|
Socket::Socket(std::string host, std::string protocol) {
|
||||||
|
this->host = host;
|
||||||
|
this->protocol = protocol;
|
||||||
|
#if HAVE_OPENSSL
|
||||||
|
if (protocol == "https") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
addrinfo hints = { 0 };
|
||||||
|
hints.ai_family = AF_UNSPEC;
|
||||||
|
hints.ai_socktype = SOCK_STREAM;
|
||||||
|
hints.ai_protocol = IPPROTO_TCP;
|
||||||
|
int re = getaddrinfo(host.c_str(), protocol.c_str(), &hints, &this->addr);
|
||||||
|
if (re) {
|
||||||
|
throw AIException(re);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Socket::~Socket() {
|
||||||
|
#if HAVE_OPENSSL
|
||||||
|
if (this->ssl_ctx) {
|
||||||
|
SSL_CTX_free(this->ssl_ctx);
|
||||||
|
this->ssl_ctx = nullptr;
|
||||||
|
}
|
||||||
|
if (this->web) {
|
||||||
|
BIO_free_all(this->web);
|
||||||
|
this->web = nullptr;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
this->close();
|
||||||
|
if (this->addr) {
|
||||||
|
freeaddrinfo(this->addr);
|
||||||
|
this->addr = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Socket::connect() {
|
||||||
|
#if HAVE_OPENSSL
|
||||||
|
if (this->protocol == "https") {
|
||||||
|
const SSL_METHOD* method = SSLv23_client_method();
|
||||||
|
if (!method) {
|
||||||
|
throw std::runtime_error("SSLv23_client_method failed");
|
||||||
|
}
|
||||||
|
this->ssl_ctx = SSL_CTX_new(method);
|
||||||
|
if (!this->ssl_ctx) {
|
||||||
|
throw std::runtime_error("SSL_CTX_new failed");
|
||||||
|
}
|
||||||
|
const long flags = SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_NO_COMPRESSION;
|
||||||
|
SSL_CTX_set_options(this->ssl_ctx, flags);
|
||||||
|
this->web = BIO_new_ssl_connect(this->ssl_ctx);
|
||||||
|
if (!this->web) {
|
||||||
|
throw std::runtime_error("BIO_new_ssl_connect failed");
|
||||||
|
}
|
||||||
|
int res =BIO_set_conn_hostname(this->web, this->host.c_str());
|
||||||
|
if (res == -1) {
|
||||||
|
throw std::runtime_error("BIO_set_conn_hostname failed");
|
||||||
|
}
|
||||||
|
BIO_get_ssl(this->web, &this->ssl);
|
||||||
|
if (!this->ssl) {
|
||||||
|
throw std::runtime_error("BIO_get_ssl failed");
|
||||||
|
}
|
||||||
|
res = SSL_set_tlsext_host_name(this->ssl, this->host.c_str());
|
||||||
|
if (res == -1) {
|
||||||
|
throw std::runtime_error("SSL_set_tlsext_host_name failed");
|
||||||
|
}
|
||||||
|
res = BIO_do_connect(this->web);
|
||||||
|
if (res != 1) {
|
||||||
|
throw std::runtime_error("BIO_do_connect failed");
|
||||||
|
}
|
||||||
|
res = BIO_do_handshake(this->web);
|
||||||
|
if (res != 1) {
|
||||||
|
throw std::runtime_error("BIO_do_handshake failed");
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
if (this->socket != -1 || !this->addr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this->socket = ::socket(this->addr->ai_family, this->addr->ai_socktype, this->addr->ai_protocol);
|
||||||
|
#if _WIN32
|
||||||
|
if (this->socket == INVALID_SOCKET) {
|
||||||
|
#else
|
||||||
|
if (this->socket == -1) {
|
||||||
|
#endif
|
||||||
|
throw SocketError();
|
||||||
|
}
|
||||||
|
int re = ::connect(this->socket, this->addr->ai_addr, this->addr->ai_addrlen);
|
||||||
|
#if _WIN32
|
||||||
|
if (re == SOCKET_ERROR) {
|
||||||
|
#else
|
||||||
|
if (re == -1) {
|
||||||
|
#endif
|
||||||
|
throw SocketError();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t Socket::send(const char* data, size_t len, int flags) {
|
||||||
|
#if HAVE_OPENSSL
|
||||||
|
if (this->protocol == "https") {
|
||||||
|
int sented = BIO_write(this->web, data, (int)len);
|
||||||
|
if (sented <= 0) {
|
||||||
|
throw std::runtime_error("BIO_write failed");
|
||||||
|
}
|
||||||
|
return sented;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
if (this->socket == -1) {
|
||||||
|
throw std::runtime_error("Socket not connected");
|
||||||
|
}
|
||||||
|
#if _WIN32
|
||||||
|
int sented = ::send(this->socket, data, (int)len, flags);
|
||||||
|
if (sented == SOCKET_ERROR) {
|
||||||
|
#else
|
||||||
|
ssize_t sented = ::send(this->socket, data, len, flags);
|
||||||
|
if (sented == -1) {
|
||||||
|
#endif
|
||||||
|
throw SocketError();
|
||||||
|
}
|
||||||
|
return sented;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t Socket::send(std::string data, int flags) {
|
||||||
|
return this->send(data.c_str(), data.length(), flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t Socket::recv(char* data, size_t len, int flags) {
|
||||||
|
#if HAVE_OPENSSL
|
||||||
|
if (this->protocol == "https") {
|
||||||
|
int recved = BIO_read(this->web, data, (int)len);
|
||||||
|
if (recved <= 0) {
|
||||||
|
throw std::runtime_error("BIO_read failed");
|
||||||
|
}
|
||||||
|
return recved;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
if (this->socket == -1) {
|
||||||
|
throw std::runtime_error("Socket not connected");
|
||||||
|
}
|
||||||
|
#if _WIN32
|
||||||
|
int recved = ::recv(this->socket, data, (int)len, flags);
|
||||||
|
if (recved == SOCKET_ERROR) {
|
||||||
|
#else
|
||||||
|
ssize_t recved = ::recv(this->socket, data, len, flags);
|
||||||
|
if (recved == -1) {
|
||||||
|
#endif
|
||||||
|
throw SocketError();
|
||||||
|
}
|
||||||
|
return recved;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string Socket::recv(size_t len, int flags) {
|
||||||
|
char* buf = new char[len];
|
||||||
|
try {
|
||||||
|
size_t recved = this->recv(buf, len, flags);
|
||||||
|
std::string re(buf, recved);
|
||||||
|
delete[] buf;
|
||||||
|
return re;
|
||||||
|
} catch (std::exception& e) {
|
||||||
|
delete[] buf;
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Socket::close() {
|
||||||
|
if (this->socket != -1 && !closed) {
|
||||||
|
#if _WIN32
|
||||||
|
closesocket(this->socket);
|
||||||
|
#else
|
||||||
|
::close(this->socket);
|
||||||
|
#endif
|
||||||
|
closed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Request::Request(std::string host, std::string path, std::string method, HeaderMap headers, HttpClientOptions options) {
|
||||||
|
this->host = host;
|
||||||
|
this->path = path;
|
||||||
|
this->method = method;
|
||||||
|
this->headers = headers;
|
||||||
|
this->options = options;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Request::send() {
|
||||||
|
std::string data;
|
||||||
|
data += this->method + " " + this->path + " HTTP/1.1\r\n";
|
||||||
|
auto hasBody = !this->body || !this->body->isFinished();
|
||||||
|
if (hasBody) {
|
||||||
|
auto type = this->body->contentType();
|
||||||
|
if (!type.empty()) {
|
||||||
|
this->headers["Content-Type"] = type;
|
||||||
|
}
|
||||||
|
if (this->body->hasLength()) {
|
||||||
|
this->headers["Content-Length"] = std::to_string(this->body->length());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (auto& header : this->headers) {
|
||||||
|
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
|
||||||
|
Socket socket(this->host, "http");
|
||||||
|
#endif
|
||||||
|
socket.connect();
|
||||||
|
socket.send(data);
|
||||||
|
if (hasBody) {
|
||||||
|
while (!this->body->isFinished()) {
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
|
||||||
|
HttpClient::HttpClient(std::string host) {
|
||||||
|
this->host = host;
|
||||||
|
#if _WIN32
|
||||||
|
if (!inited) {
|
||||||
|
WSAStartup(MAKEWORD(2, 2), &wsaData);
|
||||||
|
inited = true;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
#if HAVE_OPENSSL
|
||||||
|
if (!ssl_inited) {
|
||||||
|
#if OPENSSL_VERSION_NUMBER < 0x10100000L
|
||||||
|
SSL_library_init();
|
||||||
|
#else
|
||||||
|
SSL_load_error_strings();
|
||||||
|
#endif
|
||||||
|
ssl_inited = true;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
this->headers["Host"] = host;
|
||||||
|
this->headers["User-Agent"] = "simple-http-client";
|
||||||
|
this->headers["Accept"] = "*/*";
|
||||||
|
}
|
||||||
|
|
||||||
|
Request HttpClient::request(std::string path, std::string method) {
|
||||||
|
return Request(this->host, path, method, this->headers, this->options);
|
||||||
|
}
|
||||||
|
|
||||||
|
void HttpFullBody::pull() {
|
||||||
|
if (!this->pulled) {
|
||||||
|
this->buff = this->body();
|
||||||
|
this->len = this->buff.length();
|
||||||
|
this->pulled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t HttpFullBody::pullData(char* buf, size_t len) {
|
||||||
|
this->pull();
|
||||||
|
size_t rlen = std::min(this->buff.length(), len);
|
||||||
|
memcpy(buf, this->buff.c_str(), rlen);
|
||||||
|
this->buff = this->buff.substr(rlen);
|
||||||
|
return rlen;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HttpFullBody::isFinished() {
|
||||||
|
this->pull();
|
||||||
|
return this->buff.length() == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HttpFullBody::hasLength() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string HttpFullBody::contentType() {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t HttpFullBody::length() {
|
||||||
|
this->pull();
|
||||||
|
return this->len;
|
||||||
|
}
|
||||||
|
|
||||||
|
QueryData::QueryData() {}
|
||||||
|
|
||||||
|
QueryData::QueryData(std::string data) {
|
||||||
|
auto list = str_util::str_split(data, "&");
|
||||||
|
for (auto& item : list) {
|
||||||
|
auto kv = str_util::str_splitv(item, "=", 2);
|
||||||
|
auto k = decodeURIComponent(kv[0]);
|
||||||
|
auto v = decodeURIComponent(kv[1]);
|
||||||
|
this->append(k, v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string QueryData::contentType() {
|
||||||
|
return "application/x-www-form-urlencoded";
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string QueryData::toQuery() {
|
||||||
|
std::string re;
|
||||||
|
for (auto& item : this->data) {
|
||||||
|
for (auto& v : item.second) {
|
||||||
|
if (re.length() > 0) {
|
||||||
|
re += "&";
|
||||||
|
}
|
||||||
|
re += encodeURIComponent(item.first) + "=" + encodeURIComponent(v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return re;
|
||||||
|
}
|
||||||
|
|
||||||
|
void QueryData::append(std::string key, std::string value) {
|
||||||
|
if (this->data.find(key) != this->data.end()) {
|
||||||
|
this->data[key].push_back(value);
|
||||||
|
} else {
|
||||||
|
this->data[key] = std::vector<std::string>({ value });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void QueryData::set(std::string key, std::string value) {
|
||||||
|
this->data[key] = std::vector<std::string>({ value });
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string QueryData::body() {
|
||||||
|
return this->toQuery();
|
||||||
|
}
|
||||||
|
|
||||||
|
Request::~Request() {
|
||||||
|
if (this->body) {
|
||||||
|
delete this->body;
|
||||||
|
}
|
||||||
|
}
|
||||||
162
http_client.h
Normal file
162
http_client.h
Normal file
@@ -0,0 +1,162 @@
|
|||||||
|
#ifndef _UTIL_HTTP_CLIENT_H
|
||||||
|
#define _UTIL_HTTP_CLIENT_H
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <string>
|
||||||
|
#include <map>
|
||||||
|
#include <memory>
|
||||||
|
#include <vector>
|
||||||
|
#include "cstr_util.h"
|
||||||
|
#include "utils_config.h"
|
||||||
|
|
||||||
|
#if _WIN32
|
||||||
|
#include <winsock2.h>
|
||||||
|
#include <ws2tcpip.h>
|
||||||
|
#else
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <netdb.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if HAVE_OPENSSL
|
||||||
|
#include "openssl/ssl.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
struct HeaderNameCompare {
|
||||||
|
bool operator()(const std::string& a, const std::string& b) const {
|
||||||
|
return cstr_stricmp(a.c_str(), b.c_str()) < 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef std::map<std::string, std::string, HeaderNameCompare> HeaderMap;
|
||||||
|
|
||||||
|
std::string decodeURIComponent(std::string str);
|
||||||
|
|
||||||
|
class HttpClientOptions {
|
||||||
|
public:
|
||||||
|
bool https = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
class HttpBody {
|
||||||
|
public:
|
||||||
|
virtual size_t pullData(char* buf, size_t len) = 0;
|
||||||
|
virtual bool isFinished() = 0;
|
||||||
|
virtual std::string contentType() = 0;
|
||||||
|
virtual bool hasLength() = 0;
|
||||||
|
virtual size_t length() = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
class HttpFullBody: public HttpBody {
|
||||||
|
public:
|
||||||
|
virtual size_t pullData(char* buf, size_t len);
|
||||||
|
virtual bool isFinished();
|
||||||
|
virtual bool hasLength();
|
||||||
|
virtual std::string contentType();
|
||||||
|
virtual size_t length();
|
||||||
|
protected:
|
||||||
|
virtual std::string body() = 0;
|
||||||
|
private:
|
||||||
|
void pull();
|
||||||
|
std::string buff;
|
||||||
|
bool pulled;
|
||||||
|
size_t len;
|
||||||
|
};
|
||||||
|
|
||||||
|
class QueryData: public HttpFullBody {
|
||||||
|
public:
|
||||||
|
QueryData();
|
||||||
|
QueryData(std::string data);
|
||||||
|
std::string contentType();
|
||||||
|
std::map<std::string, std::vector<std::string>> data;
|
||||||
|
std::string toQuery();
|
||||||
|
void append(std::string key, std::string value);
|
||||||
|
void set(std::string key, std::string value);
|
||||||
|
protected:
|
||||||
|
std::string body();
|
||||||
|
};
|
||||||
|
|
||||||
|
class AIException: std::exception {
|
||||||
|
public:
|
||||||
|
AIException(int code);
|
||||||
|
const char* what();
|
||||||
|
private:
|
||||||
|
int code;
|
||||||
|
};
|
||||||
|
|
||||||
|
class SocketError: std::exception {
|
||||||
|
public:
|
||||||
|
SocketError();
|
||||||
|
const char* what();
|
||||||
|
private:
|
||||||
|
int code;
|
||||||
|
std::string message;
|
||||||
|
};
|
||||||
|
|
||||||
|
class Socket {
|
||||||
|
public:
|
||||||
|
Socket(std::string host, std::string protocol);
|
||||||
|
~Socket();
|
||||||
|
void connect();
|
||||||
|
size_t send(const char* data, size_t len, int flags = 0);
|
||||||
|
template<size_t T>
|
||||||
|
size_t send(const char(&data)[T], int flags = 0) {
|
||||||
|
return this->send(data, T, flags);
|
||||||
|
}
|
||||||
|
size_t send(std::string data, int flags = 0);
|
||||||
|
size_t recv(char* data, size_t len, int flags = 0);
|
||||||
|
template<size_t T>
|
||||||
|
size_t recv(char(&data)[T], int flags = 0) {
|
||||||
|
return this->recv(data, T, flags);
|
||||||
|
}
|
||||||
|
std::string recv(size_t len, int flags = 0);
|
||||||
|
void close();
|
||||||
|
private:
|
||||||
|
#if HAVE_OPENSSL
|
||||||
|
SSL_CTX* ssl_ctx = nullptr;
|
||||||
|
BIO* web = nullptr;
|
||||||
|
SSL* ssl = nullptr;
|
||||||
|
#endif
|
||||||
|
std::string host;
|
||||||
|
std::string protocol;
|
||||||
|
int socket = -1;
|
||||||
|
bool closed = false;
|
||||||
|
addrinfo* addr = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
class Request {
|
||||||
|
public:
|
||||||
|
Request(std::string host, std::string path, std::string method, HeaderMap headers, HttpClientOptions options);
|
||||||
|
~Request();
|
||||||
|
void send();
|
||||||
|
HeaderMap headers;
|
||||||
|
HttpClientOptions options;
|
||||||
|
void setBody(HttpBody* body) {
|
||||||
|
if (this->body != nullptr) delete this->body;
|
||||||
|
this->body = body;
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
HttpBody* body = nullptr;
|
||||||
|
std::string host;
|
||||||
|
std::string path;
|
||||||
|
std::string method;
|
||||||
|
};
|
||||||
|
|
||||||
|
class Response {
|
||||||
|
public:
|
||||||
|
Response(Request req, Socket socket);
|
||||||
|
private:
|
||||||
|
Request req;
|
||||||
|
Socket socket;
|
||||||
|
};
|
||||||
|
|
||||||
|
class HttpClient {
|
||||||
|
public:
|
||||||
|
HttpClient(std::string host);
|
||||||
|
Request request(std::string path, std::string method);
|
||||||
|
HttpClientOptions options;
|
||||||
|
HeaderMap headers;
|
||||||
|
private:
|
||||||
|
std::string host;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
12
str_util.cpp
12
str_util.cpp
@@ -18,6 +18,18 @@ bool str_util::tolowercase(std::string ori, std::string& result) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool str_util::touppercase(std::string ori, std::string& result) {
|
||||||
|
char* tmp = nullptr;
|
||||||
|
auto re = cstr_touppercase(ori.c_str(), ori.length(), &tmp);
|
||||||
|
if (re) {
|
||||||
|
result = std::string(tmp, ori.length());
|
||||||
|
free(tmp);
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
std::string str_util::str_replace(std::string input, std::string pattern, std::string new_content) {
|
std::string str_util::str_replace(std::string input, std::string pattern, std::string new_content) {
|
||||||
auto loc = input.find(pattern, 0);
|
auto loc = input.find(pattern, 0);
|
||||||
auto len = pattern.length();
|
auto len = pattern.length();
|
||||||
|
|||||||
@@ -12,6 +12,13 @@ namespace str_util {
|
|||||||
* @return true if successed.
|
* @return true if successed.
|
||||||
*/
|
*/
|
||||||
bool tolowercase(std::string ori, std::string& result);
|
bool tolowercase(std::string ori, std::string& result);
|
||||||
|
/**
|
||||||
|
* @brief Convert string to uppercase
|
||||||
|
* @param ori Origin string
|
||||||
|
* @param result Output string.
|
||||||
|
* @return true if successed.
|
||||||
|
*/
|
||||||
|
bool touppercase(std::string ori, std::string& result);
|
||||||
/**
|
/**
|
||||||
* @brief Replace all pattern to new_content
|
* @brief Replace all pattern to new_content
|
||||||
* @param input Input string
|
* @param input Input string
|
||||||
|
|||||||
@@ -17,3 +17,4 @@
|
|||||||
#cmakedefine HAVE__STRNICMP @HAVE__STRNICMP@
|
#cmakedefine HAVE__STRNICMP @HAVE__STRNICMP@
|
||||||
#cmakedefine HAVE_STRNCASECMP @HAVE_STRNCASECMP@
|
#cmakedefine HAVE_STRNCASECMP @HAVE_STRNCASECMP@
|
||||||
#cmakedefine HAVE_FCLOSEALL @HAVE_FCLOSEALL@
|
#cmakedefine HAVE_FCLOSEALL @HAVE_FCLOSEALL@
|
||||||
|
#cmakedefine HAVE_OPENSSL @HAVE_OPENSSL@
|
||||||
|
|||||||
Reference in New Issue
Block a user