前言

 编译curl主要有两种ssl模式,默认是基于windows的winssl编译,另一种是基于openssl加密库。如果不涉及到HTTPS请求访问,可以不添加winssl或者OpenSSL

https://blog.51cto.com/fengyuzaitu/2435300

https://blog.51cto.com/fengyuzaitu/5010400

VS工程说明

  下载了curl-7.61.0源码压缩包,解压之后,进入projects\Windows有VS各个版本的解决方案,目前使用的是VS2015,进入VC14文件夹,里面有lib文件夹,该文件夹里面包含了一个libcurl的解决方案和一个libcurl现有项目(该项目将会加入到实际的解决方案中)。src文件夹包含了一系列工具生成方案。 默认情况下,会编译所有支持的协议进静态库。通过宏定义HTTP_ONLY,仅将HTTP,HTTPS编译到静态库中

HTTPS支持

     curl-7.32.0(2013-08-12 00:17)版本不支持HTTPS,因此需要升级到curl-7.61.0(2018-07-11 08:01)版本。目前在进行HTTPS开发的时候,遇到curl_easy_perform函数执行之后,返回CURLE_UNSUPPORTED_PROTOCOL错误,
使用的SSL代码协议是curl_easy_setopt(hnd, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_2);

编译libcurl静态库

    确保C/C++页面下代码生成/运行库确保跟解决方案保持一致,确保常规下输出路径和目标文件名称(d)

C/C++属性页面中的预处理器属性页设置定义

_DEBUG

CURL_STATICLIB(必须的)

DEBUGBUILD

调用工程C/C++属性页面中的预处理器属性页设置定义

CURL_STATICLIB(必须的)

因为宏定义主要作用于curl/curl.h头文件,用于头文件的宏定义条件编译,必须定义这两个宏定义,否则编译出错,看出错分析


编译libcurl动态库

C/C++属性页面中的预处理器属性页设置定义

libcurl库编译预处理器包含BUILDING_LIBCURL


静态库和动态库编译选项控制代码

#ifdef CURL_STATICLIB
#  define CURL_EXTERN
//上面两行说明编译采用的是静态库
#elif defined(WIN32) || defined(_WIN32) || defined(__SYMBIAN32__)
#  if defined(BUILDING_LIBCURL)
#    define CURL_EXTERN  __declspec(dllexport)
#  else
#    define CURL_EXTERN  __declspec(dllimport)
#  endif
#elif defined(BUILDING_LIBCURL) && defined(CURL_HIDDEN_SYMBOLS)
#  define CURL_EXTERN CURL_EXTERN_SYMBOL
#else
#  define CURL_EXTERN
#endif


链接错误分析

1)无法解析的外部符号 __imp__curl_easy_init

error LNK2019: 无法解析的外部符号 __imp__curl_easy_init,该符号在函数 _main 中被引用

分析:不定义CURL_STATICLIB 编译结果,因为CURL_EXTERN CURL *curl_easy_init(void);函数之前有CURL_EXTERN定义,而该定义在

#ifdef CURL_STATICLIB

#  define CURL_EXTERN

#elif defined(WIN32) || defined(_WIN32) || defined(__SYMBIAN32__)

#  if defined(BUILDING_LIBCURL)

#    define CURL_EXTERN  __declspec(dllexport)

#  else

#    define CURL_EXTERN  __declspec(dllimport)

#  endif

#elif defined(BUILDING_LIBCURL) && defined(CURL_HIDDEN_SYMBOLS)

#  define CURL_EXTERN CURL_EXTERN_SYMBOL

#else

#  define CURL_EXTERN

#endif

这里被定义,如果没有定义CURL_STATICLIB,将会导致CURL_EXTERN被替换成__declspec(dllexport)或者__declspec(dllimport),这两个声明只应用于动态链接,不能应用于静态链接

2)无法解析的外部符号 __imp__ldap_init

error LNK2019: 无法解析的外部符号 __imp__ldap_init,该符号在函数 __ldap_free_urldesc 中被引用

在引用项目的属性中添加额外的静态库Crypt32.lib,Wldap32.lib,否则出现如下的错误,因为用到https

3)静态库编译libcurl出错提示:重定义

 C2371 “curl_easy_init”: 重定义;不同的基类型

C2371 “curl_easy_duphandle”: 重定义;不同的基类型  

C2371 “curl_multi_init”: 重定义;不同的基类型 libcurl  

C2371 “curl_share_init”: 重定义;不同的基类型 libcurl 

查看源码,如果没有定义BUILDING_LIBCURL,将导致curl_share_init在声明和定义中出现返回类型不一致的问题

#if defined(BUILDING_LIBCURL) || defined(CURL_STRICTER)

typedef struct Curl_easy CURL;

typedef struct Curl_share CURLSH;

#else

typedef void CURL;

typedef void CURLSH;

#endif

因此需要预编译处理器需要添加BUILDING_LIBCURL

4)编译器中发生内部错误

目前在VS2015环境下编译release版本静态库提示出错,暂时没有找到解决方案

5)无法解析的外部符号 __imp__wassert 

C/C++属性页面,代码生成的运行库,不一致导致的,统一修改为MTD解决问题

延申

Cryptlib是新西兰奥克兰大学的Peter Gutmann先生花费了将近五年时间开发而成的一个加密安全工具包,它基于传统的 计算机安全模型,并涉及到一个安全核心,各种抽象化了的对象位于核心之上。利用此加密库不同层次的接口,可以很容易地为各种应用系统提供 安全服务,如加/解密、  数字签名、 认证等。

目前开放源代码的加密库中,openssl和cryptlib都是比较流行的,本文将根据自己的一点理解对这两个库作一些比较,希望能对大家有用。这两个库的构造思想和目的都不太一样. Openssl主要是针对SSL/TLS协议的实现,SSL的功能体现的非常完善,而算法库只是一个附带的必要部分,当然也是非常重要和完善的一个部分。Crypylib则就是实现了加密算法以及相关的一些编码标准。


分块数据获取

      从 t.weather.sojson.com网页中获取天气信息。如果不使用libcurl库,需要实现Transfer-Encoding: chunked分块接收和Content-Encoding: gzip解压,现在提供libcurl实现代码

#include "curl/curl.h"

size_t WriteResponseBody(void *ptr, size_t size, size_t nmemb, void *userData)
{
	std::string* pStrBuffer = (std::string*)userData;
	size_t nLen = size * nmemb;
	pStrBuffer->append((char*)ptr, nLen);
	return nLen;
}

int GetWeatherInfoByLibcurl(const std::string &strUrl, std::string& strResponseData)
{
	CURL *pCurlHandle = curl_easy_init();
	curl_easy_setopt(pCurlHandle, CURLOPT_CUSTOMREQUEST, "GET");
	curl_easy_setopt(pCurlHandle, CURLOPT_URL, strUrl.c_str());
	curl_easy_setopt(pCurlHandle, CURLOPT_WRITEFUNCTION, WriteResponseBody);//设置回调函数
	curl_easy_setopt(pCurlHandle, CURLOPT_HEADER, 1);//保存HTTP头部信息到strResponseData
	curl_easy_setopt(pCurlHandle, CURLOPT_WRITEDATA, &strResponseData);//设置回调函数的参数,获取反馈信息
	curl_easy_setopt(pCurlHandle, CURLOPT_TIMEOUT, 15);//接收数据时超时设置,如果10秒内数据未接收完,直接退出
	curl_easy_setopt(pCurlHandle, CURLOPT_MAXREDIRS, 1);//查找次数,防止查找太深
	curl_easy_setopt(pCurlHandle, CURLOPT_CONNECTTIMEOUT, 5);//连接超时,这个数值如果设置太短可能导致数据请求不到就断开了
	CURLcode nRet = curl_easy_perform(pCurlHandle);
	curl_easy_cleanup(pCurlHandle);
	return nRet;
}

int main()
{
	std::string strResponseData;
	GetWeatherInfoByLibcurl("t.weather.sojson.com/api/weather/city/101200101", strResponseData);
  return 0;
  }


获取返回报文的头部信息

需要获取HTTP报头提取Cookie信息,发送给服务器,否则返回302重定向错误

static size_t Writeresponse(void *ptr, size_t size, size_t nmemb, void *userData)
{
string* pBuffer = (string*)userData;
size_t length = size * nmemb;
pBuffer->append((char*)ptr, length);
return length;
}
int CCS::LoginEx()
{
	CURL *hnd = curl_easy_init();

	curl_easy_setopt(hnd, CURLOPT_CUSTOMREQUEST, "POST");
	curl_easy_setopt(hnd, CURLOPT_URL, "http://127.0.0.1:7000/proj/login");

	struct curl_slist *headers = NULL;
	headers = curl_slist_append(headers, "Postman-Token: ec3ffce4-5c3c-4786-9396-578ff396c11d");
	headers = curl_slist_append(headers, "cache-control: no-cache");
	headers = curl_slist_append(headers, "Content-Type: application/x-www-form-urlencoded");
	curl_easy_setopt(hnd, CURLOPT_HTTPHEADER, headers);
	curl_easy_setopt(hnd, CURLOPT_POSTFIELDS, "username=slny001&password=Hx%40kj%2319&loginType=2&undefined=");

	std::string strResponse;
	curl_easy_setopt(hnd, CURLOPT_WRITEFUNCTION, Writeresponse);//设置回调函数																			//curl_easy_setopt(pCurlHandle, CURLOPT_HEADER, 1);//保存HTTP头部信息到strResponseData
	curl_easy_setopt(hnd, CURLOPT_WRITEDATA, &strResponse);//设置回调函数的参数,获取反馈信息
	curl_easy_setopt(hnd, CURLOPT_HEADERFUNCTION, Writeresponse);//设置回调函数:输出response headers
	string responseHeadBuffer;
	curl_easy_setopt(hnd, CURLOPT_HEADERDATA, &responseHeadBuffer);//设置回调函数参数
	CURLcode ret = curl_easy_perform(hnd);
	if (0 == ret)
	{
		int nPosOfCookie = responseHeadBuffer.find("Cookie: ", 0);
		if (nPosOfCookie > 0)
		{
			int nPosOfEndCookie = responseHeadBuffer.find(";", nPosOfCookie);
			m_cookie = responseHeadBuffer.substr(nPosOfCookie + 7, nPosOfEndCookie - nPosOfCookie - 7);
		}
	}
	curl_slist_free_all(headers);
	curl_easy_cleanup(hnd);
	return 0;
}


保存成图片

static size_t WriteFile(void *ptr, size_t size, size_t nmemb, void *stream)
 {
  std::ofstream* ofs = (std::ofstream*)stream;
  size_t nLen = size * nmemb;
  ofs->write((char*)ptr, nLen);
  return nLen;
 }
 static void TestStorePhotoFileFromUrl()
 {
  std::ofstream ofs;
  ofs.open("aa3ab705-a7d5-4892-b63d-2ccdb54db9a21561977015816.jpg", std::ios::out | std::ios::binary);
  std::string strUrl = "ftp://192.168.11.161:9010/successImage/aa3ab705-a7d5-4892-b63d-2ccdb54db9a21561977015816.jpg";
  std::string strPhotoBuffer;
  CURL *pCurlHandle;
  pCurlHandle = curl_easy_init();
  curl_easy_setopt(pCurlHandle, CURLOPT_URL, strUrl.c_str());
  curl_easy_setopt(pCurlHandle, CURLOPT_WRITEDATA, &ofs);
  curl_easy_setopt(pCurlHandle, CURLOPT_WRITEFUNCTION, WriteFile);
  CURLcode nCurlRet = curl_easy_perform(pCurlHandle);
  if ((nCurlRet != CURLE_OK) && (nCurlRet != CURLE_WRITE_ERROR))
  {
   std::cout << "通过LibCurl获取:" << strUrl << "图片失败,错误码是:" << nCurlRet;
  }
  ofs.close();
  curl_easy_cleanup(pCurlHandle);
 }

保存到缓存

static size_t WriteBuffer(void *ptr, size_t size, size_t nmemb, void *stream)
 {
  std::string* pStrBuffer = (std::string*)stream;
  size_t nLen = size * nmemb;
  pStrBuffer->append((char*)ptr, nLen);
  return nLen;
 }
 static void TestStoreBufferFromUrl()
 {
  std::string strUrl = "ftp://192.168.11.161:9010/successImage/aa3ab705-a7d5-4892-b63d-2ccdb54db9a21561977015816.jpg";
  std::string strPhotoBuffer;
  CURL *pCurlHandle;
  pCurlHandle = curl_easy_init();
  curl_easy_setopt(pCurlHandle, CURLOPT_URL, strUrl.c_str());
  curl_easy_setopt(pCurlHandle, CURLOPT_WRITEDATA, &strPhotoBuffer);
  curl_easy_setopt(pCurlHandle, CURLOPT_WRITEFUNCTION, WriteBuffer);
  CURLcode nCurlRet = curl_easy_perform(pCurlHandle);
  if ((nCurlRet != CURLE_OK) && (nCurlRet != CURLE_WRITE_ERROR))
  {
   std::cout << "通过LibCurl获取:" << strUrl << "图片失败,错误码是:" << nCurlRet;
  }
  else
  {
   std::ofstream ofs;
   ofs.open("aa3ab705-a7d5-4892-b63d-2ccdb54db9a21561977015816.jpg", std::ios::out | std::ios::binary);
   ofs << strPhotoBuffer;
   ofs.close();
  }
  curl_easy_cleanup(pCurlHandle);
 }


Basic Authentication基本验证使用

场景
        在安迅士摄像机网页上,配置系统选项,HTTP/RTSP Password Settings 中, 选择UnEncrypted only。获取设备的云台状态信息

代码
void CAnXunShiConn::TestlibCurlHTTPBasicAuth()
{
 CURL *pCurlHandle = curl_easy_init();
 curl_easy_setopt(pCurlHandle, CURLOPT_CUSTOMREQUEST, "GET");
 curl_easy_setopt(pCurlHandle, CURLOPT_URL, "http://192.168.18.84/axis-cgi/com/ptz.cgi?camera=1&query=position");
 curl_easy_setopt(pCurlHandle, CURLOPT_USERPWD, "root:admin12345");
 curl_easy_setopt(pCurlHandle, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
 curl_easy_setopt(pCurlHandle, CURLOPT_WRITEFUNCTION, WriteResponseBody);//设置回调函数                  
//curl_easy_setopt(pCurlHandle, CURLOPT_HEADER, 1);//保存HTTP头部信息到strResponseData
 curl_easy_setopt(pCurlHandle, CURLOPT_WRITEDATA, &strResponseData);//设置回调函数的参数,获取反馈信息
 curl_easy_setopt(pCurlHandle, CURLOPT_TIMEOUT, 15);//接收数据时超时设置,如果10秒内数据未接收完,直接退出
 curl_easy_setopt(pCurlHandle, CURLOPT_MAXREDIRS, 1);//查找次数,防止查找太深
 curl_easy_setopt(pCurlHandle, CURLOPT_CONNECTTIMEOUT, 5);//连接超时,这个数值如果设置太短可能导致数据请求不到就断开了
 CURLcode nRet = curl_easy_perform(pCurlHandle);
 if (0 == nRet)
 {
  std::cout << strResponseData << std::endl;
 }
 curl_easy_cleanup(pCurlHandle);
}

Digest Authentication摘要验证使用

场景
         在安迅士摄像机网页上,配置系统选项,HTTP/RTSP Password Settings 中, 选择Encrypted only。获取设备的云台状态信息,使用的是摘要认证

例子
void CAnXunShiConn::TestlibCurlHTTPDegistAuth()
{
 CURL *pCurlHandle = curl_easy_init();
 curl_easy_setopt(pCurlHandle, CURLOPT_CUSTOMREQUEST, "GET");
 curl_easy_setopt(pCurlHandle, CURLOPT_URL, "http://192.168.18.84/axis-cgi/com/ptz.cgi?camera=1&query=position");
 curl_easy_setopt(pCurlHandle, CURLOPT_USERPWD, "root:admin12345");
 curl_easy_setopt(pCurlHandle, CURLOPT_HTTPAUTH, CURLAUTH_DIGEST);
 curl_easy_setopt(pCurlHandle, CURLOPT_WRITEFUNCTION, WriteResponseBody);//设置回调函数                  
//curl_easy_setopt(pCurlHandle, CURLOPT_HEADER, 1);//保存HTTP头部信息到strResponseData
 curl_easy_setopt(pCurlHandle, CURLOPT_WRITEDATA, &strResponseData);//设置回调函数的参数,获取反馈信息
 curl_easy_setopt(pCurlHandle, CURLOPT_TIMEOUT, 15);//接收数据时超时设置,如果10秒内数据未接收完,直接退出
 curl_easy_setopt(pCurlHandle, CURLOPT_MAXREDIRS, 1);//查找次数,防止查找太深
 curl_easy_setopt(pCurlHandle, CURLOPT_CONNECTTIMEOUT, 5);//连接超时,这个数值如果设置太短可能导致数据请求不到就断开了
 CURLcode nRet = curl_easy_perform(pCurlHandle);
 if (0 == nRet)
 {
  std::cout << strResponseData << std::endl;
 }
 curl_easy_cleanup(pCurlHandle);
}

基础知识
◆ 摘要认证 digest authentication   ← HTTP1.1提出的基本认证的替代方法
    服务器端以nonce进行质询,客户端以用户名,密码,nonce,HTTP方法,请求的URI等信息为基础产生的response信息进行认证的方式。
    ※ 不包含密码的明文传递
   
    摘要认证步骤:
     1. 客户端访问一个受http摘要认证保护的资源。
     2. 服务器返回401状态以及nonce等信息,要求客户端进行认证。
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Digest
realm="testrealm@host.com",
qop="auth,auth-int",
nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093",
opaque="5ccc069c403ebaf9f0171e9517f40e41"
     3. 客户端将以用户名,密码,nonce值,HTTP方法, 和被请求的URI为校验值基础而加密(默认为MD5算法)的摘要信息返回给服务器。
           认证必须的五个情报:
     ・ realm : 响应中包含信息
     ・ nonce : 响应中包含信息
     ・ username : 用户名
     ・ digest-uri : 请求的URI
     ・ response : 以上面四个信息加上密码信息,使用MD5算法得出的字符串。
Authorization: Digest
username="Mufasa",  ← 客户端已知信息
realm="testrealm@host.com",   ← 服务器端质询响应信息
nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093",  ← 服务器端质询响应信息
uri="/dir/index.html", ← 客户端已知信息
qop=auth,   ← 服务器端质询响应信息
nc=00000001, ← 客户端计算出的信息
cnonce="0a4f113b", ← 客户端计算出的客户端nonce
response="6629fae49393a05397450978507c4ef1", ← 最终的摘要信息 ha3
opaque="5ccc069c403ebaf9f0171e9517f40e41"  ← 服务器端质询响应信息
     4. 如果认证成功,则返回相应的资源。如果认证失败,则仍返回401状态,要求重新进行认证。

    特记事项:
     1. 避免将密码作为明文在网络上传递,相对提高了HTTP认证的安全性。
     2. 当用户为某个realm首次设置密码时,服务器保存的是以用户名,realm,密码为基础计算出的哈希值(ha1),而非密码本身。
     3. 如果qop=auth-int,在计算ha2时,除了包括HTTP方法,URI路径外,还包括请求实体主体,从而防止PUT和POST请求表示被人篡改。
     4. 但是因为nonce本身可以被用来进行摘要认证,所以也无法确保认证后传递过来的数据的安全性。

   ※ nonce:随机字符串,每次返回401响应的时候都会返回一个不同的nonce。
   ※ nounce:随机字符串,每个请求都得到一个不同的nounce。
      ※ MD5(Message Digest algorithm 5,信息摘要算法)
         ① 用户名:realm:密码 ⇒ ha1
         ② HTTP方法:URI ⇒ ha2
         ③ ha1:nonce:nc:cnonce:qop:ha2 ⇒ ha3
 WSSE(WS-Security)认证  ← 扩展HTTP认证
   WSSE UsernameToken
    服务器端以nonce进行质询,客户端以用户名,密码,nonce,HTTP方法,请求的URI等信息为基础产生的response信息进行认证的方式。
    ※ 不包含密码的明文传递
   
    WSSE认证步骤:
     1. 客户端访问一个受WSSE认证保护的资源。
     2. 服务器返回401状态,要求客户端进行认证。
HTTP/1.1 401 Unauthorized
WWW-Authenticate: WSSE
realm="testrealm@host.com",
profile="UsernameToken" ← 服务器期望你用UsernameToken规则生成回应
※ UsernameToken规则:客户端生成一个nonce,然后根据该nonce,密码和当前日时来算出哈希值。
     3. 客户端将生成一个nonce值,并以该nonce值,密码,当前日时为基础,算出哈希值返回给服务器。
Authorization: WSSE profile="UsernameToken"
X-WSSE:UsernameToken
username="Mufasa",
PasswordDigest="Z2Y......",
Nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093",
Created="2010-01-01T09:00:00Z"
     4. 如果认证成功,则返回相应的资源。如果认证失败,则仍返回401状态,要求重新进行认证。
 特记事项:
     1. 避免将密码作为明文在网络上传递。
     2. 不需要在服务器端作设置。
     3. 服务器端必须保存密码本身,否则无法进行身份验证。