initial commit

This commit is contained in:
2025-08-05 15:53:44 +08:00
commit 09dc02ae52
553 changed files with 137665 additions and 0 deletions

View File

@@ -0,0 +1,225 @@
#include "AsyncHttpClient.h"
namespace hv {
int AsyncHttpClient::send(const HttpRequestPtr& req, HttpResponseCallback resp_cb) {
hloop_t* loop = EventLoopThread::hloop();
if (loop == NULL) return -1;
auto task = std::make_shared<HttpClientTask>();
task->req = req;
task->cb = std::move(resp_cb);
task->start_time = hloop_now_hrtime(loop);
if (req->retry_count > 0 && req->retry_delay > 0) {
req->retry_count = MIN(req->retry_count, req->timeout * 1000 / req->retry_delay - 1);
}
return send(task);
}
// createsocket => startConnect =>
// onconnect => sendRequest => startRead =>
// onread => HttpParser => resp_cb
int AsyncHttpClient::doTask(const HttpClientTaskPtr& task) {
const HttpRequestPtr& req = task->req;
if (req->cancel) {
return -1;
}
// queueInLoop timeout?
uint64_t now_hrtime = hloop_now_hrtime(EventLoopThread::hloop());
int elapsed_ms = (now_hrtime - task->start_time) / 1000;
int timeout_ms = req->timeout * 1000;
if (timeout_ms > 0 && elapsed_ms >= timeout_ms) {
hlogw("%s queueInLoop timeout!", req->url.c_str());
return -10;
}
req->ParseUrl();
sockaddr_u peeraddr;
memset(&peeraddr, 0, sizeof(peeraddr));
const char* host = req->host.c_str();
int ret = sockaddr_set_ipport(&peeraddr, host, req->port);
if (ret != 0) {
hloge("unknown host %s", host);
return -20;
}
int connfd = -1;
// first get from conn_pools
char strAddr[SOCKADDR_STRLEN] = {0};
SOCKADDR_STR(&peeraddr, strAddr);
auto iter = conn_pools.find(strAddr);
if (iter != conn_pools.end()) {
// hlogd("get from conn_pools");
iter->second.get(connfd);
}
if (connfd < 0) {
// create socket
connfd = socket(peeraddr.sa.sa_family, SOCK_STREAM, 0);
if (connfd < 0) {
perror("socket");
return -30;
}
hio_t* connio = hio_get(EventLoopThread::hloop(), connfd);
assert(connio != NULL);
hio_set_peeraddr(connio, &peeraddr.sa, sockaddr_len(&peeraddr));
addChannel(connio);
// https
if (req->IsHttps() && !req->IsProxy()) {
hio_enable_ssl(connio);
if (!is_ipaddr(host)) {
hio_set_hostname(connio, host);
}
}
}
const SocketChannelPtr& channel = getChannel(connfd);
assert(channel != NULL);
HttpClientContext* ctx = channel->getContext<HttpClientContext>();
ctx->task = task;
channel->onconnect = [&channel]() {
sendRequest(channel);
};
channel->onread = [this, &channel](Buffer* buf) {
HttpClientContext* ctx = channel->getContext<HttpClientContext>();
if (ctx->task == NULL) return;
if (ctx->task->req->cancel) {
channel->close();
return;
}
const char* data = (const char*)buf->data();
int len = buf->size();
int nparse = ctx->parser->FeedRecvData(data, len);
if (nparse != len) {
ctx->errorCallback();
channel->close();
return;
}
if (ctx->parser->IsComplete()) {
auto& req = ctx->task->req;
auto& resp = ctx->resp;
bool keepalive = req->IsKeepAlive() && resp->IsKeepAlive();
if (req->redirect && HTTP_STATUS_IS_REDIRECT(resp->status_code)) {
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();
send(ctx->task);
// NOTE: detatch from original channel->context
ctx->cancelTask();
}
} else {
ctx->successCallback();
}
if (keepalive) {
// NOTE: add into conn_pools to reuse
// hlogd("add into conn_pools");
conn_pools[channel->peeraddr()].add(channel->fd());
} else {
channel->close();
}
}
};
channel->onclose = [this, &channel]() {
HttpClientContext* ctx = channel->getContext<HttpClientContext>();
// NOTE: remove from conn_pools
// hlogd("remove from conn_pools");
auto iter = conn_pools.find(channel->peeraddr());
if (iter != conn_pools.end()) {
iter->second.remove(channel->fd());
}
const HttpClientTaskPtr& task = ctx->task;
if (task) {
if (ctx->parser &&
ctx->parser->IsEof()) {
ctx->successCallback();
}
else if (task->req &&
task->req->cancel == 0 &&
task->req->retry_count-- > 0) {
if (task->req->retry_delay > 0) {
// try again after delay
setTimeout(task->req->retry_delay, [this, task](TimerID timerID){
hlogi("retry %s %s", http_method_str(task->req->method), task->req->url.c_str());
sendInLoop(task);
});
} else {
send(task);
}
}
else {
ctx->errorCallback();
}
}
removeChannel(channel);
};
// timer
if (timeout_ms > 0) {
ctx->timerID = setTimeout(timeout_ms - elapsed_ms, [&channel](TimerID timerID){
HttpClientContext* ctx = channel->getContext<HttpClientContext>();
if (ctx && ctx->task) {
hlogw("%s timeout!", ctx->task->req->url.c_str());
}
if (channel) {
channel->close();
}
});
}
if (channel->isConnected()) {
// sendRequest
sendRequest(channel);
} else {
// startConnect
if (req->connect_timeout > 0) {
channel->setConnectTimeout(req->connect_timeout * 1000);
}
channel->startConnect();
}
return 0;
}
// InitResponse => SubmitRequest => while(GetSendData) write => startRead
int AsyncHttpClient::sendRequest(const SocketChannelPtr& channel) {
HttpClientContext* ctx = (HttpClientContext*)channel->context();
assert(ctx != NULL && ctx->task != NULL);
if (ctx->resp == NULL) {
ctx->resp = std::make_shared<HttpResponse>();
}
HttpRequest* req = ctx->task->req.get();
HttpResponse* resp = ctx->resp.get();
assert(req != NULL && resp != NULL);
if (req->http_cb) resp->http_cb = std::move(req->http_cb);
if (ctx->parser == NULL) {
ctx->parser.reset(HttpParser::New(HTTP_CLIENT, (http_version)req->http_major));
}
ctx->parser->InitResponse(resp);
ctx->parser->SubmitRequest(req);
char* data = NULL;
size_t len = 0;
while (ctx->parser->GetSendData(&data, &len)) {
if (req->cancel) {
channel->close();
return -1;
}
// NOTE: ensure write buffer size is enough
if (len > (1 << 22) /* 4M */) {
channel->setMaxWriteBufsize(len);
}
channel->write(data, len);
}
channel->startRead();
return 0;
}
}

View File

@@ -0,0 +1,164 @@
#ifndef HV_ASYNC_HTTP_CLIENT_H_
#define HV_ASYNC_HTTP_CLIENT_H_
#include <map>
#include <list>
#include "EventLoopThread.h"
#include "Channel.h"
#include "HttpMessage.h"
#include "HttpParser.h"
namespace hv {
template<typename Conn>
class ConnPool {
public:
int size() {
return conns_.size();
}
bool get(Conn& conn) {
if (conns_.empty()) return false;
conn = conns_.front();
conns_.pop_front();
return true;
}
bool add(const Conn& conn) {
conns_.push_back(conn);
return true;
}
bool remove(const Conn& conn) {
auto iter = conns_.begin();
while (iter != conns_.end()) {
if (*iter == conn) {
iter = conns_.erase(iter);
return true;
} else {
++iter;
}
}
return false;
}
private:
std::list<Conn> conns_;
};
struct HttpClientTask {
HttpRequestPtr req;
HttpResponseCallback cb;
uint64_t start_time;
};
typedef std::shared_ptr<HttpClientTask> HttpClientTaskPtr;
struct HttpClientContext {
HttpClientTaskPtr task;
HttpResponsePtr resp;
HttpParserPtr parser;
TimerID timerID;
HttpClientContext() {
timerID = INVALID_TIMER_ID;
}
~HttpClientContext() {
cancelTimer();
}
void cancelTimer() {
if (timerID != INVALID_TIMER_ID) {
killTimer(timerID);
timerID = INVALID_TIMER_ID;
}
}
void cancelTask() {
cancelTimer();
task = NULL;
}
void callback() {
cancelTimer();
if (task && task->cb) {
task->cb(resp);
}
// NOTE: task done
task = NULL;
}
void successCallback() {
callback();
resp = NULL;
}
void errorCallback() {
resp = NULL;
callback();
}
};
class HV_EXPORT AsyncHttpClient : private EventLoopThread {
public:
AsyncHttpClient(EventLoopPtr loop = NULL) : EventLoopThread(loop) {
if (loop == NULL) {
EventLoopThread::start(true);
}
}
~AsyncHttpClient() {
EventLoopThread::stop(true);
}
// thread-safe
int send(const HttpRequestPtr& req, HttpResponseCallback resp_cb);
int send(const HttpClientTaskPtr& task) {
EventLoopThread::loop()->queueInLoop(std::bind(&AsyncHttpClient::sendInLoop, this, task));
return 0;
}
protected:
void sendInLoop(HttpClientTaskPtr task) {
int err = doTask(task);
if (err != 0 && task->cb) {
task->cb(NULL);
}
}
int doTask(const HttpClientTaskPtr& task);
static int sendRequest(const SocketChannelPtr& channel);
// channel
const SocketChannelPtr& getChannel(int fd) {
return channels[fd];
// return fd < channels.capacity() ? channels[fd] : NULL;
}
const SocketChannelPtr& addChannel(hio_t* io) {
auto channel = std::make_shared<SocketChannel>(io);
channel->newContext<HttpClientContext>();
int fd = channel->fd();
channels[fd] = channel;
return channels[fd];
}
void removeChannel(const SocketChannelPtr& channel) {
channel->deleteContext<HttpClientContext>();
int fd = channel->fd();
channels.erase(fd);
}
private:
// NOTE: just one loop thread, no need mutex.
// fd => SocketChannelPtr
std::map<int, SocketChannelPtr> channels;
// peeraddr => ConnPool
std::map<std::string, ConnPool<int>> conn_pools;
};
}
#endif // HV_ASYNC_HTTP_CLIENT_H_

733
third_party/libhv/http/client/HttpClient.cpp vendored Executable file
View File

@@ -0,0 +1,733 @@
#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));
}

171
third_party/libhv/http/client/HttpClient.h vendored Executable file
View File

@@ -0,0 +1,171 @@
#ifndef HV_HTTP_CLIENT_H_
#define HV_HTTP_CLIENT_H_
#include "hexport.h"
#include "hssl.h"
#include "HttpMessage.h"
/*
#include <stdio.h>
#include "HttpClient.h"
int main(int argc, char* argv[]) {
HttpRequest req;
req.method = HTTP_GET;
req.url = "http://www.example.com";
HttpResponse res;
int ret = http_client_send(&req, &res);
printf("%s\n", req.Dump(true,true).c_str());
if (ret != 0) {
printf("* Failed:%s:%d\n", http_client_strerror(ret), ret);
}
else {
printf("%s\n", res.Dump(true,true).c_str());
}
return ret;
}
*/
typedef struct http_client_s http_client_t;
HV_EXPORT http_client_t* http_client_new(const char* host = NULL, int port = DEFAULT_HTTP_PORT, int https = 0);
HV_EXPORT int http_client_del(http_client_t* cli);
HV_EXPORT const char* http_client_strerror(int errcode);
// timeout: s
HV_EXPORT int http_client_set_timeout(http_client_t* cli, int timeout);
// SSL/TLS
HV_EXPORT int http_client_set_ssl_ctx(http_client_t* cli, hssl_ctx_t ssl_ctx);
// hssl_ctx_new(opt) -> http_client_set_ssl_ctx
HV_EXPORT int http_client_new_ssl_ctx(http_client_t* cli, hssl_ctx_opt_t* opt);
// common headers
HV_EXPORT int http_client_clear_headers(http_client_t* cli);
HV_EXPORT int http_client_set_header(http_client_t* cli, const char* key, const char* value);
HV_EXPORT int http_client_del_header(http_client_t* cli, const char* key);
HV_EXPORT const char* http_client_get_header(http_client_t* cli, const char* key);
// http_proxy
HV_EXPORT int http_client_set_http_proxy(http_client_t* cli, const char* host, int port);
// https_proxy
HV_EXPORT int http_client_set_https_proxy(http_client_t* cli, const char* host, int port);
// no_proxy
HV_EXPORT int http_client_add_no_proxy(http_client_t* cli, const char* host);
// sync
HV_EXPORT int http_client_send(http_client_t* cli, HttpRequest* req, HttpResponse* resp);
// async
// Intern will start an EventLoopThread when http_client_send_async first called,
// http_client_del will destroy the thread.
HV_EXPORT int http_client_send_async(http_client_t* cli, HttpRequestPtr req, HttpResponseCallback resp_cb = NULL);
// top-level api
// http_client_new -> http_client_send -> http_client_del
HV_EXPORT int http_client_send(HttpRequest* req, HttpResponse* resp);
// http_client_send_async(&default_async_client, ...)
HV_EXPORT int http_client_send_async(HttpRequestPtr req, HttpResponseCallback resp_cb = NULL);
// low-level api
// @retval >=0 connfd, <0 error
HV_EXPORT int http_client_connect(http_client_t* cli, const char* host, int port, int https, int timeout);
HV_EXPORT int http_client_send_header(http_client_t* cli, HttpRequest* req);
HV_EXPORT int http_client_send_data(http_client_t* cli, const char* data, int size);
HV_EXPORT int http_client_recv_data(http_client_t* cli, char* data, int size);
HV_EXPORT int http_client_recv_response(http_client_t* cli, HttpResponse* resp);
HV_EXPORT int http_client_close(http_client_t* cli);
namespace hv {
class HttpClient {
public:
HttpClient(const char* host = NULL, int port = DEFAULT_HTTP_PORT, int https = 0) {
_client = http_client_new(host, port, https);
}
~HttpClient() {
if (_client) {
http_client_del(_client);
_client = NULL;
}
}
// timeout: s
int setTimeout(int timeout) {
return http_client_set_timeout(_client, timeout);
}
// SSL/TLS
int setSslCtx(hssl_ctx_t ssl_ctx) {
return http_client_set_ssl_ctx(_client, ssl_ctx);
}
int newSslCtx(hssl_ctx_opt_t* opt) {
return http_client_new_ssl_ctx(_client, opt);
}
// headers
int clearHeaders() {
return http_client_clear_headers(_client);
}
int setHeader(const char* key, const char* value) {
return http_client_set_header(_client, key, value);
}
int delHeader(const char* key) {
return http_client_del_header(_client, key);
}
const char* getHeader(const char* key) {
return http_client_get_header(_client, key);
}
// http_proxy
int setHttpProxy(const char* host, int port) {
return http_client_set_http_proxy(_client, host, port);
}
// https_proxy
int setHttpsProxy(const char* host, int port) {
return http_client_set_https_proxy(_client, host, port);
}
// no_proxy
int addNoProxy(const char* host) {
return http_client_add_no_proxy(_client, host);
}
// sync
int send(HttpRequest* req, HttpResponse* resp) {
return http_client_send(_client, req, resp);
}
// async
int sendAsync(HttpRequestPtr req, HttpResponseCallback resp_cb = NULL) {
return http_client_send_async(_client, req, std::move(resp_cb));
}
// low-level api
int connect(const char* host, int port = DEFAULT_HTTP_PORT, int https = 0, int timeout = DEFAULT_HTTP_CONNECT_TIMEOUT) {
return http_client_connect(_client, host, port, https, timeout);
}
int sendHeader(HttpRequest* req) {
return http_client_send_header(_client, req);
}
int sendData(const char* data, int size) {
return http_client_send_data(_client, data, size);
}
int recvData(char* data, int size) {
return http_client_recv_data(_client, data, size);
}
int recvResponse(HttpResponse* resp) {
return http_client_recv_response(_client, resp);
}
int close() {
return http_client_close(_client);
}
private:
http_client_t* _client;
};
}
#endif // HV_HTTP_CLIENT_H_

View File

@@ -0,0 +1,217 @@
#include "WebSocketClient.h"
#include "base64.h"
#include "hlog.h"
#define DEFAULT_WS_PING_INTERVAL 3000 // ms
namespace hv {
WebSocketClient::WebSocketClient(EventLoopPtr loop)
: TcpClientTmpl<WebSocketChannel>(loop)
{
state = WS_CLOSED;
ping_interval = DEFAULT_WS_PING_INTERVAL;
ping_cnt = 0;
}
WebSocketClient::~WebSocketClient() {
stop();
}
/*
* ParseUrl => createsocket => start =>
* TCP::onConnection => websocket_handshake => WS::onopen =>
* TCP::onMessage => WebSocketParser => WS::onmessage =>
* TCP::onConnection => WS::onclose
*/
int WebSocketClient::open(const char* _url, const http_headers& headers) {
close();
// ParseUrl
if (_url) {
if (strncmp(_url, "ws", 2) != 0) {
url = "ws://";
url += _url;
} else {
url = _url;
}
}
hlogi("%s", url.c_str());
if (!http_req_) {
http_req_ = std::make_shared<HttpRequest>();
}
// ws => http
http_req_->url = "http" + url.substr(2, -1);
http_req_->ParseUrl();
int connfd = createsocket(http_req_->port, http_req_->host.c_str());
if (connfd < 0) {
hloge("createsocket %s:%d return %d!", http_req_->host.c_str(), http_req_->port, connfd);
return connfd;
}
// wss
bool wss = strncmp(url.c_str(), "wss", 3) == 0;
if (wss) {
withTLS();
}
for (auto& header : headers) {
http_req_->headers[header.first] = header.second;
}
onConnection = [this](const WebSocketChannelPtr& channel) {
if (channel->isConnected()) {
state = CONNECTED;
// websocket_handshake
http_req_->headers["Connection"] = "Upgrade";
http_req_->headers["Upgrade"] = "websocket";
if (http_req_->GetHeader(SEC_WEBSOCKET_KEY).empty()) {
// generate SEC_WEBSOCKET_KEY
unsigned char rand_key[16] = {0};
int *p = (int*)rand_key;
for (int i = 0; i < 4; ++i, ++p) {
*p = rand();
}
char ws_key[32] = {0};
hv_base64_encode(rand_key, 16, ws_key);
http_req_->headers[SEC_WEBSOCKET_KEY] = ws_key;
}
if (http_req_->GetHeader(SEC_WEBSOCKET_VERSION).empty()) {
http_req_->headers[SEC_WEBSOCKET_VERSION] = "13";
}
std::string http_msg = http_req_->Dump(true, true);
// printf("%s", http_msg.c_str());
// NOTE: not use WebSocketChannel::send
channel->write(http_msg);
state = WS_UPGRADING;
// prepare HttpParser
http_parser_.reset(HttpParser::New(HTTP_CLIENT, HTTP_V1));
http_resp_ = std::make_shared<HttpResponse>();
http_parser_->InitResponse(http_resp_.get());
} else {
state = WS_CLOSED;
if (onclose) onclose();
}
};
onMessage = [this](const WebSocketChannelPtr& channel, Buffer* buf) {
const char* data = (const char*)buf->data();
size_t size = buf->size();
if (state == WS_UPGRADING) {
int nparse = http_parser_->FeedRecvData(data, size);
if (nparse != size && http_parser_->GetError()) {
hloge("http parse error!");
channel->close();
return;
}
data += nparse;
size -= nparse;
if (http_parser_->IsComplete()) {
if (http_resp_->status_code != HTTP_STATUS_SWITCHING_PROTOCOLS) {
// printf("websocket response:\n%s\n", http_resp_->Dump(true, true).c_str());
if (http_req_->redirect && HTTP_STATUS_IS_REDIRECT(http_resp_->status_code)) {
std::string location = http_resp_->headers["Location"];
if (!location.empty()) {
hlogi("redirect %s => %s", http_req_->url.c_str(), location.c_str());
std::string ws_url = location;
if (hv::startswith(location, "http")) {
ws_url = hv::replace(location, "http", "ws");
}
// NOTE: not triggle onclose when redirecting.
channel->onclose = NULL;
open(ws_url.c_str());
return;
}
}
hloge("server side could not upgrade to websocket: status_code=%d", http_resp_->status_code);
channel->close();
return;
}
std::string ws_key = http_req_->GetHeader(SEC_WEBSOCKET_KEY);
char ws_accept[32] = {0};
ws_encode_key(ws_key.c_str(), ws_accept);
std::string ws_accept2 = http_resp_->GetHeader(SEC_WEBSOCKET_ACCEPT);
if (strcmp(ws_accept, ws_accept2.c_str()) != 0) {
hloge("Sec-WebSocket-Accept not match!");
channel->close();
return;
}
ws_parser_ = std::make_shared<WebSocketParser>();
// websocket_onmessage
ws_parser_->onMessage = [this, &channel](int opcode, const std::string& msg) {
channel->opcode = (enum ws_opcode)opcode;
switch (opcode) {
case WS_OPCODE_CLOSE:
channel->send(msg, WS_OPCODE_CLOSE);
channel->close();
break;
case WS_OPCODE_PING:
{
// printf("recv ping\n");
// printf("send pong\n");
channel->send(msg, WS_OPCODE_PONG);
break;
}
case WS_OPCODE_PONG:
// printf("recv pong\n");
ping_cnt = 0;
break;
case WS_OPCODE_TEXT:
case WS_OPCODE_BINARY:
if (onmessage) onmessage(msg);
break;
default:
break;
}
};
state = WS_OPENED;
// ping
if (ping_interval > 0) {
ping_cnt = 0;
channel->setHeartbeat(ping_interval, [this](){
auto& channel = this->channel;
if (channel == NULL) return;
if (ping_cnt++ == 3) {
hloge("websocket no pong!");
channel->close();
return;
}
// printf("send ping\n");
channel->sendPing();
});
}
if (onopen) onopen();
}
}
if (state == WS_OPENED && size != 0) {
int nparse = ws_parser_->FeedRecvData(data, size);
if (nparse != size) {
hloge("websocket parse error!");
channel->close();
return;
}
}
};
state = CONNECTING;
start();
return 0;
}
int WebSocketClient::close() {
closesocket();
state = WS_CLOSED;
return 0;
}
int WebSocketClient::send(const std::string& msg) {
return send(msg.c_str(), msg.size(), WS_OPCODE_TEXT);
}
int WebSocketClient::send(const char* buf, int len, enum ws_opcode opcode) {
if (channel == NULL) return -1;
return channel->send(buf, len, opcode);
}
}

View File

@@ -0,0 +1,71 @@
#ifndef HV_WEBSOCKET_CLIENT_H_
#define HV_WEBSOCKET_CLIENT_H_
/*
* @demo examples/websocket_client_test.cpp
*/
#include "hexport.h"
#include "TcpClient.h"
#include "WebSocketChannel.h"
#include "HttpParser.h"
#include "WebSocketParser.h"
namespace hv {
class HV_EXPORT WebSocketClient : public TcpClientTmpl<WebSocketChannel> {
public:
std::string url;
std::function<void()> onopen;
std::function<void()> onclose;
std::function<void(const std::string& msg)> onmessage;
// PATCH: onmessage not given opcode
enum ws_opcode opcode() { return channel ? channel->opcode : WS_OPCODE_CLOSE; }
WebSocketClient(EventLoopPtr loop = NULL);
virtual ~WebSocketClient();
// url = ws://ip:port/path
// url = wss://ip:port/path
int open(const char* url, const http_headers& headers = DefaultHeaders);
int close();
int send(const std::string& msg);
int send(const char* buf, int len, enum ws_opcode opcode = WS_OPCODE_BINARY);
// setConnectTimeout / setPingInterval / setReconnect
void setPingInterval(int ms) {
ping_interval = ms;
}
// NOTE: call before open
void setHttpRequest(const HttpRequestPtr& req) {
http_req_ = req;
}
// NOTE: call when onopen
const HttpResponsePtr& getHttpResponse() {
return http_resp_;
}
private:
enum State {
CONNECTING,
CONNECTED,
WS_UPGRADING,
WS_OPENED,
WS_CLOSED,
} state;
HttpParserPtr http_parser_;
HttpRequestPtr http_req_;
HttpResponsePtr http_resp_;
WebSocketParserPtr ws_parser_;
// ping/pong
int ping_interval;
int ping_cnt;
};
}
#endif // HV_WEBSOCKET_CLIENT_H_

193
third_party/libhv/http/client/axios.h vendored Executable file
View File

@@ -0,0 +1,193 @@
#ifndef HV_AXIOS_H_
#define HV_AXIOS_H_
#include "json.hpp"
#include "requests.h"
/*
* Inspired by js axios
*
* @code
#include "axios.h"
int main() {
const char* strReq = R"(
{
"method": "POST",
"url": "http://127.0.0.1:8080/echo",
"timeout": 10,
"params": {
"page_no": "1",
"page_size": "10"
},
"headers": {
"Content-Type": "application/json"
},
"body": {
"app_id": "123456",
"app_secret": "abcdefg"
}
}
)";
// sync
auto resp = axios::axios(strReq);
if (resp == NULL) {
printf("request failed!\n");
} else {
printf("%s\n", resp->body.c_str());
}
// async
int finished = 0;
axios::axios(strReq, [&finished](const HttpResponsePtr& resp) {
if (resp == NULL) {
printf("request failed!\n");
} else {
printf("%s\n", resp->body.c_str());
}
finished = 1;
});
// wait async finished
while (!finished) hv_sleep(1);
return 0;
}
**/
using nlohmann::json;
using requests::Request;
using requests::Response;
using requests::ResponseCallback;
namespace axios {
HV_INLINE Request newRequestFromJson(const json& jreq) {
auto req = std::make_shared<HttpRequest>();
// url
if (jreq.contains("url")) {
req->url = jreq["url"];
}
// params
if (jreq.contains("params")) {
req->query_params = jreq["params"].get<hv::QueryParams>();
}
// headers
if (jreq.contains("headers")) {
req->headers = jreq["headers"].get<http_headers>();
}
// body/data
const char* body_field = nullptr;
if (jreq.contains("body")) {
body_field = "body";
} else if (jreq.contains("data")) {
body_field = "data";
}
if (body_field) {
const json& jbody = jreq[body_field];
if (jbody.is_object() || jbody.is_array()) {
req->json = jbody;
} else if (jbody.is_string()) {
req->body = jbody;
}
}
// method
if (jreq.contains("method")) {
std::string method = jreq["method"];
req->method = http_method_enum(method.c_str());
} else if (body_field) {
req->method = HTTP_POST;
} else {
req->method = HTTP_GET;
}
// timeout
if (jreq.contains("timeout")) {
req->timeout = jreq["timeout"];
}
return req;
}
HV_INLINE Request newRequestFromJsonString(const char* req_str) {
return newRequestFromJson(json::parse(req_str));
}
// sync
HV_INLINE Response axios(const json& jreq, http_method method = HTTP_GET, const char* url = nullptr) {
auto req = newRequestFromJson(jreq);
if (method != HTTP_GET) {
req->method = method;
}
if (url) {
req->url = url;
}
return req ? requests::request(req) : nullptr;
}
HV_INLINE Response axios(const char* req_str, http_method method = HTTP_GET, const char* url = nullptr) {
return req_str ? axios(json::parse(req_str), method, url)
: requests::request(method, url);
}
HV_INLINE Response head(const char* url, const json& jreq) {
return axios(jreq, HTTP_HEAD, url);
}
HV_INLINE Response head(const char* url, const char* req_str = nullptr) {
return axios(req_str, HTTP_HEAD, url);
}
HV_INLINE Response get(const char* url, const json& jreq) {
return axios(jreq, HTTP_GET, url);
}
HV_INLINE Response get(const char* url, const char* req_str = nullptr) {
return axios(req_str, HTTP_GET, url);
}
HV_INLINE Response post(const char* url, const json& jreq) {
return axios(jreq, HTTP_POST, url);
}
HV_INLINE Response post(const char* url, const char* req_str = nullptr) {
return axios(req_str, HTTP_POST, url);
}
HV_INLINE Response put(const char* url, const json& jreq) {
return axios(jreq, HTTP_PUT, url);
}
HV_INLINE Response put(const char* url, const char* req_str = nullptr) {
return axios(req_str, HTTP_PUT, url);
}
HV_INLINE Response patch(const char* url, const json& jreq) {
return axios(jreq, HTTP_PATCH, url);
}
HV_INLINE Response patch(const char* url, const char* req_str = nullptr) {
return axios(req_str, HTTP_PATCH, url);
}
HV_INLINE Response Delete(const char* url, const json& jreq) {
return axios(jreq, HTTP_DELETE, url);
}
HV_INLINE Response Delete(const char* url, const char* req_str = nullptr) {
return axios(req_str, HTTP_DELETE, url);
}
// async
HV_INLINE int axios(const json& jreq, ResponseCallback resp_cb) {
auto req = newRequestFromJson(jreq);
return req ? requests::async(req, std::move(resp_cb)) : -1;
}
HV_INLINE int axios(const char* req_str, ResponseCallback resp_cb) {
return axios(json::parse(req_str), std::move(resp_cb));
}
}
#endif // HV_AXIOS_H_

233
third_party/libhv/http/client/requests.h vendored Executable file
View File

@@ -0,0 +1,233 @@
#ifndef HV_REQUESTS_H_
#define HV_REQUESTS_H_
/*
* Inspired by python requests
*
* @code
#include "requests.h"
int main() {
auto resp = requests::get("http://127.0.0.1:8080/ping");
if (resp == NULL) {
printf("request failed!\n");
} else {
printf("%d %s\r\n", resp->status_code, resp->status_message());
printf("%s\n", resp->body.c_str());
}
resp = requests::post("http://127.0.0.1:8080/echo", "hello,world!");
if (resp == NULL) {
printf("request failed!\n");
} else {
printf("%d %s\r\n", resp->status_code, resp->status_message());
printf("%s\n", resp->body.c_str());
}
return 0;
}
**/
#include <memory>
#include "HttpClient.h"
namespace requests {
typedef HttpRequestPtr Request;
typedef HttpResponsePtr Response;
typedef HttpResponseCallback ResponseCallback;
HV_INLINE Response request(Request req) {
auto resp = std::make_shared<HttpResponse>();
int ret = http_client_send(req.get(), resp.get());
return ret ? NULL : resp;
}
HV_INLINE Response request(http_method method, const char* url, const http_body& body = NoBody, const http_headers& headers = DefaultHeaders) {
auto req = std::make_shared<HttpRequest>();
req->method = method;
req->url = url;
if (&body != &NoBody) {
req->body = body;
}
if (&headers != &DefaultHeaders) {
req->headers = headers;
}
return request(req);
}
HV_INLINE Response head(const char* url, const http_headers& headers = DefaultHeaders) {
return request(HTTP_HEAD, url, NoBody, headers);
}
HV_INLINE Response get(const char* url, const http_headers& headers = DefaultHeaders) {
return request(HTTP_GET, url, NoBody, headers);
}
HV_INLINE Response post(const char* url, const http_body& body = NoBody, const http_headers& headers = DefaultHeaders) {
return request(HTTP_POST, url, body, headers);
}
HV_INLINE Response put(const char* url, const http_body& body = NoBody, const http_headers& headers = DefaultHeaders) {
return request(HTTP_PUT, url, body, headers);
}
HV_INLINE Response patch(const char* url, const http_body& body = NoBody, const http_headers& headers = DefaultHeaders) {
return request(HTTP_PATCH, url, body, headers);
}
// delete is c++ keyword, we have to replace delete with Delete.
HV_INLINE Response Delete(const char* url, const http_headers& headers = DefaultHeaders) {
return request(HTTP_DELETE, url, NoBody, headers);
}
HV_INLINE int async(Request req, ResponseCallback resp_cb) {
return http_client_send_async(req, std::move(resp_cb));
}
// Sample codes for uploading and downloading files
HV_INLINE Response uploadFile(const char* url, const char* filepath, http_method method = HTTP_POST, const http_headers& headers = DefaultHeaders) {
auto req = std::make_shared<HttpRequest>();
req->method = method;
req->url = url;
req->timeout = 600; // 10min
if (req->File(filepath) != 200) return NULL;
if (&headers != &DefaultHeaders) {
req->headers = headers;
}
return request(req);
}
#ifndef WITHOUT_HTTP_CONTENT
HV_INLINE Response uploadFormFile(const char* url, const char* name, const char* filepath, std::map<std::string, std::string>& params = hv::empty_map, http_method method = HTTP_POST, const http_headers& headers = DefaultHeaders) {
auto req = std::make_shared<HttpRequest>();
req->method = method;
req->url = url;
req->timeout = 600; // 10min
req->content_type = MULTIPART_FORM_DATA;
req->SetFormFile(name, filepath);
for (auto& param : params) {
req->SetFormData(param.first.c_str(), param.second);
}
if (&headers != &DefaultHeaders) {
req->headers = headers;
}
return request(req);
}
#endif
typedef std::function<void(size_t sended_bytes, size_t total_bytes)> upload_progress_cb;
HV_INLINE Response uploadLargeFile(const char* url, const char* filepath, upload_progress_cb progress_cb = NULL, http_method method = HTTP_POST, const http_headers& headers = DefaultHeaders) {
// open file
HFile file;
int ret = file.open(filepath, "rb");
if (ret != 0) {
return NULL;
}
hv::HttpClient cli;
auto req = std::make_shared<HttpRequest>();
req->method = method;
req->url = url;
req->timeout = 3600; // 1h
if (&headers != &DefaultHeaders) {
req->headers = headers;
}
// connect
req->ParseUrl();
int connfd = cli.connect(req->host.c_str(), req->port, req->IsHttps(), req->connect_timeout);
if (connfd < 0) {
return NULL;
}
// send header
size_t total_bytes = file.size(filepath);
req->SetHeader("Content-Length", hv::to_string(total_bytes));
ret = cli.sendHeader(req.get());
if (ret != 0) {
return NULL;
}
// send file
size_t sended_bytes = 0;
char filebuf[40960]; // 40K
int filebuflen = sizeof(filebuf);
int nread = 0, nsend = 0;
while (sended_bytes < total_bytes) {
nread = file.read(filebuf, filebuflen);
if (nread <= 0) {
return NULL;
}
nsend = cli.sendData(filebuf, nread);
if (nsend != nread) {
return NULL;
}
sended_bytes += nsend;
if (progress_cb) {
progress_cb(sended_bytes, total_bytes);
}
}
// recv response
auto resp = std::make_shared<HttpResponse>();
ret = cli.recvResponse(resp.get());
if (ret != 0) {
return NULL;
}
return resp;
}
// see examples/wget.cpp
typedef std::function<void(size_t received_bytes, size_t total_bytes)> download_progress_cb;
HV_INLINE size_t downloadFile(const char* url, const char* filepath, download_progress_cb progress_cb = NULL) {
// open file
std::string filepath_download(filepath);
filepath_download += ".download";
HFile file;
int ret = file.open(filepath_download.c_str(), "wb");
if (ret != 0) {
return 0;
}
// download
auto req = std::make_shared<HttpRequest>();
req->method = HTTP_GET;
req->url = url;
req->timeout = 3600; // 1h
size_t content_length = 0;
size_t received_bytes = 0;
req->http_cb = [&file, &content_length, &received_bytes, &progress_cb]
(HttpMessage* resp, http_parser_state state, const char* data, size_t size) {
if (!resp->headers["Location"].empty()) return;
if (state == HP_HEADERS_COMPLETE) {
content_length = hv::from_string<size_t>(resp->GetHeader("Content-Length"));
} else if (state == HP_BODY) {
if (data && size) {
// write file
file.write(data, size);
received_bytes += size;
if (progress_cb) {
progress_cb(received_bytes, content_length);
}
}
}
};
auto resp = request(req);
file.close();
if (resp == NULL || resp->status_code != 200) {
return 0;
}
// check filesize
if (content_length != 0 && hv_filesize(filepath_download.c_str()) != content_length) {
remove(filepath_download.c_str());
return 0;
}
rename(filepath_download.c_str(), filepath);
return hv_filesize(filepath);
}
}
#endif // HV_REQUESTS_H_