libcurl安装
libcurl的编译安装请参考博客https://blog.csdn.net/gg_simida/article/details/80536860
HttpClient
我们的目标是封装一个HttpClient类,支持GET、POST或者自定义方法,支持发送和接受文本、json、xml、form-data、x-www-form-urlencoded数据,支持自定义头部Headers等
// HttpClient.h
#ifndef HTTP_CLIENT_H
#define HTTP_CLIENT_H
/***************************************************************
HttpClient based libcurl
***************************************************************/
#include <string>
#include <vector>
#include <map>
using namespace std;
#include <curl/curl.h>
#include "hw/hstring.h"
// F(id, str)
#define FOREACH_CONTENT_TYPE(F) \
F(TEXT_PLAIN, "text/plain") \
F(TEXT_HTML, "text/html") \
F(TEXT_XML, "text/xml") \
F(APPLICATION_JSON, "application/json") \
F(APPLICATION_XML, "application/xml") \
F(APPLICATION_JAVASCRIPT, "application/javascript") \
\
F(FORM_DATA, "multipart/form-data") \
\
F(X_WWW_FORM_URLENCODED, "application/x-www-form-urlencoded") \
F(QUERY_STRING, "text/plain")
#define ENUM_CONTENT_TYPE(id, _) id,
typedef std::map<std::string, std::string> KeyValue;
struct FormData{
enum FormDataType {
CONTENT,
FILENAME
} type;
string data;
FormData() {
type = CONTENT;
}
FormData(const char* data, FormDataType type = CONTENT) {
this->type = type;
this->data = data;
}
FormData(int n) {
this->type = CONTENT;
this->data = ntoa(n);
}
FormData(float f) {
this->type = CONTENT;
this->data = ftoa(f);
}
};
typedef std::map<std::string, FormData> Form;
struct HttpRequest {
string method;
string url;
enum ContentType {
NONE,
FOREACH_CONTENT_TYPE(ENUM_CONTENT_TYPE)
LAST
} content_type;
string text;
KeyValue kvs;
Form form;
};
typedef std::string HttpResponse;
class HttpClient {
public:
HttpClient();
~HttpClient();
int Send(HttpRequest& req, HttpResponse& res);
void setDebug(bool b) { m_bDebug = b; }
void setTimeout(int second) { m_timeout = second; }
void addHeader(string header) { m_headers.push_back(header); }
void resetHeader() { m_headers.clear(); }
protected:
int curl(HttpRequest& req, HttpResponse& res);
private:
static bool s_bInit;
bool m_bDebug;
vector<string> m_headers;
int m_timeout;
};
#endif
头文件中定义了HttpRequest和HttpResponse,定义了支持的content-type,KeyValue、FormData等数据结构
// HttpClient.cpp
#include "HttpClient.h"
#include "hw/hlog.h"
bool HttpClient::s_bInit = false;
HttpClient::HttpClient() {
if (!s_bInit) {
curl_global_init(CURL_GLOBAL_ALL);
s_bInit = true;
}
m_timeout = 0;
m_bDebug = false;
}
HttpClient::~HttpClient() {
// curl_global_cleanup();
}
int HttpClient::Send(HttpRequest& req, HttpResponse& res) {
return curl(req, res);
}
static size_t s_write_cb(char *buf, size_t size, size_t cnt, void *userdata) {
if (buf == NULL || userdata == NULL) return 0;
HttpResponse* pRes = (HttpResponse*)userdata;
pRes->append(buf, size*cnt);
return size*cnt;
}
int HttpClient::curl(HttpRequest& req, HttpResponse& res) {
if (m_bDebug) {
hlogd("%s %s\n%s", req.method.c_str(), req.url.c_str(), req.text.c_str());
}
CURL* handle = curl_easy_init();
// method
curl_easy_setopt(handle, CURLOPT_CUSTOMREQUEST, req.method.c_str());
// url
curl_easy_setopt(handle, CURLOPT_URL, req.url.c_str());
// header
struct curl_slist *headers = NULL;
if (m_headers.size() != 0) {
for (int i = 0; i < m_headers.size(); ++i) {
headers = curl_slist_append(headers, m_headers[i].c_str());
}
}
const char* psz = "text/plain";
switch (req.content_type) {
#define CASE_CONTENT_TYPE(id, str) \
case HttpRequest::id: psz = str; break;
FOREACH_CONTENT_TYPE(CASE_CONTENT_TYPE)
#undef CASE_CONTENT_TYPE
}
string strContentType("Content-type: ");
strContentType += psz;
headers = curl_slist_append(headers, strContentType.c_str());
curl_easy_setopt(handle, CURLOPT_HTTPHEADER, headers);
// body or params
switch (req.content_type) {
case HttpRequest::NONE:
break;
case HttpRequest::FORM_DATA: {
struct curl_httppost* httppost = NULL;
struct curl_httppost* lastpost = NULL;
auto iter = req.form.begin();
while (iter != req.form.end()) {
CURLformoption opt = CURLFORM_COPYCONTENTS;
if (iter->second.type == FormData::FILENAME) {
opt = CURLFORM_FILE;
}
curl_formadd(&httppost, &lastpost,
CURLFORM_COPYNAME, iter->first.c_str(),
opt, iter->second.data.c_str(),
CURLFORM_END);
iter++;
}
if (httppost) {
curl_easy_setopt(handle, CURLOPT_HTTPPOST, httppost);
}
}
break;
case HttpRequest::QUERY_STRING:
case HttpRequest::X_WWW_FORM_URLENCODED: {
string params;
auto iter = req.kvs.begin();
while (iter != req.kvs.end()) {
if (iter != req.kvs.begin()) {
params += '&';
}
params += iter->first;
params += '=';
params += iter->second;
}
if (req.content_type == HttpRequest::QUERY_STRING) {
string url_with_params(req.url);
url_with_params += '?';
url_with_params += params;
curl_easy_setopt(handle, CURLOPT_URL, url_with_params.c_str());
} else {
curl_easy_setopt(handle, CURLOPT_POSTFIELDS, params.c_str());
}
}
break;
default: {
curl_easy_setopt(handle, CURLOPT_POSTFIELDS, req.text.c_str());
}
break;
}
if (m_timeout != 0) {
curl_easy_setopt(handle, CURLOPT_TIMEOUT, m_timeout);
}
curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, s_write_cb);
curl_easy_setopt(handle, CURLOPT_WRITEDATA, &res);
int ret = curl_easy_perform(handle);
if (ret != 0) {
hloge("%s", curl_easy_strerror((CURLcode)ret));
}
if (headers) {
curl_slist_free_all(headers);
}
curl_easy_cleanup(handle);
if (m_bDebug) {
hlogd("%s", res.c_str());
}
return ret;
}
源文件中主要是使用curl完成请求,在回调函数中填充HttpResponse
// test.cpp
#include "HttpClient.h"
int main(){
HttpClinet client;
HttpRequest req;
req.method = "GET";
req.url = "http://www.baidu.com";
HttpResponse res;
int ret = clinet->Send(req, res);
if (ret == 0){
puts(res.c_str());
}
return 0;
}
使用起来相当简单