@[toc]
libcurl安裝
libcurl的編譯安裝請(qǐng)參考博客https://blog.csdn.net/gg_simida/article/details/80536860
HttpClient
我們的目標(biāo)是封裝一個(gè)HttpClient類,支持GET、POST或者自定義方法嘉蕾,支持發(fā)送和接收文本诊赊、json侠姑、xml、form-data、x-www-form-urlencoded數(shù)據(jù),支持自定義頭部Headers等
// HttpRequest.h
#ifndef HTTP_REQUEST_H_
#define HTTP_REQUEST_H_
#include <string>
#include <vector>
#include <map>
using std::string;
using std::vector;
using std::map;
typedef std::map<std::string, std::string> KeyValue;
// 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,
enum ContentType {
FOREACH_CONTENT_TYPE(ENUM_CONTENT_TYPE)
};
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(const string& str, FormDataType type = CONTENT) {
this->type = type;
this->data = str;
}
FormData(int n) {
this->type = CONTENT;
this->data = std::to_string(n);
}
FormData(long long n) {
this->type = CONTENT;
this->data = std::to_string(n);
}
FormData(float f) {
this->type = CONTENT;
this->data = std::to_string(f);
}
FormData(double lf) {
this->type = CONTENT;
this->data = std::to_string(lf);
}
};
typedef std::multimap<std::string, FormData> Form;
struct HttpRequest {
// request line
string method;
string url;
string version;
// headers
KeyValue headers;
// body
ContentType content_type;
string text;
KeyValue kvs; // QUERY_STRING,X_WWW_FORM_URLENCODED
Form form; // FORM_DATA
};
struct HttpResponse {
// status line
string version;
int status_code;
string status_message;
// headers
KeyValue headers;
// body
string body;
};
#endif // HTTP_REQUEST_H_
HttpRequest.h
頭文件中定義了ContentType
,FormData
,HttpRequest
,HttpResponse
等數(shù)據(jù)結(jié)構(gòu)橄仆;
// HttpClient.h
#ifndef HTTP_CLIENT_H_
#define HTTP_CLIENT_H_
/***************************************************************
HttpClient based libcurl
***************************************************************/
#include <curl/curl.h>
#include "HttpRequest.h"
class HttpClient {
public:
HttpClient();
~HttpClient();
int Send(const HttpRequest& req, HttpResponse* res);
static const char* strerror(int errcode);
void SetTimeout(int sec) {m_timeout = sec;}
void AddHeader(string key, string value) {
m_headers[key] = value;
}
void DelHeader(string key) {
auto iter = m_headers.find(key);
if (iter != m_headers.end()) {
m_headers.erase(iter);
}
}
void ClearHeader() {
m_headers.clear();
}
protected:
int curl(const HttpRequest& req, HttpResponse* res);
private:
int m_timeout; // unit:s default:10s
KeyValue m_headers;
};
#endif // HTTP_CLIENT_H_
HttpClient.h
頭文件聲明了基于libcurl
的HTTP客戶端類;
// HttpClient.cpp
#include "HttpClient.h"
#include <string.h>
#include <string>
using std::string;
#define SPACE_CHARS " \t\r\n"
static string trim(const string& str) {
string::size_type pos1 = str.find_first_not_of(SPACE_CHARS);
if (pos1 == string::npos) return "";
string::size_type pos2 = str.find_last_not_of(SPACE_CHARS);
return str.substr(pos1, pos2-pos1+1);
}
static inline bool is_unambiguous(char c) {
return (c >= '0' && c <= '9') ||
(c >= 'A' && c <= 'Z') ||
(c >= 'a' && c <= 'z') ||
c == '-' ||
c == '_' ||
c == '.' ||
c == '~';
}
static string escape(const string& istr) {
string ostr;
const char* p = istr.c_str();
int len = istr.size();
char szHex[4] = {0};
for (int i = 0; i < len; ++i) {
if (is_unambiguous(p[i])) {
ostr += p[i];
}
else {
sprintf(szHex, "%%%02X", p[i]);
ostr += szHex;
}
}
return ostr;
}
#define DEFAULT_TIMEOUT 10
HttpClient::HttpClient() {
m_timeout = DEFAULT_TIMEOUT;
}
HttpClient::~HttpClient() {
}
int HttpClient::Send(const HttpRequest& req, HttpResponse* res) {
return curl(req, res);
}
static size_t s_formget_cb(void *arg, const char *buf, size_t len) {
return len;
}
static size_t s_header_cb(char* buf, size_t size, size_t cnt, void* userdata) {
if (buf == NULL || userdata == NULL) return 0;
HttpResponse* res = (HttpResponse*)userdata;
string str(buf);
string::size_type pos = str.find_first_of(':');
if (pos == string::npos) {
if (res->version.empty() && strncmp(buf, "HTTP", 4) == 0) {
// status line
// HTTP/1.1 200 OK\r\n
char* space1 = strchr(buf, ' ');
char* space2 = strchr(space1+1, ' ');
if (space1 && space2) {
*space1 = '\0';
*space2 = '\0';
res->version = buf;
res->status_code = atoi(space1+1);
res->status_message = trim(space2+1);
}
}
}
else {
// headers
string key = trim(str.substr(0, pos));
string value = trim(str.substr(pos+1));
res->headers[key] = value;
}
return size*cnt;
}
static size_t s_body_cb(char *buf, size_t size, size_t cnt, void *userdata) {
if (buf == NULL || userdata == NULL) return 0;
HttpResponse* res = (HttpResponse*)userdata;
res->body.append(buf, size*cnt);
return size*cnt;
}
int HttpClient::curl(const HttpRequest& req, HttpResponse* res) {
CURL* handle = curl_easy_init();
// SSL
curl_easy_setopt(handle, CURLOPT_SSL_VERIFYPEER, 0);
curl_easy_setopt(handle, CURLOPT_SSL_VERIFYHOST, 0);
// 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 (auto& pair : m_headers) {
string header = pair.first;
header += ": ";
header += pair.second;
headers = curl_slist_append(headers, header.c_str());
}
}
const char* psz = "text/plain";
switch (req.content_type) {
#define CASE_CONTENT_TYPE(id, str) \
case 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);
//hlogd("%s %s", req.method.c_str(), req.url.c_str());
//hlogd("%s", strContentType.c_str());
// body or params
struct curl_httppost* httppost = NULL;
struct curl_httppost* lastpost = NULL;
switch (req.content_type) {
case FORM_DATA:
{
for (auto& pair : req.form) {
CURLformoption opt = pair.second.type == FormData::FILENAME ? CURLFORM_FILE : CURLFORM_COPYCONTENTS;
curl_formadd(&httppost, &lastpost,
CURLFORM_COPYNAME, pair.first.c_str(),
opt, pair.second.data.c_str(),
CURLFORM_END);
}
if (httppost) {
curl_easy_setopt(handle, CURLOPT_HTTPPOST, httppost);
curl_formget(httppost, NULL, s_formget_cb);
}
}
break;
case QUERY_STRING:
case X_WWW_FORM_URLENCODED:
{
string params;
auto iter = req.kvs.begin();
while (iter != req.kvs.end()) {
if (iter != req.kvs.begin()) {
params += '&';
}
params += escape(iter->first);
params += '=';
params += escape(iter->second);
iter++;
}
if (req.content_type == 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());
//hlogd("%s", url_with_params.c_str());
}
else {
curl_easy_setopt(handle, CURLOPT_POSTFIELDS, params.c_str());
//hlogd("%s", params.c_str());
}
}
break;
default:
{
if (req.text.size() != 0) {
curl_easy_setopt(handle, CURLOPT_POSTFIELDS, req.text.c_str());
//hlogd("%s", req.text.c_str());
}
}
break;
}
if (m_timeout != 0) {
curl_easy_setopt(handle, CURLOPT_TIMEOUT, m_timeout);
}
curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, s_body_cb);
curl_easy_setopt(handle, CURLOPT_WRITEDATA, res);
curl_easy_setopt(handle, CURLOPT_HEADER, 0);
curl_easy_setopt(handle, CURLOPT_HEADERFUNCTION, s_header_cb);
curl_easy_setopt(handle, CURLOPT_HEADERDATA, res);
int ret = curl_easy_perform(handle);
if (ret != 0) {
//hloge("%d: %s", ret, curl_easy_strerror((CURLcode)ret));
}
//if (res->body.length() != 0) {
//hlogd("Response:%s", res->body.c_str());
//}
//double total_time, name_time, conn_time, pre_time;
//curl_easy_getinfo(handle, CURLINFO_TOTAL_TIME, &total_time);
//curl_easy_getinfo(handle, CURLINFO_NAMELOOKUP_TIME, &name_time);
//curl_easy_getinfo(handle, CURLINFO_CONNECT_TIME, &conn_time);
//curl_easy_getinfo(handle, 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);
}
curl_easy_cleanup(handle);
return ret;
}
const char* HttpClient::strerror(int errcode) {
return curl_easy_strerror((CURLcode)errcode);
}
HttpClient.cpp
源文件根據(jù)HttpRequest
利用libcurl
完成請(qǐng)求衅斩,在回調(diào)函數(shù)中解析填充HttpResponse
// test.cpp
#include "HttpClient.h"
#include <stdio.h>
int main(int argc, char* argv[]) {
HttpClient session;
HttpRequest req;
req.method = "GET";
req.url = "www.baidu.com";
HttpResponse res;
int ret = session.Send(req, &res);
if (ret != 0) {
printf("%s %s failed => %d:%s\n", req.method.c_str(), req.url.c_str(), ret, HttpClient::strerror(ret));
}
else {
printf("%s %d %s\r\n", res.version.c_str(), res.status_code, res.status_message.c_str());
for (auto& header : res.headers) {
printf("%s: %s\r\n", header.first.c_str(), header.second.c_str());
}
printf("\r\n");
printf("%s", res.body.c_str());
printf("\n");
}
return ret;
}
g++ -std=c++11 HttpClient.cpp test.cpp -o test -lcurl
封裝過后使用起來相當(dāng)方便吧盆顾,一點(diǎn)不輸python
的requests
完整http客戶端
完整的http客戶端接口見 https://github.com/ithewei/hw/blob/master/http/client/http_client.h
git clone https://github.com/ithewei/hw.git
# 使用libcurl
make curl DEFINES="WITH_CURL CURL_STATICLIB"
bin/curl -h
bin/curl -v www.baidu.com
# 不使用libcurl,自實(shí)現(xiàn)的http客戶端
make curl
# 使用openssl支持https
make curl DEFINES="WITH_OPENSSL"
bin/curl -v https:///www.baidu.com
# 使用nghhtp2支持http2
make curl DEFINES="WITH_NGHTTP2"
bin/curl -v www.nghttp2.org --http2