734 lines
21 KiB
C++
734 lines
21 KiB
C++
#include "HttpClient.h"
|
|
|
|
#include <mutex>
|
|
|
|
#ifdef WITH_CURL
|
|
#include "curl/curl.h"
|
|
#endif
|
|
|
|
#include "herr.h"
|
|
#include "hlog.h"
|
|
#include "htime.h"
|
|
#include "hstring.h"
|
|
#include "hsocket.h"
|
|
#include "hssl.h"
|
|
#include "HttpParser.h"
|
|
|
|
// for async
|
|
#include "AsyncHttpClient.h"
|
|
|
|
using namespace hv;
|
|
|
|
struct http_client_s {
|
|
std::string host;
|
|
int port;
|
|
int https;
|
|
int timeout; // s
|
|
http_headers headers;
|
|
// http_proxy
|
|
std::string http_proxy_host;
|
|
int http_proxy_port;
|
|
// https_proxy
|
|
std::string https_proxy_host;
|
|
int https_proxy_port;
|
|
// no_proxy
|
|
StringList no_proxy_hosts;
|
|
//private:
|
|
#ifdef WITH_CURL
|
|
CURL* curl;
|
|
#endif
|
|
// for sync
|
|
int fd;
|
|
unsigned int keepalive_requests;
|
|
hssl_t ssl;
|
|
hssl_ctx_t ssl_ctx;
|
|
bool alloced_ssl_ctx;
|
|
HttpParserPtr parser;
|
|
// for async
|
|
std::mutex mutex_;
|
|
std::shared_ptr<hv::AsyncHttpClient> async_client_;
|
|
|
|
http_client_s() {
|
|
host = LOCALHOST;
|
|
port = DEFAULT_HTTP_PORT;
|
|
https = 0;
|
|
timeout = DEFAULT_HTTP_TIMEOUT;
|
|
http_proxy_port = DEFAULT_HTTP_PORT;
|
|
https_proxy_port = DEFAULT_HTTP_PORT;
|
|
#ifdef WITH_CURL
|
|
curl = NULL;
|
|
#endif
|
|
fd = -1;
|
|
keepalive_requests = 0;
|
|
ssl = NULL;
|
|
ssl_ctx = NULL;
|
|
alloced_ssl_ctx = false;
|
|
}
|
|
|
|
~http_client_s() {
|
|
Close();
|
|
if (ssl_ctx && alloced_ssl_ctx) {
|
|
hssl_ctx_free(ssl_ctx);
|
|
ssl_ctx = NULL;
|
|
}
|
|
}
|
|
|
|
void Close() {
|
|
#ifdef WITH_CURL
|
|
if (curl) {
|
|
curl_easy_cleanup(curl);
|
|
curl = NULL;
|
|
}
|
|
#endif
|
|
if (ssl) {
|
|
hssl_free(ssl);
|
|
ssl = NULL;
|
|
}
|
|
SAFE_CLOSESOCKET(fd);
|
|
}
|
|
};
|
|
|
|
http_client_t* http_client_new(const char* host, int port, int https) {
|
|
http_client_t* cli = new http_client_t;
|
|
if (host) cli->host = host;
|
|
cli->port = port;
|
|
cli->https = https;
|
|
cli->headers["Connection"] = "keep-alive";
|
|
return cli;
|
|
}
|
|
|
|
int http_client_del(http_client_t* cli) {
|
|
if (cli == NULL) return 0;
|
|
delete cli;
|
|
return 0;
|
|
}
|
|
|
|
int http_client_set_timeout(http_client_t* cli, int timeout) {
|
|
cli->timeout = timeout;
|
|
return 0;
|
|
}
|
|
|
|
int http_client_set_ssl_ctx(http_client_t* cli, hssl_ctx_t ssl_ctx) {
|
|
cli->ssl_ctx = ssl_ctx;
|
|
return 0;
|
|
}
|
|
|
|
int http_client_new_ssl_ctx(http_client_t* cli, hssl_ctx_opt_t* opt) {
|
|
opt->endpoint = HSSL_CLIENT;
|
|
hssl_ctx_t ssl_ctx = hssl_ctx_new(opt);
|
|
if (ssl_ctx == NULL) return ERR_NEW_SSL_CTX;
|
|
cli->alloced_ssl_ctx = true;
|
|
return http_client_set_ssl_ctx(cli, ssl_ctx);
|
|
}
|
|
|
|
int http_client_clear_headers(http_client_t* cli) {
|
|
cli->headers.clear();
|
|
return 0;
|
|
}
|
|
|
|
int http_client_set_header(http_client_t* cli, const char* key, const char* value) {
|
|
cli->headers[key] = value;
|
|
return 0;
|
|
}
|
|
|
|
int http_client_del_header(http_client_t* cli, const char* key) {
|
|
auto iter = cli->headers.find(key);
|
|
if (iter != cli->headers.end()) {
|
|
cli->headers.erase(iter);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
const char* http_client_get_header(http_client_t* cli, const char* key) {
|
|
auto iter = cli->headers.find(key);
|
|
if (iter != cli->headers.end()) {
|
|
return iter->second.c_str();
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
int http_client_set_http_proxy(http_client_t* cli, const char* host, int port) {
|
|
cli->http_proxy_host = host;
|
|
cli->http_proxy_port = port;
|
|
return 0;
|
|
}
|
|
|
|
int http_client_set_https_proxy(http_client_t* cli, const char* host, int port) {
|
|
cli->https_proxy_host = host;
|
|
cli->https_proxy_port = port;
|
|
return 0;
|
|
}
|
|
|
|
int http_client_add_no_proxy(http_client_t* cli, const char* host) {
|
|
cli->no_proxy_hosts.push_back(host);
|
|
return 0;
|
|
}
|
|
|
|
static int http_client_make_request(http_client_t* cli, HttpRequest* req) {
|
|
if (req->url.empty() || *req->url.c_str() == '/') {
|
|
req->scheme = cli->https ? "https" : "http";
|
|
req->host = cli->host;
|
|
req->port = cli->port;
|
|
}
|
|
req->ParseUrl();
|
|
|
|
bool https = req->IsHttps();
|
|
bool use_proxy = https ? (!cli->https_proxy_host.empty()) : (!cli->http_proxy_host.empty());
|
|
if (use_proxy) {
|
|
if (req->host == "127.0.0.1" || req->host == "localhost") {
|
|
use_proxy = false;
|
|
}
|
|
}
|
|
if (use_proxy) {
|
|
for (const auto& host : cli->no_proxy_hosts) {
|
|
if (req->host == host) {
|
|
use_proxy = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (use_proxy) {
|
|
req->SetProxy(https ? cli->https_proxy_host.c_str() : cli->http_proxy_host.c_str(),
|
|
https ? cli->https_proxy_port : cli->http_proxy_port);
|
|
}
|
|
|
|
if (req->timeout == 0) {
|
|
req->timeout = cli->timeout;
|
|
}
|
|
|
|
for (const auto& pair : cli->headers) {
|
|
if (req->headers.find(pair.first) == req->headers.end()) {
|
|
req->headers.insert(pair);
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int http_client_connect(http_client_t* cli, const char* host, int port, int https, int timeout) {
|
|
cli->Close();
|
|
int blocktime = DEFAULT_CONNECT_TIMEOUT;
|
|
if (timeout > 0) {
|
|
blocktime = MIN(timeout*1000, blocktime);
|
|
}
|
|
int connfd = ConnectTimeout(host, port, blocktime);
|
|
if (connfd < 0) {
|
|
hloge("connect %s:%d failed!", host, port);
|
|
return connfd;
|
|
}
|
|
tcp_nodelay(connfd, 1);
|
|
|
|
if (https && cli->ssl == NULL) {
|
|
// cli->ssl_ctx > g_ssl_ctx > hssl_ctx_new
|
|
hssl_ctx_t ssl_ctx = NULL;
|
|
if (cli->ssl_ctx) {
|
|
ssl_ctx = cli->ssl_ctx;
|
|
} else if (g_ssl_ctx) {
|
|
ssl_ctx = g_ssl_ctx;
|
|
} else {
|
|
cli->ssl_ctx = ssl_ctx = hssl_ctx_new(NULL);
|
|
cli->alloced_ssl_ctx = true;
|
|
}
|
|
if (ssl_ctx == NULL) {
|
|
closesocket(connfd);
|
|
return NABS(ERR_NEW_SSL_CTX);
|
|
}
|
|
cli->ssl = hssl_new(ssl_ctx, connfd);
|
|
if (cli->ssl == NULL) {
|
|
closesocket(connfd);
|
|
return NABS(ERR_NEW_SSL);
|
|
}
|
|
if (!is_ipaddr(host)) {
|
|
hssl_set_sni_hostname(cli->ssl, host);
|
|
}
|
|
so_rcvtimeo(connfd, blocktime);
|
|
int ret = hssl_connect(cli->ssl);
|
|
if (ret != 0) {
|
|
fprintf(stderr, "* ssl handshake failed: %d\n", ret);
|
|
hloge("ssl handshake failed: %d", ret);
|
|
hssl_free(cli->ssl);
|
|
cli->ssl = NULL;
|
|
closesocket(connfd);
|
|
return NABS(ret);
|
|
}
|
|
}
|
|
|
|
cli->fd = connfd;
|
|
cli->keepalive_requests = 0;
|
|
return connfd;
|
|
}
|
|
|
|
int http_client_close(http_client_t* cli) {
|
|
if (cli == NULL) return 0;
|
|
cli->Close();
|
|
return 0;
|
|
}
|
|
|
|
int http_client_send_data(http_client_t* cli, const char* data, int size) {
|
|
if (!cli || !data || size <= 0) return -1;
|
|
|
|
if (cli->ssl) {
|
|
return hssl_write(cli->ssl, data, size);
|
|
}
|
|
|
|
return send(cli->fd, data, size, 0);
|
|
}
|
|
|
|
int http_client_recv_data(http_client_t* cli, char* data, int size) {
|
|
if (!cli || !data || size <= 0) return -1;
|
|
|
|
if (cli->ssl) {
|
|
return hssl_read(cli->ssl, data, size);
|
|
}
|
|
|
|
return recv(cli->fd, data, size, 0);
|
|
}
|
|
|
|
static int http_client_exec(http_client_t* cli, HttpRequest* req, HttpResponse* resp) {
|
|
// connect -> send -> recv -> http_parser
|
|
int err = 0;
|
|
int connfd = cli->fd;
|
|
bool https = req->IsHttps() && !req->IsProxy();
|
|
bool keepalive = true;
|
|
|
|
time_t connect_timeout = MIN(req->connect_timeout, req->timeout);
|
|
time_t timeout_ms = req->timeout * 1000;
|
|
time_t start_time = gettick_ms();
|
|
time_t cur_time = start_time, left_time = INFINITE;
|
|
|
|
#define CHECK_TIMEOUT \
|
|
do { \
|
|
if (timeout_ms > 0) { \
|
|
cur_time = gettick_ms(); \
|
|
if (cur_time - start_time >= timeout_ms) { \
|
|
goto timeout; \
|
|
} \
|
|
left_time = timeout_ms - (cur_time - start_time); \
|
|
} \
|
|
} while(0);
|
|
|
|
uint32_t retry_count = req->retry_count;
|
|
if (cli->keepalive_requests > 0 && retry_count == 0) {
|
|
// maybe keep-alive timeout, retry at least once
|
|
retry_count = 1;
|
|
}
|
|
|
|
if (cli->parser == NULL) {
|
|
cli->parser = HttpParserPtr(HttpParser::New(HTTP_CLIENT, (http_version)req->http_major));
|
|
if (cli->parser == NULL) {
|
|
hloge("New HttpParser failed!");
|
|
return ERR_NULL_POINTER;
|
|
}
|
|
}
|
|
|
|
if (connfd <= 0 || cli->host != req->host || cli->port != req->port) {
|
|
cli->host = req->host;
|
|
cli->port = req->port;
|
|
connect:
|
|
connfd = http_client_connect(cli, req->host.c_str(), req->port, https, connect_timeout);
|
|
if (connfd < 0) {
|
|
return connfd;
|
|
}
|
|
}
|
|
|
|
cli->parser->SubmitRequest(req);
|
|
char recvbuf[1024] = {0};
|
|
int total_nsend, nsend, nrecv;
|
|
total_nsend = nsend = nrecv = 0;
|
|
send:
|
|
char* data = NULL;
|
|
size_t len = 0;
|
|
while (cli->parser->GetSendData(&data, &len)) {
|
|
total_nsend = 0;
|
|
while (total_nsend < len) {
|
|
CHECK_TIMEOUT
|
|
if (left_time != INFINITE) {
|
|
so_sndtimeo(cli->fd, left_time);
|
|
}
|
|
if (req->cancel) goto disconnect;
|
|
nsend = http_client_send_data(cli, data + total_nsend, len - total_nsend);
|
|
if (req->cancel) goto disconnect;
|
|
if (nsend <= 0) {
|
|
CHECK_TIMEOUT
|
|
err = socket_errno();
|
|
if (err == EINTR) continue;
|
|
if (retry_count-- > 0 && left_time > req->retry_delay + connect_timeout * 1000) {
|
|
err = 0;
|
|
if (req->retry_delay > 0) hv_msleep(req->retry_delay);
|
|
goto connect;
|
|
}
|
|
goto disconnect;
|
|
}
|
|
total_nsend += nsend;
|
|
}
|
|
}
|
|
if (resp == NULL) return 0;
|
|
cli->parser->InitResponse(resp);
|
|
recv:
|
|
do {
|
|
CHECK_TIMEOUT
|
|
if (left_time != INFINITE) {
|
|
so_rcvtimeo(cli->fd, left_time);
|
|
}
|
|
if (req->cancel) goto disconnect;
|
|
nrecv = http_client_recv_data(cli, recvbuf, sizeof(recvbuf));
|
|
if (req->cancel) goto disconnect;
|
|
if (nrecv <= 0) {
|
|
CHECK_TIMEOUT
|
|
err = socket_errno();
|
|
if (err == EINTR) continue;
|
|
if (cli->parser->IsEof()) {
|
|
err = 0;
|
|
goto disconnect;
|
|
}
|
|
if (retry_count-- > 0 && left_time > req->retry_delay + connect_timeout * 1000) {
|
|
err = 0;
|
|
if (req->retry_delay > 0) hv_msleep(req->retry_delay);
|
|
goto connect;
|
|
}
|
|
goto disconnect;
|
|
}
|
|
int nparse = cli->parser->FeedRecvData(recvbuf, nrecv);
|
|
if (nparse != nrecv) {
|
|
return ERR_PARSE;
|
|
}
|
|
} while(!cli->parser->IsComplete());
|
|
|
|
keepalive = req->IsKeepAlive() && resp->IsKeepAlive();
|
|
if (keepalive) {
|
|
++cli->keepalive_requests;
|
|
} else {
|
|
cli->Close();
|
|
}
|
|
return 0;
|
|
timeout:
|
|
err = ERR_TASK_TIMEOUT;
|
|
disconnect:
|
|
cli->Close();
|
|
return err;
|
|
}
|
|
|
|
int http_client_send_header(http_client_t* cli, HttpRequest* req) {
|
|
if (!cli || !req) return ERR_NULL_POINTER;
|
|
|
|
http_client_make_request(cli, req);
|
|
|
|
return http_client_exec(cli, req, NULL);
|
|
}
|
|
|
|
int http_client_recv_response(http_client_t* cli, HttpResponse* resp) {
|
|
if (!cli || !resp) return ERR_NULL_POINTER;
|
|
if (!cli->parser) {
|
|
hloge("Call http_client_send_header first!");
|
|
return ERR_NULL_POINTER;
|
|
}
|
|
|
|
char recvbuf[1024] = {0};
|
|
cli->parser->InitResponse(resp);
|
|
|
|
do {
|
|
int nrecv = http_client_recv_data(cli, recvbuf, sizeof(recvbuf));
|
|
if (nrecv <= 0) {
|
|
int err = socket_errno();
|
|
if (err == EINTR) continue;
|
|
cli->Close();
|
|
return err;
|
|
}
|
|
int nparse = cli->parser->FeedRecvData(recvbuf, nrecv);
|
|
if (nparse != nrecv) {
|
|
return ERR_PARSE;
|
|
}
|
|
} while(!cli->parser->IsComplete());
|
|
|
|
return 0;
|
|
}
|
|
|
|
#ifdef WITH_CURL
|
|
static size_t s_header_cb(char* buf, size_t size, size_t cnt, void* userdata) {
|
|
if (buf == NULL || userdata == NULL) return 0;
|
|
size_t len = size * cnt;
|
|
std::string str(buf, len);
|
|
HttpResponse* resp = (HttpResponse*)userdata;
|
|
std::string::size_type pos = str.find_first_of(':');
|
|
if (pos == std::string::npos) {
|
|
if (strncmp(buf, "HTTP/", 5) == 0) {
|
|
// status line
|
|
//hlogd("%s", buf);
|
|
int http_major = 1, http_minor = 1, status_code = 200;
|
|
if (buf[5] == '1') {
|
|
// HTTP/1.1 200 OK\r\n
|
|
sscanf(buf, "HTTP/%d.%d %d", &http_major, &http_minor, &status_code);
|
|
}
|
|
else if (buf[5] == '2') {
|
|
// HTTP/2 200\r\n
|
|
sscanf(buf, "HTTP/%d %d", &http_major, &status_code);
|
|
http_minor = 0;
|
|
}
|
|
resp->http_major = http_major;
|
|
resp->http_minor = http_minor;
|
|
resp->status_code = (http_status)status_code;
|
|
if (resp->http_cb) {
|
|
resp->http_cb(resp, HP_MESSAGE_BEGIN, NULL, 0);
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
// headers
|
|
std::string key = trim(str.substr(0, pos));
|
|
std::string value = trim(str.substr(pos+1));
|
|
resp->headers[key] = value;
|
|
}
|
|
return len;
|
|
}
|
|
|
|
static size_t s_body_cb(char* buf, size_t size, size_t cnt, void *userdata) {
|
|
if (buf == NULL || userdata == NULL) return 0;
|
|
size_t len = size * cnt;
|
|
HttpMessage* resp = (HttpMessage*)userdata;
|
|
if (resp->http_cb) {
|
|
if (resp->content == NULL && resp->content_length == 0) {
|
|
resp->content = buf;
|
|
resp->content_length = len;
|
|
resp->http_cb(resp, HP_HEADERS_COMPLETE, NULL, 0);
|
|
}
|
|
resp->http_cb(resp, HP_BODY, buf, len);
|
|
} else {
|
|
resp->body.append(buf, len);
|
|
}
|
|
return len;
|
|
}
|
|
|
|
static int http_client_exec_curl(http_client_t* cli, HttpRequest* req, HttpResponse* resp) {
|
|
if (cli->curl == NULL) {
|
|
cli->curl = curl_easy_init();
|
|
}
|
|
CURL* curl = cli->curl;
|
|
|
|
// proxy
|
|
if (req->IsProxy()) {
|
|
curl_easy_setopt(curl, CURLOPT_PROXY, req->host.c_str());
|
|
curl_easy_setopt(curl, CURLOPT_PROXYPORT, req->port);
|
|
}
|
|
|
|
// SSL
|
|
if (req->IsHttps()) {
|
|
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0);
|
|
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0);
|
|
}
|
|
|
|
// http2
|
|
if (req->http_major == 2) {
|
|
#if LIBCURL_VERSION_NUM < 0x073100 // 7.49.0
|
|
curl_easy_setopt(curl, CURLOPT_HTTP_VERSION, (long)CURL_HTTP_VERSION_2_0);
|
|
#else
|
|
// No Connection: Upgrade
|
|
curl_easy_setopt(curl, CURLOPT_HTTP_VERSION, (long)CURL_HTTP_VERSION_2_PRIOR_KNOWLEDGE);
|
|
#endif
|
|
}
|
|
|
|
// TCP_NODELAY
|
|
curl_easy_setopt(curl, CURLOPT_TCP_NODELAY, 1);
|
|
|
|
// method
|
|
curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, http_method_str(req->method));
|
|
|
|
// url
|
|
req->DumpUrl();
|
|
curl_easy_setopt(curl, CURLOPT_URL, req->url.c_str());
|
|
//hlogd("%s %s HTTP/%d.%d", http_method_str(req->method), req->url.c_str(), req->http_major, req->http_minor);
|
|
|
|
// headers
|
|
req->FillContentType();
|
|
struct curl_slist *headers = NULL;
|
|
for (auto& pair : req->headers) {
|
|
std::string header = pair.first;
|
|
header += ": ";
|
|
header += pair.second;
|
|
headers = curl_slist_append(headers, header.c_str());
|
|
}
|
|
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
|
|
|
|
// body
|
|
//struct curl_httppost* httppost = NULL;
|
|
//struct curl_httppost* lastpost = NULL;
|
|
if (req->body.size() == 0) {
|
|
req->DumpBody();
|
|
/*
|
|
if (req->body.size() == 0 &&
|
|
req->content_type == MULTIPART_FORM_DATA) {
|
|
for (auto& pair : req->mp) {
|
|
if (pair.second.filename.size() != 0) {
|
|
curl_formadd(&httppost, &lastpost,
|
|
CURLFORM_COPYNAME, pair.first.c_str(),
|
|
CURLFORM_FILE, pair.second.filename.c_str(),
|
|
CURLFORM_END);
|
|
}
|
|
else if (pair.second.content.size() != 0) {
|
|
curl_formadd(&httppost, &lastpost,
|
|
CURLFORM_COPYNAME, pair.first.c_str(),
|
|
CURLFORM_COPYCONTENTS, pair.second.content.c_str(),
|
|
CURLFORM_END);
|
|
}
|
|
}
|
|
if (httppost) {
|
|
curl_easy_setopt(curl, CURLOPT_HTTPPOST, httppost);
|
|
}
|
|
}
|
|
*/
|
|
}
|
|
if (req->body.size() != 0) {
|
|
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, req->body.c_str());
|
|
curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, req->body.size());
|
|
}
|
|
|
|
if (req->connect_timeout > 0) {
|
|
curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, req->connect_timeout);
|
|
}
|
|
if (req->timeout > 0) {
|
|
curl_easy_setopt(curl, CURLOPT_TIMEOUT, req->timeout);
|
|
}
|
|
|
|
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, s_body_cb);
|
|
curl_easy_setopt(curl, CURLOPT_WRITEDATA, resp);
|
|
|
|
curl_easy_setopt(curl, CURLOPT_HEADER, 0);
|
|
curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, s_header_cb);
|
|
curl_easy_setopt(curl, CURLOPT_HEADERDATA, resp);
|
|
|
|
int ret = curl_easy_perform(curl);
|
|
/*
|
|
if (ret != 0) {
|
|
hloge("curl error: %d: %s", ret, curl_easy_strerror((CURLcode)ret));
|
|
}
|
|
if (resp->body.length() != 0) {
|
|
hlogd("[Response]\n%s", resp->body.c_str());
|
|
}
|
|
double total_time, name_time, conn_time, pre_time;
|
|
curl_easy_getinfo(curl, CURLINFO_TOTAL_TIME, &total_time);
|
|
curl_easy_getinfo(curl, CURLINFO_NAMELOOKUP_TIME, &name_time);
|
|
curl_easy_getinfo(curl, CURLINFO_CONNECT_TIME, &conn_time);
|
|
curl_easy_getinfo(curl, CURLINFO_PRETRANSFER_TIME, &pre_time);
|
|
hlogd("TIME_INFO: %lf,%lf,%lf,%lf", total_time, name_time, conn_time, pre_time);
|
|
*/
|
|
|
|
if (headers) {
|
|
curl_slist_free_all(headers);
|
|
}
|
|
/*
|
|
if (httppost) {
|
|
curl_formfree(httppost);
|
|
}
|
|
*/
|
|
|
|
if (resp->http_cb) {
|
|
resp->http_cb(resp, HP_MESSAGE_COMPLETE, NULL, 0);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
const char* http_client_strerror(int errcode) {
|
|
return curl_easy_strerror((CURLcode)errcode);
|
|
}
|
|
|
|
#else
|
|
|
|
const char* http_client_strerror(int errcode) {
|
|
return socket_strerror(errcode);
|
|
}
|
|
|
|
#endif
|
|
|
|
static int http_client_redirect(HttpRequest* req, HttpResponse* resp) {
|
|
std::string location = resp->headers["Location"];
|
|
if (!location.empty()) {
|
|
hlogi("redirect %s => %s", req->url.c_str(), location.c_str());
|
|
req->url = location;
|
|
req->ParseUrl();
|
|
req->headers["Host"] = req->host;
|
|
resp->Reset();
|
|
return http_client_send(req, resp);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int http_client_send(http_client_t* cli, HttpRequest* req, HttpResponse* resp) {
|
|
if (!cli || !req || !resp) return ERR_NULL_POINTER;
|
|
|
|
http_client_make_request(cli, req);
|
|
|
|
if (req->http_cb) resp->http_cb = std::move(req->http_cb);
|
|
|
|
#if WITH_CURL
|
|
int ret = http_client_exec_curl(cli, req, resp);
|
|
#else
|
|
int ret = http_client_exec(cli, req, resp);
|
|
#endif
|
|
if (ret != 0) return ret;
|
|
|
|
// redirect
|
|
if (req->redirect && HTTP_STATUS_IS_REDIRECT(resp->status_code)) {
|
|
return http_client_redirect(req, resp);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int http_client_send(HttpRequest* req, HttpResponse* resp) {
|
|
if (!req || !resp) return ERR_NULL_POINTER;
|
|
|
|
http_client_t cli;
|
|
return http_client_send(&cli, req, resp);
|
|
}
|
|
|
|
// below for async
|
|
static int http_client_exec_async(http_client_t* cli, HttpRequestPtr req, HttpResponseCallback resp_cb) {
|
|
if (cli->async_client_ == NULL) {
|
|
cli->mutex_.lock();
|
|
if (cli->async_client_ == NULL) {
|
|
cli->async_client_ = std::make_shared<hv::AsyncHttpClient>();
|
|
}
|
|
cli->mutex_.unlock();
|
|
}
|
|
|
|
return cli->async_client_->send(req, std::move(resp_cb));
|
|
}
|
|
|
|
int http_client_send_async(http_client_t* cli, HttpRequestPtr req, HttpResponseCallback resp_cb) {
|
|
if (!cli || !req) return ERR_NULL_POINTER;
|
|
http_client_make_request(cli, req.get());
|
|
return http_client_exec_async(cli, req, std::move(resp_cb));
|
|
}
|
|
|
|
static http_client_t* s_async_http_client = NULL;
|
|
static void hv_destroy_default_async_http_client() {
|
|
hlogi("destory default http async client");
|
|
if (s_async_http_client) {
|
|
http_client_del(s_async_http_client);
|
|
s_async_http_client = NULL;
|
|
}
|
|
}
|
|
static http_client_t* hv_default_async_http_client() {
|
|
static std::mutex s_mutex;
|
|
if (s_async_http_client == NULL) {
|
|
s_mutex.lock();
|
|
if (s_async_http_client == NULL) {
|
|
hlogi("create default http async client");
|
|
s_async_http_client = http_client_new();
|
|
// NOTE: I have No better idea
|
|
atexit(hv_destroy_default_async_http_client);
|
|
}
|
|
s_mutex.unlock();
|
|
}
|
|
return s_async_http_client;
|
|
}
|
|
|
|
int http_client_send_async(HttpRequestPtr req, HttpResponseCallback resp_cb) {
|
|
if (req == NULL) return ERR_NULL_POINTER;
|
|
|
|
if (req->timeout == 0) {
|
|
req->timeout = DEFAULT_HTTP_TIMEOUT;
|
|
}
|
|
|
|
return http_client_exec_async(hv_default_async_http_client(), req, std::move(resp_cb));
|
|
}
|