HTTP/HTTPS客户端源码示例
环境: zlib-1.2.8 openssl-1.0.1g curl-7.36
Author: Kagula
LastUpdateDate: 2016-05-09
阅读前提:CMake工具的基本使用、配置openssl-1.0.1g 开发环境
编译zlib库
下载zlib-1.2.8.tar.gz并解压缩到" D:\SDK\zlib-1.2.8",使用CMake工具生成zlib.sln,在Visual Studio2013中打开并编译即可。
编译curl-7.36.0
假设Open SSL已经安装到“D:\SDK\openssl-1.0.1g”,先设置下面的环境变量
OPENSSL_LIBRARIES=D:\SDK\openssl-1.0.1g\out32
OPENSSL_ROOT_DIR=D:\SDK\openssl-1.0.1g
从http://curl.haxx.se/下载curl-7.36.0.zip并解压缩到“D:\SDK\curl-7.36.0”启动CMake工具Configure,分别设置LIB_EAY_RELEASE和SSL_EAY_RELEASE变量为“D:\SDK\openssl-1.0.1g\out32\libeay32.lib”,“D:\SDK\openssl-1.0.1g\out32\ssleay32.lib”,产生sln文件后打开,为里面的curl工程项目添加“USE_MANUAL”宏定义,然后build里面的4个项目成功。
为项目添加链接库libcurl_imp.lib , 把libcurl.dll文件复制到C++项目路径下,否则程序运行会提示找不到动态链接库。
下面是HTTP/HTTPS客户端示例
如何使用
- #include <iostream>
- #include <string>
-
- using namespace std;
-
- #include "httpclient.h"
-
- int main(int argc, char *argv[])
- {
- string response;
-
-
- kagula::network::CHttpClient client;
- int nR = client.Post("https://lijun:8443/escortcashbox/main/login.do",
- "data={\"version\":\"1.0.0.0\",\"user\":\"admin\",\"password\":\"123\"}",
- response,true,
- "d:\cookie.txt",
- "D:/workspace_qt/build-escortcashbox_c-Mingw32-Debug/debug/tomcat7.pem",
- "D:/workspace_qt/build-escortcashbox_c-Mingw32-Debug/debug/client_all.pem",
- "123456");
- cout << nR << endl <<response << endl;
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- nR = client.GetFile("https://lijun:8443/escortcashbox/upload/img/20160527_110514_679_426.png",
- "d:/ee.png",
- "d:\cookie.txt",
- "D:/workspace_qt/build-escortcashbox_c-Mingw32-Debug/debug/tomcat7.pem",
- "D:/workspace_qt/build-escortcashbox_c-Mingw32-Debug/debug/client_all.pem",
- "123456");
- cout << nR << endl;
-
- return 0;
- }
HttpClient.h封装好的头文件
-
- #ifndef _HTTPCLIENT_H_
- #define _HTTPCLIENT_H_
-
- #include <string>
- #include <map>
- #include <vector>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- namespace kagula
- {
- namespace network
- {
- void Init();
- void Cleanup();
-
- class CHttpClient
- {
- public:
- CHttpClient(void);
- ~CHttpClient(void);
-
- public:
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- int Post(const char* pUrl,
- const char* pPost,
- std::string &strResponse,
- bool bPost,
- const char* pCookie,
- const char* pCaPath = NULL,
- const char* pClientCalPath = NULL,
- const char* pClientCalPassword = NULL);
-
- int MultipartFormdata(const char *pUrl,
- const std::map<std::string,std::string> & mapFields,
- const std::map<std::string,std::vector<std::string>> & mapFiles,
- std::string & strResponse,
- const char *pCookie,
- const char * pCaPath = NULL,
- const char * pClientCalPath = NULL,
- const char * pClientCalPassword = NULL);
-
- int GetFile(const char* pUrl,
- const char* pLocalFullPath,
- const char* pCookie,
- const char* pCaPath = NULL,
- const char* pClientCalPath = NULL,
- const char* pClientCalPassword = NULL);
- public:
- void SetDebug(bool bDebug);
- std::string getMsgInChinese(int code);
-
- private:
- bool m_bDebug;
-
- bool PrintCookies(void* curl, std::string& strOut);
- };
- }
- }
- #endif
源文件清单
-
- #include "HttpClient.h"
- #include <iostream>
- #include <curl/curl.h>
- #include <iomanip>
- #include <sstream>
-
- #ifdef WIN32
- #pragma comment(lib,"libcurl_imp.lib")
- #endif
-
- namespace kagula
- {
- namespace network
- {
- CHttpClient::CHttpClient(void) :
- m_bDebug(false)
- {
-
- }
-
- CHttpClient::~CHttpClient(void)
- {
-
- }
-
- bool CHttpClient::PrintCookies(void* curl, std::string& strOut)
- {
- std::ostringstream ostr;
-
- CURLcode res;
- struct curl_slist *cookies;
-
- res = curl_easy_getinfo(curl, CURLINFO_COOKIELIST, &cookies);
- if (res != CURLE_OK) {
-
- ostr << "Curl curl_easy_getinfo failed:" << curl_easy_strerror(res) << std::endl;
- strOut = ostr.str();
- return false;
- }
- const struct curl_slist *nc = cookies;
- int i = 1;
- ostr << "Cookies, curl knows:" << std::endl;
- while (nc) {
- ostr << "[" << i++ << "]: " << nc->data << std::endl;
- nc = nc->next;
- }
-
- return true;
- }
-
- static int OnDebug(CURL *, curl_infotype itype, char * pData, size_t size, void *)
- {
- if (itype == CURLINFO_TEXT)
- {
-
- }
- else if (itype == CURLINFO_HEADER_IN)
- {
- printf("[HEADER_IN]%s\n", pData);
- }
- else if (itype == CURLINFO_HEADER_OUT)
- {
- printf("[HEADER_OUT]%s\n", pData);
- }
- else if (itype == CURLINFO_DATA_IN)
- {
- printf("[DATA_IN]%s\n", pData);
- }
- else if (itype == CURLINFO_DATA_OUT)
- {
- printf("[DATA_OUT]%s\n", pData);
- }
- return 0;
- }
-
- size_t OnWriteData_Post(void* buffer, size_t size, size_t nmemb, void* lpVoid)
- {
- std::string* str = reinterpret_cast<std::string*>(lpVoid);
- if (NULL == str || NULL == buffer)
- {
- return -1;
- }
-
- char* pData = reinterpret_cast<char*>(buffer);
- str->append(pData, size * nmemb);
- return nmemb;
- }
-
- size_t OnWriteData_MultipartFormdata( void *inBuffer, size_t size, size_t nmemb, void *outBuffer )
- {
- int len = size * nmemb;
- char *temp = new char[len+1];
- memcpy(temp,inBuffer,len);
- temp[len]=0;
- reinterpret_cast<std::string *>(outBuffer)->append(temp);
- delete temp;
- return len;
- }
-
- size_t OnWriteData_GetFile(void *inBuffer, int size, int nmemb, std::string &content)
- {
- long len = size * nmemb;
- std::string temp((char *)inBuffer, len);
- content += temp;
- return len;
- }
-
- std::string CHttpClient::getMsgInChinese(int code)
- {
- switch(code)
- {
- case 0:
- return "通讯成功";
- case 7:
- return "服务器连接失败。";
- case 28:
- return "连接超时。";
- case 58:
- return "服务端验证客户端证书失败。";
- case 60:
- return "客户端验证服务端证书失败。";
- default:
- return "";
- }
- }
-
- int CHttpClient::Post(const char* pUrl,
- const char* pPost,
- std::string & strResponse,
- bool bPost,
- const char* pCookie,
- const char* pCaPath,
- const char* pClientCalPath,
- const char* pClientCalPassword)
- {
- strResponse = "";
-
-
- CURLcode res;
- CURL* curl = curl_easy_init();
- if (NULL == curl)
- {
- return CURLE_FAILED_INIT;
- }
- if (m_bDebug)
- {
- curl_easy_setopt(curl, CURLOPT_VERBOSE, 1);
- curl_easy_setopt(curl, CURLOPT_DEBUGFUNCTION, OnDebug);
- }
- curl_easy_setopt(curl, CURLOPT_URL, pUrl);
- if(bPost)
- {
- curl_easy_setopt(curl, CURLOPT_POST, 1);
- curl_easy_setopt(curl, CURLOPT_POSTFIELDS, pPost);
- }
- curl_easy_setopt(curl, CURLOPT_READFUNCTION, NULL);
- curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, OnWriteData_Post);
- curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&strResponse);
- curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1);
-
- if (pCookie!=0)
- {
- curl_easy_setopt(curl, CURLOPT_COOKIEFILE, (void *)pCookie);
- curl_easy_setopt(curl, CURLOPT_COOKIEJAR, (void *)pCookie);
- }
-
- if (NULL == pCaPath)
- {
- curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, false);
- curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, false);
-
-
-
-
-
-
- curl_easy_setopt(curl, CURLOPT_ACCEPT_ENCODING, "gzip");
- }
- else
- {
-
-
- curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, true);
- curl_easy_setopt(curl, CURLOPT_CAINFO, pCaPath);
-
-
- if(pClientCalPath!=NULL)
- {
- curl_easy_setopt(curl,CURLOPT_SSLCERT, pClientCalPath);
- curl_easy_setopt(curl,CURLOPT_SSLCERTPASSWD, pClientCalPassword);
- curl_easy_setopt(curl,CURLOPT_SSLCERTTYPE, "PEM");
- curl_easy_setopt(curl,CURLOPT_SSLKEY, pClientCalPath);
- curl_easy_setopt(curl,CURLOPT_SSLKEYPASSWD, pClientCalPassword);
- curl_easy_setopt(curl,CURLOPT_SSLKEYTYPE, "PEM");
- }
- }
-
- curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 3);
- curl_easy_setopt(curl, CURLOPT_TIMEOUT, 3);
-
-
-
- curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION,1);
-
- res = curl_easy_perform(curl);
- curl_easy_cleanup(curl);
- return res;
- }
-
-
- int CHttpClient::MultipartFormdata(const char *pUrl,
- const std::map<std::string,std::string> & mapFields,
- const std::map<std::string,std::vector<std::string>> & mapFiles,
- std::string & strResponse,
- const char *pCookie,const char * pCaPath,
- const char * pClientCalPath,const char * pClientCalPassword)
- {
- CURL *curl;
- CURLcode res;
-
- curl = curl_easy_init();
- strResponse ="";
-
- curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, OnWriteData_MultipartFormdata);
- curl_easy_setopt(curl, CURLOPT_WRITEDATA, &strResponse);
-
- struct curl_httppost *formpost = 0;
- struct curl_httppost *lastptr = 0;
-
-
- std::map<std::string,std::string>::const_iterator iterFields = mapFields.begin();
- while(iterFields!=mapFields.end())
- {
-
- curl_formadd(&formpost, &lastptr,CURLFORM_COPYNAME,iterFields->first.c_str(),
- CURLFORM_COPYCONTENTS, iterFields->second.c_str(),CURLFORM_END);
- iterFields++;
- }
-
- std::map<std::string,std::vector<std::string>>::const_iterator iterFiles = mapFiles.begin();
- while(iterFiles!=mapFiles.end())
- {
-
-
- curl_formadd(&formpost, &lastptr,CURLFORM_PTRNAME,
- iterFiles->first.c_str(), CURLFORM_FILE,
- iterFiles->second[0].c_str(),CURLFORM_CONTENTTYPE,
- iterFiles->second[1].c_str(), CURLFORM_END);
- iterFiles++;
- }
-
-
-
- curl_easy_setopt(curl, CURLOPT_URL, pUrl);
- curl_easy_setopt(curl, CURLOPT_HTTPPOST, formpost);
-
- if (pCookie!=0)
- {
- curl_easy_setopt(curl, CURLOPT_COOKIEFILE, (void *)pCookie);
- curl_easy_setopt(curl, CURLOPT_COOKIEJAR, (void *)pCookie);
- }
-
-
- if(pCaPath!=0)
- {
- curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, true);
- curl_easy_setopt(curl, CURLOPT_CAINFO, pCaPath);
- }
-
-
- if(pClientCalPath!=0 && pClientCalPassword!=0)
- {
- curl_easy_setopt(curl,CURLOPT_SSLCERT, pClientCalPath);
- curl_easy_setopt(curl,CURLOPT_SSLCERTPASSWD, pClientCalPassword);
- curl_easy_setopt(curl,CURLOPT_SSLCERTTYPE, "PEM");
- curl_easy_setopt(curl,CURLOPT_SSLKEY, pClientCalPath);
- curl_easy_setopt(curl,CURLOPT_SSLKEYPASSWD, pClientCalPassword);
- curl_easy_setopt(curl,CURLOPT_SSLKEYTYPE, "PEM");
- }
-
- res = curl_easy_perform(curl);
- curl_easy_cleanup(curl);
-
- curl_formfree(formpost);
- return res;
- }
-
- int CHttpClient::GetFile(const char* pUrl,
- const char* pLocalFullPath,
- const char* pCookie,
- const char* pCaPath,
- const char* pClientCalPath,
- const char* pClientCalPassword)
- {
- CURL *curl = NULL;
- CURLcode code;
- char bufError[CURL_ERROR_SIZE];
- std::string content;
- long retcode = 0;
-
- code = curl_global_init(CURL_GLOBAL_DEFAULT);
- if (code != CURLE_OK)
- {
-
- return -100;
- }
-
- curl = curl_easy_init();
- if (curl == NULL)
- {
-
- return -200;
- }
- code = curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, bufError);
- if (code != CURLE_OK)
- {
-
- return code;
- }
-
- code = curl_easy_setopt(curl, CURLOPT_URL, pUrl);
- if (code != CURLE_OK)
- {
-
- goto _END;
- }
- code = curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1);
- if (code != CURLE_OK)
- {
-
- goto _END;
- }
- code = curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, OnWriteData_GetFile);
- if (code != CURLE_OK)
- {
-
- goto _END;
- }
- code = curl_easy_setopt(curl, CURLOPT_WRITEDATA, &content);
- if (code != CURLE_OK)
- {
-
- goto _END;
- }
-
- if (pCookie!=0)
- {
- curl_easy_setopt(curl, CURLOPT_COOKIEFILE, (void *)pCookie);
- curl_easy_setopt(curl, CURLOPT_COOKIEJAR, (void *)pCookie);
- }
-
-
- if(pCaPath!=0)
- {
- curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, true);
- curl_easy_setopt(curl, CURLOPT_CAINFO, pCaPath);
- }
-
-
- if(pClientCalPath!=0 && pClientCalPassword!=0)
- {
- curl_easy_setopt(curl,CURLOPT_SSLCERT, pClientCalPath);
- curl_easy_setopt(curl,CURLOPT_SSLCERTPASSWD, pClientCalPassword);
- curl_easy_setopt(curl,CURLOPT_SSLCERTTYPE, "PEM");
- curl_easy_setopt(curl,CURLOPT_SSLKEY, pClientCalPath);
- curl_easy_setopt(curl,CURLOPT_SSLKEYPASSWD, pClientCalPassword);
- curl_easy_setopt(curl,CURLOPT_SSLKEYTYPE, "PEM");
- }
-
- code = curl_easy_perform(curl);
- if (code != CURLE_OK)
- {
-
- goto _END;
- }
-
- code = curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &retcode);
- if ((code == CURLE_OK) && retcode == 200)
- {
- double length = 0;
- code = curl_easy_getinfo(curl, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &length);
-
- FILE * file = fopen(pLocalFullPath, "wb");
- fseek(file, 0, SEEK_SET);
- fwrite(content.c_str(), 1, (size_t)length, file);
- fclose(file);
-
- code = CURLE_OK;
- goto _END;
- }
- _END:
- curl_easy_cleanup(curl);
-
- return code;
- }
-
-
- void CHttpClient::SetDebug(bool bDebug)
- {
- m_bDebug = bDebug;
- }
-
- void Init()
- {
-
- curl_global_init(CURL_GLOBAL_ALL);
- }
-
- void Cleanup()
- {
- curl_global_cleanup();
- }
- }
- }
注意:
[1]MSys64 Mingw32 QtCreator 32位C++程序 配置libcurl
假设Msys64是安装在C盘默认路径上
第一步:在Msys64中查询可用的curl包
pacman -Ss curl
第二步:安装
pacman -S mingw-w64-i686-curl
第三步:在.pro文件下加入下面的代码
LIBS += C:\msys64\mingw32\lib\libcurl.dll.a
INCLUDEPATH += C:\msys64\mingw32\include
最后:编译链接依赖libcurl的项目成功。
[2]如何产生cer文件
https原理及tomcat配置https方法
http://jingyan.baidu.com/article/a948d6515d3e850a2dcd2ee6.html
[3]单向认证,让客户端信任服务端证书
第一步:
双击从服务端keystore中导出的cer文件可以导入证书。
windows下“certmgr.msc”命令可以进入证书管理。
自己制作的证书导入到Win7后默认在“中级证书颁发机构”->“证书”节点里。
第二步:
需要把你做的证书拖到“受信任的根证书颁发机构”->“证书”节点中去,否则
浏览器会提醒“此网站出具的安全证书不是由受信任的证书颁发机构颁发的”等类似错误。
chrome需要重启,IE直接刷新页面,就不会出现警告了。
注意:
数字证书转换cer---pem
在Msys2 Shell中确保安装好openssl.
在Msys2 Shell中使用openssl命令后进入openssl提示符,输入下面的命令
x509 -inform der -in d:/MyServerSecurity/tomcat7.cer -out d:/MyServerSecurity/tomcat7.pem
[4]双向认证,让服务端信任客户端的证书
相当于“Https单向认证”,添加了“服务端验证客户端身份真实性”的动作。
需要注意的是:服务端的密钥库参数“CN”必须与服务端的IP地址相同,否则会报错,客户端的任意。
如果服务端的CN为lijun,则客户端不能为lijun,即这两个不能是同一个名字。
第一步:客户端生成证书(用于服务端验证客户端)
keytool -validity 365 -genkeypair -v -alias kagula -keyalg RSA -storetype PKCS12 -keystore D:\MyClientSecurity\kagulakey.p12 -storepass 123456 -keypass 123456
kagula为证书的名称,D:\MyClientSecurity\kagulakey.p12证书的存放位置。
第二步:证书格式转为cer文件。
keytool -export -v -alias kagula -keystore D:\MyClientSecurity\kagulakey.p12 -storetype PKCS12 -storepass 123456 -rfc -file D:\MyClientSecurity\kagulakey.cer
kagula为证书的名称,D:\MyClientSecurity\kagulakey.p12证书的存放位置,123456证书密码,D:\MyClientSecurity\kagulakey.cer导出的文件。
第三步:添加到(或新建)一个keystore文件
keytool -import -v -alias kagula -file D:\MyClientSecurity\kagulakey.cer -keystore D:\MyServerSecurity\tomcat7_client.keystore -storepass 123456
tomcat7_client.keystore如果不存在就会新建一个keystore,如果存在会添加到已经存在的keystore中。
第四步:查看keystore文件中的内容
keytool -list -keystore D:\MyServerSecurity\tomcat7_client.keystore
第五步:修改tomcat7中的server.xml文件
原单向认证的配置如下
<Connector port="8443" protocol="org.apache.coyote.http11.Http11Protocol"
maxThreads="150" SSLEnabled="true" scheme="https" secure="true"
clientAuth="false" sslProtocol="TLS"
keystoreFile="D:\\MyServerSecurity\\tomcat7.keystore" keystorePass="123456"/>
现在修改后,如下
<Connector port="8443" protocol="org.apache.coyote.http11.Http11Protocol"
maxThreads="150" SSLEnabled="true" scheme="https" secure="true"
clientAuth="true" sslProtocol="TLS"
keystoreFile="D:\\MyServerSecurity\\tomcat7.keystore" keystorePass="123456"
truststoreFile="D:\\MyServerSecurity\\tomcat7_client.keystore" truststorePass="123456"/>
添加了最后一行属性使它指向客户端证书,并把clientAuth属性从false改为true。
第六步(可选):
Step6-1:
测试Windows下的Chrome是否还能访问服务器,果然刷新浏览器后
“https://lijun:8443/escortcashbox/main/aboutUs.do”返回错误信息
Step6-2:
测试libcurl是否还能访问服务器,现在libcurl返回7(无法连接服务器)的错误信息。
最后一步-让Windows下的Chrome和IE能访问服务端:
双击D:\MyClientSecurity\kagulakey.p12文件,“不需要添加到受信任的根证书机构”结点,直接导入证书即可。
默认在certmgr.msc命令,“个人”->“证书”节点下。
最后一步-让libcurl能访问服务端:
使用msys64打开openssl命令行工具
#客户端个人证书的公钥
openssl>pkcs12 -in D:\MyClientSecurity\myclientkey.p12 -out D:\MyClientSecurity\client_publickey.pem -nokeys
也许如果在当前command shell下能找到openssl.exe也可以用下面的命令
“openssl pkcs12 -in D:\MyClientSecurity\myclientkey.p12 -out D:\MyClientSecurity\client_publickey.pem -nokeys”
#客户端个人证书的私钥
openssl pkcs12 -in D:\MyClientSecurity\myclientkey.p12 -out D:\MyClientSecurity\client_privatekey.pem -nocerts -nodes
#也可以转换为公钥与私钥合二为一的文件; 客户端公钥与私钥,一起存在all.pem中
openssl>pkcs12 -in D:\MyClientSecurity\kagulakey.p12 -out D:\MyClientSecurity\client_all.pem -nodes
1、使用client_publickey.pem + client_privatekey.pem (未在win console shell下测试)
curl -k --cert client_publickey.pem --key D:\MyClientSecurity\client_privatekey.pem https://lijun:8443/escortcashbox/main/aboutUs.do
2、可以在Msys64 Shell中执行curl命令(如果已经安装了curl)...使用all.pem
curl -k --cert /d/MyClientSecurity/client_all.pem https://lijun:8443/escortcashbox/main/aboutUs.do
下面是双向认证的参考资料
SSL——Secure Sockets Layer
http://www.blogjava.net/icewee/archive/2012/06/04/379947.html
补充阅读资料
使用libcurl实现上传文件到FTP服务器
http://blog.youkuaiyun.com/lee353086/article/details/5823145
使用libcurl下载http://www.baidu.com/img/baidu.gif示例
http://www.cppblog.com/qiujian5628/archive/2008/06/28/54873.html
libcurl的使用总结(一)
http://www.vimer.cn/2010/03/libcurl%E7%9A%84%E4%BD%BF%E7%94%A8%E6%80%BB%E7%BB%93%EF%BC%88%E4%B8%80%EF%BC%89.html
PHP的curl实现get,post 和 cookie
http://www.tsingpost.com/articles/201403/525.html
文章来源:
http://blog.youkuaiyun.com/lee353086/article/details/40349761