C/C++: OpenSSL实现https GET POST请求
生活随笔
收集整理的這篇文章主要介紹了
C/C++: OpenSSL实现https GET POST请求
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
前言
筆者這里有需求,需要用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 value0:成功 strResponse為響應結果 -1:失敗,strResponse為錯誤信息*/int getRequest(const std::string& strSvrIp, int iSvrPort, const std::string& strUri, std::string& strResponse);/* https post請求return value0:成功 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 value0:成功 -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") #endifMyHttpsUtil::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 WIN32closesocket(socketFd); #elseclose(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) {// 讀取響應體數據,一次讀1kchar 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; }
總結
以上是生活随笔為你收集整理的C/C++: OpenSSL实现https GET POST请求的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: linux备份还原系统程序
- 下一篇: Linux系统编程:习题,父子进程通过信