第一部分 原理及環境搭建
參考
httplib庫原理
httplib搭建簡單伺服器與浏覽器互動
httplib GitHub
1.0 certmgr證書管理工具
該項目本部分需要實作本地用戶端與遠端伺服器進行通信(例如國外網站),那麼如果直接進行Socket連接配接将會非常慢或者撞牆,那麼這個時候考慮使用本地用戶端先與本地代理伺服器進行通信,然後本地用戶端每次通路一次URL位址,都需要将證書添加到受信任的根證書頒發機構,不然證書以及私鑰對不上伺服器将不允許通路。檢視證書可以通過cmd指令行certmgr.msc通路證書管理器工具。
那麼怎麼将我們的CA憑證添加到受信任的根證書頒發機構是本項目的第一個難點。我查閱了MSDN關于certmgr的相關指令
Certmgr.exe(證書管理器工具)
使用以下指令可以将想要添加的證書添加到root根目錄。
certmgr /c /add TrustedCert.cer /s root
1.1 設定本地代理伺服器
設定本地代理伺服器就是需要将要通路的IP位址或者域名添加到本地環回位址127.0.0.1,這樣每次通路遠端伺服器時,用戶端先與本地代理伺服器進行通信,并且通過CA憑證與Server證書和Server私鑰進行比對,比對成功則讓用戶端以為本地代理伺服器即是需要通路的位址,現在需要的就是在浏覽器中通路 https://www.xxx.com,讓浏覽器認為本地代理伺服器就是 www.xxx.com,并且顯示本地代理服務其傳回的一句話(随便寫,比如 hello xxx!)
添加IP位址或域名到環回位址的方式可以通過修改driver中的hosts檔案得到,Win10使用者的hosts檔案路徑如下:
C:\Windows\System32\drivers\etc\hosts
打開hosts檔案(無字尾),顯示如下:
# BitDefender has cleaned hosts file
127.0.0.1 localhost
#Original code from this file
#<Line Removed>:# Copyright (c) 1993-2009 Microsoft Corp.
#<Line Removed>:#
#<Line Removed>:# This is a sample HOSTS file used by Microsoft TCP/IP for Windows.
#<Line Removed>:#
#<Line Removed>:# This file contains the mappings of IP addresses to host names. Each
#<Line Removed>:# entry should be kept on an individual line. The IP address should
#<Line Removed>:# be placed in the first column followed by the corresponding host name.
#<Line Removed>:# The IP address and the host name should be separated by at least one
#<Line Removed>:# space.
#<Line Removed>:#
#<Line Removed>:# Additionally, comments (such as these) may be inserted on individual
#<Line Removed>:# lines or following the machine name denoted by a '#' symbol.
#<Line Removed>:#
#<Line Removed>:# For example:
#<Line Removed>:#
#<Line Removed>:# 102.54.94.97 rhino.acme.com # source server
#<Line Removed>:# 38.25.63.10 x.acme.com # x client host
#<Line Removed>:# localhost name resolution is handled within DNS itself.
#<Line Removed>:# 127.0.0.1 localhost
#<Line Removed>:# ::1 localhost
#<Line Removed>:127.0.0.1 ieonline.microsoft.com
127.0.0.1 ieonline.microsoft.com
在檔案尾部添加一行代碼,比如添加百度為環回位址:
這裡需要注意!!!!如果後面需要正常通路百度則需要删除這一行,不然浏覽器通路www.baidu.com會一直認為這是一個環回位址!
127.0.0.1 www.baidu.com
到這裡代理伺服器環境就搭建好了。
1.2 httplib實作https架構搭建
cpp-httplib是一個c++封裝的http庫,使用這個庫可以在windows平台下完成http用戶端、http服務端的搭建。
本次我們主要用到https用戶端、https伺服器端進行網絡通信,值得一提的是httplib提供http、http2、https協定。
在用到https協定的時候,需要使用openSSL簽發證書和私鑰,另外https客服端的類名是SSLClient,它繼承自Client類,https伺服器端類名是SSLServer,它繼承自Server類。
OpenSSL下載下傳位址
OpenSSL生成CA憑證、伺服器證書及私鑰
生成如下的.pem .crt證書檔案
cert.pem轉cert.crt檔案的OpenSSL環境指令為:
openssl x509 -outform der -in cert.pem -out cert.crt
最終需要的證書如下:
1.3 httplib環境搭建及原理
因為本項目是在windowsPC端進行,是以開發IDE為VS2017
首先httplib庫在windows 的頭檔案include格式為
1、通過宏開關CPPHTTPLIB_OPENSSL_SUPPORT控制是否使用Https
如果項目隻使用https的話可以直接在頭檔案中輸入一行代碼,表示我們要使用的是https格式。
#define CPPHTTPLIB_OPENSSL_SUPPORT
2、然後要使用openSSL證書簽發工具的,加入openssl包含路徑:C:\OpenSSL-Win64\include,加入openssl的導入庫路徑C:\OpenSSL-Win64\lib,并連結libcrypto.lib、libssl.lib、openssl.lib
到此VShttplib環境就搭建好了。
第二部分 代碼
2.0SSLServer和SSLCilent
需要支援OpenSSL的話 ,伺服器和用戶端的初始化格式如下:此過程需要CA憑證、伺服器證書、伺服器私鑰
2.1 代理伺服器端
服務端首先Get(),先注冊對應關系,先告訴自己的伺服器,當我們遇到什麼請求方法,請求什麼資源,在回調什麼函數。将浙西全部都記錄在map中。當listen監聽的時候,才建立起服務端。
若服務端收到了http請求,解析之後。若請求中的path,對應了Get接口傳入的path(也就是能在map中找到對應關系),則服務端會建立一個線程回調這個傳入的函數helloworld()對這次的請求進行業務處理
伺服器端監聽的是www.ludashi.com,端口為8080
#include <iostream>
#include <httplib.h>
#include <Windows.h>
#include <iostream>
#include <shellapi.h>
#define SERVER_CERT_FILE "C:\\cert.pem"
#define SERVER_PRIVATE_KEY_FILE "C:\\key.pem"
//#define CPPHTTPLIB_OPENSSL_SUPPORT
using namespace std;
using namespace httplib;
#pragma comment(lib, "openssl.lib")
#pragma comment(lib, "libcrypto.lib")
#pragma comment(lib, "libssl.lib")
std::string dump_headers(const Headers &headers) {
std::string s;
char buf[BUFSIZ];
for (auto it = headers.begin(); it != headers.end(); ++it) {
const auto &x = *it;
snprintf(buf, sizeof(buf), "%s: %s\n", x.first.c_str(), x.second.c_str());
s += buf;
}
return s;
}
std::string log(const Request &req, const Response &res) {
std::string s;
char buf[BUFSIZ];
s += "================================\n";
snprintf(buf, sizeof(buf), "%s %s %s", req.method.c_str(),
req.version.c_str(), req.path.c_str());
s += buf;
std::string query;
for (auto it = req.params.begin(); it != req.params.end(); ++it) {
const auto &x = *it;
snprintf(buf, sizeof(buf), "%c%s=%s",
(it == req.params.begin()) ? '?' : '&', x.first.c_str(),
x.second.c_str());
query += buf;
}
snprintf(buf, sizeof(buf), "%s\n", query.c_str());
s += buf;
s += dump_headers(req.headers);
s += "--------------------------------\n";
snprintf(buf, sizeof(buf), "%d %s\n", res.status, res.version.c_str());
s += buf;
s += dump_headers(res.headers);
s += "\n";
if (!res.body.empty()) { s += res.body; }
s += "\n";
return s;
}
int main(void) {
SSLServer svr(SERVER_CERT_FILE, SERVER_PRIVATE_KEY_FILE);
cout << "Waiting for the connection..."<<endl;
if (!svr.is_valid()) {
printf("server has an error...\n");
return -1;
}
svr.Get("/", [=](const Request & /*req*/, Response &res) {
res.set_redirect("/hi");
});
svr.Get("/hi", [](const Request & /*req*/, Response &res) {
res.set_content("<html><h1>Hello ludashi!</h1></html>", "text/html");
});
svr.Get("/slow", [](const Request & /*req*/, Response &res) {
std::this_thread::sleep_for(std::chrono::seconds(2));
res.set_content("Slow...\n", "text/plain");
});
svr.Get("/dump", [](const Request &req, Response &res) {
res.set_content(dump_headers(req.headers), "text/plain");
});
svr.Get("/stop", [&](const Request & /*req*/, Response & /*res*/)
{ svr.stop(); });
svr.set_error_handler([](const Request & /*req*/, Response &res) {
const char *fmt = "<p>Error Status: <span style='color:red;'>%d</span></p>";
char buf[BUFSIZ];
snprintf(buf, sizeof(buf), fmt, res.status);
res.set_content(buf, "text/html");
});
svr.set_logger([](const Request &req, const Response &res) {
printf("%s", log(req, res).c_str());
});
svr.listen("www.baidu.com", 8080);
system("pause");
return 0;
}
2.2 用戶端
首先向受信任根目錄中添加證書,然後向伺服器Get請求
system("C:\\certmgr.exe /add /c C:\\cert.crt /s root");
auto res = cli.Get("/hi");
#include<httplib.h>
#include<windows.h>
#include<iostream>
#include<shellapi.h>
#define CA_CERT_FILE "./cert.crt"
#pragma comment(lib, "openssl.lib")
#pragma comment(lib, "libcrypto.lib")
#pragma comment(lib, "libssl.lib")
using namespace std;
using namespace httplib;
int main(void) {
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
system("C:\\certmgr.exe /add /c C:\\cert.crt /s root");
cout << "Try to connect....." << endl;
Sleep(5000);
httplib::SSLClient cli("www.ludashi.com", 8080);
cli.set_ca_cert_path(CA_CERT_FILE);
cli.enable_server_certificate_verification(true);
#else
httplib::Client cli("www.ludashi.com", 8080);
#endif
auto res = cli.Get("/hi");
if (res) {
cout << res->status << endl;
cout << res->get_header_value("Content-Type") << endl;
cout << res->body << endl;
}
else {
cout << "error" << endl;
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
auto result = cli.get_openssl_verify_result();
if (result) {
cout << "verify error: " << X509_verify_cert_error_string(result) << endl;
}
#endif
}
return 0;
}
第三部分 結果
首先回詢問是否要安裝證書,這裡點選是就将證書直接添加到了受信任的證書根目錄下,這一步非常重要,因為不添加證書的話,無法進行https通路。
然後,為了防止證書添加的同時可能出現的問題,程式會先等待5秒鐘證書添加完畢(實際不需要這麼久,這裡隻是為了測試友善)
最終通信成功!