互联网时代,单机客户端应用几乎不存在,许多服务存在云端,客户端通过HTTP(Restful API)访问云端服务,所以HTTP请求基础能力是客户端必备的。
HTTP请求实现可以基于Windows SDK提供的WinHTTP, WinHTTP在WinXP下不支持https请求。本文介绍内容是基于libcurl库实现,libcurl库免费、开源、跨平台,支持HTTP、FTP等许多协议,被许多应用使用如:Google Chrome、Google Youtube、Apple iTunes。
接下来,以curl-7.56.3,Visual Studio 2013,Win7系统下介绍libcurl的编译。
第一,libcurl官网下载curl-7.65.3,解压到$(rootpath)\curl-7.65.3,$(rootpath)指解压的根目录。
第二,如果不需要支持https协议,此步骤跳过。下载openssl-1.0.2t-vc14-x86,地址:https://windows.php.net/downloads/php-sdk/deps/。创建$(rootpath)\deps目录,将openssl的lib、include、bin拷贝到$(rootpath)\deps目录下。
第三,启动命令行,执行命令 cd $(rootpath)\curl-7.65.3\winbuild,执行命令 $(VSInstallPath)\VC\vcvarsall.bat,$(VSInstallPath)指Visual Studio安装路径。
第四,编译,执行命令 nmake /f Makefile.vc mode=dll VC=12 WITH_SSL=dll GEN_PDB=yes DEBUG=no MACHINE=x86,其中mode参数指定编译成动态库(dll)还是静态库(static), 如果不需要支持https要去除WITH_SSL=dll,DEBUG参数指定Debug版还是Release版。
libcurl有两种使用方法 Easy 和 Multi。Easy是同步、高效、快速简单的使用方式。Multi是异步的使用方式,一个线程同时可以进行多个网络传输。本文先介绍Easy使用方式。
Easy使用流程如下图所示:
第一,调curl_global_init()接口进行全局初始化,一个进程只需调用一次。如果一次都未调用,curl_easy_init()接口内部会自动调curl_global_init(),因为curl_easy_init()并不是多线程安全,如果两个线程同时调用curl_easy_init(),会概率出现一个线程在没有全局初始化下就进行网络传输导致崩溃,于是强烈建议显示调用curl_global_init()进行libcurl库进行全局初始化。
第二,调curl_easy_init()接口分配一个网络传输对象,因为libcurl是以C语言接口形式提供,所以后续调用的接口都需要提供该接口返回的句柄。
第三,调curl_easy_setopt()设置网络传输对象参数,该接口的第2个参数指定设置的参数类型,第3个参数是一个指针,其值依第2个参数不同而不同(详见libcurl帮助文档),下面主要列举http请求常用的设置项,具体使用参考后面的demo代码:
1)CURLOPT_VERBOSE,设置值为1启用调试输出,此时要设置CURLOPT_DEBUGFUNCTION 调试输出函数,排查问题时使用。
2)CURLOPT_URL,设置URL地址
3)CURLOPT_PUT,设置HTTP请求方法为PUT,CURLOPT_POST设置HTTP请求方法为POST,要设置HTTP请求方法为DELETE或PATCH,就得用CURLOPT_CUSTOMREQUEST。
4)CURLOPT_POSTFIELDS,设置HTTP请求body内容,CURLOPT_POSTFIELDSIZE设置body大小,如果body内容是以\0结尾,可以不指定body大小。
5)CURLOPT_HTTPHEADER,设置HTTP头部,HTTP头部是用curl_slist结构的链表,curl_slist_append()添加HTTP头部,可以调多次添加多个头部,curl_slist_free_all()释放curl_slis对象。
6)CURLOPT_WRITEFUNCTION,设置HTTP请求body的数据输出函数,同时可以指定CURLOPT_WRITEDATA作为输出函数的user_data,libcurl会透传user_data。
7)CURLOPT_TIMEOUT_MS设置网络请求总超时值,CURLOPT_CONNECTTIMEOUT_MS设置网络socket连接超时值。
8)CURLOPT_SSL_VERIFYPEER 设置0时不校验服务端,HTTPS请求时如果本地没有服务端证书,需要设置为0。
第四,调curl_easy_perform()接口执行网络请求,返回值CURLE_OK表示成功,只有成功时获取响应码和响应body才有效。
第五,调curl_easy_getinfo()接口获取网络请求响应信息,它类似于curl_easy_setopt()接口,第2个参数指定获取项,第3个参数依第2个参数不同而不同,比较常用的是CURLINFO_RESPONSE_CODE,获取状态码。
第六,调curl_easy_cleanup()接口释放资源
第七,调curl_global_cleanup()接口释放全局资源
下面的demo代码介绍如何使用libcurl库进行HTTP GET和POST请求。
#include <stdio.h>
#include <curl/curl.h>
#include <string>
size_t write_callback(char *ptr, size_t size, size_t nmemb, void *userdata)
{
((std::string*)userdata)->append(ptr, nmemb);
return nmemb;
}
int main(void)
{
curl_global_init(CURL_GLOBAL_DEFAULT);
CURL* curl = curl_easy_init();
if (curl)
{
curl_easy_setopt(curl, CURLOPT_URL, "https://www.baidu.com");
// no certificate, not verify server certificate
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
std::string response_data;
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response_data);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
CURLcode res = curl_easy_perform(curl);
if (res != CURLE_OK)
{
fprintf(stderr, "curl_easy_perform() failed: %s\n",
curl_easy_strerror(res));
}
else
{
long response_code;
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response_code);
printf("response code %d \n", response_code);
printf("response data : \n ");
printf(response_data.c_str());
}
curl_easy_cleanup(curl);
}
curl_global_cleanup();
return 0;
}
#include <stdio.h>
#include <curl/curl.h>
#include <string>
size_t write_callback(char *ptr, size_t size, size_t nmemb, void *userdata)
{
((std::string*)userdata)->append(ptr, nmemb);
return nmemb;
}
int main(int argc, void* argv)
{
curl_global_init(CURL_GLOBAL_DEFAULT);
CURL* curl = curl_easy_init();
if (curl)
{
// set url
curl_easy_setopt(curl, CURLOPT_URL, "https://jsonplaceholder.typicode.com/posts");
// no certificate, not verify server certificate
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
// set http method
curl_easy_setopt(curl, CURLOPT_POST, 1);
// set header
struct curl_slist * slist = nullptr;
slist = curl_slist_append(slist, "Content-Type : application/json");
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, slist);
// set body
std::string body = "{\
\"title\":\"libcurl post title\",\
\"body\" : \"libcurl post body\",\
\"userId\" : 1}";
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, body.c_str());
// set response callback to read response
std::string response_data;
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response_data);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
CURLcode res = curl_easy_perform(curl);
if (res != CURLE_OK)
{
fprintf(stderr, "curl_easy_perform() failed: %s\n",
curl_easy_strerror(res));
}
else
{
// get response code
long response_code;
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response_code);
printf("response code %d \n", response_code);
printf("response data : \n ");
printf(response_data.c_str());
}
curl_slist_free_all(slist);
curl_easy_cleanup(curl);
}
curl_global_cleanup();
return 0;
}