之前使用socket来进行客户端和服务器的通信,必须自己构造HTTP头,HTTP体,非常麻烦,而且容易出错。后来使用了libcurl,真心觉得这是一个好东西。
下面是一个简单的封装类。
http_client.h
#ifndef __HTTP_CLIENT_H__
#define __HTTP_CLIENT_H__
#include <string>
#include <curl/curl.h>
/*
*@Author LinuxPhilosopy
*Date: 2013-04-28
*/
using namespace std;
class HttpClient
{
public:
HttpClient();
~HttpClient();
/*
* connect_timeout_ms 连接超时时间,单位ms
* timeout 超时时间
* retry_times 重试时间
* dns_cache_timeout dns缓存时间
*/
int init(int connect_timeout_ms,int timeout,int retry_times,int dns_cache_timeout=60);
/*
* function: POST
* url 请求的url
* data HTTP包体
* resp 响应的包体内容
* 0 成功 -1 失败
*/
int post_data(const string& url,const string& data,string& resp);
/*
* function: GET
* url 请求的url
* resp 响应
* 0 成功 -1 失败
*/
int get_data(const string& url,string& resp);
/*
* Debug
*/
void set_verbose(const int verbose);
/*
*回调函数
*/
static size_t on_write_data(void * buffer, size_t size, size_t nmemb, string & resp);
private:
int _connect_timeout_ms;
int _timeout;
int _retry_times;
int _dns_cache_timeout;
int _verbose;
CURL* _handler;
};
#endif
http_client.cpp
#include "http_client.h"
HttpClient::HttpClient()
{
}
HttpClient::~HttpClient()
{
curl_easy_cleanup(_handler);
curl_global_cleanup();
}
int HttpClient::init(const int connect_timeout_ms,const int timeout, const int retry_times, int dns_cache_timeout)
{
_connect_timeout_ms=connect_timeout_ms;
_timeout = _timeout;
_retry_times = retry_times;
_dns_cache_timeout = dns_cache_timeout;
_verbose=0;
CURLcode code = curl_global_init(CURL_GLOBAL_ALL);
if (code != CURLE_OK)
{
return -1;
}
_handler = curl_easy_init();
if(!_handler)
{
return -1;
}
return 0;
}
size_t HttpClient::on_write_data(void * buffer, size_t size, size_t nmemb, string & resp)
{
long len=size*nmemb;
resp+=(char*)buffer;
return len;
}
int HttpClient::post_data(const string & url, const string & data, string & resp)
{
if( _handler)
{
curl_easy_reset(_handler);
}else
{
_handler= curl_easy_init();
}
curl_easy_setopt(_handler,CURLOPT_URL,url.c_str());
curl_easy_setopt(_handler,CURLOPT_POSTFIELDS,data.c_str());
curl_easy_setopt(_handler,CURLOPT_POSTFIELDSIZE,data.length());
curl_easy_setopt(_handler,CURLOPT_TIMEOUT,_timeout);
curl_easy_setopt(_handler,CURLOPT_CONNECTTIMEOUT_MS,_connect_timeout_ms);
curl_easy_setopt(_handler,CURLOPT_DNS_CACHE_TIMEOUT,_dns_cache_timeout);
curl_easy_setopt(_handler,CURLOPT_NOSIGNAL,1);
curl_easy_setopt(_handler,CURLOPT_WRITEFUNCTION,on_write_data);
curl_easy_setopt(_handler,CURLOPT_WRITEDATA,&resp);
if(_verbose==1)
{
curl_easy_setopt(_handler,CURLOPT_VERBOSE,1);
}
int i=0;
CURLcode res;
for(i=0;i<_retry_times;i++){
res=curl_easy_perform(_handler);
if( res == CURLE_OK)
{
break;
}
resp="";
}
if(i==_retry_times)
return -1;
return 0;
}
int HttpClient::get_data(const string& url,string& resp)
{
if( _handler)
{
curl_easy_reset(_handler);
}else
{
_handler= curl_easy_init();
}
curl_easy_setopt(_handler,CURLOPT_URL,url.c_str());
curl_easy_setopt(_handler,CURLOPT_TIMEOUT,_timeout);
curl_easy_setopt(_handler,CURLOPT_CONNECTTIMEOUT_MS,_connect_timeout_ms);
curl_easy_setopt(_handler,CURLOPT_DNS_CACHE_TIMEOUT,_dns_cache_timeout);
curl_easy_setopt(_handler,CURLOPT_NOSIGNAL,1);
curl_easy_setopt(_handler,CURLOPT_WRITEFUNCTION,on_write_data);
curl_easy_setopt(_handler,CURLOPT_WRITEDATA,&resp);
if(_verbose==1)
{
curl_easy_setopt(_handler,CURLOPT_VERBOSE,1);
}
int i=0;
CURLcode res;
for(i=0;i<_retry_times;i++){
res=curl_easy_perform(_handler);
if( res == CURLE_OK)
{
break;
}
resp="";
}
if(i==_retry_times)
return -1;
return 0;
}
void HttpClient::set_verbose(int verbose)
{
if(verbose!=0)
_verbose=1;
}
下面是测试demo.
main.cpp
#include "http_client.h"
#include <iostream>
using namespace std;
int main(int argc,char** argv)
{
HttpClient _client;
if(_client.init(2000,5,3)<0)
{
cout<<"init client failed."<<endl;
return -1;
}
_client.set_verbose(1);
string url="www.baidu.com";
string resp;
if(_client.get_data(url,resp)<0)
{
cout<<"curl error."<<endl;
return -1;
}
cout<<"GET 响应:\n"<<resp<<endl;
url="http://js.vnet.cn/ProvinceForDACS/services/ProvinceForDACS?wsdl";
string data="<?xml version='1.0' encoding='UTF-8'?><soap:Envelope xmlns:soap='http://schemas.xmlsoap.org/soap/envelope/' soap:encodingStyle='http://www.w3.org/2001/12/soap-encoding' ><soap:Body><dacs:speedUp xmlns:dacs='http://lccss.linkage.com'><spid>11002001</spid><serviceid>1100200199999998</serviceid><authenticator>fYRJhNKy15fEyzDyt/gq065s5PLe0+Yw</authenticator><starttime>1365749347617</starttime><userid>11009499085</userid><clientIP>114.221.84.35</clientIP><time>0</time><port>8999</port></dacs:speedUp></soap:Body></soap:Envelope>";
resp="";
if(_client.post_data(url,data,resp)<0)
{
cout<<"post curl error."<<endl;
return -1;
}
cout<<"POST 响应:\n"<<resp<<endl;
return 0;
}
运行结果:
POST请求中,因为这里是向Web Service提交请求,需要包含相应的请求头,数据包才会被服务器端正确解析。具体自己添加请求头,可以使用如下代码
struct curl_slist* headers=NULL;
headers=curl_slist_append(headers,"Content-Type:text/xml");
curl_slist_append(headers,"Accept:text/xml");
curl_slist_append(headers,"SOAPAction:''");
curl_easy_setopt(handle,CURLOPT_HTTPHEADER,headers);
最后记得释放headers
curl_slist_free_all(headers);
所以从这里也看出这个封装类不够灵活,如果需要自定义HTTP HEADERS,可以在get_data,post_data方法中添加个map参数。但是对于一般的需求这个HttpClient应该大体满足了。。。所以就没加上了。。。
最后,需要注意几点:
1.回调函数必须是类的静态成员函数。
2.如果使用到定时器(我们这里设置了连接超时时间和响应超时等),应设置CURLOPT_NOSIGNAL为1,防止如果主线程使用到sleep或wait,程序自动退出。。。
3.该封装类可以指定重试次数,也比较合理