问题背景描述:
最近有个项目,需要获取天气预报的信息,查了好多天气预报API都是需要付费的,免费的大多数只有一个温度,其他的啥信息都没有(免费的都没好货)。找了好久,对比了天气信息以及费用的,最后发现和风天气预报API不错,不单只免费,而且需要的空气质量以及紫外线强度都有,最后决定使用它(每天普通用户免费3000次,开发者16000次,最后使用公司的电话号码,申请了好几个Key,以达到每天访问10万次,ps:通过切换key)。
其实开发这个也没什么技术含量的,而且也挺顺利,直接通过MFC框架的CInternetSession类,调用OpenURL函数获取返回的天气信息就结束了。
CInternetSession InternetSession;
CStdioFile *pFile;
char sText[1024]={0},sBuff[1024]={0};
try
{
if (NULL != (pFile = InternetSession.OpenURL(strServer)))
{
CString strLine, strText;
while(pFile->ReadString(sText,1024))
{
Utf8ToAnsi(sText,sBuff);
strLine.Format("%s",sBuff);
strText += strLine + "\n";
memset(sText,'\0',1024);
memset(sBuff,'\0',1024);
}
pFile->Close();
InternetSession.Close();
delete pFile;
return strText;
}
}
catch (CInternetException* pEx)
{
TCHAR sz[1024] = {0};
pEx->GetErrorMessage(sz, 1024);
LXY_PRINTF(LEVEL_WARN,"[ 获取天气预报信息异常:错误码:%d ]::%s\n",pEx->m_dwError,sz);
pEx->Delete();
return sz;
}
问题1:
突然有天,测试部反馈问题,无法获取天气信息,而且返回错误。但是我在自己开发的电脑上测试了,发现是没问题的,当时就懵逼了,而且不是一台电脑,而是陆陆续续有好几个客户也反馈了,这就让我不得不重视了。
最后,我在开发的环境中重现了该问题,嗯,包括重启电脑,重启软件等等。终于获取到错误的信息。
错误码12057 :Unable to validate the revocation of the SSL certificate because the revocation server is unavailable
说什么ssl,安全性的问题(至此我还是没有想到是什么问题,因为自己不熟悉web这块,所以对证书那些有点懵),只能通过百度了,在百度上查了一下,发现遇到的这个问题都是关于IE浏览器的。因为MFC框架的CInternetSession类,应该是调用IE内核的,所以问题出现在了IE浏览器上。
对于上面的错误,百度网友给了以下两个解决办法:
A、就是手动去把IE浏览器中,“高级”里面的“检查服务器证书是否已吊销”√去掉,不在检查服务器证书;
B、在代码中实现A的效果;
看了B后,果断选择了B(A解决办法会被客户烦死):
CHttpConnection* pHttpConnect = session.GetHttpConnection(strServer, INTERNET_FLAG_SECURE, nPort, NULL, NULL);
if(pHttpConnect)
{
CHttpFile* pHttpFile = (CHttpFile*)pHttpConnect->OpenRequest(CHttpConnection::HTTP_VERB_POST, strObject, NULL, 1,
NULL, NULL,
INTERNET_FLAG_RELOAD | INTERNET_FLAG_NO_CACHE_WRITE | INTERNET_FLAG_KEEP_CONNECTION|
INTERNET_FLAG_SECURE | INTERNET_FLAG_IGNORE_CERT_CN_INVALID | INTERNET_FLAG_IGNORE_CERT_DATE_INVALID
//SECURITY_FLAG_IGNORE_REVOCATION
);
//get web server option
pHttpFile->QueryOption(INTERNET_OPTION_SECURITY_FLAGS, dwFlags);
dwFlags |= SECURITY_FLAG_IGNORE_UNKNOWN_CA;//忽略错误与未知的证书颁发机构
dwFlags |= SECURITY_FLAG_IGNORE_REVOCATION;//忽略错误与撤销相关证书
//<span style="white-space:pre"> </span>
//set web server option
pHttpFile->SetOption(INTERNET_OPTION_SECURITY_FLAGS, dwFlags);
if(pHttpFile->SendRequest())
{
//get response status if success, return 200
pHttpFile->QueryInfo(HTTP_QUERY_FLAG_NUMBER | HTTP_QUERY_STATUS_CODE, &dwStatus, &dwStatusLen, 0);
while(pHttpFile->ReadString(sText,1024))
{
Utf8ToAnsi(sText,sBuff);
strLine.Format("%s",sBuff);
strHtml += strLine + "\n";
memset(sText,'\0',1024);
memset(sBuff,'\0',1024);
}
}
dwFlags |= SECURITY_FLAG_IGNORE_UNKNOWN_CA;//忽略错误与未知的证书颁发机构
dwFlags |= SECURITY_FLAG_IGNORE_REVOCATION;//忽略错误与撤销相关证书
其中这两个参数是关键。
至此,问题就告一段落了,客户也在没有反馈这个问题。正当我松一口气的时候,以为天气预报这个问题解决了,谁知新的问题也在酝酿中。
问题2:IE8不支持HTTPS
客户:李工,那个天气预报在XP系统下无法获取信息,并且报错“The connection with the server was reset”(这个是浏览器直接返回的错误信息)
我:哦,是吗,那我这边试一下,晚点回复你(心中有一万头草泥马在崩腾......)
(俗话说有问题,找度娘,嗯,我不知道没有网络我是否还能做个程序员,难怪每个月就这么点工资,哎。)
经过一系列的查证,定位,发现问题还是出现在IE浏览器中,IE8不支持HTTPS,因为这个天气预报是HTTPS的。所以xp系统下的IE8获取不了天气预报信息。
最后在勾选了IE浏览器中“Internet选项”,“高级”中的“使用SSL2.0”以及“使用SSL3.0”,但是还是无法访问HTTPS。
在万般无奈中,我唯有借助第三方库了,OpenSSL。
可以参考:http://slproweb.com/products/Win32OpenSSL.html
由于我的项目是32位的,所以我是下载了Win32 OpenSSL v1.1.1d这个版本。
编码很简单,百度上有很多基于socket+OpenSSL原生模拟HTTPS使用post和get的例子,但是当我再一次把打包好的程序放到xp系统下运行时,报错“缺少VCRUNTIME1400.dll”,看了这个错误我知道应该是使用了OpenSSL的dll,xp系统下没有安装VC++2005等那些组件。
第一时间,我想到了可以完全使用静态库,不在使用dll,这样就可以避免了上面缺少dll的报错。但是我发现,当我使用libcrypto_static.lib,libssl_static.lib或者是libcrypto32MTd.lib,libssl32MTd.lib,都报了非常多的连接错误,不匹配VS2010的。
后来,我发现了一个可以基于VS2010编译的OpenSSL,https://www.npcglib.org/~stathis/blog/precompiled-openssl/ 下载了对应的VS2010版本后,这次编译之后连接错误就只剩下7个了。
报错如下:
1>libcrypto.lib(e_capi.obj) : error LNK2001: 无法解析的外部符号 __imp__CertFreeCertificateContext@4
1>libcrypto.lib(e_capi.obj) : error LNK2001: 无法解析的外部符号 __imp__CertGetCertificateContextProperty@16
1>libcrypto.lib(e_capi.obj) : error LNK2001: 无法解析的外部符号 __imp__CertOpenStore@20
1>libcrypto.lib(e_capi.obj) : error LNK2001: 无法解析的外部符号 __imp__CertFindCertificateInStore@24
1>libcrypto.lib(e_capi.obj) : error LNK2001: 无法解析的外部符号 __imp__CertEnumCertificatesInStore@8
1>libcrypto.lib(e_capi.obj) : error LNK2001: 无法解析的外部符号 __imp__CertCloseStore@8
1>libcrypto.lib(e_capi.obj) : error LNK2001: 无法解析的外部符号 __imp__CertDuplicateCertificateContext@4
这个错误是因为openssl库使用了windows的一个密码学库: Crypt32.lib
添加完以上的静态库后就运行成功了,PS:Crypt32.lib 这个静态库不用下载,直接添加到“属性”-->“链接器”-->“输入”-->“附加依赖项”。或者在代码中添加 #pragma comment(lib,"Crypt32.lib")
#include <openssl/ssl.h>
//初始化OpenSSL库
//(虽然不知道为什么,但是不加这三行似乎并不会导致什么问题,在不加这3行的情况下测试了几个网站并没有发现任何问题喵)
SSL_library_init();
SSLeay_add_ssl_algorithms();
SSL_load_error_strings();
//创建SSL会话环境等
pSslCtx = SSL_CTX_new(TLSv1_2_client_method());
if (pSslCtx == NULL)
{
LXY_PRINTF(LEVEL_ERROR,"[ https get 服务:创建ssl ctx异常 ]::Create SSL_CTX_new error.\n");
lxy_tcp_close(socketfd);
return -1;
}
psslSSL = SSL_new(pSslCtx);
if (psslSSL == NULL)
{
LXY_PRINTF(LEVEL_ERROR,"[ https get 服务:创建ssl异常 ]::Create SSL_new error.\n");
lxy_tcp_close(socketfd);
return -1;
}
SSL_set_fd(psslSSL, socketfd);
nErrorConnect = SSL_connect(psslSSL);
if (nErrorConnect < 0)
{
LXY_PRINTF(LEVEL_ERROR,"[ https get 服务:ssl 连接异常 ]::To using SSL_connect error.\n");
lxy_tcp_close(socketfd);
return -1;
}
/********** 开始发送ssl加密包 **********/
memset(data,'\0',BUFSIZE);
sprintf(data,HTTPS_GET,file,s_Ip,n_port);
nErrorWrite = SSL_write(psslSSL,data,strlen(data)) < 0;
if (nErrorWrite < 0)
{
LXY_PRINTF(LEVEL_ERROR,"[ https get 服务:发送ssl加密包异常 ]::To using SSL_write send data error.\n");
lxy_tcp_close(socketfd);
return -1;
}
//收包并输出
//这里接受的是char形式的,所以中文会乱码
//如果要正常显示中文,需要再转换为wchar_t或std::wstring
memset(data,'\0',BUFSIZE);
len = lxy_https_recv_response_head(psslSSL,data,BUFSIZE);
if (len <0 )
{
lxy_tcp_close(socketfd);
return -1;
}
lxy_https_parse_response_head(data,&nStatus,&nTotalDownLoad);
if ( nStatus == 200 )
{
memset(data,'\0',BUFSIZE);
while ( len = SSL_read(psslSSL,data,BUFSIZE - 1) )
{
LXY_PRINTF(LEVEL_WARN,"[ https get 服务:接收数据 ]::SSL_read%s\n",data);
nTotalRead += len;
if ( nTotalRead < BUFSIZE )
{
strcat(outBuf,data);
}
memset(data,'\0',BUFSIZE);
}
}
以上的代码都是在一位网友的博客找到的,我想回来找那个博客的时候发现找不到了,感谢这位网友,如果有侵权的请联系我QQ706414069。
总结:
1、对HTTPS的协议不熟悉,以为和HTTP一样,直接通过socket连接就可以post。
2、编程思维有点头痛医头,脚痛医脚的感觉,不会站在全局上去思考问题。
3、写这个博客是因为记忆力越来越差了,人到中年了嘛,所以还是动动笔记一下比较好,忘记了就回来看看。