65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.83/5 (13投票s)

2008年6月1日

CPOL

6分钟阅读

viewsIcon

63376

downloadIcon

1492

本文介绍了可移植网络库(ahttp)和小型 HTTP 服务器,是现代 C++ 编程方法研究的成果。

备注

  • 要启动服务器,请进入 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 screen - browse folder content

引言

首先——本文档并非对稳定应用程序的介绍——ahttpserver 仅仅是我对现代 C++ 编程方法进行创造性探索的结果。在我目前的工作中,我无法满足我对学习新编程技术的渴望和品味,这也是这个项目启动的根本原因——我启动这个项目是为了复习/扩展和巩固我的 C++ 技能。

HTTP 服务器应用程序被选为一组复杂的功能部分

  • TCP 套接字 + HTTP 协议
  • 并行 HTTP 请求处理(多线程)
  • 易于修改的服务器设置存储(XML、INI 文件)
  • 与服务器文件系统的交互
  • 模块化架构(插件支持)
  • 服务器端脚本支持
  • 服务器应用程序功能(服务/守护进程)
  • 错误日志记录/服务器控制

项目开始时也选定了几个目标

  1. 代码必须是可移植的(项目至少应能在 Windows 和 Linux 下编译/运行)。我六个月前安装了 Ubuntu Linux 7.10,并对其赞不绝口。
  2. 代码应组织成独立的部件,这些部件可以在其他项目中重用。
  3. 项目代码必须尽可能小(可以利用已知的 C++ 库)。

目前,ahttpserver 项目包含三个主要部分

  1. ahttplib 静态 库(aconnectahttp 命名空间
  2. ahttpserver - 服务器应用程序核心
  3. 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::mutexboost::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... 它包含一组日志方法:infowarnerror,用于记录具有适当级别的消息。
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_ReleasePyEval_SaveThread... 但尚未找到快速适用的解决方案。我计划研究现有的 Python 模块(mod_wsgiPyISAPIe

历史

  • 2008-05-29:发布版本 0.1
© . All rights reserved.