/* * client.cpp * * Created on: 2021年8月16日 * Author: pengzc */ #include "http_client.h" #include #include #include #include #include #include "utils/Log.h" #include "manager/ConfigManager.h" namespace { class CURLGlobal { public: CURLGlobal() { CURLcode res = curl_global_init(CURL_GLOBAL_ALL); if (res != CURLE_OK) { LOGE("CURL curl_global_init failed"); } //忽略SIGPIPE struct sigaction sa; sa.sa_handler = SIG_IGN; sa.sa_flags = 0; if (sigemptyset(&sa.sa_mask) == -1 || sigaction(SIGPIPE, &sa, 0) == -1) { LOGE("failed ignore SIGPIPE"); } } ~ CURLGlobal() { curl_global_cleanup(); } }; } /* anonymous namespace */ namespace base { HttpClient::HttpClient() { static CURLGlobal global; curl_ = curl_easy_init(); if (curl_ == NULL) { LOGE("curl_easy_init failed"); } timeout_ = 0; connection_timeout_ = 0; ssl_verify_ = true; aborted_ = false; low_speed_limit_.bytes = 0; low_speed_limit_.seconds = 0; debug_ = false; ca_info_file_path_ = CONFIGMANAGER->getResFilePath("cacert.pem"); } HttpClient::~HttpClient() { if (curl_) { curl_easy_cleanup(curl_); } } HttpResponse HttpClient::Do(const HttpRequest& request) { return Do(request, "", NULL); } HttpResponse HttpClient::Do(const HttpRequest& request, ProgressCallback progress) { return Do(request, "", progress); } HttpResponse HttpClient::Do(const HttpRequest& request, const std::string& save_path, ProgressCallback progress) { Mutex::Autolock lock(do_mutex_); if (curl_ == NULL) { return HttpResponse(); } curl_easy_reset(curl_); aborted_ = false; HttpResponse response; std::string url = request.url_; std::string header_string; CURLcode res = CURLE_OK; /** set query URL */ curl_easy_setopt(curl_, CURLOPT_URL, url.c_str()); #if 0 if (request.method_.compare("download") != 0) { /** set callback function */ curl_easy_setopt(this->curl_, CURLOPT_WRITEFUNCTION, Helpers::write_callback); /** set data object to pass to callback function */ curl_easy_setopt(this->curl_, CURLOPT_WRITEDATA, &ret); } else #endif /** set data callback */ curl_easy_setopt(curl_, CURLOPT_WRITEFUNCTION, curl_response_write_callback); /** set data object to pass to callback function */ curl_easy_setopt(curl_, CURLOPT_WRITEDATA, &response); if (request.method_.compare("GET") == 0) { } else if (request.method_.compare("POST") == 0) { /** Now specify we want to POST data */ curl_easy_setopt(this->curl_, CURLOPT_POST, 1L); if (!request.body_.empty()) { /** set post fields */ curl_easy_setopt(this->curl_, CURLOPT_POSTFIELDS, request.body_.c_str()); curl_easy_setopt(this->curl_, CURLOPT_POSTFIELDSIZE, request.body_.size()); } if (!request.multiparts_.empty()) { struct curl_httppost *formpost = NULL; struct curl_httppost *lastptr = NULL; for (auto it = request.multiparts_.begin(); it != request.multiparts_.end(); ++it) { curl_formadd(&formpost, &lastptr, CURLFORM_PTRNAME, it->first.c_str(), CURLFORM_PTRCONTENTS, it->second.data(), CURLFORM_CONTENTSLENGTH, it->second.length(), CURLFORM_END); } /** Now specify we want to POST data */ curl_easy_setopt(this->curl_, CURLOPT_HTTPPOST, formpost); } } #if 0 /** set the header callback function */ curl_easy_setopt(this->curl_, CURLOPT_HEADERFUNCTION, header_callback); /** callback object for headers */ curl_easy_setopt(this->curl_, CURLOPT_HEADERDATA, &response); #endif /** set http headers */ curl_slist* header_list = NULL; for (HttpRequest::HeaderFields::const_iterator it = request.header.begin(); it != request.header.end(); ++it) { header_string = it->first; header_string += ": "; header_string += it->second; curl_slist* temp = curl_slist_append(header_list, header_string.c_str()); if (temp == NULL) { LOGE("curl_slist_append failed"); curl_slist_free_all(header_list); header_list = NULL; break; } header_list = temp; } if (header_list != NULL) { curl_easy_setopt(curl_, CURLOPT_HTTPHEADER, header_list); } progress_callback_ = progress; curl_easy_setopt(curl_, CURLOPT_XFERINFOFUNCTION, HttpClient::curl_progress_callback); curl_easy_setopt(curl_, CURLOPT_XFERINFODATA, this); curl_easy_setopt(curl_, CURLOPT_NOPROGRESS, 0); if ((low_speed_limit_.bytes > 0) && (low_speed_limit_.seconds > 0)) { curl_easy_setopt(curl_, CURLOPT_LOW_SPEED_LIMIT, low_speed_limit_.bytes); curl_easy_setopt(curl_, CURLOPT_LOW_SPEED_TIME, low_speed_limit_.seconds); } #if 0 // set basic auth if configured if (this->basicAuth.username.length() > 0) { std::string authString = std::string(this->basicAuth.username + ":" + this->basicAuth.password); curl_easy_setopt(this->curl_, CURLOPT_HTTPAUTH, CURLAUTH_BASIC); curl_easy_setopt(this->curl_, CURLOPT_USERPWD, authString.c_str()); } #endif #if 0 /** set user agent */ curl_easy_setopt(this->curl_, CURLOPT_USERAGENT, this->GetUserAgent().c_str()); #endif // set timeout if (connection_timeout_ > 0) { curl_easy_setopt(this->curl_, CURLOPT_CONNECTTIMEOUT_MS, connection_timeout_); // dont want to get a sig alarm on timeout curl_easy_setopt(curl_, CURLOPT_NOSIGNAL, 1L); } if (timeout_ > 0) { curl_easy_setopt(this->curl_, CURLOPT_TIMEOUT_MS, timeout_); // dont want to get a sig alarm on timeout curl_easy_setopt(curl_, CURLOPT_NOSIGNAL, 1L); } //允许重定向 curl_easy_setopt(this->curl_, CURLOPT_FOLLOWLOCATION, 1L); curl_easy_setopt(this->curl_, CURLOPT_MAXREDIRS, static_cast(9)); //多线程下使用超时禁用SIGNAL curl_easy_setopt(curl_, CURLOPT_NOSIGNAL, 1L); //指定DNS解析规范为IPV4 curl_easy_setopt(curl_, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4); //指定DNS curl_easy_setopt(curl_, CURLOPT_DNS_SERVERS, "1.2.4.8,210.2.4.8,114.114.114.114,119.29.29.29"); // if provided, supply CA path if (!this->ca_info_file_path_.empty()) { curl_easy_setopt(curl_, CURLOPT_CAINFO, ca_info_file_path_.c_str()); } if (!ssl_verify_) { curl_easy_setopt(curl_, CURLOPT_SSL_VERIFYPEER, 0L); curl_easy_setopt(curl_, CURLOPT_SSL_VERIFYHOST, 0L); } if (debug_) { curl_easy_setopt(curl_, CURLOPT_DEBUGFUNCTION, debug_trace); /* the DEBUGFUNCTION has no effect until we enable VERBOSE */ curl_easy_setopt(curl_, CURLOPT_VERBOSE, 1L); } if (!save_path.empty()) { response.dst_file_ = fopen(save_path.c_str(), "wb"); if (response.dst_file_ == NULL) { LOGE("open %s failed, %s", save_path.c_str(), strerror(errno)); response.error_code_ = errno; response.error_message_ = strerror(errno); return response; } } #if 0 // set cert file path if (!this->certPath.empty()) { curl_easy_setopt(this->curl_, CURLOPT_SSLCERT, this->certPath.c_str()); } // set cert type if (!this->certType.empty()) { curl_easy_setopt(this->curl_, CURLOPT_SSLCERTTYPE, this->certType.c_str()); } // set key file path if (!this->keyPath.empty()) { curl_easy_setopt(this->curl_, CURLOPT_SSLKEY, this->keyPath.c_str()); } // set key password if (!this->keyPassword.empty()) { curl_easy_setopt(this->curl_, CURLOPT_KEYPASSWD, this->keyPassword.c_str()); } // set web proxy address if (!this->uriProxy.empty()) { curl_easy_setopt(this->curl_, CURLOPT_PROXY, uriProxy.c_str()); curl_easy_setopt(this->curl_, CURLOPT_HTTPPROXYTUNNEL, 1L); } #endif char error_buffer[CURL_ERROR_SIZE] = { 0 }; curl_easy_setopt(curl_, CURLOPT_ERRORBUFFER, error_buffer); res = curl_easy_perform(curl_); if (res != CURLE_OK) { response.error_message_ = error_buffer; response.error_code_ = res; } else { int64_t http_code = 0; curl_easy_getinfo(curl_, CURLINFO_RESPONSE_CODE, &http_code); response.status_code_ = static_cast(http_code); } curl_easy_getinfo(curl_, CURLINFO_TOTAL_TIME, &response.total_time); curl_easy_getinfo(this->curl_, CURLINFO_NAMELOOKUP_TIME, &response.name_lookup_time); curl_easy_getinfo(this->curl_, CURLINFO_CONNECT_TIME, &response.connect_time); curl_easy_getinfo(this->curl_, CURLINFO_APPCONNECT_TIME, &response.app_connect_time); curl_easy_getinfo(this->curl_, CURLINFO_PRETRANSFER_TIME, &response.pre_transfer_time); curl_easy_getinfo(this->curl_, CURLINFO_STARTTRANSFER_TIME, &response.start_transfer_time); curl_easy_getinfo(this->curl_, CURLINFO_REDIRECT_TIME, &response.redirect_time); curl_easy_getinfo(this->curl_, CURLINFO_REDIRECT_COUNT, &response.redirect_count); if (response.dst_file_ != NULL) { fflush(response.dst_file_); fclose(response.dst_file_); sync(); response.dst_file_ = NULL; } // free header list curl_slist_free_all(header_list); return response; } /** * @brief set custom Certificate Authority (CA) path * * @param caInfoFilePath - The path to a file holding the certificates used to * verify the peer with. See CURLOPT_CAINFO * */ void HttpClient::SetCAInfoFilePath(const std::string& caInfoFilePath) { this->ca_info_file_path_ = caInfoFilePath; } #if 0 /** * @brief set username and password for basic auth * * @param username * @param password * */ void HttpClient::SetBasicAuth(const std::string& username, const std::string& password) { this->basicAuth.username = username; this->basicAuth.password = password; } #endif /** * @brief set certificate path * * @param path to certificate file * */ //void //HttpClient::SetCertPath(const std::string& cert) { // this->certPath = cert; //} /** * @brief set certificate type * * @param certificate type (e.g. "PEM" or "DER") * */ //void //HttpClient::SetCertType(const std::string& certType) { // this->certType = certType; //} /** * @brief set key path * * @param path to key file * */ //void //HttpClient::SetKeyPath(const std::string& keyPath) { // this->keyPath = keyPath; //} /** * @brief set key password * * @param key password * */ //void //HttpClient::SetKeyPassword(const std::string& keyPassword) { // this->keyPassword = keyPassword; //} /** * @brief set HTTP proxy address and port * * @param proxy address with port number * */ //void //HttpClient::SetProxy(const std::string& uriProxy) { // std::string uriProxyUpper = uriProxy; // // check if the provided address is prefixed with "http" // std::transform(uriProxyUpper.begin(), uriProxyUpper.end(), // uriProxyUpper.begin(), ::toupper); // // if ((uriProxy.length() > 0) && (uriProxyUpper.compare(0, 4, "HTTP") != 0)) { // this->uriProxy = "http://" + uriProxy; // } else { // this->uriProxy = uriProxy; // } //} /** * @brief helper function to get called from the actual request methods to * prepare the curlHandle for transfer with generic options, perform the * request and record some stats from the last request and then reset the * handle with curl_easy_reset to its default state. This will keep things * like connections and session ID intact but makes sure you can change * parameters on the object for another request. * * @param uri URI to query * @param ret Reference to the Response struct that should be filled * * @return 0 on success and 1 on error */ //Response //HttpClient::performCurlRequest(const std::string& uri, const std::string method_type) {} /** * @brief HTTP GET method * * @param url to query * * @return response struct */ //Response //HttpClient::get(const std::string& url) { // return this->performCurlRequest(url); //} /** * @brief HTTP POST method * * @param url to query * @param data HTTP POST body * * @return response struct */ //Response //HttpClient::post(const std::string& url, // const std::string& data) { // /** Now specify we want to POST data */ // curl_easy_setopt(this->curlHandle, CURLOPT_POST, 1L); // /** set post fields */ // curl_easy_setopt(this->curlHandle, CURLOPT_POSTFIELDS, data.c_str()); // curl_easy_setopt(this->curlHandle, CURLOPT_POSTFIELDSIZE, data.size()); // // return this->performCurlRequest(url); //} //Response //HttpClient::post(const std::string& url, // const std::map& form_data) { // // struct curl_httppost *formpost = 0; // struct curl_httppost *lastptr = 0; // for (auto it = form_data.begin(); it != form_data.end(); ++it) { // curl_formadd(&formpost, &lastptr, // CURLFORM_PTRNAME, it->first.c_str(), // CURLFORM_PTRCONTENTS, it->second.data(), // CURLFORM_CONTENTSLENGTH, it->second.length(), // CURLFORM_END); // } // // /** Now specify we want to POST data */ // curl_easy_setopt(this->curlHandle, CURLOPT_HTTPPOST, formpost); // // Response response = this->performCurlRequest(url); // curl_formfree(formpost); // return response; //} HttpClient& HttpClient::SetConnectionTimeout(int ms) { connection_timeout_ = ms; return *this; } HttpClient& HttpClient::SetTimeout(int ms) { timeout_ = ms; return *this; } // trim from start static inline std::string <rim(std::string &s) { // NOLINT s.erase(s.begin(), std::find_if(s.begin(), s.end(), std::not1(std::ptr_fun(std::isspace)))); return s; } // trim from end static inline std::string &rtrim(std::string &s) { // NOLINT s.erase(std::find_if(s.rbegin(), s.rend(), std::not1(std::ptr_fun(std::isspace))).base(), s.end()); return s; } // trim from both ends static inline std::string &trim(std::string &s) { // NOLINT return ltrim(rtrim(s)); } size_t HttpClient::curl_response_write_callback(void* data, size_t size, size_t nmemb, void* userdata) { HttpResponse* response = (HttpResponse*) userdata; if (response->dst_file_ == NULL) { response->body_.append(reinterpret_cast(data), size * nmemb); return size * nmemb; } return fwrite(reinterpret_cast(data), 1, size * nmemb, response->dst_file_); } size_t HttpClient::header_callback(void *data, size_t size, size_t nmemb, void *userdata) { HttpResponse* r = (HttpResponse*)userdata; std::string header(reinterpret_cast(data), size*nmemb); //LOGD("Header Callback [%s]", trim(header).c_str()); size_t seperator = header.find_first_of(':'); if ( std::string::npos == seperator ) { // roll with non seperated headers... trim(header); if (0 == header.length()) { return (size * nmemb); // blank line; } r->header_[header] = "present"; } else { std::string key = header.substr(0, seperator); trim(key); std::string value = header.substr(seperator + 1); trim(value); auto it = r->header_.find(key); if (it == r->header_.end()) { //LOGD("It's new Head Field %s", key.c_str()); r->header_[key] = value; } else { //LOGD("Duplicate Head Field %s", key.c_str()); } //LOGD("headers size %d", r->header_.size()); // LOGD("HHHHHHHHHHHHHead%p %s %s", r, key.c_str(), value.c_str()); } return (size * nmemb); } void HttpClient::SetSSLVerify(bool verify) { ssl_verify_ = verify; } void HttpClient::Abort() { aborted_ = true; Mutex::Autolock lock(do_mutex_); } int HttpClient::curl_progress_callback(void* data, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow) { HttpClient* client = (HttpClient*)data; if (client->aborted_) { return -1; } if (client->progress_callback_ != NULL) { return client->progress_callback_(dltotal, dlnow, ultotal, ulnow); } return 0; } static void dump(const char *text, unsigned char *ptr, size_t size) { LOGD("%s, %10.10ld bytes (0x%8.8lx)\n", text, (long)size, (long)size); } int HttpClient::debug_trace(CURL *handle, curl_infotype type, char *data, ::size_t size, void *userp) { const char *text; (void)handle; /* prevent compiler warning */ (void)userp; switch (type) { case CURLINFO_TEXT: LOGD("== Info: %s", data); default: /* in case a new one is introduced to shock us */ return 0; case CURLINFO_HEADER_OUT: text = "=> Send header"; break; case CURLINFO_DATA_OUT: text = "=> Send data"; break; case CURLINFO_SSL_DATA_OUT: text = "=> Send SSL data"; break; case CURLINFO_HEADER_IN: text = "<= Recv header"; break; case CURLINFO_DATA_IN: text = "<= Recv data"; break; case CURLINFO_SSL_DATA_IN: text = "<= Recv SSL data"; break; } dump(text, (unsigned char *)data, size); return 0; } HttpClient& HttpClient::SetLowSpeedLimit(int bytes, int seconds) { low_speed_limit_.bytes = bytes; low_speed_limit_.seconds = seconds; return *this; } void HttpClient::SetDebug(bool debug) { debug_ = debug; } } /* namespace base */