initial commit
This commit is contained in:
225
third_party/libhv/http/client/AsyncHttpClient.cpp
vendored
Executable file
225
third_party/libhv/http/client/AsyncHttpClient.cpp
vendored
Executable 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;
|
||||
}
|
||||
|
||||
}
|
||||
164
third_party/libhv/http/client/AsyncHttpClient.h
vendored
Executable file
164
third_party/libhv/http/client/AsyncHttpClient.h
vendored
Executable 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
733
third_party/libhv/http/client/HttpClient.cpp
vendored
Executable 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
171
third_party/libhv/http/client/HttpClient.h
vendored
Executable 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_
|
||||
217
third_party/libhv/http/client/WebSocketClient.cpp
vendored
Executable file
217
third_party/libhv/http/client/WebSocketClient.cpp
vendored
Executable 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);
|
||||
}
|
||||
|
||||
}
|
||||
71
third_party/libhv/http/client/WebSocketClient.h
vendored
Executable file
71
third_party/libhv/http/client/WebSocketClient.h
vendored
Executable 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
193
third_party/libhv/http/client/axios.h
vendored
Executable 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
233
third_party/libhv/http/client/requests.h
vendored
Executable 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_
|
||||
Reference in New Issue
Block a user