C++ 开发 Web 服务框架 - HTTPS 的原理及其 Web 框架的设计与实(三)

C++ 开发 Web 服务框架 - HTTPS 的原理及其 Web 框架的设计与实现

一、概述

项目介绍

服务器开发中 Web 服务是一个基本的代码单元,将服务端的请求和响应部分的逻辑抽象出来形成框架,能够做到最高级别的框架级代码复用。本次项目将综合使用 C++11 及 Boost 中的 Asio 实现 HTTP 和 HTTPS 的服务器框架。

项目涉及的知识点

  • C++基本知识
    • 面向对象
    • 模板
    • 命名空间
    • 常用 IO 库
  • C++11 相关
    • lambda expression
    • std::shared_ptr
    • std::make_shared
    • std::unordered_map
    • std::regex
    • std::smatch
    • std::regex_match
    • std::function
    • std::thread
  • Boost Asio 相关
    • boost::asio::io_service
    • boost::asio::ip::tcp::socket
    • boost::asio::ip::tcp::v4()
    • boost::asio::ip::tcp::endpoint
    • boost::asio::ip::tcp::acceptor
    • boost::asio::streambuf
    • boost::asio::async_read
    • boost::asio::async_read_until
    • boost::asio::async_write
    • boost::asio::transfer_exactly
    • boost::asio::ssl::stream
    • boost::asio::ssl::stream_base::server
    • boost::asio::ssl::context
    • boost::asio::ssl::context::sslv23
    • boost::asio::ssl::context::pem
    • boost::system::error_code

编译环境提示

本次实验中的代码使用了 C++11 标准库中的正则表达式库,在 g++ 4.9 之前, regex 库并不支持 ECMAScript 的正则语法,因此需要将 g++ 版本升级至 4.9 以上。

 
  1. // 下面的这段代码可以测试你的编译器对正则表达式的支持情况

  2. #include <iostream>

  3. #include <regex>

  4.  
  5. int main()

  6. {

  7. std::regex r1("S");

  8. printf("S works.\n");

  9. std::regex r2(".");

  10. printf(". works.\n");

  11. std::regex r3(".+");

  12. printf(".+ works.\n");

  13. std::regex r4("[0-9]");

  14. printf("[0-9] works.\n");

  15. return 0;

  16. }

如果你的运行结果遇到了下图所示的错误,说明你确实需要升级你的 g++ 了:

此处输入图片的描述

使用 g++ -v 可以查看到当前编译器版本:

此处输入图片的描述

如果你最后一行中的 gcc version 显示的是 4.8.x,那么你需要手动将编译器版本升级至 4.9 以上,方法如下:

 
  1. # 安装 add-apt-repository 工具

  2. sudo apt-get install software-properties-common

  3. # 增加源

  4. sudo add-apt-repository ppa:ubuntu-toolchain-r/test

  5. # 更新源

  6. sudo apt-get update

  7. # 更新安装

  8. sudo apt-get upgrade

  9. # 安装 gcc/g++ 4.9

  10. sudo apt-get install gcc-4.9 g++-4.9

  11. # 更新链接

  12. sudo updatedb

  13. sudo ldconfig

  14. sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-4.8 48 \

  15. --slave /usr/bin/g++ g++ /usr/bin/g++-4.8 \

  16. --slave /usr/bin/gcc-ar gcc-ar /usr/bin/gcc-ar-4.8 \

  17. --slave /usr/bin/gcc-nm gcc-nm /usr/bin/gcc-nm-4.8 \

  18. --slave /usr/bin/gcc-ranlib gcc-ranlib /usr/bin/gcc-ranlib-4.8

  19. sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-4.9 49 \

  20. --slave /usr/bin/g++ g++ /usr/bin/g++-4.9 \

  21. --slave /usr/bin/gcc-ar gcc-ar /usr/bin/gcc-ar-4.9 \

  22. --slave /usr/bin/gcc-nm gcc-nm /usr/bin/gcc-nm-4.9 \

  23. --slave /usr/bin/gcc-ranlib gcc-ranlib /usr/bin/gcc-ranlib-4.9

二、实现 HTTPS 框架

在上一节实验中,我们已经实现了 HTTP 的框架,利用这个框架,我们便能更加方便的进行框架级的复用。Boost Asio 包含了一个类及类模板用于对基本的 SSL 进行支持,这个类使我们实现 HTTPS 服务器成为可能。从实现上来看,我们只需要对已经存在的流再进行一层加密封装,比如加密 TCP Socket。这个过程异常的简单,我们只需稍加利用即可实现整个HTTPS 的框架,如下:

 
  1. //

  2. // server_https.hpp

  3. // web_server

  4. // created by changkun at shiyanlou.com

  5. //

  6.  
  7. #ifndef SERVER_HTTPS_HPP

  8. #define SERVER_HTTPS_HPP

  9.  
  10. #include "server_http.hpp"

  11. #include <boost/asio/ssl.hpp>

  12.  
  13. namespace ShiyanlouWeb {

  14.  
  15. // 定义 HTTPS 类型

  16. typedef boost::asio::ssl::stream<boost::asio::ip::tcp::socket> HTTPS;

  17.  
  18. // 定义 HTTPS 服务, 模板类型为 HTTPS

  19. template<>

  20. class Server<HTTPS> : public ServerBase<HTTPS> {

  21. public:

  22. // 一个 HTTPS 的服务器比 HTTP 服务器多增加了两个参数,一个是证书文件,另一个是私钥文件

  23. Server(unsigned short port, size_t num_threads,

  24. const std::string& cert_file, const std::string& private_key_file) :

  25. ServerBase<HTTPS>::ServerBase(port, num_threads),

  26. context(boost::asio::ssl::context::sslv23) {

  27. // 使用证书文件

  28. context.use_certificate_chain_file(cert_file);

  29. // 使私钥文件, 相比之下需要多传入一个参数来指明文件的格式

  30. context.use_private_key_file(private_key_file, boost::asio::ssl::context::pem);

  31. }

  32.  
  33. private:

  34. // 和 HTTP 服务器相比,需要多定义一个 ssl context 对象

  35. boost::asio::ssl::context context;

  36.  
  37. // HTTPS 服务器和 HTTP 服务器相比

  38. // 其区别在于对 socket 对象的构造方式有所不同

  39. // HTTPS 会在 socket 这一步中对 IO 流进行加密

  40. // 因此实现的 accept() 方法需要对 socket 用 ssl context 初始化

  41. void accept() {

  42. // 为当前连接创建一个新的 socket

  43. // Shared_ptr 用于传递临时对象给匿名函数

  44. // socket 类型会被推导为: std::shared_ptr<HTTPS>

  45. auto socket = std::make_shared<HTTPS>(m_io_service, context);

  46.  
  47. acceptor.async_accept(

  48. (*socket).lowest_layer(),

  49. [this, socket](const boost::system::error_code& ec) {

  50. // 立即启动并接受一个新连接

  51. accept();

  52.  
  53. // 处理错误

  54. if(!ec) {

  55. (*socket).async_handshake(boost::asio::ssl::stream_base::server,

  56. [this, socket](const boost::system::error_code& ec) {

  57. if(!ec) process_request_and_respond(socket);

  58. });

  59. }

  60. });

  61. }

  62. };

  63. }

  64.  
  65. #endif /* SERVER_HTTPS_HPP */

在上面整个过程中,我们仅仅只是重新实现了 accept() 方法,将启用一个 HTTPS 服务器需要的两个文件传递给了 Boost Asio,就完成了整个服务器框架。

三、开发 HTTPS 服务器

我们已经在上一节实验中写过了 handler.hpp,这个文件中实现了服务器资源的访问和处理逻辑。而这部分逻辑,本质上是独立于服务器类型而存在的,因此我们根本不需要在进行任何开发,只需将 main_http.cpp 中的服务器类型修改就能获得一个完整的 HTTPS 服务器:

 
  1. //

  2. // main_https.cpp

  3. // web_server

  4. // created by changkun at shiyanlou.com

  5. //

  6.  
  7. #include "server_https.hpp"

  8. #include "handler.hpp"

  9. using namespace ShiyanlouWeb;

  10.  
  11. int main() {

  12. //HTTPS 服务运行在 12345 端口,并启用四个线程

  13. Server<HTTPS> server(12345, 4, "server.crt", "server.key");

  14. start_server<Server<HTTPS>>(server);

  15. return 0;

  16. }

在这个服务器上,我们额外传入了 HTTPS 服务器需要的证书和秘钥文件。

为了编译我们的 HTTPS 服务器,在 Makefile 中增加对 HTTPS 服务器的编译选项:

 
  1. #

  2. # Makefile

  3. # web_server

  4. #

  5. # created by changkun at shiyanlou.com

  6. #

  7.  
  8. CXX = g++

  9. EXEC_HTTP = server_http

  10. EXEC_HTTPS = server_https

  11.  
  12. SOURCE_HTTP = main_http.cpp

  13. SOURCE_HTTPS = main_https.cpp

  14.  
  15. OBJECTS_HTTP = main_http.o

  16. OBJECTS_HTTPS = main_https.o

  17.  
  18. LDFLAGS_COMMON = -std=c++11 -O3 -pthread -lboost_system

  19. LDFLAGS_HTTP =

  20. LDFLAGS_HTTPS = -lssl -lcrypto

  21.  
  22. LPATH_COMMON = -I/usr/include/boost

  23. LPATH_HTTP =

  24. LPATH_HTTPS = -I/usr/include/openssl

  25.  
  26. LLIB_COMMON = -L/usr/lib

  27.  
  28. all:

  29. make http

  30. make https

  31.  
  32. http:

  33. $(CXX) $(SOURCE_HTTP) $(LDFLAGS_COMMON) $(LDFLAGS_HTTP) $(LPATH_COMMON) $(LPATH_HTTP) $(LLIB_COMMON) $(LLIB_HTTP) -o $(EXEC_HTTP)

  34. https:

  35. $(CXX) $(SOURCE_HTTPS) $(LDFLAGS_COMMON) $(LDFLAGS_HTTPS) $(LPATH_COMMON) $(LPATH_HTTPS) $(LLIB_COMMON) $(LLIB_HTTPS) -o $(EXEC_HTTPS)

  36.  
  37. clean:

  38. rm -f $(EXEC_HTTP) $(EXEC_HTTPS) *.o

这时,我们的整个目录树为:

 
  1. src

  2. ├── Makefile

  3. ├── handler.hpp

  4. ├── main_http.cpp

  5. ├── main_https.cpp

  6. ├── server_base.hpp

  7. ├── server_http.hpp

  8. ├── server_https.hpp

  9. └── www

  10. ├── index.html

  11. └── test.html

现在,我们可以:

  1. 使用 make 一次性编译 http 和 https 服务器;
  2. 使用 make http 单独编译 http 服务器;
  3. 使用 make https 单独编译 https 服务器.

此处输入图片的描述

完成了编译后还不够,我们还需要对创建 HTTPS 服务器所需的证书。

四、创建证书文件

第一步:生成私钥

openssl 工具包提供了一个生成 RSA 私钥和 CSR(Certificate Signing Request) 文件的工具。这使得我们可以将其用于生成自签名的证书,从而用于供给 HTTPS 服务器使用。

首先,就是要生成 RSA 私钥。我们生成一个 1024 位的 RSA 秘钥,并使用三重 DES 加密方式,并按照 PEM 格式存储(在库中我们指定了私钥的格式是boost::asio::ssl::context::pem):

openssl genrsa -des3 -out server.key 1024

如图所示,在产生 server.key 时,我们还被要求设置密码,这个密码保护了当别人尝试访问这个私钥时,需要提供密码(作为演示,不妨设置成 123456): 此处输入图片的描述

完成后,可以看到产生了 server.key 这个文件。

第二步:生成 CSR

私钥生成后,就可以据此生成一个 CSR 文件了:

openssl req -new -key server.key -out server.csr

在生成 CSR 文件的过程中,会被要求输入刚才我们设置的保护密码,同时还需要输入一些相关的信息,例如这个证书会被用在哪个域名下。最后会要求设置一个 challenge passwrod,通常不去设置这个密码。如图:

此处输入图片的描述

第三步:从秘钥中移除密码

如果证书有密码,那么每次使用证书时都讲需要输入一次密码,这不是很方便,况且,秘钥证书位于我们服务器上,不太容易被泄露,因此我们可以将秘钥中的密码移除,首先我们先保存一份秘钥的备份:

 
  1. cp server.key server.key.old

  2. openssl rsa -in server.key.old -out server.key

此处输入图片的描述

第四步:生成自签名证书

最后,生成一个自签名的证书,并设置证书的过期时间为一年:

openssl x509 -req -days 365 -in server.csr -signkey server.key -out server.crt

此处输入图片的描述

至此,我们便完成了所有的步骤,现在我们可以运行服务器:

./server_https

然后在浏览器中访问现在这个运行在 12345 端口的 https 服务器了,输入:https://localhost:12345

这时,我们会看到浏览器正在告诉我们这个链接不安全。

此处输入图片的描述

这是由于我们的证书是自签名的产生的原因。一般情况下,自签名的 SSL 证书可以随意的签发,没有第三方监督的审核,并不能收到浏览器的信任。这就非常容易造成伪造证书的中间人攻击,从而导致劫持 SSL 加密流量。

我们刚才在创建证书的时候,指定了这个证书会被用于 shiyanlou.com 这个域名,而实际上我们在访问时,访问的 URL 是 localhost,这时浏览器识别到这个不同,也就阻止了这次连接。

为了测试,我们可以将本次连接添加新人列表中,增加一个安全例外:

此处输入图片的描述

这样我们就能看到使用 HTTPS 访问到的资源内容了:

此处输入图片的描述

可惜的是,我们依然不能做到像『正经』厂商一样,让那一把小锁变成绿色:

此处输入图片的描述

原因就如同之前我们所提到的那样,SSL 证书受到第三方监管,浏览器信任的证书一般来自国外的几个指明 SSL 证书签发商,而这种证书的签发往往需要向签发商支付一定的费用,虽然也有诸如 StartSSL 这样的提供免费 SSL 证书的签发商,但由于我们没有域名进行测试,这里就不再赘述了。

总结

经过本次项目,我们走过了很多艰难的历程。首先,我们基于 C++11 和 Boost Asio 的诸多特性,开发了一个 HTTP 服务器的 Web 框架,为了测试我们的框架,我们编写了自己的 HTTP 服务器。

我们的设计非常巧妙,在完成 HTTP 服务器 Web 框架和相关测试代码后,进一步扩展为 HTTPS 时,只使用了极少的代码量便完成了整个框架的开发。

我们的开发的框架一共包含三个文件:

  1. server_base.hpp
  2. server_http.hpp
  3. server_https.hpp

而我们基于这个框架,开发了简易的 http 和 https 的 web 服务器,但我们依然复用了服务器实际逻辑的代码,写在了:

  1. handler.hpp

之中。此外,我们基于这套框架实现的 http 和 https 服务器在本质上,只有一行代码的不同:

 
  1. // http server:

  2. Server<HTTP> server(12345, 4);

  3. // https server:

  4. Server<HTTPS> server(12345, 4, "server.crt", "server.key");

作为参考,这里附上本次项目中全部的代码,你可以使用 wget 来获取:

http://labfile.oss.aliyuncs.com/courses/568/web_server.zip

值得一提的是,里面没有包含 SSL 证书,你需要自己手动创建它们。

参考资料

 

 

出自:https://www.shiyanlou.com/courses/568/labs/1985/document

基于C ++ 14/17的HTTP应用程序框架drogon,Drogon可用于使用C ++轻松构建各种类型的Web应用程序服务器程序。 Drogon是一个跨平台框架,它支持Linux,macOS,FreeBSD和Windows。其主要特点如下: *使用基于epoll的非阻塞I / O网络库(macOS / FreeBSD下的kqueue)提供高并发,高性能的网络IO,请访问[TFB测试结果](https://www.techempower。 com / benchmarks /#section = data-r19&hw = ph&test = composite)以获取更多详细信息; *提供完全异步的编程模式; *支持Http1.0 / 1.1(服务器端和客户端); *基于模板,实现了一种简单的反射机制,以完全解耦主程序框架,控制器和视图。 *支持cookie和内置会话; *支持后端渲染,控制器将数据生成到视图以生成Html页面。视图由CSP模板文件描述,C ++代码通过CSP标记嵌入到HTML页面中。 drogon命令行工具会自动生成C ++代码文件进行编译; *支持视图页面动态加载(运行时动态编译和加载); *提供从路径到控制器处理程序的便捷灵活的路由解决方案; *支持过滤器链,以方便在处理HTTP请求之前执行统一的逻辑(例如登录验证,Http方法约束验证等); *支持https(基于OpenSSL); *支持WebSocket(服务器端和客户端); *支持JSON格式的请求和响应,对Restful API应用程序开发非常友好; *支持文件下载和上传; *支持gzip,brotli压缩传输; *支持流水线; *提供轻量级的命令行工具drogon_ctl,以简化Drogon中各种类的创建以及视图代码的生成; *支持基于非阻塞I / O的异步读写数据库(PostgreSQL和MySQL(MariaDB)数据库); *支持基于线程池的异步读写sqlite3数据库; *支持ARM体系结构; *提供方便的轻量级ORM实现,支持常规的对象到数据库双向映射; *支持可在加载时由配置文件安装的插件; *通过内置连接点支持AOP。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值