前言
笔者这里有需求,需要用C++实现 https 的GET、POST请求 以及GET下载文件 而且需要实现跨平台 在Linux、Windows都能正常运行。最好的是 只用一套代码 而不是根据具不同平台 跑不同代码,所以我们得找一个跨平台的支持https协议的库。都不用想,最好的当然是 OpenSSL啦。也有其他的比如libcurl 啥的,但是libcurl如果要支持https的也要链接OpenSSL了,还不如直接用OpenSSL。当然这里还有个不错的选择就是使用boost::asio库,但是笔者觉得使用上没有OpenSSL好用,故此没有用boost库。
网上示例代码比较多,笔者这里也是简单写了小demo,在Windows平台和Linux平台都能正常运行,只是为在互联网上多一个链接,让需要者多一篇可参考的博客而已。 这里使用的是openssl-1.0.2m 版本,OpenSSL更高的版本没有测试,想来 api改动应该不大。
代码
MyHttpsUtil.h
#pragma once
#include <string>
#include <openssl/ssl.h>
enum REQ_TYPE
{
GET_STRING = 0, // GET请求获取响应字符串
POST_STRING, // POST请求获得响应字符串
GET_FILE // GET请求下载文件
};
class MyHttpsUtil
{
public:
virtual ~MyHttpsUtil(void);
static MyHttpsUtil* getInstance();
/*
https get请求
return value
0:成功 strResponse为响应结果 -1:失败,strResponse为错误信息
*/
int getRequest(const std::string& strSvrIp, int iSvrPort,
const std::string& strUri, std::string& strResponse);
/*
https post请求
return value
0:成功 strResponse为响应结果 -1:失败,strResponse为错误信息
*/
int postRequest(const std::string& strSvrIp, int iSvrPort,
const std::string& strUri, const std::string& strBody, std::string& strResponse);
/*
https get请求下载文件
return value
0:成功 -1:失败,strErrMsg为错误信息
*/
int getFile(const std::string& strSvrIp, int iSvrPort,
const std::string& strUri, const std::string& strBody, const std::string& strFilePath, std::string& strErrMsg );
private:
MyHttpsUtil(void);
private:
// 建立TCP连接套接字
int tcpConn(const char* pSvrIp, int iSvrPort, int& socket);
/*
发送数据到https服务器
参数1:请求类型,决定最后3个参数的作用
参数2:服务器IP
参数3:服务器端口
参数4:uri
参数5:reqType:1 为POST_STRING时(POST请求) 请求参数,也就是请求体。
参数6:reqType:2 为GET_FILE时(GET请求下载资源文件) 文件存储路径。
参数7:reqType:0/1 为GET_STRING/POST_STRING 时(GET/POST请求响应字符串) 响应字符串在strResponse, 出现错误时 错误描述信息在strResponse中。
*/
int sendDataToSvr(REQ_TYPE reqType, const std::string& strSvrIp, int iSvrPort,
const std::string& strUri, const std::string& strBody, const std::string& strFilePath, std::string& strResponse );
// 组装GET请求数据
int getGetReqData(const std::string& strSvrIp, int iSvrPort,
const std::string& strUri, std::string& strReqData);
// 组装POST请求数据
int getPostReqData(const std::string& strSvrIp, int iSvrPort,
const std::string& strUri, const std::string& strBody, std::string& strReqData);
// 读取响应字符串
int readResponseToString(SSL* ssl, std::string& strRespData);
// 读取响应二进制数据到文件
int readResponseToFile(SSL* ssl, const std::string& strFilePath, std::string& strErrMsg);
};
MyHttpsUtil.cpp
#include "MyHttpsUtil.h"
#ifdef WIN32
#include <winsock.h>
#else
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <netdb.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#endif
#include <openssl/err.h>
#include <openssl/rand.h>
#include <openssl/crypto.h>
#define CODE_SUCCESS 0
#define CODE_FALID -1
#ifdef WIN32
#pragma comment(lib, "libeay32.lib")
#pragma comment(lib, "ssleay32.lib")
#endif
MyHttpsUtil::MyHttpsUtil(void)
{
}
MyHttpsUtil::~MyHttpsUtil(void)
{
}
MyHttpsUtil* MyHttpsUtil::getInstance()
{
static MyHttpsUtil httpsClient;
return &httpsClient;
}
int MyHttpsUtil::getRequest(const std::string& strSvrIp, int iSvrPort,
const std::string& strUri, std::string& strResponse)
{
return sendDataToSvr(GET_STRING, strSvrIp, iSvrPort, strUri, "", "", strResponse);
}
int MyHttpsUtil::postRequest(const std::string& strSvrIp, int iSvrPort,
const std::string& strUri, const std::string& strBody, std::string& strResponse)
{
return sendDataToSvr(POST_STRING, strSvrIp, iSvrPort, strUri, strBody, "", strResponse);
}
int MyHttpsUtil::getFile(const std::string& strSvrIp, int iSvrPort,
const std::string& strUri, const std::string& strBody, const std::string& strFilePath, std::string& strErrMsg)
{
return sendDataToSvr(GET_FILE, strSvrIp, iSvrPort, strUri, "", strFilePath, strErrMsg);
}
int MyHttpsUtil::sendDataToSvr(REQ_TYPE reqType, const std::string& strSvrIp, int iSvrPort, const std::string& strUri, const std::string& strBody, const std::string& strFilePath, std::string& strResponse)
{
int iRet = CODE_FALID;
int socketFd = 0;
SSL_CTX* ctx = 0;
SSL* ssl = 0;
do
{
char* pSvrIp = NULL;
struct hostent *pHostent = NULL;
pHostent = gethostbyname(strSvrIp.c_str());
if (pHostent == NULL)
{
break;
}
pSvrIp = inet_ntoa(*(struct in_addr*)pHostent->h_addr_list[0]);
// 1.建立TCP连接
if( tcpConn(pSvrIp, iSvrPort, socketFd) != CODE_SUCCESS)
{
break;
}
// 2.SSL初始化, 关联Socket到SSL,并建立连接
SSL_library_init();
OpenSSL_add_all_algorithms();
SSL_load_error_strings();
ctx = SSL_CTX_new(SSLv23_client_method());
if(ctx == NULL)
{
break;
}
ssl = SSL_new(ctx);
SSL_set_fd(ssl, socketFd);
int retCode = SSL_connect(ssl);
if ( retCode != 1)
{
int sslErrCode = SSL_get_error(ssl,retCode);
strResponse = "SSL_connect error,openssl errCode = ";
char errCode[11] = {0};
sprintf_s(errCode, "%d", sslErrCode);
strResponse.append(errCode);
break;
}
std::string strReqData;
if(GET_FILE == reqType || GET_STRING == reqType)
{
getGetReqData(strSvrIp, iSvrPort, strUri, strReqData);
}
else
{
getPostReqData(strSvrIp, iSvrPort, strUri, strBody, strReqData);
}
// 4.通过SSL发送数据,数据量不大一次write就够了,如果是文件上传就需要循环写了。
int writeLen = SSL_write(ssl, strReqData.c_str(), strReqData.length());
if (writeLen <=0)
{
int sslErrCode = SSL_get_error(ssl,writeLen);
strResponse = "SSL_write error,openssl errCode = ";
char errCode[11] = {0};
sprintf_s(errCode, "%d", sslErrCode);
strResponse.append(errCode);
break;
}
// 5.读取响应数据
int readLen = 0;
char pHeader[1] = {0};
int i = 0;
// 响应头以\r\n\r\n 结束, 此处判断头是否传输完成
while((readLen = SSL_read(ssl, pHeader, 1)) == 1)
{
if(i < 4){
if(pHeader[0] == '\r' || pHeader[0] == '\n')
{
i++;
if(i >= 4)
{
break;
}
}
else
{
i = 0;
}
}
}
if( readLen < 0 )
{
int sslErrCode = SSL_get_error(ssl,readLen);
strResponse = "SSL_read error,openssl errCode = ";
char errCode[11] = {0};
sprintf_s(errCode, "%d", sslErrCode);
strResponse.append(errCode);
break;
}
if(GET_FILE == reqType)
{
iRet = readResponseToFile(ssl, strFilePath, strResponse);
}
else
{
iRet = readResponseToString(ssl, strResponse);
}
} while (false);
// 6.关闭socket、断开连接
if (socket)
{
#ifdef WIN32
closesocket(socketFd);
#else
close(socketFd);
#endif
}
if (ctx)
{
SSL_CTX_free(ctx);
}
if (ssl)
{
SSL_shutdown (ssl);
SSL_free(ssl);
}
return iRet;
}
int MyHttpsUtil::tcpConn(const char* pSvrIp, int iSvrPort, int& socket)
{
socket = ::socket(AF_INET,SOCK_STREAM,0);
if( socket == -1 )
{
return CODE_FALID;
}
sockaddr_in sa;
sa.sin_addr.s_addr = inet_addr(pSvrIp);
sa.sin_port = htons(iSvrPort);
sa.sin_family = AF_INET;
int retCode = ::connect(socket,(struct sockaddr*)&sa,sizeof(sa));
if(retCode == -1)
{
return CODE_FALID;
}
return CODE_SUCCESS;
}
int MyHttpsUtil::getGetReqData(const std::string& strSvrIp, int iSvrPort,
const std::string& strUri, std::string& strReqData)
{
char pLine[256] = {0};
sprintf_s(pLine, "GET %s HTTP/1.1\r\n", strUri.c_str());
strReqData.append(pLine);
memset(pLine, 0, sizeof(pLine));
sprintf_s(pLine, "Host: %s:%d\r\n",strSvrIp.c_str(), iSvrPort);
strReqData.append(pLine);
memset(pLine, 0, sizeof(pLine));
strReqData.append("Accept: */*\r\n");
strReqData.append("Connection: close\r\n\r\n");
//strReqData.append("Connection: keep-alive\r\n\r\n");
return CODE_SUCCESS;
}
int MyHttpsUtil::getPostReqData(const std::string& strSvrIp, int iSvrPort,
const std::string& strUri, const std::string& strBody, std::string& strReqData)
{
char pLine[256] = {0};
sprintf_s(pLine, "POST %s HTTP/1.1\r\n", strUri.c_str());
strReqData.append(pLine);
memset(pLine, 0, sizeof(pLine));
sprintf_s(pLine, "Host: %s:%d\r\n",strSvrIp.c_str(), iSvrPort);
strReqData.append(pLine);
memset(pLine, 0, sizeof(pLine));
strReqData.append("Accept: */*\r\n");
strReqData.append("Content-Type: application/json; charset=utf-8\r\n");
memset(pLine, 0, sizeof(pLine));
sprintf_s(pLine, "Content-Length: %d\r\n", strBody.length());
strReqData.append("Connection: close\r\n\r\n");
strReqData.append(strBody);
return CODE_SUCCESS;
}
int MyHttpsUtil::readResponseToString(SSL* ssl, std::string& strRespData)
{
// 读取响应体数据,一次读1k
char pBody[1024 + 1] = {0};
int readSize = sizeof(pBody) -1;
int readLen = 0;
while( (readLen = SSL_read(ssl, pBody, readSize)) > 0 )
{
strRespData.append(pBody);
memset(pBody, 0, sizeof(pBody));
}
if(readLen < 0)
{
int sslErrCode = SSL_get_error(ssl,readLen);
strRespData = "SSL_read error,openssl errCode = ";
char errCode[11] = {0};
sprintf_s(errCode, "%d", sslErrCode);
strRespData.append(errCode);
return CODE_FALID;
}
strRespData.append(pBody);
return CODE_SUCCESS;
}
int MyHttpsUtil::readResponseToFile(SSL* ssl, const std::string& strFilePath, std::string& strErrMsg)
{
FILE *fp = fopen(strFilePath.c_str(), "wb+");
if(fp == NULL)
{
strErrMsg = "fopen error,filePath:";
strErrMsg.append(strFilePath);
return CODE_FALID;
}
char pBody[1024 + 1] = {0};
int readSize = sizeof(pBody) -1;
int readLen = 0;
while( (readLen = SSL_read(ssl, pBody, readSize)) > 0 )
{
if( fwrite(pBody, 1, readLen, fp) != readLen)
{
strErrMsg = "fwrite error";
return CODE_FALID;
}
memset(pBody, 0, sizeof(pBody));
}
if(readLen < 0)
{
int sslErrCode = SSL_get_error(ssl,readLen);
strErrMsg = "SSL_read error,openssl errCode = ";
char errCode[11] = {0};
sprintf_s(errCode, "%d", sslErrCode);
strErrMsg.append(errCode);
return CODE_FALID;
}
if( fwrite(pBody, 1, readLen, fp) != readLen)
{
strErrMsg = "fwrite error";
return CODE_FALID;
}
fclose(fp);
return CODE_SUCCESS;
}
测试
这里我们以 https://www.baidu.com 进行测试 GET、POST、GET下载文件,接口均正常。
test.cpp
#include "MyHttpsUtil.h"
#include <iostream>
int main(int argc, char* argv[])
{
std::string strResponse;
if( MyHttpsUtil::getInstance()->getRequest("www.baidu.com", 443, "/", strResponse) == 0)
{
//std::cout << strResponse << std::endl; // 太长了,下个输出无法输出
std::cout << "get req suc" << std::endl;
}
else
{
std::cout << "get req error" << std::endl;
}
std::cout << "=======================" << std::endl;
strResponse.clear();
if( MyHttpsUtil::getInstance()->postRequest("www.baidu.com", 443, "/", "{}", strResponse) == 0)
{
std::cout << "post req suc" << std::endl;
std::cout << strResponse << std::endl;
}
else
{
std::cout << "post req error" << std::endl;
}
std::cout << "=======================" << std::endl;
std::string strErrMsg;
if( MyHttpsUtil::getInstance()->getFile("www.baidu.com", 443, "/", strResponse,"c:\\1.txt", strErrMsg) == 0 )
{
std::cout << "get req downlaod file suc" << std::endl;
}
else
{
std::cout << "get req downlaod file error:" << strErrMsg << std::endl;
}
std::cout << "=======================" << std::endl;
getchar();
return 0;
}