美文网首页
基于libcurl封装的HTTP客户端库

基于libcurl封装的HTTP客户端库

作者: ithewei | 来源:发表于2019-11-19 11:30 被阅读0次

    @[toc]

    libcurl安装

    libcurl的编译安装请参考博客https://blog.csdn.net/gg_simida/article/details/80536860

    HttpClient

    我们的目标是封装一个HttpClient类,支持GET、POST或者自定义方法,支持发送和接收文本、json、xml、form-data、x-www-form-urlencoded数据,支持自定义头部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等数据结构;

    // 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源文件根据HttpRequest利用libcurl完成请求,在回调函数中解析填充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
    

    封装过后使用起来相当方便吧,一点不输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,自实现的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
    

    相关文章

      网友评论

          本文标题:基于libcurl封装的HTTP客户端库

          本文链接:https://www.haomeiwen.com/subject/ysqymftx.html