C++ 的强大之处——使用 Boost、TinyXML 开发可移植的 Python 解释器 HTTP 服务器






4.83/5 (13投票s)
本文介绍了可移植网络库(ahttp)
备注
- 要启动服务器,请进入 server 目录并运行 ahttpserver.exe start
- 要查看服务器统计信息,请运行 ahttpserver.exe stat
- 要停止服务器,请运行 ahttpserver.exe stop
- 要使用 Python 解释器,应安装 Python 2.5
- 要删除 Python 支持,请从 server.config 文件中删除所有带有内容的
<handlers>
标签
备注
- 项目基于 Boost 1.34.1 创建——您应该拥有它才能编译解决方案
- 要编译
python_handler
,您需要安装 Python 2.5 - 要在 Visual Studio .NET 2005 下调试
ahttpserver
,请将 ahttpserver->Properties->Debugging->Command Arguments 设置为:run
引言
首先——本文档并非对稳定应用程序的介绍——ahttpserver
仅仅是我对现代 C++ 编程方法进行创造性探索的结果。在我目前的工作中,我无法满足我对学习新编程技术的渴望和品味,这也是这个项目启动的根本原因——我启动这个项目是为了复习/扩展和巩固我的 C++ 技能。
HTTP 服务器应用程序被选为一组复杂的功能部分
- TCP 套接字 + HTTP 协议
- 并行 HTTP 请求处理(多线程)
- 易于修改的服务器设置存储(XML、INI 文件)
- 与服务器文件系统的交互
- 模块化架构(插件支持)
- 服务器端脚本支持
- 服务器应用程序功能(服务/守护进程)
- 错误日志记录/服务器控制
项目开始时也选定了几个目标
- 代码必须是可移植的(项目至少应能在 Windows 和 Linux 下编译/运行)。我六个月前安装了 Ubuntu Linux 7.10,并对其赞不绝口。
- 代码应组织成独立的部件,这些部件可以在其他项目中重用。
- 项目代码必须尽可能小(可以利用已知的 C++ 库)。
目前,ahttpserver
项目包含三个主要部分
ahttplib 静态
库(aconnect
和ahttp 命名空间
)ahttpserver
- 服务器应用程序核心python_handler
- 用于服务器端 Python 脚本执行的模块
Using the Code
那么,让我们从一个简单的 aconnect
库实际应用的例子开始——以下代码演示了一个非常简单的回显服务器。
#include "aconnect/aconnect.hpp"
#include "aconnect/util.hpp"
using namespace aconnect;
// global server instance
Server server;
void threadProc (const ClientInfo& client) {
static EndMarkSocketStateCheck check;
string request = client.getRequest (check);
request.erase ( request.find(check.endMark()) );
string response;
bool stopServer = false;
if (util::equals (request, "STOP") ) {
response = "Processed";
stopServer = true;
} else {
response = "Echo: " + request;
}
// write response
client.writeResponse(response);
if (stopServer)
exit (0);
}
// test it: https://:8888/
int main (int argc, char* args[])
{
Initializer init;
FileLogger logger;
// {timestamp} - will be replaced with generated timestamp
// (example: 22_05_2008_20_17_35),
// third parameter - max. size of log file - it will be rotated automatically
logger.init (Log::Debug, "c:\\temp\\server_log_{timestamp}.log", 4194304);
// init command server
ServerSettings settings;
settings.socketReadTimeout =
settings.socketWriteTimeout = 300; // sec
// init HTTP server
server.setLog ( &logger);
server.init (8888, threadProc, settings);
server.start(); // started in child thread
server.join();
}
Initializer
是一个 RAII 风格的保护程序,用于初始化依赖于操作系统的网络功能——在 Windows 上,它在构造函数中调用 WSAStartup
,在析构函数中调用 WSACleanup
。Server
是主要的职能类——它创建一个 TCP 服务器套接字,将其绑定到选定的端口(代码中为 8888),并开始在该端口上监听。服务器可以在后台线程(如示例所示)或主执行线程中启动:server.start (true)
。
在服务器初始化时,ServerSettings
对象被应用于服务器。
// server settings storage - used to setup default server settings
struct ServerSettings
{
int backlog;
int domain;
bool reuseAddr;
bool enablePooling;
int workersCount;
int workerLifeTime; // sec
int socketReadTimeout; // sec
int socketWriteTimeout; // sec
// default settings
ServerSettings () :
backlog (SOMAXCONN), // backlog in listen() call
domain (AF_INET), // domain for 'socket' function call
reuseAddr (false), // SO_REUSEADDR flag setup on server socket
enablePooling (true), // show whether create worker-threads pool or not
workersCount (500), // maximum worker-threads count
workerLifeTime (300), // thread in pool lifetime
socketReadTimeout (60), // server socket SO_RCVTIMEO timeout
socketWriteTimeout (60) // server socket SO_SNDTIMEO timeout
{ }
};
每个接受的 TCP 连接都在后台工作线程中处理——使用了可移植的 Boost.Thread 库。aconnect::Server
中使用 boost::mutex 和 boost::condition 实现了一个简单的线程池。如果服务器设置中的 enablePooling
字段为 true
,那么当初始 TCP 交互完成时,工作线程将等待新的请求,等待时间为 workerLifeTime
。如果在超时结束时未找到请求,则线程将从池中移除。
当服务器接受客户端 TCP 连接时,它会用客户端相关数据填充 ClientInfo
对象。
struct ClientInfo
{
port_type port; // int
ip_addr_type ip; // unsigned char[4]
socket_type socket; // OS-depended, under Win32 - SOCKET, Linux - int
class Server *server;
};
在客户端信息加载完成后,执行将转移到工作线程(新的或从池中借用的),该线程执行线程过程(代码中的 threadProc
)。
FileLogger
- aconnect::Logger
接口的实现,用于将消息记录到文件。aconnect::Logger
是一个简单的日志功能示例,其开发方式类似于 log4... 它包含一组日志方法:info
、warn
、error
,用于记录具有适当级别的消息。ConsoleLogger
将消息写入 std::cout
,而 FileLogger
将消息写入文件,FileLogger
可以在达到最大文件大小时旋转文件。FileLogger
的初始化非常简单——只需定义日志级别、文件路径和单个日志文件的最大大小(默认大小:4 MB)。
ahttp 库
aconnect
库是首先开发的——除了 Server
类,它还包含一组实用程序(类型定义、套接字控制、string
比较、日期/时间函数)。
在 TCP 服务器实现之后,开发了 ahttp
库——该库包含 HttpServer
定义和一组与 HTTP 协议相关的功能。要启动 ahttp::HttpServer
,需要填充 HttpServerSettings
实例(参见监听)——即使是这个简单的服务器,也有很多设置。因此,存储这些设置的首选位置是 XML 文件,该文件可以手动更新并快速加载。请参阅源码 ahttpserver
源码(out\server.config 文件)中的设置文件示例。有些设置在此文件中进行了描述,其他设置可以从上下文中理解。
class HttpServerSettings
{
public:
HttpServerSettings();
~HttpServerSettings();
void load (aconnect::string_constptr docPath) throw (settings_load_error);
...
protected:
aconnect::ServerSettings settings_;
aconnect::port_type port_;
aconnect::port_type commandPort_;
aconnect::string rootDirName_;
aconnect::string appLocaton_;
// logger
aconnect::Log::LogLevel logLevel_;
aconnect::string logFileTemplate_;
size_t maxLogFileSize_;
bool enableKeepAlive_;
int keepAliveTimeout_;
int commandSocketTimeout_;
size_t responseBufferSize_;
size_t maxChunkSize_;
directories_map directories_;
aconnect::str2str_map mimeTypes_;
aconnect::Logger* logger_;
aconnect::string serverVersion_;
global_handlers_map registeredHandlers_;
bool firstLoad_;
aconnect::string directoryConfigFile_;
};
目前,ahttp::HttpServer
具有以下功能
- HTTP 方法
GET
/POST
/HEAD
POST
参数解析 + 上传文件处理(“multipart/form-data
” 请求类型)- Keep-alive 模式
- “chunked”传输编码——支持动态创建的响应
- 按文件扩展名评估 MIME 类型(out\mime-types.config)
- 自动 URL 映射——(参见 out\web\directory.config 中的示例)
- 默认文档加载(index.html)
- 具有可自定义 UI 的目录浏览
服务器具有模块化架构——新功能可以作为新处理器(handler)添加。处理器通过 HttpServerSettings
连接到服务器——配置文件中的 <handlers> 部分。处理器应用于文件扩展名(例如“.py”,“. ”——空扩展名)以及当前虚拟目录(“*”)中的所有文件。每个处理器都有一个主处理函数。
HANDLER_EXPORT bool processHandlerRequest (ahttp::HttpContext& context);
如果该函数返回 true
(请求已完成)——在调用该函数后,HTTP 处理器的处理将停止。否则——将应用下一个已注册的处理器(此模式可用于缓存/身份验证/扩展日志记录模块)。
请参阅代码中的更多详细信息——我尽量将所有代码写得尽可能简单。
关注点
因此,在 ahttpserver
开发时,我研究了一系列有用的库,这些库我将来可以在专业工作中加以利用。首先,我想提一下 boost 库的 Thread、Filesystem 和 String Algorithm——它们都是很棒的工具,并且包含许多实用的日常编程功能。Boost Python Library 是编写自定义 Python 模块的完美工具——该库使用先进的元编程技术,简化了其对用户的语法,从而包装代码看起来就像一种声明性接口定义语言(IDL)。
对该应用程序的工作尚未停止——还有几个未完成的(计划中的)任务
- gzip/deflate 内容编码支持(使用 zlib 或
boost::iostreams
) - 服务器消息本地化
- 基本身份验证处理器
- 内存缓存处理器
- 重写 Python 解释器处理器以避免多线程请求处理时崩溃(目前,关键地方只有几个锁。我尝试了几种 Python 文档中的变通方法:
PyGILState_Ensure
/PyGILState_Release
、PyEval_SaveThread
... 但尚未找到快速适用的解决方案。我计划研究现有的 Python 模块(mod_wsgi、PyISAPIe)
历史
- 2008-05-29:发布版本 0.1