基于libcurl封裝的HTTP客戶端庫(kù)

@[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)不輸pythonrequests

完整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
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末畏梆,一起剝皮案震驚了整個(gè)濱河市您宪,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌奠涌,老刑警劉巖宪巨,帶你破解...
    沈念sama閱讀 206,214評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異溜畅,居然都是意外死亡捏卓,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門达皿,熙熙樓的掌柜王于貴愁眉苦臉地迎上來天吓,“玉大人贿肩,你說我怎么就攤上這事峦椰。” “怎么了汰规?”我有些...
    開封第一講書人閱讀 152,543評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵汤功,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我溜哮,道長(zhǎng)滔金,這世上最難降的妖魔是什么色解? 我笑而不...
    開封第一講書人閱讀 55,221評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮餐茵,結(jié)果婚禮上科阎,老公的妹妹穿的比我還像新娘。我一直安慰自己忿族,他們只是感情好锣笨,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,224評(píng)論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著道批,像睡著了一般错英。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上隆豹,一...
    開封第一講書人閱讀 49,007評(píng)論 1 284
  • 那天椭岩,我揣著相機(jī)與錄音,去河邊找鬼璃赡。 笑死判哥,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的碉考。 我是一名探鬼主播姨伟,決...
    沈念sama閱讀 38,313評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼豆励!你這毒婦竟也來了夺荒?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,956評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤良蒸,失蹤者是張志新(化名)和其女友劉穎技扼,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體嫩痰,經(jīng)...
    沈念sama閱讀 43,441評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡剿吻,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,925評(píng)論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了串纺。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片丽旅。...
    茶點(diǎn)故事閱讀 38,018評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖纺棺,靈堂內(nèi)的尸體忽然破棺而出榄笙,到底是詐尸還是另有隱情,我是刑警寧澤祷蝌,帶...
    沈念sama閱讀 33,685評(píng)論 4 322
  • 正文 年R本政府宣布茅撞,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏米丘。R本人自食惡果不足惜剑令,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,234評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望拄查。 院中可真熱鬧吁津,春花似錦、人聲如沸堕扶。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)挣柬。三九已至潮酒,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間邪蛔,已是汗流浹背急黎。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評(píng)論 1 261
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留侧到,地道東北人勃教。 一個(gè)月前我還...
    沈念sama閱讀 45,467評(píng)論 2 352
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像匠抗,于是被迫代替她去往敵國(guó)和親故源。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,762評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容

  • 最近項(xiàng)目需要使用 Java 重度調(diào)用 HTTP API 接口汞贸,于是想著封裝一個(gè)團(tuán)隊(duì)公用的 HTTP client ...
    java菜閱讀 619評(píng)論 0 2
  • 最近項(xiàng)目需要使用 Java 重度調(diào)用 HTTP API 接口绳军,于是想著封裝一個(gè)團(tuán)隊(duì)公用的 HTTP client ...
    java菜閱讀 741評(píng)論 0 3
  • 前言 超文本傳輸協(xié)議(HTTP)也許是當(dāng)今互聯(lián)網(wǎng)上使用的最重要的協(xié)議了。Web服務(wù)矢腻,有網(wǎng)絡(luò)功能的設(shè)備和網(wǎng)絡(luò)計(jì)算的發(fā)...
    狂奔的蝸牛_wxc閱讀 5,497評(píng)論 0 12
  • 上篇文章介紹了通用文件服務(wù)組件(Netty實(shí)現(xiàn)版本)门驾,本文介紹基于HTTP與Apache FileUpload的方...
    landy8530閱讀 828評(píng)論 0 1
  • 摘錄《懂你》 一個(gè)人懂你,就是時(shí)時(shí)關(guān)心你多柑;就是刻刻在乎你奶是;就是凡事想著你。懂你的人竣灌,會(huì)想著你的冷暖聂沙,想著你的憂樂,...
    金星人_丫頭閱讀 2,379評(píng)論 0 2