/*Software License Copyright(C) 2024[liuyingjie] License Terms Usage Rights Any individual or entity is free to use, copy, and distribute the binary form of this software without modification to the source code, without the need to disclose the source code. If the source code is modified, the modifications must be open - sourced under the same license.This means that the modifications must be disclosed and accompanied by a copy of this license. Future Versions Updates From this version onwards, all future releases will be governed by the terms of the latest version of the license.This license will automatically be nullified and replaced by the new version. Users must comply with the terms of the new license issued in future releases. Liability and Disclaimer This software is provided “as is”, without any express or implied warranties, including but not limited to the warranties of merchantability, fitness for a particular purpose, and non - infringement.In no event shall the author or copyright holder be liable for any claims, damages, or other liabilities, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the software or the use or other dealings in the software. Contact Information If you have any questions, please contact us: 1585346868@qq.com Or visit our website fwlua.com. */ #include "net/http_client_plus.h" #if USE_NET_HTTP_CLIENT #include "HPSocket/HPSocket.h" #include "HPSocket/HPSocket-SSL.h" #include #include #include #include #include #include "base/conversion.h" #include "util/codec.h" #include "util/strutils.h" #include "util/time.h" #include "util/system.h" #define _DEBUG_CLIENT 0 #define CLIENT ((IHttpClient*)client()) class http_client_listener :public IHttpClientListener { public: http_client_listener() { m_client = nullptr; m_status = 0; m_download_callback = nullptr; m_download_callback_end = nullptr; m_download_callback_failed = nullptr; m_content_length = 0; m_recv_body_length = 0; transfer_encoding_length = -1; } // 通过 IHttpClientListener 继承 virtual EnHttpParseResult OnMessageBegin(IHttpClient* pSender, CONNID dwConnID) override { if (m_client->m_close == true) return HPR_ERROR; #if _DEBUG_CLIENT == 1 std::cout << "[OnMessageBegin]:\t" << dwConnID << std::endl; #endif transfer_encoding_length = -1; m_response_body.clear(); m_recv_body_length = 0; m_status = 0; return HPR_OK; } virtual EnHttpParseResult OnRequestLine(IHttpClient* pSender, CONNID dwConnID, LPCSTR lpszMethod, LPCSTR lpszUrl) override { #if _DEBUG_CLIENT == 1 std::cout << "[OnRequestLine]:\t" << dwConnID<<"\t"<m_close == true) return HPR_ERROR; #if _DEBUG_CLIENT == 1 std::cout << "[OnHeader]:\t" << dwConnID << "\t" << lpszName << ": " << lpszValue << std::endl; #endif if (strcmp("Content-Length", lpszName) == 0) { m_content_length = ylib::stoull(lpszValue); } m_headers_response.set(lpszName,lpszValue); return HPR_OK; } virtual EnHttpParseResult OnHeadersComplete(IHttpClient* pSender, CONNID dwConnID) override { #if _DEBUG_CLIENT == 1 std::cout << "[OnHeadersComplete]"<< std::endl; #endif //m_recv_state = 1; return HPR_OK; } std::string dec2hex(int i) //将int转成16进制字符串 { std::stringstream ioss; //定义字符串流 std::string s_temp; //存放转化后字符 ioss << std::setiosflags(std::ios::uppercase) << std::hex << i; //以十六制(大写)形式输出 //ioss << resetiosflags(ios::uppercase) << hex << i; //以十六制(小写)形式输出//取消大写的设置 ioss >> s_temp; return s_temp; } virtual EnHttpParseResult OnBody(IHttpClient* pSender, CONNID dwConnID, const BYTE* pData, int iLength) override { #if _DEBUG_CLIENT == 1 std::cout << "[OnBody]:\t"<< iLength << std::endl; #endif if (m_client->m_close == true) return HPR_ERROR; if (m_download_callback != nullptr) { m_recv_body_length += iLength; if (m_download_callback((void*)pData, iLength, m_recv_body_length,m_content_length, *m_client)) return EnHttpParseResult::HPR_OK; else return EnHttpParseResult::HPR_ERROR; } else { //if (transfer_encoding_length != -1) //{ // std::string length = dec2hex(iLength) + "\r\n"; // m_response_body.append(length); //} m_response_body.append((char*)pData, iLength); #ifdef _DEBUG iLength++; #endif } return EnHttpParseResult::HPR_OK; } virtual EnHttpParseResult OnChunkHeader(IHttpClient* pSender, CONNID dwConnID, int iLength) override { if (m_client->m_close == true) return HPR_ERROR; transfer_encoding_length += iLength; #if _DEBUG_CLIENT == 1 std::cout << "[OnChunkHeader]:\t" << dwConnID<<"\t"<m_close == true) return EnHandleResult::HR_ERROR; return EnHandleResult::HR_OK; } virtual EnHandleResult OnReceive(ITcpClient* pSender, CONNID dwConnID, const BYTE* pData, int iLength) override { #if _DEBUG_CLIENT == 1 std::cout << "[OnReceive]:\t Length:" << iLength << " dwConnID:" << dwConnID << std::endl; #endif if (m_client->m_close == true) return EnHandleResult::HR_ERROR; return EnHandleResult::HR_OK; } virtual EnHandleResult OnReceive(ITcpClient* pSender, CONNID dwConnID, int iLength) override { #if _DEBUG_CLIENT == 1 std::cout << "[OnReceive2]:\t Length:" << iLength << " dwConnID:" << dwConnID << std::endl; #endif if (m_client->m_close == true) return EnHandleResult::HR_ERROR; return EnHandleResult::HR_OK; } virtual EnHandleResult OnClose(ITcpClient* pSender, CONNID dwConnID, EnSocketOperation enOperation, int iErrorCode) override { #if _DEBUG_CLIENT == 1 std::cout << "[OnClose]:\t" << dwConnID << std::endl; #endif m_recv_state = 2; m_connect_state = 0; return EnHandleResult::HR_OK; } virtual EnHandleResult OnPrepareConnect(ITcpClient* pSender, CONNID dwConnID, SOCKET socket) override { #if _DEBUG_CLIENT == 1 std::cout << "[OnPrepareConnect]:\t" << dwConnID << std::endl; #endif return EnHandleResult::HR_OK; } virtual EnHandleResult OnConnect(ITcpClient* pSender, CONNID dwConnID) override { #if _DEBUG_CLIENT == 1 std::cout << "[OnConnect]:\t" << dwConnID << std::endl; #endif return EnHandleResult::HR_OK; } public: int32 transfer_encoding_length; // 预计接收长度 uint64 m_content_length; // 已接收长度 uint64 m_recv_body_length; // 客户端 network::http::client_plus *m_client; // 0=已断开 1=连接中 2=已连接 int m_connect_state = 0; // 0=请求中 1=接收成功 2=请求失败 int m_recv_state = 0; // 返回数据 ylib::buffer m_response_body; // [header] 响应 network::http::header_list m_headers_response; // 状态码 ushort m_status; std::function m_download_callback; std::function m_download_callback_end; std::function m_download_callback_failed; }; void ylib::network::http::client_plus::close() { m_close = true; } ylib::network::http::client_plus::client_plus() { m_listener = new http_client_listener; m_client_ssl = HP_Create_HttpsClient(m_listener); m_client = HP_Create_HttpClient(m_listener); m_listener->m_client = this; m_init = false; m_port = 0; m_ssl = false; m_timeout_connect_msec = 3000; m_timeout_recv_msec = 8000; m_cache = nullptr; m_close = false; } ylib::network::http::client_plus::~client_plus() { ((IHttpClient*)m_client_ssl)->Stop(); ((IHttpClient*)m_client_ssl)->Wait(); HP_Destroy_HttpsClient(((IHttpClient*)m_client_ssl)); ((IHttpClient*)m_client)->Stop(); ((IHttpClient*)m_client)->Wait(); HP_Destroy_HttpClient(((IHttpClient*)m_client)); delete m_listener; } void ylib::network::http::client_plus::set_timeout(uint32 connect_msec, uint32 recv_msec) { m_timeout_connect_msec = connect_msec; m_timeout_recv_msec = recv_msec; } bool ylib::network::http::client_plus::del(const std::string& url, const std::map& value) { m_method = "DELETE"; std::string param_str; for_iter(iter, value) param_str += (param_str.length() == 0 ? "" : "&") + iter->first + "=" + iter->second; if(!init()) return false; if(!parseurl(std::string(url + (param_str.length() == 0 ? "" : "?") + param_str))) return false; m_url = url; if(!connect()) return false; return request(); } bool ylib::network::http::client_plus::get(const std::string& url, const std::map& value,bool wait) { m_request_wait = wait; m_method = "GET"; std::string param_str; for_iter(iter, value) param_str += (param_str.length() == 0 ? "" : "&") + iter->first + "=" + iter->second; if(!init()) return false; if(!parseurl(std::string(url + (param_str.length() == 0 ? "" : "?") + param_str))) return false; m_url = url; if(!connect()) return false; return request(); } bool ylib::network::http::client_plus::post(const std::string& url, const std::map& value,bool to_utf8) { m_request_wait = true; std::string param_str; for_iter(iter, value) param_str += (param_str.length() == 0 ? "" : "&") + iter->first + "=" + iter->second; if (to_utf8) { param_str = codec::to_utf8(param_str); m_headers_request.set("Content-Type", "application/x-www-form-urlencoded;charset=utf-8"); } else { m_headers_request.set("Content-Type", "application/x-www-form-urlencoded"); } m_request_body = param_str; return post(url); } bool ylib::network::http::client_plus::post(const std::string& url, const ylib::json& value, bool to_utf8) { m_headers_request.set("Content-Type", "application/json"); ylib::buffer data; if (to_utf8) data = codec::to_utf8(value.to_string()); else data = value.to_string(); return post(url,data); } bool ylib::network::http::client_plus::post(const std::string& url, const ylib::buffer& value) { m_request_body = value; return post(url); } //bool ylib::network::http::client_plus::post(const std::string& url, const http::make_form& value) //{ // std::string boundary; // value.make(m_request_body, boundary); // m_headers_request.set("Content-Type", "boundary=" + boundary + "; multipart/form-data"); // return post(url); //} bool ylib::network::http::client_plus::head(const std::string& url) { m_method = "HEAD"; if (!init()) return false; if (!parseurl(url)) return false; m_url = url; if (!connect()) return false; return request(); } void ylib::network::http::client_plus::setproxy(const std::string& address, ushort port) { m_proxy.address = address; m_proxy.port = port; } network::http::header_list& ylib::network::http::client_plus::headers_request() { // TODO: 在此处插入 return 语句 return m_headers_request; } network::http::header_list& ylib::network::http::client_plus::headers_response() { // TODO: 在此处插入 return 语句 return m_listener->m_headers_response; } uint32 ylib::network::http::client_plus::status() { return m_listener->m_status; } ylib::buffer& ylib::network::http::client_plus::response() { return m_listener->m_response_body; } void ylib::network::http::client_plus::cache(client_cache* cache) { m_cache = cache; } bool ylib::network::http::client_plus::parseurl(std::string url) { url = strutils::trim_end(url, { '/','\\'}); if (strutils::left(url,7) == "http://") m_ssl = false; else if (strutils::left(url,8) == "https://") m_ssl = true; else { m_lastErrorDesc = "Only https and http protocols are supported,Bad address:"+std::string(url); return false; } std::string pu; if (m_ssl) pu = strutils::right(url,url.length() - 8); else pu = strutils::right(url,url.length() - 7); size_t index = pu.find("/"); if (index != -1) { m_path = strutils::right(pu,pu.length() - index); auto arr =strutils::split(pu,'/'); if (arr.size() < 2) { m_lastErrorDesc = "parseurl failed 0x01"; return false; } pu = arr[0]; } else m_path = "/"; // www.baidu.com:443 index = pu.find(":"); if (index == -1) { if (m_ssl) m_port = 443; else m_port = 80; } else { auto arr = strutils::split(pu,':'); if (arr.size() != 2) { m_lastErrorDesc = "parseurl failed 0x02"; return false; } pu = arr[0]; m_port = (ushort)ylib::stoi(arr[1]); } m_ipaddress = pu;// network::to_ip(pu); /*if (m_proxy.address != "") m_ssl = false;*/ // if (m_ssl) // { // std::cout << "The hpsocket does not support the https protocol. Please define a LIB_ HPSOCKET_ SSL Macro" << std::endl; // } return true; } bool ylib::network::http::client_plus::connect() { if (m_listener->m_connect_state == 2) return true; m_close = false; m_listener->m_connect_state = 1; bool connect_result = false; if (m_proxy.address == "") connect_result = CLIENT->Start(m_ipaddress.c_str(), m_port); else connect_result = CLIENT->Start(m_proxy.address.c_str(), m_proxy.port); if (connect_result == false) { m_lastErrorDesc = std::string("start failed," + std::to_string((uint64)SYS_GetLastError())); return false; } timestamp start_msec = time::now_msec(); while (m_listener->m_connect_state == 1) { system::sleep_msec(10); if (start_msec + m_timeout_connect_msec < time::now_msec()) { m_lastErrorDesc = "connection timed out"; return false; } } if(m_proxy.address == "") return m_listener->m_connect_state == 2; return init_proxy(); } bool ylib::network::http::client_plus::init() { ((IHttpClient*)m_client)->Stop(); ((IHttpClient*)m_client)->Wait(); ((IHttpClient*)m_client_ssl)->Stop(); ((IHttpClient*)m_client_ssl)->Wait(); ((IHttpClient*)m_client_ssl)->CleanupSSLContext(); if (((IHttpClient*)m_client_ssl)->SetupSSLContext() == false) { m_lastErrorDesc = std::string("SetupSSLContext failed," + std::to_string((uint64)SYS_GetLastError())).c_str(); return false; } return true; } bool ylib::network::http::client_plus::request() { // std::cout << CLIENT->GetSSLCipherList() << std::endl; m_listener->m_recv_state = 0; if (m_method == "GET") { //【缓存】置头 if (m_cache != nullptr) { m_cache->set_header(this, m_url); } } //置Cookie { if (m_cookie.to_string() != "") { m_headers_request.set("Cookie", m_cookie.to_string()); } } //完善协议头 { if(m_headers_request.exist("User-Agent") == false) m_headers_request.set("User-Agent","Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:108.0) Gecko/20100101 Firefox/108.0"); if (m_headers_request.exist("Accept") == false) m_headers_request.set("Accept", "*/*"); if (m_headers_request.exist("Accept-Language") == false) m_headers_request.set("Accept-Language", "zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2"); if (m_headers_request.exist("Connection") == false) { if(m_proxy.address == "") m_headers_request.set("Connection", "keep-alive"); else m_headers_request.set("Connection", "close"); } if (m_headers_request.exist("Host") == false) { std::string header_host = m_ipaddress; if (m_port != 80 && m_port != 443) header_host += (":" + std::to_string(m_port)); m_headers_request.set("Host", header_host); } } THeader* pHeader = nullptr; auto header_map = m_headers_request.to(); size_t nHeader = header_map.size(); if (nHeader != 0) pHeader = new THeader[nHeader]; size_t count = 0; for_iter(iter,header_map) { pHeader[count].name = iter->first.c_str(); pHeader[count].value = iter->second.c_str(); count++; } bool result = CLIENT->SendRequest( m_method.c_str(), m_path.c_str(), pHeader, (int)nHeader, (const BYTE*)m_request_body.data(), (int)m_request_body.length() ); delete[] pHeader; if (result == false) { uint64 error = SYS_GetLastError(); m_lastErrorDesc = std::string("request failed," +std::to_string((uint64)error)); return false; } if(m_request_wait == false) return true; timestamp start_msec = time::now_msec(); while (m_listener->m_recv_state == 0) { system::sleep_msec(10); if (start_msec + m_timeout_recv_msec < time::now_msec()) { m_lastErrorDesc = "recv timed out"; return false; } } result = m_listener->m_recv_state == 1; if(!result) return false; //【缓存】判断缓存 if (m_cache != nullptr && m_method == "GET") { bool cache = false; if (this->status() == 304) { cache = m_cache->read(this, m_listener->m_response_body); } if (cache == false) { m_cache->write(this); } } //合并响应cookie { if (m_listener->m_headers_response.exist("Set-Cookie") != false && m_listener->m_headers_response.get("Set-Cookie").to_string() != "") m_cookie.merge(m_listener->m_headers_response.get("Set-Cookie").to_string()); } return !m_close; } bool ylib::network::http::client_plus::post(const std::string& url) { m_method = "POST"; if(!init()) return false; if(!parseurl(url)) return false; m_url = url; if(!connect()) return false; return request(); } void* ylib::network::http::client_plus::client() { if (m_ssl) { return m_client_ssl; } else return m_client; } bool ylib::network::http::client_plus::init_proxy() { std::string path = m_ipaddress+":"+std::to_string(m_port); THeader header[2]; header[0].name = "Host"; header[0].value = path.c_str(); header[1].name = "Proxy-Connection"; header[1].value = "keep-alive"; bool result = CLIENT->SendRequest("CONNECT",path.c_str(),header,2); if (result == false) { uint64 error = SYS_GetLastError(); m_lastErrorDesc = std::string("proxy request failed," + std::to_string((uint64)error)); return false; } if (m_request_wait == false) return true; timestamp start_msec = time::now_msec(); while (m_listener->m_recv_state == 0) { system::sleep_msec(10); if (start_msec + m_timeout_recv_msec < time::now_msec()) { m_lastErrorDesc = "recv timed out"; return false; } } result = m_listener->m_recv_state == 1; if (!result) return false; return m_listener->m_status == 200; } void ylib::network::http::client_plus::on_down_ing(const std::function & callback) { m_listener->m_download_callback = callback; } void ylib::network::http::client_plus::on_down_end(const std::function& callback) { m_listener->m_download_callback_end = callback; } void ylib::network::http::client_plus::on_down_failed(const std::function& callback) { m_listener->m_download_callback_failed = callback; } void ylib::network::http::client_plus::clear_all_cookies() { HP_HttpCookie_MGR_ClearCookies(); } #endif