基于libcurl实现的HTTP客户端


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;
}

使用起来相当简单

猜你喜欢

转载自blog.csdn.net/gg_simida/article/details/80828974